Просмотр исходного кода

Merge branch 'feat/growi-bot-proxy' into feat/create-endpoint-growi-register

zahmis 5 лет назад
Родитель
Сommit
feabb340e7

+ 0 - 1
packages/slack/.gitignore

@@ -1,2 +1 @@
 /dist/
-/data/sqlite.db

+ 0 - 1
packages/slack/package.json

@@ -28,7 +28,6 @@
     "@typescript-eslint/parser": "^4.18.0",
     "eslint-import-resolver-typescript": "^2.4.0",
     "eslint-plugin-jest": "^24.3.2",
-    "sqlite3": "^5.0.2",
     "ts-jest": "^26.5.4",
     "ts-node": "^9.1.1",
     "ts-node-dev": "^1.1.6",

+ 5 - 2
packages/slackbot-proxy/.env.development

@@ -1,2 +1,5 @@
-TYPEORM_CONNECTION=sqlite
-TYPEORM_DATABASE=./data/sqlite.db
+TYPEORM_CONNECTION=mysql
+TYPEORM_HOST=mysql
+TYPEORM_DATABASE=growi-slackbot-proxy
+TYPEORM_USERNAME=growi-slackbot-proxy
+TYPEORM_PASSWORD=YrkUi7rCW46Z2N6EXSFUBwaQTUR8biCU

+ 0 - 1
packages/slackbot-proxy/.gitignore

@@ -1,2 +1 @@
 /dist/
-/data/sqlite.db

+ 0 - 0
packages/slackbot-proxy/data/sqlite.db


+ 32 - 0
packages/slackbot-proxy/docker-compose.dev.yml

@@ -0,0 +1,32 @@
+version: '3'
+services:
+
+  mysql:
+    image: mysql:8.0
+    restart: unless-stopped
+    ports:
+      - 3306:3306
+    environment:
+      - MYSQL_ALLOW_EMPTY_PASSWORD=yes
+      - MYSQL_USER=growi-slackbot-proxy
+      - MYSQL_PASSWORD=YrkUi7rCW46Z2N6EXSFUBwaQTUR8biCU
+      - MYSQL_DATABASE=growi-slackbot-proxy
+    volumes:
+      - /data/db
+
+  phpmyadmin:
+    depends_on:
+      - mysql
+    image: phpmyadmin
+    restart: unless-stopped
+    ports:
+      - 13306:80
+    environment:
+      - PMA_HOST=mysql
+      - PMA_USER=growi-slackbot-proxy
+      - PMA_PASSWORD=YrkUi7rCW46Z2N6EXSFUBwaQTUR8biCU
+
+networks:
+  default:
+    external:
+      name: growi_devcontainer_default

+ 0 - 1
packages/slackbot-proxy/package.json

@@ -38,7 +38,6 @@
     "@typescript-eslint/eslint-plugin": "^4.18.0",
     "@typescript-eslint/parser": "^4.18.0",
     "eslint-import-resolver-typescript": "^2.4.0",
-    "sqlite3": "^5.0.2",
     "ts-jest": "^26.5.4",
     "ts-node": "^9.1.1",
     "ts-node-dev": "^1.1.6",

+ 23 - 4
packages/slackbot-proxy/src/Server.ts

@@ -1,4 +1,4 @@
-import { Configuration, Inject } from '@tsed/di';
+import { Configuration, Inject, InjectorService } from '@tsed/di';
 import { PlatformApplication } from '@tsed/common';
 import '@tsed/platform-express'; // /!\ keep this import
 import bodyParser from 'body-parser';
@@ -6,15 +6,23 @@ import compress from 'compression';
 import cookieParser from 'cookie-parser';
 import methodOverride from 'method-override';
 import '@tsed/swagger';
-import '@tsed/typeorm';
+import { TypeORMService } from '@tsed/typeorm';
 import { ConnectionOptions } from 'typeorm';
 
+
 export const rootDir = __dirname;
 
 const connectionOptions: ConnectionOptions = {
   type: process.env.TYPEORM_CONNECTION,
+  host: process.env.TYPEORM_HOST,
   database: process.env.TYPEORM_DATABASE,
+  username: process.env.TYPEORM_USERNAME,
+  password: process.env.TYPEORM_PASSWORD,
+  synchronize: true,
+  logging: true,
 } as ConnectionOptions;
+
+
 @Configuration({
   rootDir,
   acceptMimes: ['application/json'],
@@ -33,13 +41,13 @@ const connectionOptions: ConnectionOptions = {
     {
       ...connectionOptions,
       entities: [
-        `${rootDir}/entity/*{.ts,.js}`,
+        `${rootDir}/entities/*{.ts,.js}`,
       ],
       migrations: [
         `${rootDir}/migrations/*{.ts,.js}`,
       ],
       subscribers: [
-        `${rootDir}/subscriber/*{.ts,.js}`,
+        `${rootDir}/subscribers/*{.ts,.js}`,
       ],
     } as ConnectionOptions,
   ],
@@ -61,6 +69,9 @@ export class Server {
   @Configuration()
   settings: Configuration;
 
+  @Inject()
+  injector: InjectorService;
+
   $beforeRoutesInit(): void {
     this.app
       .use(cookieParser())
@@ -72,4 +83,12 @@ export class Server {
       }));
   }
 
+  async $onReady(): Promise<void> {
+    const typeormService = this.injector.get<TypeORMService>(TypeORMService);
+    console.log(typeormService);
+
+    const connection = typeormService?.connectionManager.get('0');
+    console.log(connection);
+  }
+
 }

+ 23 - 1
packages/slackbot-proxy/src/controllers/slack.ts

@@ -1,16 +1,38 @@
 import {
-  BodyParams, Controller, Get, Post, Req, Res,
+  BodyParams, Controller, Get, Inject, Post, Req, Res,
 } from '@tsed/common';
+import { Installation } from '~/entities/installation';
+import { InstallationRepository } from '~/repositories/installation';
 
 import { InstallerService } from '~/services/InstallerService';
 
 @Controller('/slack')
 export class SlackCtrl {
 
+  @Inject()
+  installationRepository: InstallationRepository;
+
   // eslint-disable-next-line no-useless-constructor
   constructor(private readonly installerService: InstallerService) {
   }
 
+  @Get('/testsave')
+  testsave(): void {
+    const installation = new Installation();
+    installation.data = {
+      team: undefined,
+      enterprise: undefined,
+      user: {
+        id: '',
+        token: undefined,
+        scopes: undefined,
+      },
+    };
+
+    this.installationRepository.save(installation);
+  }
+
+
   @Get('/install')
   async install(): Promise<string> {
     const url = await this.installerService.installer.generateInstallUrl({

+ 36 - 20
packages/slackbot-proxy/src/services/InstallerService.ts

@@ -1,32 +1,23 @@
 import {
-  Installation, InstallationQuery, InstallationStore, InstallProvider,
+  Installation as SlackInstallation, InstallationQuery, InstallProvider,
 } from '@slack/oauth';
 import { Service } from '@tsed/di';
 
-
-const installationStore: InstallationStore = {
-  storeInstallation: async(installation: Installation<'v1' | 'v2', boolean>) => {
-    console.log({ installation });
-  },
-  fetchInstallation: async(installQuery: InstallationQuery<boolean>) => {
-    const installation: Installation<'v1' | 'v2', boolean> = {
-      team: undefined,
-      enterprise: undefined,
-      user: {
-        id: '',
-        token: undefined,
-        scopes: undefined,
-      },
-    };
-    return installation;
-  },
-};
+import { Installation } from '~/entities/installation';
+import { InstallationRepository } from '~/repositories/installation';
 
 @Service()
 export class InstallerService {
 
   installer: InstallProvider;
 
+  repository: InstallationRepository;
+
+  // eslint-disable-next-line no-useless-constructor
+  constructor(repository: InstallationRepository) {
+    this.repository = repository;
+  }
+
   $onInit(): Promise<any> | void {
     const clientId = process.env.SLACK_CLIENT_ID;
     const clientSecret = process.env.SLACK_CLIENT_SECRET;
@@ -39,11 +30,36 @@ export class InstallerService {
       throw new Error('The environment variable \'SLACK_CLIENT_SECRET\' must be defined.');
     }
 
+    const { repository } = this;
+
     this.installer = new InstallProvider({
       clientId,
       clientSecret,
       stateSecret,
-      installationStore,
+      installationStore: {
+        storeInstallation: async(slackInstallation: SlackInstallation<'v1' | 'v2', boolean>) => {
+          console.log({ slackInstallation });
+
+          const installation = new Installation();
+          installation.data = slackInstallation;
+
+          await repository.save(installation);
+
+          return;
+        },
+        fetchInstallation: async(installQuery: InstallationQuery<boolean>) => {
+          const installation: SlackInstallation<'v1' | 'v2', boolean> = {
+            team: undefined,
+            enterprise: undefined,
+            user: {
+              id: '',
+              token: undefined,
+              scopes: undefined,
+            },
+          };
+          return installation;
+        },
+      },
     });
   }
 

+ 1 - 0
resource/locales/en_US/admin/admin.json

@@ -253,6 +253,7 @@
     "delete": "Delete"
   },
   "slack_integration": {
+    "bot_reset_successful": "Bot settings have been reset.",
     "modal": {
       "warning": "Warning",
       "sure_change_bot_type": "Are you sure you want to change the bot type?",

+ 1 - 0
resource/locales/ja_JP/admin/admin.json

@@ -251,6 +251,7 @@
     "Directory_hierarchy_tag": "ディレクトリ階層タグ"
   },
   "slack_integration": {
+    "bot_reset_successful": "Botの設定を消去しました。",
     "modal": {
       "warning": "注意",
       "sure_change_bot_type": "Botの種類を変更しますか?",

+ 1 - 0
resource/locales/zh_CN/admin/admin.json

@@ -261,6 +261,7 @@
 		"delete": "删除"
   },
   "slack_integration": {
+    "bot_reset_successful": "删除了BOT设置。",
     "modal": {
       "warning": "警告",
       "sure_change_bot_type": "您确定要更改设置吗?",

+ 34 - 5
src/client/js/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx

@@ -2,21 +2,40 @@ import React, { useState, useEffect, useCallback } from 'react';
 import { useTranslation } from 'react-i18next';
 import PropTypes from 'prop-types';
 import AppContainer from '../../../services/AppContainer';
+import AdminAppContainer from '../../../services/AdminAppContainer';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { toastSuccess, toastError } from '../../../util/apiNotification';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
+import SlackGrowiBridging from './SlackGrowiBridging';
+
 
 const CustomBotWithoutProxySettings = (props) => {
-  const { appContainer } = props;
+  const { appContainer, adminAppContainer } = props;
   const { t } = useTranslation();
 
   const [slackSigningSecret, setSlackSigningSecret] = useState('');
   const [slackBotToken, setSlackBotToken] = useState('');
   const [slackSigningSecretEnv, setSlackSigningSecretEnv] = useState('');
   const [slackBotTokenEnv, setSlackBotTokenEnv] = useState('');
-  const botType = 'custom-bot-without-proxy';
+  const [slackWSNameInWithoutProxy, setSlackWSNameInWithoutProxy] = useState(null);
+  // get site name from this GROWI
+  // eslint-disable-next-line no-unused-vars
+  const [siteName, setSiteName] = useState('');
+  const currentBotType = 'custom-bot-without-proxy';
+
+  const getSlackWSInWithoutProxy = useCallback(async() => {
+    try {
+      const res = await appContainer.apiv3.get('/slack-integration/custom-bot-without-proxy/slack-workspace-name');
+      setSlackWSNameInWithoutProxy(res.data.slackWorkSpaceName);
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }, [appContainer]);
+
   const fetchData = useCallback(async() => {
     try {
+      await adminAppContainer.retrieveAppSettingsData();
       const res = await appContainer.apiv3.get('/slack-integration/');
       const {
         slackSigningSecret, slackBotToken, slackSigningSecretEnvVars, slackBotTokenEnvVars,
@@ -25,11 +44,12 @@ const CustomBotWithoutProxySettings = (props) => {
       setSlackBotToken(slackBotToken);
       setSlackSigningSecretEnv(slackSigningSecretEnvVars);
       setSlackBotTokenEnv(slackBotTokenEnvVars);
+      setSiteName(adminAppContainer.state.title);
     }
     catch (err) {
       toastError(err);
     }
-  }, [appContainer]);
+  }, [appContainer, adminAppContainer]);
 
   useEffect(() => {
     fetchData();
@@ -40,8 +60,9 @@ const CustomBotWithoutProxySettings = (props) => {
       await appContainer.apiv3.put('/slack-integration/custom-bot-without-proxy', {
         slackSigningSecret,
         slackBotToken,
-        botType,
+        currentBotType,
       });
+      getSlackWSInWithoutProxy();
       toastSuccess(t('toaster.update_successed', { target: t('admin:slack_integration.custom_bot_without_proxy_settings') }));
     }
     catch (err) {
@@ -52,6 +73,11 @@ const CustomBotWithoutProxySettings = (props) => {
   return (
     <>
       <h2 className="admin-setting-header">{t('admin:slack_integration.custom_bot_without_proxy_settings')}</h2>
+      {/* temporarily put bellow component */}
+      <SlackGrowiBridging
+        siteName={siteName}
+        slackWorkSpaceName={slackWSNameInWithoutProxy}
+      />
       <div className="row my-5">
         <div className="mx-auto">
           <button
@@ -91,6 +117,7 @@ const CustomBotWithoutProxySettings = (props) => {
                 readOnly
               />
               <p className="form-text text-muted">
+                {/* eslint-disable-next-line react/no-danger */}
                 <small dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.use_env_var_if_empty', { variable: 'SLACK_SIGNING_SECRET' }) }} />
               </p>
             </td>
@@ -113,6 +140,7 @@ const CustomBotWithoutProxySettings = (props) => {
                 readOnly
               />
               <p className="form-text text-muted">
+                {/* eslint-disable-next-line react/no-danger */}
                 <small dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.use_env_var_if_empty', { variable: 'SLACK_BOT_TOKEN' }) }} />
               </p>
             </td>
@@ -127,10 +155,11 @@ const CustomBotWithoutProxySettings = (props) => {
   );
 };
 
-const CustomBotWithoutProxySettingsWrapper = withUnstatedContainers(CustomBotWithoutProxySettings, [AppContainer]);
+const CustomBotWithoutProxySettingsWrapper = withUnstatedContainers(CustomBotWithoutProxySettings, [AppContainer, AdminAppContainer]);
 
 CustomBotWithoutProxySettings.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminAppContainer: PropTypes.instanceOf(AdminAppContainer).isRequired,
 };
 
 export default CustomBotWithoutProxySettingsWrapper;

+ 17 - 0
src/client/js/components/Admin/SlackIntegration/SlackGrowiBridging.jsx

@@ -0,0 +1,17 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const SlackGrowiBridging = (props) => {
+  return (
+    <>
+      {props.slackWorkSpaceName}{props.siteName}
+    </>
+  );
+};
+
+SlackGrowiBridging.propTypes = {
+  slackWorkSpaceName: PropTypes.string,
+  siteName: PropTypes.string,
+};
+
+export default SlackGrowiBridging;

+ 50 - 10
src/client/js/components/Admin/SlackIntegration/SlackIntegration.jsx

@@ -1,15 +1,37 @@
-import React, { useState } from 'react';
-
+import React, { useState, useEffect, useCallback } from 'react';
+import PropTypes from 'prop-types';
+import { useTranslation } from 'react-i18next';
+import AppContainer from '../../../services/AppContainer';
+import { withUnstatedContainers } from '../../UnstatedUtils';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
 import AccessTokenSettings from './AccessTokenSettings';
 import OfficialBotSettings from './OfficialBotSettings';
 import CustomBotWithoutProxySettings from './CustomBotWithoutProxySettings';
 import CustomBotWithProxySettings from './CustomBotWithProxySettings';
 import ConfirmBotChangeModal from './ConfirmBotChangeModal';
 
-const SlackIntegration = () => {
+
+const SlackIntegration = (props) => {
+  const { appContainer } = props;
+  const { t } = useTranslation();
   const [currentBotType, setCurrentBotType] = useState(null);
   const [selectedBotType, setSelectedBotType] = useState(null);
 
+  const fetchData = useCallback(async() => {
+    try {
+      const response = await appContainer.apiv3.get('slack-integration/');
+      const { currentBotType } = response.data.slackBotSettingParams;
+      setCurrentBotType(currentBotType);
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }, [appContainer.apiv3]);
+
+  useEffect(() => {
+    fetchData();
+  }, [fetchData]);
+
   const handleBotTypeSelect = (clickedBotType) => {
     if (clickedBotType === currentBotType) {
       return;
@@ -25,9 +47,20 @@ const SlackIntegration = () => {
     setSelectedBotType(null);
   };
 
-  const changeCurrentBotSettings = () => {
-    setCurrentBotType(selectedBotType);
-    setSelectedBotType(null);
+  const handleChangeCurrentBotSettings = async() => {
+    try {
+      const res = await appContainer.apiv3.put('slack-integration/custom-bot-without-proxy', {
+        slackSigningSecret: '',
+        slackBotToken: '',
+        currentBotType: selectedBotType,
+      });
+      setCurrentBotType(res.data.customBotWithoutProxySettingParams.slackBotType);
+      setSelectedBotType(null);
+      toastSuccess(t('admin:slack_integration.bot_reset_successful'));
+    }
+    catch (err) {
+      toastError(err);
+    }
   };
 
   let settingsComponent = null;
@@ -37,7 +70,9 @@ const SlackIntegration = () => {
       settingsComponent = <OfficialBotSettings />;
       break;
     case 'custom-bot-without-proxy':
-      settingsComponent = <CustomBotWithoutProxySettings />;
+      settingsComponent = (
+        <CustomBotWithoutProxySettings />
+      );
       break;
     case 'custom-bot-with-proxy':
       settingsComponent = <CustomBotWithProxySettings />;
@@ -49,7 +84,7 @@ const SlackIntegration = () => {
       <div className="container">
         <ConfirmBotChangeModal
           isOpen={selectedBotType != null}
-          onConfirmClick={changeCurrentBotSettings}
+          onConfirmClick={handleChangeCurrentBotSettings}
           onCancelClick={handleCancelBotChange}
         />
       </div>
@@ -61,7 +96,6 @@ const SlackIntegration = () => {
         </div>
       </div>
 
-
       <div className="row my-5">
         <div className="card-deck mx-auto">
 
@@ -103,4 +137,10 @@ const SlackIntegration = () => {
   );
 };
 
-export default SlackIntegration;
+const SlackIntegrationWrapper = withUnstatedContainers(SlackIntegration, [AppContainer]);
+
+SlackIntegration.propTypes = {
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+};
+
+export default SlackIntegrationWrapper;

+ 138 - 14
src/server/routes/apiv3/slack-integration.js

@@ -3,6 +3,7 @@ const loggerFactory = require('@alias/logger');
 const logger = loggerFactory('growi:routes:apiv3:notification-setting');
 const express = require('express');
 const { body } = require('express-validator');
+const { WebClient } = require('@slack/web-api');
 const crypto = require('crypto');
 const ErrorV3 = require('../../models/vo/error-apiv3');
 
@@ -27,7 +28,13 @@ const router = express.Router();
  *            type: string
  *          slackBotToken:
  *            type: string
- *          botType:
+ *          currentBotType:
+ *            type: string
+ *      SlackIntegration:
+ *        description: SlackIntegration
+ *        type: object
+ *        properties:
+ *          currentBotType:
  *            type: string
  */
 
@@ -39,12 +46,15 @@ module.exports = (crowi) => {
   const csrf = require('../../middlewares/csrf')(crowi);
   const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
 
-
   const validator = {
-    CusotmBotWithoutProxy: [
+    CustomBotWithoutProxy: [
       body('slackSigningSecret').isString(),
       body('slackBotToken').isString(),
-      body('botType').isString(),
+      body('currentBotType').isString(),
+    ],
+    SlackIntegration: [
+      body('currentBotType')
+        .isIn(['official-bot', 'custom-bot-without-proxy', 'custom-bot-with-proxy']),
     ],
   };
 
@@ -69,16 +79,15 @@ module.exports = (crowi) => {
    *      get:
    *        tags: [SlackBotSettingParams]
    *        operationId: getSlackBotSettingParams
-   *        summary: /slack-integration
+   *        summary: get /slack-integration
    *        description: Get slackBot setting params.
    *        responses:
    *          200:
    *            description: Succeeded to get slackBot setting params.
    */
   router.get('/', accessTokenParser, loginRequiredStrictly, adminRequired, async(req, res) => {
-
     const slackBotSettingParams = {
-      slackBotType: crowi.configManager.getConfig('crowi', 'slackbot:type'),
+      currentBotType: crowi.configManager.getConfig('crowi', 'slackbot:currentBotType'),
       // TODO impl when creating official bot
       officialBotSettings: {
         // TODO impl this after GW-4939
@@ -101,6 +110,52 @@ module.exports = (crowi) => {
     return res.apiv3({ slackBotSettingParams });
   });
 
+  /**
+   * @swagger
+   *
+   *    /slack-integration/:
+   *      put:
+   *        tags: [SlackIntegration]
+   *        operationId: putSlackIntegration
+   *        summary: put /slack-integration
+   *        description: Put SlackIntegration setting.
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            application/json:
+   *              schema:
+   *                $ref: '#/components/schemas/SlackIntegration'
+   *        responses:
+   *           200:
+   *             description: Succeeded to put Slack Integration setting.
+   */
+  router.put('/',
+    accessTokenParser, loginRequiredStrictly, adminRequired, csrf, validator.SlackIntegration, apiV3FormValidator, async(req, res) => {
+      const { currentBotType } = req.body;
+
+      const requestParams = {
+        'slackbot:currentBotType': currentBotType,
+      };
+
+      try {
+        await updateSlackBotSettings(requestParams);
+
+        // initialize bolt service
+        crowi.boltService.initialize();
+        crowi.boltService.publishUpdatedMessage();
+
+        const slackIntegrationSettingsParams = {
+          currentBotType: crowi.configManager.getConfig('crowi', 'slackbot:currentBotType'),
+        };
+        return res.apiv3({ slackIntegrationSettingsParams });
+      }
+      catch (error) {
+        const msg = 'Error occured in updating Slack bot setting';
+        logger.error('Error', error);
+        return res.apiv3Err(new ErrorV3(msg, 'update-SlackIntegrationSetting-failed'));
+      }
+    });
+
   /**
    * @swagger
    *
@@ -121,13 +176,13 @@ module.exports = (crowi) => {
    *             description: Succeeded to put CustomBotWithoutProxy setting.
    */
   router.put('/custom-bot-without-proxy',
-    accessTokenParser, loginRequiredStrictly, adminRequired, csrf, validator.CusotmBotWithoutProxy, apiV3FormValidator, async(req, res) => {
-      const { slackSigningSecret, slackBotToken, botType } = req.body;
+    accessTokenParser, loginRequiredStrictly, adminRequired, csrf, validator.CustomBotWithoutProxy, apiV3FormValidator, async(req, res) => {
+      const { slackSigningSecret, slackBotToken, currentBotType } = req.body;
 
       const requestParams = {
         'slackbot:signingSecret': slackSigningSecret,
         'slackbot:token': slackBotToken,
-        'slackbot:type': botType,
+        'slackbot:currentBotType': currentBotType,
       };
 
       try {
@@ -141,17 +196,51 @@ module.exports = (crowi) => {
         const customBotWithoutProxySettingParams = {
           slackSigningSecret: crowi.configManager.getConfig('crowi', 'slackbot:signingSecret'),
           slackBotToken: crowi.configManager.getConfig('crowi', 'slackbot:token'),
-          slackBotType: crowi.configManager.getConfig('crowi', 'slackbot:type'),
+          slackBotType: crowi.configManager.getConfig('crowi', 'slackbot:currentBotType'),
         };
         return res.apiv3({ customBotWithoutProxySettingParams });
       }
       catch (error) {
         const msg = 'Error occured in updating Custom bot setting';
         logger.error('Error', error);
-        return res.apiv3Err(new ErrorV3(msg, 'update-CustomBotSetting-failed'));
+        return res.apiv3Err(new ErrorV3(msg, 'update-CustomBotSetting-failed'), 500);
       }
     });
 
+  /**
+   * @swagger
+   *
+   *    /slack-integration/custom-bot-without-proxy/slack-workspace-name:
+   *      get:
+   *        tags: [slackWorkSpaceName]
+   *        operationId: getSlackWorkSpaceName
+   *        summary: Get slack work space name for custom bot without proxy
+   *        description: get slack WS name in custom bot without proxy
+   *        responses:
+   *          200:
+   *            description: Succeeded to get slack ws name for custom bot without proxy
+   */
+  router.get('/custom-bot-without-proxy/slack-workspace-name', async(req, res) => {
+    // get ws name in custom bot from slackbot token
+    const slackBotToken = crowi.configManager.getConfig('crowi', 'slackbot:token');
+
+    let slackWorkSpaceName = null;
+    if (slackBotToken != null) {
+      const web = new WebClient(slackBotToken);
+      try {
+        const slackTeamInfo = await web.team.info();
+        slackWorkSpaceName = slackTeamInfo.team.name;
+      }
+      catch (error) {
+        const msg = 'Error occured in slack_bot_token';
+        logger.error('Error', msg);
+        return res.apiv3Err(new ErrorV3(msg, 'get-SlackWorkSpaceName-failed'));
+      }
+    }
+
+    return res.apiv3({ slackWorkSpaceName });
+  });
+
   /**
    * @swagger
    *
@@ -165,18 +254,53 @@ module.exports = (crowi) => {
    *          200:
    *            description: Succeeded to update access token for slack
    */
-  router.put('/access-token', loginRequiredStrictly, adminRequired, async(req, res) => {
+  router.put('/access-token', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
 
     try {
       const accessToken = generateAccessToken(req.user);
       await updateSlackBotSettings({ 'slackbot:access-token': accessToken });
 
+      // initialize bolt service
+      crowi.boltService.initialize();
+      crowi.boltService.publishUpdatedMessage();
+
       return res.apiv3({ accessToken });
     }
     catch (error) {
       const msg = 'Error occured in updating access token for access token';
       logger.error('Error', error);
-      return res.apiv3Err(new ErrorV3(msg, 'update-accessToken-failed'));
+      return res.apiv3Err(new ErrorV3(msg, 'update-accessToken-failed'), 500);
+    }
+  });
+
+  /**
+   * @swagger
+   *
+   *    /slack-integration/access-token:
+   *      delete:
+   *        tags: [SlackIntegration]
+   *        operationId: deleteAccessTokenForSlackBot
+   *        summary: /slack-integration
+   *        description: Delete accessToken
+   *        responses:
+   *          200:
+   *            description: Succeeded to delete accessToken
+   */
+  router.delete('/access-token', loginRequiredStrictly, adminRequired, csrf, async(req, res) => {
+
+    try {
+      await updateSlackBotSettings({ 'slackbot:access-token': null });
+
+      // initialize bolt service
+      crowi.boltService.initialize();
+      crowi.boltService.publishUpdatedMessage();
+
+      return res.apiv3({});
+    }
+    catch (error) {
+      const msg = 'Error occured in discard of slackbotAccessToken';
+      logger.error('Error', error);
+      return res.apiv3Err(new ErrorV3(msg, 'discard-slackbotAccessToken-failed'), 500);
     }
   });
 

+ 1 - 1
src/server/service/config-loader.js

@@ -412,7 +412,7 @@ const ENV_VAR_NAME_TO_CONFIG_INFO = {
   },
   SLACK_BOT_TYPE: {
     ns:      'crowi',
-    key:     'slackbot:type', // eg. official || custom-without-proxy || custom-with-proxy
+    key:     'slackbot:currentBotType', // 'official-bot' || 'custom-bot-without-proxy' || 'custom-bot-with-proxy'
     type:    TYPES.STRING,
     default: null,
   },

+ 1 - 32
yarn.lock

@@ -11555,11 +11555,6 @@ nocache@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.0.0.tgz#202b48021a0c4cbde2df80de15a17443c8b43980"
 
-node-addon-api@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239"
-  integrity sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw==
-
 node-dev@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/node-dev/-/node-dev-4.0.0.tgz#c03a492c517ed3153693f9082e46c304c522a48d"
@@ -11606,7 +11601,7 @@ node-forge@^0.9.0:
   resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.1.tgz#775368e6846558ab6676858a4d8c6e8d16c677b5"
   integrity sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==
 
-node-gyp@3.x, node-gyp@^3.8.0:
+node-gyp@^3.8.0:
   version "3.8.0"
   resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c"
   integrity sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==
@@ -11702,22 +11697,6 @@ node-object-hash@^1.2.0:
   resolved "https://registry.yarnpkg.com/node-object-hash/-/node-object-hash-1.4.2.tgz#385833d85b229902b75826224f6077be969a9e94"
   integrity sha512-UdS4swXs85fCGWWf6t6DMGgpN/vnlKeSGEQ7hJcrs7PBFoxoKLmibc3QRb7fwiYsjdL7PX8iI/TMSlZ90dgHhQ==
 
-node-pre-gyp@^0.11.0:
-  version "0.11.0"
-  resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054"
-  integrity sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==
-  dependencies:
-    detect-libc "^1.0.2"
-    mkdirp "^0.5.1"
-    needle "^2.2.1"
-    nopt "^4.0.1"
-    npm-packlist "^1.1.6"
-    npmlog "^4.0.2"
-    rc "^1.2.7"
-    rimraf "^2.6.1"
-    semver "^5.3.0"
-    tar "^4"
-
 node-pre-gyp@^0.13.0:
   version "0.13.0"
   resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz#df9ab7b68dd6498137717838e4f92a33fc9daa42"
@@ -15727,16 +15706,6 @@ sprintf-js@~1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
 
-sqlite3@^5.0.2:
-  version "5.0.2"
-  resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.0.2.tgz#00924adcc001c17686e0a6643b6cbbc2d3965083"
-  integrity sha512-1SdTNo+BVU211Xj1csWa8lV6KM0CtucDwRyA0VHl91wEH1Mgh7RxUpI4rVvG7OhHrzCSGaVyW5g8vKvlrk9DJA==
-  dependencies:
-    node-addon-api "^3.0.0"
-    node-pre-gyp "^0.11.0"
-  optionalDependencies:
-    node-gyp "3.x"
-
 sqlstring@^2.3.2:
   version "2.3.2"
   resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.2.tgz#cdae7169389a1375b18e885f2e60b3e460809514"