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

configure biome for slackbot-proxy (excluding services)

Futa Arai 9 месяцев назад
Родитель
Сommit
9e7960d778
36 измененных файлов с 670 добавлено и 475 удалено
  1. 1 2
      apps/slackbot-proxy/.eslintignore
  2. 0 29
      apps/slackbot-proxy/.eslintrc.js
  3. 44 56
      apps/slackbot-proxy/src/Server.ts
  4. 0 1
      apps/slackbot-proxy/src/config/logger/config.dev.ts
  5. 0 1
      apps/slackbot-proxy/src/config/logger/config.prod.ts
  6. 139 65
      apps/slackbot-proxy/src/controllers/growi-to-slack.ts
  7. 1 3
      apps/slackbot-proxy/src/controllers/privacy.ts
  8. 268 122
      apps/slackbot-proxy/src/controllers/slack.ts
  9. 1 3
      apps/slackbot-proxy/src/controllers/term.ts
  10. 1 5
      apps/slackbot-proxy/src/controllers/top.ts
  11. 6 7
      apps/slackbot-proxy/src/entities/installation.ts
  12. 7 4
      apps/slackbot-proxy/src/entities/order.ts
  13. 8 4
      apps/slackbot-proxy/src/entities/relation.ts
  14. 5 3
      apps/slackbot-proxy/src/entities/system-information.ts
  15. 12 10
      apps/slackbot-proxy/src/filters/CustomHttpErrorFilter.ts
  16. 10 7
      apps/slackbot-proxy/src/filters/ResourceNotFoundFilter.ts
  17. 1 2
      apps/slackbot-proxy/src/index.ts
  18. 9 9
      apps/slackbot-proxy/src/interfaces/growi-uri-injector.ts
  19. 5 4
      apps/slackbot-proxy/src/interfaces/slack-to-growi/slack-oauth-req.ts
  20. 14 12
      apps/slackbot-proxy/src/middlewares/GlobalHttpErrorHandlingMiddleware.ts
  21. 12 11
      apps/slackbot-proxy/src/middlewares/growi-to-slack/add-webclient-response-to-res.ts
  22. 6 6
      apps/slackbot-proxy/src/middlewares/slack-to-growi/add-signing-secret-to-req.ts
  23. 58 27
      apps/slackbot-proxy/src/middlewares/slack-to-growi/authorizer.ts
  24. 4 9
      apps/slackbot-proxy/src/middlewares/slack-to-growi/extract-growi-uri-from-req.ts
  25. 5 9
      apps/slackbot-proxy/src/middlewares/slack-to-growi/join-to-conversation.ts
  26. 1 7
      apps/slackbot-proxy/src/middlewares/slack-to-growi/parse-interaction-req.ts
  27. 6 8
      apps/slackbot-proxy/src/middlewares/slack-to-growi/url-verification.ts
  28. 0 4
      apps/slackbot-proxy/src/models/errors.ts
  29. 4 6
      apps/slackbot-proxy/src/repositories/installation.ts
  30. 2 6
      apps/slackbot-proxy/src/repositories/order.ts
  31. 2 6
      apps/slackbot-proxy/src/repositories/relation.ts
  32. 5 6
      apps/slackbot-proxy/src/repositories/system-information.ts
  33. 2 3
      apps/slackbot-proxy/src/utils/logger/index.ts
  34. 26 12
      apps/slackbot-proxy/src/utils/welcome-message.ts
  35. 2 4
      apps/slackbot-proxy/tsconfig.json
  36. 3 2
      biome.json

+ 1 - 2
apps/slackbot-proxy/.eslintignore

@@ -1,2 +1 @@
-/dist/**
-/src/public/bootstrap/**
+*

+ 0 - 29
apps/slackbot-proxy/.eslintrc.js

@@ -1,29 +0,0 @@
-module.exports = {
-  env: {
-  },
-  globals: {
-  },
-  settings: {
-  },
-  rules: {
-    indent: 'off',
-    '@typescript-eslint/indent': [
-      'error',
-      2,
-      {
-        SwitchCase: 1,
-        ArrayExpression: 'first',
-        FunctionDeclaration: { body: 1, parameters: 2 },
-        FunctionExpression: { body: 1, parameters: 2 },
-        ignoredNodes: [
-          'ClassBody > PropertyDefinition[decorators] > Identifier',
-          'ClassBody > MethodDefinition[decorators] > Identifier',
-        ],
-      },
-    ],
-
-    // set 'warn' temporarily -- 2022.07.13 Yuki Takei
-    '@typescript-eslint/no-explicit-any': ['warn'],
-    '@typescript-eslint/consistent-type-imports': 'off',
-  },
-};

+ 44 - 56
apps/slackbot-proxy/src/Server.ts

@@ -31,7 +31,6 @@ const isProduction = process.env.NODE_ENV === 'production';
 
 
 const logger = loggerFactory('slackbot-proxy:server');
 const logger = loggerFactory('slackbot-proxy:server');
 
 
-
 const connectionOptions: ConnectionOptions = {
 const connectionOptions: ConnectionOptions = {
   // The 'name' property must be set. Otherwise, the 'name' will be '0' and won't work well. -- 2021.04.05 Yuki Takei
   // The 'name' property must be set. Otherwise, the 'name' will be '0' and won't work well. -- 2021.04.05 Yuki Takei
   // see: https://github.com/TypedProject/tsed/blob/7630cda20a1f6fa3a692ecc3e6cd51d37bc3c45f/packages/typeorm/src/utils/createConnection.ts#L10
   // see: https://github.com/TypedProject/tsed/blob/7630cda20a1f6fa3a692ecc3e6cd51d37bc3c45f/packages/typeorm/src/utils/createConnection.ts#L10
@@ -45,25 +44,29 @@ const connectionOptions: ConnectionOptions = {
   synchronize: true,
   synchronize: true,
 } as ConnectionOptions;
 } as ConnectionOptions;
 
 
-const swaggerSettings = isProduction ? swaggerSettingsForProd : swaggerSettingsForDev;
-const helmetOptions = isProduction ? {
-  contentSecurityPolicy: false,
-  expectCt: false,
-  referrerPolicy: false,
-  permittedCrossDomainPolicies: false,
-} : {
-  contentSecurityPolicy: {
-    directives: {
-      defaultSrc: ['\'self\''],
-      styleSrc: ['\'self\'', '\'unsafe-inline\''],
-      imgSrc: ['\'self\'', 'data:', 'https:'],
-      scriptSrc: ['\'self\'', 'https: \'unsafe-inline\''],
-    },
-  },
-  expectCt: false,
-  referrerPolicy: false,
-  permittedCrossDomainPolicies: false,
-};
+const swaggerSettings = isProduction
+  ? swaggerSettingsForProd
+  : swaggerSettingsForDev;
+const helmetOptions = isProduction
+  ? {
+      contentSecurityPolicy: false,
+      expectCt: false,
+      referrerPolicy: false,
+      permittedCrossDomainPolicies: false,
+    }
+  : {
+      contentSecurityPolicy: {
+        directives: {
+          defaultSrc: ["'self'"],
+          styleSrc: ["'self'", "'unsafe-inline'"],
+          imgSrc: ["'self'", 'data:', 'https:'],
+          scriptSrc: ["'self'", "https: 'unsafe-inline'"],
+        },
+      },
+      expectCt: false,
+      referrerPolicy: false,
+      permittedCrossDomainPolicies: false,
+    };
 
 
 @Configuration({
 @Configuration({
   rootDir,
   rootDir,
@@ -73,35 +76,20 @@ const helmetOptions = isProduction ? {
   // disable RequestLogger of @tsed/logger
   // disable RequestLogger of @tsed/logger
   logger: { logRequest: false },
   logger: { logRequest: false },
   mount: {
   mount: {
-    '/': [
-      `${rootDir}/controllers/*.ts`,
-      `${rootDir}/middlewares/*.ts`,
-    ],
+    '/': [`${rootDir}/controllers/*.ts`, `${rootDir}/middlewares/*.ts`],
   },
   },
-  middlewares: [
-    helmet(helmetOptions),
-  ],
-  componentsScan: [
-    `${rootDir}/services/*.ts`,
-  ],
+  middlewares: [helmet(helmetOptions)],
+  componentsScan: [`${rootDir}/services/*.ts`],
   typeorm: [
   typeorm: [
     {
     {
       ...connectionOptions,
       ...connectionOptions,
-      entities: [
-        `${rootDir}/entities/*{.ts,.js}`,
-      ],
-      migrations: [
-        `${rootDir}/migrations/*{.ts,.js}`,
-      ],
-      subscribers: [
-        `${rootDir}/subscribers/*{.ts,.js}`,
-      ],
+      entities: [`${rootDir}/entities/*{.ts,.js}`],
+      migrations: [`${rootDir}/migrations/*{.ts,.js}`],
+      subscribers: [`${rootDir}/subscribers/*{.ts,.js}`],
     } as ConnectionOptions,
     } as ConnectionOptions,
   ],
   ],
   swagger: swaggerSettings,
   swagger: swaggerSettings,
-  exclude: [
-    '**/*.spec.ts',
-  ],
+  exclude: ['**/*.spec.ts'],
   viewsDir: `${rootDir}/views`,
   viewsDir: `${rootDir}/views`,
   views: {
   views: {
     root: `${rootDir}/views`,
     root: `${rootDir}/views`,
@@ -118,9 +106,7 @@ const helmetOptions = isProduction ? {
     ],
     ],
   },
   },
 })
 })
-
 export class Server {
 export class Server {
-
   @Inject()
   @Inject()
   app: PlatformApplication<Express>;
   app: PlatformApplication<Express>;
 
 
@@ -134,20 +120,21 @@ export class Server {
     const serverUri = process.env.SERVER_URI;
     const serverUri = process.env.SERVER_URI;
 
 
     if (serverUri === undefined) {
     if (serverUri === undefined) {
-      throw new Error('The environment variable \'SERVER_URI\' must be defined.');
+      throw new Error("The environment variable 'SERVER_URI' must be defined.");
     }
     }
   }
   }
 
 
   $beforeRoutesInit(): void {
   $beforeRoutesInit(): void {
-
     this.app
     this.app
       .use(cookieParser())
       .use(cookieParser())
       .use(compress({}))
       .use(compress({}))
       .use(methodOverride())
       .use(methodOverride())
       .use(bodyParser.json())
       .use(bodyParser.json())
-      .use(bodyParser.urlencoded({
-        extended: true,
-      }));
+      .use(
+        bodyParser.urlencoded({
+          extended: true,
+        }),
+      );
 
 
     this.setupLogger();
     this.setupLogger();
   }
   }
@@ -161,13 +148,13 @@ export class Server {
 
 
     // init terminus
     // init terminus
     createTerminus(server, {
     createTerminus(server, {
-      onSignal: async() => {
+      onSignal: async () => {
         logger.info('server is starting cleanup');
         logger.info('server is starting cleanup');
         const connectionManager = getConnectionManager();
         const connectionManager = getConnectionManager();
         const defaultConnection = connectionManager.get('default');
         const defaultConnection = connectionManager.get('default');
         await defaultConnection.close();
         await defaultConnection.close();
       },
       },
-      onShutdown: async() => {
+      onShutdown: async () => {
         logger.info('cleanup finished, server is shutting down');
         logger.info('cleanup finished, server is shutting down');
       },
       },
     });
     });
@@ -181,10 +168,12 @@ export class Server {
     if (isProduction) {
     if (isProduction) {
       const logger = loggerFactory('express');
       const logger = loggerFactory('express');
 
 
-      this.app.use(expressBunyanLogger({
-        logger,
-        excludes: ['*'],
-      }));
+      this.app.use(
+        expressBunyanLogger({
+          logger,
+          excludes: ['*'],
+        }),
+      );
     }
     }
     // use morgan
     // use morgan
     else {
     else {
@@ -193,5 +182,4 @@ export class Server {
       this.app.use(morgan('dev'));
       this.app.use(morgan('dev'));
     }
     }
   }
   }
-
 }
 }

+ 0 - 1
apps/slackbot-proxy/src/config/logger/config.dev.ts

@@ -11,7 +11,6 @@ const config: UniversalBunyanConfig = {
   // 'express:*': 'debug',
   // 'express:*': 'debug',
   // 'slackbot-proxy:*': 'debug',
   // 'slackbot-proxy:*': 'debug',
   'slackbot-proxy:controllers:growi-to-slack': 'debug',
   'slackbot-proxy:controllers:growi-to-slack': 'debug',
-
 };
 };
 
 
 export default config;
 export default config;

+ 0 - 1
apps/slackbot-proxy/src/config/logger/config.prod.ts

@@ -10,7 +10,6 @@ const config: UniversalBunyanConfig = {
    */
    */
   // 'express:*': 'debug',
   // 'express:*': 'debug',
   // 'slackbot-proxy:*': 'debug',
   // 'slackbot-proxy:*': 'debug',
-
 };
 };
 
 
 export default config;
 export default config;

+ 139 - 65
apps/slackbot-proxy/src/controllers/growi-to-slack.ts

@@ -1,18 +1,32 @@
 import { type BlockKitRequest, REQUEST_TIMEOUT_FOR_PTOG } from '@growi/slack';
 import { type BlockKitRequest, REQUEST_TIMEOUT_FOR_PTOG } from '@growi/slack';
 import { verifyGrowiToSlackRequest } from '@growi/slack/dist/middlewares';
 import { verifyGrowiToSlackRequest } from '@growi/slack/dist/middlewares';
-import { getConnectionStatuses, getConnectionStatus } from '@growi/slack/dist/utils/check-communicable';
+import {
+  getConnectionStatus,
+  getConnectionStatuses,
+} from '@growi/slack/dist/utils/check-communicable';
 import { generateWebClient } from '@growi/slack/dist/utils/webclient-factory';
 import { generateWebClient } from '@growi/slack/dist/utils/webclient-factory';
 import { ErrorCode, WebAPICallResult } from '@slack/web-api';
 import { ErrorCode, WebAPICallResult } from '@slack/web-api';
 import {
 import {
-  Controller, Get, Post, Inject, Req, Res, UseBefore, PathParams, Put, QueryParams,
+  Controller,
+  Get,
+  Inject,
+  PathParams,
+  Post,
+  Put,
+  QueryParams,
+  Req,
+  Res,
+  UseBefore,
 } from '@tsed/common';
 } from '@tsed/common';
 import axios from 'axios';
 import axios from 'axios';
 import { addHours } from 'date-fns/addHours';
 import { addHours } from 'date-fns/addHours';
 import createError from 'http-errors';
 import createError from 'http-errors';
 
 
-
 import { GrowiReq } from '~/interfaces/growi-to-slack/growi-req';
 import { GrowiReq } from '~/interfaces/growi-to-slack/growi-req';
-import { WebclientRes, AddWebclientResponseToRes } from '~/middlewares/growi-to-slack/add-webclient-response-to-res';
+import {
+  AddWebclientResponseToRes,
+  WebclientRes,
+} from '~/middlewares/growi-to-slack/add-webclient-response-to-res';
 import { InstallationRepository } from '~/repositories/installation';
 import { InstallationRepository } from '~/repositories/installation';
 import { OrderRepository } from '~/repositories/order';
 import { OrderRepository } from '~/repositories/order';
 import { RelationRepository } from '~/repositories/relation';
 import { RelationRepository } from '~/repositories/relation';
@@ -22,20 +36,19 @@ import { SectionBlockPayloadDelegator } from '~/services/growi-uri-injector/Sect
 import { ViewInteractionPayloadDelegator } from '~/services/growi-uri-injector/ViewInteractionPayloadDelegator';
 import { ViewInteractionPayloadDelegator } from '~/services/growi-uri-injector/ViewInteractionPayloadDelegator';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
-
 const logger = loggerFactory('slackbot-proxy:controllers:growi-to-slack');
 const logger = loggerFactory('slackbot-proxy:controllers:growi-to-slack');
 
 
-export type RespondReqFromGrowi = Req & BlockKitRequest & {
-  // appended by GROWI
-  headers:{ 'x-growi-app-site-url'?: string },
+export type RespondReqFromGrowi = Req &
+  BlockKitRequest & {
+    // appended by GROWI
+    headers: { 'x-growi-app-site-url'?: string };
 
 
-  // will be extracted from header
-  appSiteUrl: string,
-}
+    // will be extracted from header
+    appSiteUrl: string;
+  };
 
 
 @Controller('/g2s')
 @Controller('/g2s')
 export class GrowiToSlackCtrl {
 export class GrowiToSlackCtrl {
-
   @Inject()
   @Inject()
   installerService: InstallerService;
   installerService: InstallerService;
 
 
@@ -57,28 +70,38 @@ export class GrowiToSlackCtrl {
   @Inject()
   @Inject()
   sectionBlockPayloadDelegator: SectionBlockPayloadDelegator;
   sectionBlockPayloadDelegator: SectionBlockPayloadDelegator;
 
 
-  async urlVerificationRequestToGrowi(growiUrl:string, tokenPtoG:string):Promise<void> {
+  async urlVerificationRequestToGrowi(
+    growiUrl: string,
+    tokenPtoG: string,
+  ): Promise<void> {
     const url = new URL('/_api/v3/slack-integration/proxied/verify', growiUrl);
     const url = new URL('/_api/v3/slack-integration/proxied/verify', growiUrl);
-    await axios.post(url.toString(), {
-      type: 'url_verification',
-      challenge: 'this_is_my_challenge_token',
-    },
-    {
-      headers: {
-        'x-growi-ptog-tokens': tokenPtoG,
+    await axios.post(
+      url.toString(),
+      {
+        type: 'url_verification',
+        challenge: 'this_is_my_challenge_token',
       },
       },
-      timeout: REQUEST_TIMEOUT_FOR_PTOG,
-    });
+      {
+        headers: {
+          'x-growi-ptog-tokens': tokenPtoG,
+        },
+        timeout: REQUEST_TIMEOUT_FOR_PTOG,
+      },
+    );
   }
   }
 
 
   @Get('/connection-status')
   @Get('/connection-status')
   @UseBefore(verifyGrowiToSlackRequest)
   @UseBefore(verifyGrowiToSlackRequest)
-  async getConnectionStatuses(@Req() req: GrowiReq, @Res() res: Res): Promise<void|string|Res|WebAPICallResult> {
+  async getConnectionStatuses(
+    @Req() req: GrowiReq,
+    @Res() res: Res,
+  ): Promise<void | string | Res | WebAPICallResult> {
     // asserted (tokenGtoPs.length > 0) by verifyGrowiToSlackRequest
     // asserted (tokenGtoPs.length > 0) by verifyGrowiToSlackRequest
     const { tokenGtoPs } = req;
     const { tokenGtoPs } = req;
 
 
     // retrieve Relation with Installation
     // retrieve Relation with Installation
-    const relations = await this.relationRepository.createQueryBuilder('relation')
+    const relations = await this.relationRepository
+      .createQueryBuilder('relation')
       .where('relation.tokenGtoP IN (:...tokens)', { tokens: tokenGtoPs })
       .where('relation.tokenGtoP IN (:...tokens)', { tokens: tokenGtoPs })
       .leftJoinAndSelect('relation.installation', 'installation')
       .leftJoinAndSelect('relation.installation', 'installation')
       .getMany();
       .getMany();
@@ -86,7 +109,7 @@ export class GrowiToSlackCtrl {
     logger.debug(`${relations.length} relations found`, relations);
     logger.debug(`${relations.length} relations found`, relations);
 
 
     // key: tokenGtoP, value: botToken
     // key: tokenGtoP, value: botToken
-    const botTokenResolverMapping: {[tokenGtoP:string]:string} = {};
+    const botTokenResolverMapping: { [tokenGtoP: string]: string } = {};
 
 
     relations.forEach((relation) => {
     relations.forEach((relation) => {
       const botToken = relation.installation?.data?.bot?.token;
       const botToken = relation.installation?.data?.bot?.token;
@@ -95,17 +118,26 @@ export class GrowiToSlackCtrl {
       }
       }
     });
     });
 
 
-    const connectionStatuses = await getConnectionStatuses(Object.keys(botTokenResolverMapping), (tokenGtoP:string) => botTokenResolverMapping[tokenGtoP]);
+    const connectionStatuses = await getConnectionStatuses(
+      Object.keys(botTokenResolverMapping),
+      (tokenGtoP: string) => botTokenResolverMapping[tokenGtoP],
+    );
     return res.send({ connectionStatuses });
     return res.send({ connectionStatuses });
   }
   }
 
 
   @Put('/supported-commands')
   @Put('/supported-commands')
   @UseBefore(verifyGrowiToSlackRequest)
   @UseBefore(verifyGrowiToSlackRequest)
-  async putSupportedCommands(@Req() req: GrowiReq, @Res() res: Res): Promise<void|string|Res|WebAPICallResult> {
+  async putSupportedCommands(
+    @Req() req: GrowiReq,
+    @Res() res: Res,
+  ): Promise<void | string | Res | WebAPICallResult> {
     // asserted (tokenGtoPs.length > 0) by verifyGrowiToSlackRequest
     // asserted (tokenGtoPs.length > 0) by verifyGrowiToSlackRequest
     const { tokenGtoPs } = req;
     const { tokenGtoPs } = req;
 
 
-    const { permissionsForBroadcastUseCommands, permissionsForSingleUseCommands } = req.body;
+    const {
+      permissionsForBroadcastUseCommands,
+      permissionsForSingleUseCommands,
+    } = req.body;
 
 
     if (tokenGtoPs.length !== 1) {
     if (tokenGtoPs.length !== 1) {
       throw createError(400, 'installation is invalid');
       throw createError(400, 'installation is invalid');
@@ -114,7 +146,8 @@ export class GrowiToSlackCtrl {
     const tokenGtoP = tokenGtoPs[0];
     const tokenGtoP = tokenGtoPs[0];
 
 
     const relation = await this.relationRepository.update(
     const relation = await this.relationRepository.update(
-      { tokenGtoP }, { permissionsForBroadcastUseCommands, permissionsForSingleUseCommands },
+      { tokenGtoP },
+      { permissionsForBroadcastUseCommands, permissionsForSingleUseCommands },
     );
     );
 
 
     return res.send({ relation });
     return res.send({ relation });
@@ -122,7 +155,10 @@ export class GrowiToSlackCtrl {
 
 
   @Post('/relation-test')
   @Post('/relation-test')
   @UseBefore(verifyGrowiToSlackRequest)
   @UseBefore(verifyGrowiToSlackRequest)
-  async postRelation(@Req() req: GrowiReq, @Res() res: Res): Promise<void|string|Res|WebAPICallResult> {
+  async postRelation(
+    @Req() req: GrowiReq,
+    @Res() res: Res,
+  ): Promise<void | string | Res | WebAPICallResult> {
     const { tokenGtoPs } = req;
     const { tokenGtoPs } = req;
 
 
     if (tokenGtoPs.length !== 1) {
     if (tokenGtoPs.length !== 1) {
@@ -132,7 +168,8 @@ export class GrowiToSlackCtrl {
     const tokenGtoP = tokenGtoPs[0];
     const tokenGtoP = tokenGtoPs[0];
 
 
     // retrieve relation with Installation
     // retrieve relation with Installation
-    const relation = await this.relationRepository.createQueryBuilder('relation')
+    const relation = await this.relationRepository
+      .createQueryBuilder('relation')
       .where('tokenGtoP = :token', { token: tokenGtoP })
       .where('tokenGtoP = :token', { token: tokenGtoP })
       .leftJoinAndSelect('relation.installation', 'installation')
       .leftJoinAndSelect('relation.installation', 'installation')
       .getOne();
       .getOne();
@@ -147,24 +184,33 @@ export class GrowiToSlackCtrl {
       }
       }
 
 
       try {
       try {
-        await this.urlVerificationRequestToGrowi(relation.growiUri, relation.tokenPtoG);
-      }
-      catch (err) {
+        await this.urlVerificationRequestToGrowi(
+          relation.growiUri,
+          relation.tokenPtoG,
+        );
+      } catch (err) {
         logger.error(err);
         logger.error(err);
-        throw createError(400, `failed to request to GROWI. err: ${err.message}`);
+        throw createError(
+          400,
+          `failed to request to GROWI. err: ${err.message}`,
+        );
       }
       }
 
 
       const status = await getConnectionStatus(token);
       const status = await getConnectionStatus(token);
 
 
       if (status.error != null) {
       if (status.error != null) {
-        throw createError(400, `failed to get connection. err: ${status.error}`);
+        throw createError(
+          400,
+          `failed to get connection. err: ${status.error}`,
+        );
       }
       }
 
 
       return res.send({ relation, slackBotToken: token });
       return res.send({ relation, slackBotToken: token });
     }
     }
 
 
     // retrieve latest Order with Installation
     // retrieve latest Order with Installation
-    const order = await this.orderRepository.createQueryBuilder('order')
+    const order = await this.orderRepository
+      .createQueryBuilder('order')
       .orderBy('order.createdAt', 'DESC')
       .orderBy('order.createdAt', 'DESC')
       .where('tokenGtoP = :token', { token: tokenGtoP })
       .where('tokenGtoP = :token', { token: tokenGtoP })
       .leftJoinAndSelect('order.installation', 'installation')
       .leftJoinAndSelect('order.installation', 'installation')
@@ -177,8 +223,7 @@ export class GrowiToSlackCtrl {
     // Access the GROWI URL saved in the Order record and check if the GtoP token is valid.
     // Access the GROWI URL saved in the Order record and check if the GtoP token is valid.
     try {
     try {
       await this.urlVerificationRequestToGrowi(order.growiUrl, order.tokenPtoG);
       await this.urlVerificationRequestToGrowi(order.growiUrl, order.tokenPtoG);
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       logger.error(err);
       throw createError(400, `failed to request to GROWI. err: ${err.message}`);
       throw createError(400, `failed to request to GROWI. err: ${err.message}`);
     }
     }
@@ -200,25 +245,35 @@ export class GrowiToSlackCtrl {
     // temporary cache for 48 hours
     // temporary cache for 48 hours
     const expiredAtCommands = addHours(new Date(), 48);
     const expiredAtCommands = addHours(new Date(), 48);
 
 
-    const response = await this.relationRepository.createQueryBuilder('relation')
+    const response = await this.relationRepository
+      .createQueryBuilder('relation')
       .insert()
       .insert()
       .values({
       .values({
         installation: order.installation,
         installation: order.installation,
         tokenGtoP: order.tokenGtoP,
         tokenGtoP: order.tokenGtoP,
         tokenPtoG: order.tokenPtoG,
         tokenPtoG: order.tokenPtoG,
         growiUri: order.growiUrl,
         growiUri: order.growiUrl,
-        permissionsForBroadcastUseCommands: req.body.permissionsForBroadcastUseCommands,
-        permissionsForSingleUseCommands: req.body.permissionsForSingleUseCommands,
+        permissionsForBroadcastUseCommands:
+          req.body.permissionsForBroadcastUseCommands,
+        permissionsForSingleUseCommands:
+          req.body.permissionsForSingleUseCommands,
         expiredAtCommands,
         expiredAtCommands,
       })
       })
       // https://github.com/typeorm/typeorm/issues/1090#issuecomment-634391487
       // https://github.com/typeorm/typeorm/issues/1090#issuecomment-634391487
       .orUpdate({
       .orUpdate({
         conflict_target: ['installation', 'growiUri'],
         conflict_target: ['installation', 'growiUri'],
-        overwrite: ['tokenGtoP', 'tokenPtoG', 'permissionsForBroadcastUseCommands', 'permissionsForSingleUseCommands'],
+        overwrite: [
+          'tokenGtoP',
+          'tokenPtoG',
+          'permissionsForBroadcastUseCommands',
+          'permissionsForSingleUseCommands',
+        ],
       })
       })
       .execute();
       .execute();
 
 
-    const generatedRelation = await this.relationRepository.findOne({ id: response.identifiers[0].id });
+    const generatedRelation = await this.relationRepository.findOne({
+      id: response.identifiers[0].id,
+    });
 
 
     return res.send({ relation: generatedRelation, slackBotToken: token });
     return res.send({ relation: generatedRelation, slackBotToken: token });
   }
   }
@@ -231,20 +286,28 @@ export class GrowiToSlackCtrl {
     if (req.body.view != null) {
     if (req.body.view != null) {
       const parsedElement = JSON.parse(req.body.view);
       const parsedElement = JSON.parse(req.body.view);
       // delegate to ViewInteractionPayloadDelegator
       // delegate to ViewInteractionPayloadDelegator
-      if (this.viewInteractionPayloadDelegator.shouldHandleToInject(parsedElement)) {
+      if (
+        this.viewInteractionPayloadDelegator.shouldHandleToInject(parsedElement)
+      ) {
         this.viewInteractionPayloadDelegator.inject(parsedElement, growiUri);
         this.viewInteractionPayloadDelegator.inject(parsedElement, growiUri);
         req.body.view = JSON.stringify(parsedElement);
         req.body.view = JSON.stringify(parsedElement);
       }
       }
-    }
-    else if (req.body.blocks != null) {
-      const parsedElement = (typeof req.body.blocks === 'string') ? JSON.parse(req.body.blocks) : req.body.blocks;
+    } else if (req.body.blocks != null) {
+      const parsedElement =
+        typeof req.body.blocks === 'string'
+          ? JSON.parse(req.body.blocks)
+          : req.body.blocks;
       // delegate to ActionsBlockPayloadDelegator
       // delegate to ActionsBlockPayloadDelegator
-      if (this.actionsBlockPayloadDelegator.shouldHandleToInject(parsedElement)) {
+      if (
+        this.actionsBlockPayloadDelegator.shouldHandleToInject(parsedElement)
+      ) {
         this.actionsBlockPayloadDelegator.inject(parsedElement, growiUri);
         this.actionsBlockPayloadDelegator.inject(parsedElement, growiUri);
         req.body.blocks = JSON.stringify(parsedElement);
         req.body.blocks = JSON.stringify(parsedElement);
       }
       }
       // delegate to SectionBlockPayloadDelegator
       // delegate to SectionBlockPayloadDelegator
-      if (this.sectionBlockPayloadDelegator.shouldHandleToInject(parsedElement)) {
+      if (
+        this.sectionBlockPayloadDelegator.shouldHandleToInject(parsedElement)
+      ) {
         this.sectionBlockPayloadDelegator.inject(parsedElement, growiUri);
         this.sectionBlockPayloadDelegator.inject(parsedElement, growiUri);
         req.body.blocks = JSON.stringify(parsedElement);
         req.body.blocks = JSON.stringify(parsedElement);
       }
       }
@@ -253,21 +316,23 @@ export class GrowiToSlackCtrl {
 
 
   @Post('/respond')
   @Post('/respond')
   async respondUsingResponseUrl(
   async respondUsingResponseUrl(
-    @QueryParams('response_url') responseUrl: string, @Req() req: RespondReqFromGrowi, @Res() res: WebclientRes,
+    @QueryParams('response_url') responseUrl: string,
+    @Req() req: RespondReqFromGrowi,
+    @Res() res: WebclientRes,
   ): Promise<WebclientRes> {
   ): Promise<WebclientRes> {
-
     // get growi url from header
     // get growi url from header
     const growiUri = req.headers['x-growi-app-site-url'];
     const growiUri = req.headers['x-growi-app-site-url'];
 
 
     if (growiUri == null) {
     if (growiUri == null) {
-      logger.error('Request to this endpoint requires the x-growi-app-site-url header.');
+      logger.error(
+        'Request to this endpoint requires the x-growi-app-site-url header.',
+      );
       return res.status(400).send('Failed to respond.');
       return res.status(400).send('Failed to respond.');
     }
     }
 
 
     try {
     try {
       this.injectGrowiUri(req, growiUri);
       this.injectGrowiUri(req, growiUri);
-    }
-    catch (err) {
+    } catch (err) {
       logger.error('Error occurred while injecting GROWI uri:\n', err);
       logger.error('Error occurred while injecting GROWI uri:\n', err);
 
 
       return res.status(400).send('Failed to respond.');
       return res.status(400).send('Failed to respond.');
@@ -275,8 +340,7 @@ export class GrowiToSlackCtrl {
 
 
     try {
     try {
       await axios.post(responseUrl, req.body);
       await axios.post(responseUrl, req.body);
-    }
-    catch (err) {
+    } catch (err) {
       logger.error('Error occurred while request via axios:', err);
       logger.error('Error occurred while request via axios:', err);
       return res.status(502).send(err.message);
       return res.status(502).send(err.message);
     }
     }
@@ -286,31 +350,43 @@ export class GrowiToSlackCtrl {
   @Post('/:method')
   @Post('/:method')
   @UseBefore(AddWebclientResponseToRes, verifyGrowiToSlackRequest)
   @UseBefore(AddWebclientResponseToRes, verifyGrowiToSlackRequest)
   async callSlackApi(
   async callSlackApi(
-    @PathParams('method') method: string, @Req() req: GrowiReq, @Res() res: WebclientRes,
+    @PathParams('method') method: string,
+    @Req() req: GrowiReq,
+    @Res() res: WebclientRes,
   ): Promise<WebclientRes> {
   ): Promise<WebclientRes> {
     const { tokenGtoPs } = req;
     const { tokenGtoPs } = req;
 
 
     logger.debug('Slack API called: ', { method });
     logger.debug('Slack API called: ', { method });
 
 
     if (tokenGtoPs.length !== 1) {
     if (tokenGtoPs.length !== 1) {
-      return res.simulateWebAPIPlatformError('tokenGtoPs is invalid', 'invalid_tokenGtoP');
+      return res.simulateWebAPIPlatformError(
+        'tokenGtoPs is invalid',
+        'invalid_tokenGtoP',
+      );
     }
     }
 
 
     const tokenGtoP = tokenGtoPs[0];
     const tokenGtoP = tokenGtoPs[0];
 
 
     // retrieve relation with Installation
     // retrieve relation with Installation
-    const relation = await this.relationRepository.createQueryBuilder('relation')
+    const relation = await this.relationRepository
+      .createQueryBuilder('relation')
       .where('tokenGtoP = :token', { token: tokenGtoP })
       .where('tokenGtoP = :token', { token: tokenGtoP })
       .leftJoinAndSelect('relation.installation', 'installation')
       .leftJoinAndSelect('relation.installation', 'installation')
       .getOne();
       .getOne();
 
 
     if (relation == null) {
     if (relation == null) {
-      return res.simulateWebAPIPlatformError('relation is invalid', 'invalid_relation');
+      return res.simulateWebAPIPlatformError(
+        'relation is invalid',
+        'invalid_relation',
+      );
     }
     }
 
 
     const token = relation.installation.data.bot?.token;
     const token = relation.installation.data.bot?.token;
     if (token == null) {
     if (token == null) {
-      return res.simulateWebAPIPlatformError('installation is invalid', 'invalid_installation');
+      return res.simulateWebAPIPlatformError(
+        'installation is invalid',
+        'invalid_installation',
+      );
     }
     }
 
 
     // generate WebClient with no retry because GROWI main side will do
     // generate WebClient with no retry because GROWI main side will do
@@ -329,8 +405,7 @@ export class GrowiToSlackCtrl {
       const result = await client.apiCall(method, opt);
       const result = await client.apiCall(method, opt);
 
 
       return res.send(result);
       return res.send(result);
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       logger.error(err);
 
 
       if (err.code === ErrorCode.PlatformError) {
       if (err.code === ErrorCode.PlatformError) {
@@ -340,5 +415,4 @@ export class GrowiToSlackCtrl {
       return res.simulateWebAPIRequestError(err.message, err.response?.status);
       return res.simulateWebAPIRequestError(err.message, err.response?.status);
     }
     }
   }
   }
-
 }
 }

+ 1 - 3
apps/slackbot-proxy/src/controllers/privacy.ts

@@ -5,15 +5,13 @@ const isOfficialMode = process.env.OFFICIAL_MODE === 'true';
 
 
 @Controller('/privacy')
 @Controller('/privacy')
 export class PrivacyCtrl {
 export class PrivacyCtrl {
-
   constructor(router: PlatformRouter) {
   constructor(router: PlatformRouter) {
     if (isOfficialMode) {
     if (isOfficialMode) {
       router.get('/', this.getPrivacy);
       router.get('/', this.getPrivacy);
     }
     }
   }
   }
 
 
-  getPrivacy(req: Request, res: Response): string|void {
+  getPrivacy(req: Request, res: Response): string | void {
     res.render('privacy.ejs');
     res.render('privacy.ejs');
   }
   }
-
 }
 }

+ 268 - 122
apps/slackbot-proxy/src/controllers/slack.ts

@@ -1,13 +1,16 @@
-
-
 import { ServerResponse } from 'http';
 import { ServerResponse } from 'http';
 
 
 import {
 import {
-  type GrowiCommand, type IChannelOptionalId,
-  requiredScopes, REQUEST_TIMEOUT_FOR_PTOG,
+  type GrowiCommand,
+  type IChannelOptionalId,
+  REQUEST_TIMEOUT_FOR_PTOG,
+  requiredScopes,
   supportedGrowiCommands,
   supportedGrowiCommands,
 } from '@growi/slack';
 } from '@growi/slack';
-import { parseSlackInteractionRequest, verifySlackRequest } from '@growi/slack/dist/middlewares';
+import {
+  parseSlackInteractionRequest,
+  verifySlackRequest,
+} from '@growi/slack/dist/middlewares';
 import { InvalidGrowiCommandError } from '@growi/slack/dist/models';
 import { InvalidGrowiCommandError } from '@growi/slack/dist/models';
 import { markdownSectionBlock } from '@growi/slack/dist/utils/block-kit-builder';
 import { markdownSectionBlock } from '@growi/slack/dist/utils/block-kit-builder';
 import { respondRejectedErrors } from '@growi/slack/dist/utils/post-ephemeral-errors';
 import { respondRejectedErrors } from '@growi/slack/dist/utils/post-ephemeral-errors';
@@ -17,7 +20,14 @@ import { generateWebClient } from '@growi/slack/dist/utils/webclient-factory';
 import { Installation } from '@slack/oauth';
 import { Installation } from '@slack/oauth';
 import { WebAPICallResult } from '@slack/web-api';
 import { WebAPICallResult } from '@slack/web-api';
 import {
 import {
-  Controller, Get, Inject, PlatformResponse, Post, Req, Res, UseBefore,
+  Controller,
+  Get,
+  Inject,
+  PlatformResponse,
+  Post,
+  Req,
+  Res,
+  UseBefore,
 } from '@tsed/common';
 } from '@tsed/common';
 import axios from 'axios';
 import axios from 'axios';
 
 
@@ -25,7 +35,9 @@ import { Relation } from '~/entities/relation';
 import { SlackOauthReq } from '~/interfaces/slack-to-growi/slack-oauth-req';
 import { SlackOauthReq } from '~/interfaces/slack-to-growi/slack-oauth-req';
 import { AddSigningSecretToReq } from '~/middlewares/slack-to-growi/add-signing-secret-to-req';
 import { AddSigningSecretToReq } from '~/middlewares/slack-to-growi/add-signing-secret-to-req';
 import {
 import {
-  AuthorizeCommandMiddleware, AuthorizeInteractionMiddleware, AuthorizeEventsMiddleware,
+  AuthorizeCommandMiddleware,
+  AuthorizeEventsMiddleware,
+  AuthorizeInteractionMiddleware,
 } from '~/middlewares/slack-to-growi/authorizer';
 } from '~/middlewares/slack-to-growi/authorizer';
 import { ExtractGrowiUriFromReq } from '~/middlewares/slack-to-growi/extract-growi-uri-from-req';
 import { ExtractGrowiUriFromReq } from '~/middlewares/slack-to-growi/extract-growi-uri-from-req';
 import { UrlVerificationMiddleware } from '~/middlewares/slack-to-growi/url-verification';
 import { UrlVerificationMiddleware } from '~/middlewares/slack-to-growi/url-verification';
@@ -39,20 +51,26 @@ import { RelationsService } from '~/services/RelationsService';
 import { SelectGrowiService } from '~/services/SelectGrowiService';
 import { SelectGrowiService } from '~/services/SelectGrowiService';
 import { UnregisterService } from '~/services/UnregisterService';
 import { UnregisterService } from '~/services/UnregisterService';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
-import { postInstallSuccessMessage, postWelcomeMessageOnce } from '~/utils/welcome-message';
-
+import {
+  postInstallSuccessMessage,
+  postWelcomeMessageOnce,
+} from '~/utils/welcome-message';
 
 
 const logger = loggerFactory('slackbot-proxy:controllers:slack');
 const logger = loggerFactory('slackbot-proxy:controllers:slack');
 
 
-const postNotAllowedMessage = async(responseUrl, disallowedGrowiUrls:Set<string>, commandName:string):Promise<void> => {
-
+const postNotAllowedMessage = async (
+  responseUrl,
+  disallowedGrowiUrls: Set<string>,
+  commandName: string,
+): Promise<void> => {
   const linkUrlList = Array.from(disallowedGrowiUrls).map((growiUrl) => {
   const linkUrlList = Array.from(disallowedGrowiUrls).map((growiUrl) => {
-    return '\n'
-      + `• ${new URL('/admin/slack-integration', growiUrl).toString()}`;
+    return (
+      '\n' + `• ${new URL('/admin/slack-integration', growiUrl).toString()}`
+    );
   });
   });
 
 
-  const growiDocsLink = 'https://docs.growi.org/en/admin-guide/upgrading/43x.html';
-
+  const growiDocsLink =
+    'https://docs.growi.org/en/admin-guide/upgrading/43x.html';
 
 
   await respond(responseUrl, {
   await respond(responseUrl, {
     text: 'Error occured.',
     text: 'Error occured.',
@@ -72,7 +90,6 @@ const postNotAllowedMessage = async(responseUrl, disallowedGrowiUrls:Set<string>
 };
 };
 @Controller('/slack')
 @Controller('/slack')
 export class SlackCtrl {
 export class SlackCtrl {
-
   @Inject()
   @Inject()
   installerService: InstallerService;
   installerService: InstallerService;
 
 
@@ -107,41 +124,59 @@ export class SlackCtrl {
    * @param body
    * @param body
    * @returns
    * @returns
    */
    */
-  private async sendCommand(growiCommand: GrowiCommand, relations: Relation[], body: any) {
+  private async sendCommand(
+    growiCommand: GrowiCommand,
+    relations: Relation[],
+    body: any,
+  ) {
     if (relations.length === 0) {
     if (relations.length === 0) {
       throw new Error('relations must be set');
       throw new Error('relations must be set');
     }
     }
 
 
     const promises = relations.map((relation: Relation) => {
     const promises = relations.map((relation: Relation) => {
       // generate API URL
       // generate API URL
-      const url = new URL('/_api/v3/slack-integration/proxied/commands', relation.growiUri);
-      return axios.post(url.toString(), {
-        ...body,
-        growiCommand,
-      }, {
-        headers: {
-          'x-growi-ptog-tokens': relation.tokenPtoG,
+      const url = new URL(
+        '/_api/v3/slack-integration/proxied/commands',
+        relation.growiUri,
+      );
+      return axios.post(
+        url.toString(),
+        {
+          ...body,
+          growiCommand,
         },
         },
-        timeout: REQUEST_TIMEOUT_FOR_PTOG,
-      });
+        {
+          headers: {
+            'x-growi-ptog-tokens': relation.tokenPtoG,
+          },
+          timeout: REQUEST_TIMEOUT_FOR_PTOG,
+        },
+      );
     });
     });
 
 
     // pickup PromiseRejectedResult only
     // pickup PromiseRejectedResult only
     const results = await Promise.allSettled(promises);
     const results = await Promise.allSettled(promises);
-    const rejectedResults: PromiseRejectedResult[] = results.filter((result): result is PromiseRejectedResult => result.status === 'rejected');
+    const rejectedResults: PromiseRejectedResult[] = results.filter(
+      (result): result is PromiseRejectedResult => result.status === 'rejected',
+    );
 
 
     try {
     try {
       return respondRejectedErrors(rejectedResults, growiCommand.responseUrl);
       return respondRejectedErrors(rejectedResults, growiCommand.responseUrl);
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       logger.error(err);
     }
     }
   }
   }
 
 
-
   @Post('/commands')
   @Post('/commands')
-  @UseBefore(AddSigningSecretToReq, verifySlackRequest, AuthorizeCommandMiddleware)
-  async handleCommand(@Req() req: SlackOauthReq, @Res() res: Res): Promise<void|string|Res|WebAPICallResult> {
+  @UseBefore(
+    AddSigningSecretToReq,
+    verifySlackRequest,
+    AuthorizeCommandMiddleware,
+  )
+  async handleCommand(
+    @Req() req: SlackOauthReq,
+    @Res() res: Res,
+  ): Promise<void | string | Res | WebAPICallResult> {
     const { body, authorizeResult } = req;
     const { body, authorizeResult } = req;
 
 
     // retrieve bot token
     // retrieve bot token
@@ -151,7 +186,9 @@ export class SlackCtrl {
       res.json({
       res.json({
         blocks: [
         blocks: [
           markdownSectionBlock('*Installation might be failed.*'),
           markdownSectionBlock('*Installation might be failed.*'),
-          markdownSectionBlock(`Access to ${serverUri} and re-install GROWI App`),
+          markdownSectionBlock(
+            `Access to ${serverUri} and re-install GROWI App`,
+          ),
         ],
         ],
       });
       });
     }
     }
@@ -160,13 +197,14 @@ export class SlackCtrl {
     let growiCommand: GrowiCommand;
     let growiCommand: GrowiCommand;
     try {
     try {
       growiCommand = parseSlashCommand(body);
       growiCommand = parseSlashCommand(body);
-    }
-    catch (err) {
+    } catch (err) {
       if (err instanceof InvalidGrowiCommandError) {
       if (err instanceof InvalidGrowiCommandError) {
         res.json({
         res.json({
           blocks: [
           blocks: [
             markdownSectionBlock('*Command type is not specified.*'),
             markdownSectionBlock('*Command type is not specified.*'),
-            markdownSectionBlock('Run `/growi help` to check the commands you can use.'),
+            markdownSectionBlock(
+              'Run `/growi help` to check the commands you can use.',
+            ),
           ],
           ],
         });
         });
       }
       }
@@ -180,19 +218,31 @@ export class SlackCtrl {
 
 
     // register
     // register
     if (this.registerService.shouldHandleCommand(growiCommand)) {
     if (this.registerService.shouldHandleCommand(growiCommand)) {
-      return this.registerService.processCommand(growiCommand, authorizeResult, body);
+      return this.registerService.processCommand(
+        growiCommand,
+        authorizeResult,
+        body,
+      );
     }
     }
 
 
     // unregister
     // unregister
     if (this.unregisterService.shouldHandleCommand(growiCommand)) {
     if (this.unregisterService.shouldHandleCommand(growiCommand)) {
-      return this.unregisterService.processCommand(growiCommand, authorizeResult);
+      return this.unregisterService.processCommand(
+        growiCommand,
+        authorizeResult,
+      );
     }
     }
 
 
     // get relations
     // get relations
-    const installationId = authorizeResult.enterpriseId || authorizeResult.teamId;
+    const installationId =
+      authorizeResult.enterpriseId || authorizeResult.teamId;
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const installation = await this.installationRepository.findByTeamIdOrEnterpriseId(installationId!);
-    const relations = await this.relationRepository.createQueryBuilder('relation')
+    const installation =
+      await this.installationRepository.findByTeamIdOrEnterpriseId(
+        installationId!,
+      );
+    const relations = await this.relationRepository
+      .createQueryBuilder('relation')
       .where('relation.installationId = :id', { id: installation?.id })
       .where('relation.installationId = :id', { id: installation?.id })
       .leftJoinAndSelect('relation.installation', 'installation')
       .leftJoinAndSelect('relation.installation', 'installation')
       .getMany();
       .getMany();
@@ -211,7 +261,9 @@ export class SlackCtrl {
       return respond(growiCommand.responseUrl, {
       return respond(growiCommand.responseUrl, {
         blocks: [
         blocks: [
           markdownSectionBlock('*Found Relations to GROWI.*'),
           markdownSectionBlock('*Found Relations to GROWI.*'),
-          ...relations.map(relation => markdownSectionBlock(`GROWI url: ${relation.growiUri}`)),
+          ...relations.map((relation) =>
+            markdownSectionBlock(`GROWI url: ${relation.growiUri}`),
+          ),
         ],
         ],
       });
       });
     }
     }
@@ -223,7 +275,9 @@ export class SlackCtrl {
         blocks: [
         blocks: [
           markdownSectionBlock('*Command is not supported*'),
           markdownSectionBlock('*Command is not supported*'),
           // eslint-disable-next-line max-len
           // eslint-disable-next-line max-len
-          markdownSectionBlock(`\`/growi ${growiCommand.growiCommandType}\` command is not supported in this version of GROWI bot. Run \`/growi help\` to see all supported commands.`),
+          markdownSectionBlock(
+            `\`/growi ${growiCommand.growiCommandType}\` command is not supported in this version of GROWI bot. Run \`/growi help\` to see all supported commands.`,
+          ),
         ],
         ],
       });
       });
     }
     }
@@ -233,8 +287,8 @@ export class SlackCtrl {
       return this.sendCommand(growiCommand, relations, body);
       return this.sendCommand(growiCommand, relations, body);
     }
     }
 
 
-    const allowedRelationsForSingleUse:Relation[] = [];
-    const allowedRelationsForBroadcastUse:Relation[] = [];
+    const allowedRelationsForSingleUse: Relation[] = [];
+    const allowedRelationsForBroadcastUse: Relation[] = [];
     const disallowedGrowiUrls: Set<string> = new Set();
     const disallowedGrowiUrls: Set<string> = new Set();
 
 
     const channel: IChannelOptionalId = {
     const channel: IChannelOptionalId = {
@@ -243,43 +297,53 @@ export class SlackCtrl {
     };
     };
 
 
     // check permission
     // check permission
-    await Promise.all(relations.map(async(relation) => {
-      const isSupportedForSingleUse = await this.relationsService.isPermissionsForSingleUseCommands(
-        relation, growiCommand.growiCommandType, channel,
-      );
-
-      let isSupportedForBroadcastUse = false;
-      if (!isSupportedForSingleUse) {
-        isSupportedForBroadcastUse = await this.relationsService.isPermissionsUseBroadcastCommands(
-          relation, growiCommand.growiCommandType, channel,
-        );
-      }
-
-      if (isSupportedForSingleUse) {
-        allowedRelationsForSingleUse.push(relation);
-      }
-      else if (isSupportedForBroadcastUse) {
-        allowedRelationsForBroadcastUse.push(relation);
-      }
-      else {
-        disallowedGrowiUrls.add(relation.growiUri);
-      }
-    }));
+    await Promise.all(
+      relations.map(async (relation) => {
+        const isSupportedForSingleUse =
+          await this.relationsService.isPermissionsForSingleUseCommands(
+            relation,
+            growiCommand.growiCommandType,
+            channel,
+          );
+
+        let isSupportedForBroadcastUse = false;
+        if (!isSupportedForSingleUse) {
+          isSupportedForBroadcastUse =
+            await this.relationsService.isPermissionsUseBroadcastCommands(
+              relation,
+              growiCommand.growiCommandType,
+              channel,
+            );
+        }
+
+        if (isSupportedForSingleUse) {
+          allowedRelationsForSingleUse.push(relation);
+        } else if (isSupportedForBroadcastUse) {
+          allowedRelationsForBroadcastUse.push(relation);
+        } else {
+          disallowedGrowiUrls.add(relation.growiUri);
+        }
+      }),
+    );
 
 
     // when all of GROWI disallowed
     // when all of GROWI disallowed
     if (relations.length === disallowedGrowiUrls.size) {
     if (relations.length === disallowedGrowiUrls.size) {
       const linkUrlList = Array.from(disallowedGrowiUrls).map((growiUrl) => {
       const linkUrlList = Array.from(disallowedGrowiUrls).map((growiUrl) => {
-        return '\n'
-          + `• ${new URL('/admin/slack-integration', growiUrl).toString()}`;
+        return (
+          '\n' + `• ${new URL('/admin/slack-integration', growiUrl).toString()}`
+        );
       });
       });
 
 
-      const growiDocsLink = 'https://docs.growi.org/en/admin-guide/upgrading/43x.html';
+      const growiDocsLink =
+        'https://docs.growi.org/en/admin-guide/upgrading/43x.html';
 
 
       return respond(growiCommand.responseUrl, {
       return respond(growiCommand.responseUrl, {
         text: 'Command not permitted.',
         text: 'Command not permitted.',
         blocks: [
         blocks: [
           markdownSectionBlock('*None of GROWI permitted the command.*'),
           markdownSectionBlock('*None of GROWI permitted the command.*'),
-          markdownSectionBlock(`*'${growiCommand.growiCommandType}'* command was not allowed.`),
+          markdownSectionBlock(
+            `*'${growiCommand.growiCommandType}'* command was not allowed.`,
+          ),
           markdownSectionBlock(
           markdownSectionBlock(
             `To use this command, modify settings from following pages: ${linkUrlList}`,
             `To use this command, modify settings from following pages: ${linkUrlList}`,
           ),
           ),
@@ -292,25 +356,47 @@ export class SlackCtrl {
 
 
     // select GROWI
     // select GROWI
     if (allowedRelationsForSingleUse.length > 0) {
     if (allowedRelationsForSingleUse.length > 0) {
-      body.growiUrisForSingleUse = allowedRelationsForSingleUse.map(v => v.growiUri);
-      return this.selectGrowiService.processCommand(growiCommand, authorizeResult, body);
+      body.growiUrisForSingleUse = allowedRelationsForSingleUse.map(
+        (v) => v.growiUri,
+      );
+      return this.selectGrowiService.processCommand(
+        growiCommand,
+        authorizeResult,
+        body,
+      );
     }
     }
 
 
     // forward to GROWI server
     // forward to GROWI server
     if (allowedRelationsForBroadcastUse.length > 0) {
     if (allowedRelationsForBroadcastUse.length > 0) {
-      return this.sendCommand(growiCommand, allowedRelationsForBroadcastUse, body);
+      return this.sendCommand(
+        growiCommand,
+        allowedRelationsForBroadcastUse,
+        body,
+      );
     }
     }
   }
   }
 
 
-
   @Post('/interactions')
   @Post('/interactions')
-  @UseBefore(AddSigningSecretToReq, verifySlackRequest, parseSlackInteractionRequest, AuthorizeInteractionMiddleware, ExtractGrowiUriFromReq)
-  async handleInteraction(@Req() req: SlackOauthReq, @Res() res: Res): Promise<void|string|Res|WebAPICallResult> {
+  @UseBefore(
+    AddSigningSecretToReq,
+    verifySlackRequest,
+    parseSlackInteractionRequest,
+    AuthorizeInteractionMiddleware,
+    ExtractGrowiUriFromReq,
+  )
+  async handleInteraction(
+    @Req() req: SlackOauthReq,
+    @Res() res: Res,
+  ): Promise<void | string | Res | WebAPICallResult> {
     logger.info('receive interaction', req.authorizeResult);
     logger.info('receive interaction', req.authorizeResult);
     logger.debug('receive interaction', req.body);
     logger.debug('receive interaction', req.body);
 
 
     const {
     const {
-      body, authorizeResult, interactionPayload, interactionPayloadAccessor, growiUri,
+      body,
+      authorizeResult,
+      interactionPayload,
+      interactionPayloadAccessor,
+      growiUri,
     } = req;
     } = req;
 
 
     // pass
     // pass
@@ -322,27 +408,48 @@ export class SlackCtrl {
     }
     }
 
 
     // register
     // register
-    const registerResult = await this.registerService.processInteraction(authorizeResult, interactionPayload, interactionPayloadAccessor);
+    const registerResult = await this.registerService.processInteraction(
+      authorizeResult,
+      interactionPayload,
+      interactionPayloadAccessor,
+    );
     if (registerResult.isTerminated) return;
     if (registerResult.isTerminated) return;
     // unregister
     // unregister
-    const unregisterResult = await this.unregisterService.processInteraction(authorizeResult, interactionPayload, interactionPayloadAccessor);
+    const unregisterResult = await this.unregisterService.processInteraction(
+      authorizeResult,
+      interactionPayload,
+      interactionPayloadAccessor,
+    );
     if (unregisterResult.isTerminated) return;
     if (unregisterResult.isTerminated) return;
 
 
     // immediate response to slack
     // immediate response to slack
     res.send();
     res.send();
 
 
     // select growi
     // select growi
-    const selectGrowiResult = await this.selectGrowiService.processInteraction(authorizeResult, interactionPayload, interactionPayloadAccessor);
+    const selectGrowiResult = await this.selectGrowiService.processInteraction(
+      authorizeResult,
+      interactionPayload,
+      interactionPayloadAccessor,
+    );
     const selectedGrowiInformation = selectGrowiResult.result;
     const selectedGrowiInformation = selectGrowiResult.result;
     if (!selectGrowiResult.isTerminated && selectedGrowiInformation != null) {
     if (!selectGrowiResult.isTerminated && selectedGrowiInformation != null) {
-      return this.sendCommand(selectedGrowiInformation.growiCommand, [selectedGrowiInformation.relation], selectedGrowiInformation.sendCommandBody);
+      return this.sendCommand(
+        selectedGrowiInformation.growiCommand,
+        [selectedGrowiInformation.relation],
+        selectedGrowiInformation.sendCommandBody,
+      );
     }
     }
 
 
     // check permission
     // check permission
-    const installationId = authorizeResult.enterpriseId || authorizeResult.teamId;
+    const installationId =
+      authorizeResult.enterpriseId || authorizeResult.teamId;
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const installation = await this.installationRepository.findByTeamIdOrEnterpriseId(installationId!);
-    const relations = await this.relationRepository.createQueryBuilder('relation')
+    const installation =
+      await this.installationRepository.findByTeamIdOrEnterpriseId(
+        installationId!,
+      );
+    const relations = await this.relationRepository
+      .createQueryBuilder('relation')
       .where('relation.installationId = :id', { id: installation?.id })
       .where('relation.installationId = :id', { id: installation?.id })
       .andWhere('relation.growiUri = :uri', { uri: growiUri })
       .andWhere('relation.growiUri = :uri', { uri: growiUri })
       .leftJoinAndSelect('relation.installation', 'installation')
       .leftJoinAndSelect('relation.installation', 'installation')
@@ -357,7 +464,8 @@ export class SlackCtrl {
       });
       });
     }
     }
 
 
-    const { actionId, callbackId } = interactionPayloadAccessor.getActionIdAndCallbackIdFromPayLoad();
+    const { actionId, callbackId } =
+      interactionPayloadAccessor.getActionIdAndCallbackIdFromPayLoad();
 
 
     const privateMeta = interactionPayloadAccessor.getViewPrivateMetaData();
     const privateMeta = interactionPayloadAccessor.getViewPrivateMetaData();
 
 
@@ -365,48 +473,74 @@ export class SlackCtrl {
       name: privateMeta?.body?.channel_name || privateMeta?.channelName,
       name: privateMeta?.body?.channel_name || privateMeta?.channelName,
     };
     };
 
 
-    const channel: IChannelOptionalId = interactionPayload.channel || channelFromMeta;
-    const permission = await this.relationsService.checkPermissionForInteractions(relations, actionId, callbackId, channel);
+    const channel: IChannelOptionalId =
+      interactionPayload.channel || channelFromMeta;
+    const permission =
+      await this.relationsService.checkPermissionForInteractions(
+        relations,
+        actionId,
+        callbackId,
+        channel,
+      );
 
 
     const {
     const {
-      allowedRelations, disallowedGrowiUrls, commandName, rejectedResults,
+      allowedRelations,
+      disallowedGrowiUrls,
+      commandName,
+      rejectedResults,
     } = permission;
     } = permission;
 
 
     try {
     try {
-      await respondRejectedErrors(rejectedResults, interactionPayloadAccessor.getResponseUrl());
-    }
-    catch (err) {
+      await respondRejectedErrors(
+        rejectedResults,
+        interactionPayloadAccessor.getResponseUrl(),
+      );
+    } catch (err) {
       logger.error(err);
       logger.error(err);
     }
     }
 
 
     if (relations.length === disallowedGrowiUrls.size) {
     if (relations.length === disallowedGrowiUrls.size) {
-      return postNotAllowedMessage(interactionPayloadAccessor.getResponseUrl(), disallowedGrowiUrls, commandName);
+      return postNotAllowedMessage(
+        interactionPayloadAccessor.getResponseUrl(),
+        disallowedGrowiUrls,
+        commandName,
+      );
     }
     }
 
 
     /*
     /*
      * forward to GROWI server
      * forward to GROWI server
      */
      */
-    allowedRelations.map(async(relation) => {
+    allowedRelations.map(async (relation) => {
       try {
       try {
         // generate API URL
         // generate API URL
-        const url = new URL('/_api/v3/slack-integration/proxied/interactions', relation.growiUri);
-        await axios.post(url.toString(), {
-          ...body,
-        }, {
-          headers: {
-            'x-growi-ptog-tokens': relation.tokenPtoG,
+        const url = new URL(
+          '/_api/v3/slack-integration/proxied/interactions',
+          relation.growiUri,
+        );
+        await axios.post(
+          url.toString(),
+          {
+            ...body,
           },
           },
-        });
-      }
-      catch (err) {
+          {
+            headers: {
+              'x-growi-ptog-tokens': relation.tokenPtoG,
+            },
+          },
+        );
+      } catch (err) {
         logger.error(err);
         logger.error(err);
       }
       }
-
     });
     });
   }
   }
 
 
   @Post('/events')
   @Post('/events')
-  @UseBefore(UrlVerificationMiddleware, AddSigningSecretToReq, verifySlackRequest, AuthorizeEventsMiddleware)
+  @UseBefore(
+    UrlVerificationMiddleware,
+    AddSigningSecretToReq,
+    verifySlackRequest,
+    AuthorizeEventsMiddleware,
+  )
   async handleEvent(@Req() req: SlackOauthReq): Promise<void> {
   async handleEvent(@Req() req: SlackOauthReq): Promise<void> {
     const { authorizeResult } = req;
     const { authorizeResult } = req;
     const client = generateWebClient(authorizeResult.botToken);
     const client = generateWebClient(authorizeResult.botToken);
@@ -416,8 +550,7 @@ export class SlackCtrl {
     if (event.type === 'app_home_opened') {
     if (event.type === 'app_home_opened') {
       try {
       try {
         await postWelcomeMessageOnce(client, event.channel);
         await postWelcomeMessageOnce(client, event.channel);
-      }
-      catch (err) {
+      } catch (err) {
         logger.error('Failed to post welcome message', err);
         logger.error('Failed to post welcome message', err);
       }
       }
     }
     }
@@ -431,26 +564,32 @@ export class SlackCtrl {
   }
   }
 
 
   @Get('/oauth_redirect')
   @Get('/oauth_redirect')
-  async handleOauthRedirect(@Req() req: Req, @Res() serverRes: ServerResponse, @Res() platformRes: PlatformResponse): Promise<void|string> {
-
+  async handleOauthRedirect(
+    @Req() req: Req,
+    @Res() serverRes: ServerResponse,
+    @Res() platformRes: PlatformResponse,
+  ): Promise<void | string> {
     // create 'Add to Slack' url
     // create 'Add to Slack' url
-    const addToSlackUrl = await this.installerService.installer.generateInstallUrl({
-      scopes: requiredScopes,
-    });
+    const addToSlackUrl =
+      await this.installerService.installer.generateInstallUrl({
+        scopes: requiredScopes,
+      });
 
 
     const state = req.query.state;
     const state = req.query.state;
     if (state == null || state === '') {
     if (state == null || state === '') {
-      return platformRes.status(400).render('install-failed.ejs', { url: addToSlackUrl });
+      return platformRes
+        .status(400)
+        .render('install-failed.ejs', { url: addToSlackUrl });
     }
     }
 
 
     // promisify
     // promisify
     const installPromise = new Promise<Installation>((resolve, reject) => {
     const installPromise = new Promise<Installation>((resolve, reject) => {
       this.installerService.installer.handleCallback(req, serverRes, {
       this.installerService.installer.handleCallback(req, serverRes, {
-        success: async(installation, metadata) => {
+        success: async (installation, metadata) => {
           logger.info('Success to install', { installation, metadata });
           logger.info('Success to install', { installation, metadata });
           resolve(installation);
           resolve(installation);
         },
         },
-        failure: async(error) => {
+        failure: async (error) => {
           reject(error); // go to catch block
           reject(error); // go to catch block
         },
         },
       });
       });
@@ -463,14 +602,21 @@ export class SlackCtrl {
 
 
       // check whether bot is not null
       // check whether bot is not null
       if (installation.bot == null) {
       if (installation.bot == null) {
-        logger.warn('Success to install but something wrong. `installation.bot` is null.');
+        logger.warn(
+          'Success to install but something wrong. `installation.bot` is null.',
+        );
         httpStatus = 500;
         httpStatus = 500;
-        httpBody = await platformRes.render('install-succeeded-but-has-problem.ejs', { reason: '`installation.bot` is null' });
+        httpBody = await platformRes.render(
+          'install-succeeded-but-has-problem.ejs',
+          { reason: '`installation.bot` is null' },
+        );
       }
       }
       // MAIN PATH: everything is fine
       // MAIN PATH: everything is fine
       else {
       else {
         const appPageUrl = `https://slack.com/apps/${installation.appId}`;
         const appPageUrl = `https://slack.com/apps/${installation.appId}`;
-        httpBody = await platformRes.render('install-succeeded.ejs', { appPageUrl });
+        httpBody = await platformRes.render('install-succeeded.ejs', {
+          appPageUrl,
+        });
 
 
         // generate client
         // generate client
         const client = generateWebClient(installation.bot.token);
         const client = generateWebClient(installation.bot.token);
@@ -485,15 +631,15 @@ export class SlackCtrl {
           // publishInitialHomeView(client, userId),
           // publishInitialHomeView(client, userId),
         ]);
         ]);
       }
       }
-    }
-    catch (error) {
+    } catch (error) {
       logger.error(error);
       logger.error(error);
       httpStatus = 500;
       httpStatus = 500;
-      httpBody = await platformRes.status(400).render('install-failed.ejs', { url: addToSlackUrl });
+      httpBody = await platformRes
+        .status(400)
+        .render('install-failed.ejs', { url: addToSlackUrl });
     }
     }
 
 
     platformRes.status(httpStatus);
     platformRes.status(httpStatus);
     return httpBody;
     return httpBody;
   }
   }
-
 }
 }

+ 1 - 3
apps/slackbot-proxy/src/controllers/term.ts

@@ -7,15 +7,13 @@ const isOfficialMode = process.env.OFFICIAL_MODE === 'true';
 
 
 @Controller('/term')
 @Controller('/term')
 export class TermCtrl {
 export class TermCtrl {
-
   constructor(router: PlatformRouter) {
   constructor(router: PlatformRouter) {
     if (isOfficialMode) {
     if (isOfficialMode) {
       router.get('/', this.getTerm);
       router.get('/', this.getTerm);
     }
     }
   }
   }
 
 
-  getTerm(req: Request, res: Response): string|void {
+  getTerm(req: Request, res: Response): string | void {
     res.render('term.ejs');
     res.render('term.ejs');
   }
   }
-
 }
 }

+ 1 - 5
apps/slackbot-proxy/src/controllers/top.ts

@@ -1,7 +1,5 @@
 import { requiredScopes } from '@growi/slack';
 import { requiredScopes } from '@growi/slack';
-import {
-  Controller, Get, Inject, View,
-} from '@tsed/common';
+import { Controller, Get, Inject, View } from '@tsed/common';
 import readPkgUp from 'read-pkg-up';
 import readPkgUp from 'read-pkg-up';
 
 
 /* eslint-disable @typescript-eslint/consistent-type-imports */
 /* eslint-disable @typescript-eslint/consistent-type-imports */
@@ -12,7 +10,6 @@ const isOfficialMode = process.env.OFFICIAL_MODE === 'true';
 
 
 @Controller('/')
 @Controller('/')
 export class TopCtrl {
 export class TopCtrl {
-
   @Inject()
   @Inject()
   installerService: InstallerService;
   installerService: InstallerService;
 
 
@@ -30,5 +27,4 @@ export class TopCtrl {
 
 
     return { url, isOfficialMode, growiBotVersion };
     return { url, isOfficialMode, growiBotVersion };
   }
   }
-
 }
 }

+ 6 - 7
apps/slackbot-proxy/src/entities/installation.ts

@@ -1,15 +1,15 @@
 import { Installation as SlackInstallation } from '@slack/oauth';
 import { Installation as SlackInstallation } from '@slack/oauth';
+import { Required } from '@tsed/schema';
 import {
 import {
-  Required,
-} from '@tsed/schema';
-import {
-  Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn,
+  Column,
+  CreateDateColumn,
+  Entity,
+  PrimaryGeneratedColumn,
+  UpdateDateColumn,
 } from 'typeorm';
 } from 'typeorm';
 
 
-
 @Entity()
 @Entity()
 export class Installation {
 export class Installation {
-
   @PrimaryGeneratedColumn()
   @PrimaryGeneratedColumn()
   readonly id: number;
   readonly id: number;
 
 
@@ -39,5 +39,4 @@ export class Installation {
     this.teamId = slackInstallation.team?.id;
     this.teamId = slackInstallation.team?.id;
     this.enterpriseId = slackInstallation.enterprise?.id;
     this.enterpriseId = slackInstallation.enterprise?.id;
   }
   }
-
 }
 }

+ 7 - 4
apps/slackbot-proxy/src/entities/order.ts

@@ -1,12 +1,16 @@
 import {
 import {
-  Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn, ManyToOne,
+  Column,
+  CreateDateColumn,
+  Entity,
+  ManyToOne,
+  PrimaryGeneratedColumn,
+  UpdateDateColumn,
 } from 'typeorm';
 } from 'typeorm';
 
 
 import { Installation } from './installation';
 import { Installation } from './installation';
 
 
 @Entity()
 @Entity()
 export class Order {
 export class Order {
-
   @PrimaryGeneratedColumn()
   @PrimaryGeneratedColumn()
   readonly id: number;
   readonly id: number;
 
 
@@ -31,11 +35,10 @@ export class Order {
   @Column()
   @Column()
   tokenPtoG: string;
   tokenPtoG: string;
 
 
-  isExpired():boolean {
+  isExpired(): boolean {
     const now = Date.now();
     const now = Date.now();
     const expiredAt = this.createdAt.getTime() + 600000;
     const expiredAt = this.createdAt.getTime() + 600000;
 
 
     return expiredAt < now;
     return expiredAt < now;
   }
   }
-
 }
 }

+ 8 - 4
apps/slackbot-proxy/src/entities/relation.ts

@@ -1,18 +1,23 @@
 import { differenceInMilliseconds } from 'date-fns/differenceInMilliseconds';
 import { differenceInMilliseconds } from 'date-fns/differenceInMilliseconds';
 import {
 import {
-  Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn, ManyToOne, Index,
+  Column,
+  CreateDateColumn,
+  Entity,
+  Index,
+  ManyToOne,
+  PrimaryGeneratedColumn,
+  UpdateDateColumn,
 } from 'typeorm';
 } from 'typeorm';
 
 
 import { Installation } from './installation';
 import { Installation } from './installation';
 
 
 export interface PermissionSettingsInterface {
 export interface PermissionSettingsInterface {
-  [commandName: string]: boolean | string[],
+  [commandName: string]: boolean | string[];
 }
 }
 
 
 @Entity()
 @Entity()
 @Index(['installation', 'growiUri'], { unique: true })
 @Index(['installation', 'growiUri'], { unique: true })
 export class Relation {
 export class Relation {
-
   @PrimaryGeneratedColumn()
   @PrimaryGeneratedColumn()
   readonly id: number;
   readonly id: number;
 
 
@@ -48,5 +53,4 @@ export class Relation {
   getDistanceInMillisecondsToExpiredAt(baseDate: Date): number {
   getDistanceInMillisecondsToExpiredAt(baseDate: Date): number {
     return differenceInMilliseconds(this.expiredAtCommands, baseDate);
     return differenceInMilliseconds(this.expiredAtCommands, baseDate);
   }
   }
-
 }
 }

+ 5 - 3
apps/slackbot-proxy/src/entities/system-information.ts

@@ -1,10 +1,13 @@
 import {
 import {
-  Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn,
+  Column,
+  CreateDateColumn,
+  Entity,
+  PrimaryGeneratedColumn,
+  UpdateDateColumn,
 } from 'typeorm';
 } from 'typeorm';
 
 
 @Entity()
 @Entity()
 export class SystemInformation {
 export class SystemInformation {
-
   @PrimaryGeneratedColumn()
   @PrimaryGeneratedColumn()
   readonly id: number;
   readonly id: number;
 
 
@@ -20,5 +23,4 @@ export class SystemInformation {
   setVersion(version: string): void {
   setVersion(version: string): void {
     this.version = version;
     this.version = version;
   }
   }
-
 }
 }

+ 12 - 10
apps/slackbot-proxy/src/filters/CustomHttpErrorFilter.ts

@@ -1,22 +1,24 @@
 import {
 import {
-  Catch, ExceptionFilterMethods, PlatformContext, PlatformResponse,
+  Catch,
+  ExceptionFilterMethods,
+  PlatformContext,
+  PlatformResponse,
 } from '@tsed/common';
 } from '@tsed/common';
 
 
 import { CustomHttpError } from '~/models/errors';
 import { CustomHttpError } from '~/models/errors';
 
 
 @Catch(CustomHttpError)
 @Catch(CustomHttpError)
 export class CustomHttpErrorFilter implements ExceptionFilterMethods {
 export class CustomHttpErrorFilter implements ExceptionFilterMethods {
-
-  async catch(exception: CustomHttpError, ctx: PlatformContext): Promise<PlatformResponse<any>> {
+  async catch(
+    exception: CustomHttpError,
+    ctx: PlatformContext,
+  ): Promise<PlatformResponse<any>> {
     const { httpError } = exception;
     const { httpError } = exception;
     const { response } = ctx;
     const { response } = ctx;
 
 
-    return response
-      .status(httpError.status)
-      .body({
-        status: httpError.status,
-        message: httpError.message,
-      });
+    return response.status(httpError.status).body({
+      status: httpError.status,
+      message: httpError.message,
+    });
   }
   }
-
 }
 }

+ 10 - 7
apps/slackbot-proxy/src/filters/ResourceNotFoundFilter.ts

@@ -1,11 +1,17 @@
 import {
 import {
-  Catch, ExceptionFilterMethods, PlatformContext, PlatformResponse, ResourceNotFound,
+  Catch,
+  ExceptionFilterMethods,
+  PlatformContext,
+  PlatformResponse,
+  ResourceNotFound,
 } from '@tsed/common';
 } from '@tsed/common';
 
 
 @Catch(ResourceNotFound)
 @Catch(ResourceNotFound)
 export class ResourceNotFoundFilter implements ExceptionFilterMethods {
 export class ResourceNotFoundFilter implements ExceptionFilterMethods {
-
-  async catch(exception: ResourceNotFound, ctx: PlatformContext): Promise<PlatformResponse<any>> {
+  async catch(
+    exception: ResourceNotFound,
+    ctx: PlatformContext,
+  ): Promise<PlatformResponse<any>> {
     const { response } = ctx;
     const { response } = ctx;
 
 
     const obj = {
     const obj = {
@@ -14,9 +20,6 @@ export class ResourceNotFoundFilter implements ExceptionFilterMethods {
       url: exception.url,
       url: exception.url,
     };
     };
 
 
-    return response
-      .status(exception.status)
-      .body(obj);
+    return response.status(exception.status).body(obj);
   }
   }
-
 }
 }

+ 1 - 2
apps/slackbot-proxy/src/index.ts

@@ -19,8 +19,7 @@ async function bootstrap() {
       $log.info('"--ci" flag is detected. Exit process.');
       $log.info('"--ci" flag is detected. Exit process.');
       process.exit();
       process.exit();
     }
     }
-  }
-  catch (er) {
+  } catch (er) {
     $log.error(er);
     $log.error(er);
     process.exit(1);
     process.exit(1);
   }
   }

+ 9 - 9
apps/slackbot-proxy/src/interfaces/growi-uri-injector.ts

@@ -1,11 +1,11 @@
 export type GrowiUriWithOriginalData = {
 export type GrowiUriWithOriginalData = {
-  growiUri: string,
-  originalData: string,
-}
+  growiUri: string;
+  originalData: string;
+};
 
 
 export type TypedBlock = {
 export type TypedBlock = {
-  type: string,
-}
+  type: string;
+};
 
 
 /**
 /**
  * Type guard for GrowiUriWithOriginalData
  * Type guard for GrowiUriWithOriginalData
@@ -13,16 +13,16 @@ export type TypedBlock = {
  * @returns
  * @returns
  */
  */
 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-export const isGrowiUriWithOriginalData = (data: any): data is GrowiUriWithOriginalData => {
+export const isGrowiUriWithOriginalData = (
+  data: any,
+): data is GrowiUriWithOriginalData => {
   return data.growiUri != null && data.originalData != null;
   return data.growiUri != null && data.originalData != null;
 };
 };
 
 
 export interface GrowiUriInjector<ISDATA, IDATA, ESDATA, EDATA> {
 export interface GrowiUriInjector<ISDATA, IDATA, ESDATA, EDATA> {
-
   shouldHandleToInject(data: ISDATA & any): data is IDATA;
   shouldHandleToInject(data: ISDATA & any): data is IDATA;
-  inject(data: IDATA, growiUri:string): void;
+  inject(data: IDATA, growiUri: string): void;
 
 
   shouldHandleToExtract(data: ESDATA & any): data is EDATA;
   shouldHandleToExtract(data: ESDATA & any): data is EDATA;
   extract(data: EDATA): GrowiUriWithOriginalData;
   extract(data: EDATA): GrowiUriWithOriginalData;
-
 }
 }

+ 5 - 4
apps/slackbot-proxy/src/interfaces/slack-to-growi/slack-oauth-req.ts

@@ -2,7 +2,8 @@ import type { RequestFromSlack } from '@growi/slack';
 import type { AuthorizeResult } from '@slack/oauth';
 import type { AuthorizeResult } from '@slack/oauth';
 import type { Req } from '@tsed/common';
 import type { Req } from '@tsed/common';
 
 
-export type SlackOauthReq = Req & RequestFromSlack & {
-  authorizeResult: AuthorizeResult,
-  growiUri?: string,
-};
+export type SlackOauthReq = Req &
+  RequestFromSlack & {
+    authorizeResult: AuthorizeResult;
+    growiUri?: string;
+  };

+ 14 - 12
apps/slackbot-proxy/src/middlewares/GlobalHttpErrorHandlingMiddleware.ts

@@ -1,7 +1,10 @@
 /* eslint-disable @typescript-eslint/consistent-type-imports */
 /* eslint-disable @typescript-eslint/consistent-type-imports */
 import {
 import {
-  PlatformContext, PlatformResponse,
-  Err, Middleware, Next,
+  Err,
+  Middleware,
+  Next,
+  PlatformContext,
+  PlatformResponse,
 } from '@tsed/common';
 } from '@tsed/common';
 /* eslint-enable @typescript-eslint/consistent-type-imports */
 /* eslint-enable @typescript-eslint/consistent-type-imports */
 import type { HttpError } from 'http-errors';
 import type { HttpError } from 'http-errors';
@@ -9,23 +12,22 @@ import { isHttpError } from 'http-errors';
 
 
 @Middleware()
 @Middleware()
 export class GlobalHttpErrorHandlingMiddleware {
 export class GlobalHttpErrorHandlingMiddleware {
-
-  use(@Err() err: unknown, @Next() next: Next, ctx: PlatformContext): PlatformResponse|void {
-
+  use(
+    @Err() err: unknown,
+    @Next() next: Next,
+    ctx: PlatformContext,
+  ): PlatformResponse | void {
     // handle if the err is a HttpError instance
     // handle if the err is a HttpError instance
     if (isHttpError(err)) {
     if (isHttpError(err)) {
       const httpError = err as HttpError;
       const httpError = err as HttpError;
       const { response } = ctx;
       const { response } = ctx;
 
 
-      return response
-        .status(httpError.status)
-        .body({
-          status: httpError.status,
-          message: httpError.message,
-        });
+      return response.status(httpError.status).body({
+        status: httpError.status,
+        message: httpError.message,
+      });
     }
     }
 
 
     next(err);
     next(err);
   }
   }
-
 }
 }

+ 12 - 11
apps/slackbot-proxy/src/middlewares/growi-to-slack/add-webclient-response-to-res.ts

@@ -1,23 +1,25 @@
 import { ErrorCode } from '@slack/web-api';
 import { ErrorCode } from '@slack/web-api';
-import {
-  IMiddleware, Middleware, Next, Req, Res,
-} from '@tsed/common';
-
+import { IMiddleware, Middleware, Next, Req, Res } from '@tsed/common';
 
 
 export type WebclientRes = Res & {
 export type WebclientRes = Res & {
-  simulateWebAPIRequestError: (error: string, statusCode: number) => WebclientRes
-  simulateWebAPIPlatformError: (error: string, errorCode?:string) => WebclientRes
+  simulateWebAPIRequestError: (
+    error: string,
+    statusCode: number,
+  ) => WebclientRes;
+  simulateWebAPIPlatformError: (
+    error: string,
+    errorCode?: string,
+  ) => WebclientRes;
 };
 };
 
 
-
 @Middleware()
 @Middleware()
 export class AddWebclientResponseToRes implements IMiddleware {
 export class AddWebclientResponseToRes implements IMiddleware {
-
   use(@Req() req: Req, @Res() res: WebclientRes, @Next() next: Next): void {
   use(@Req() req: Req, @Res() res: WebclientRes, @Next() next: Next): void {
-
     // https://github.com/slackapi/node-slack-sdk/blob/7b95663a9ef31036367c066ccbf0021423278f40/packages/web-api/src/WebClient.ts#L356-L358
     // https://github.com/slackapi/node-slack-sdk/blob/7b95663a9ef31036367c066ccbf0021423278f40/packages/web-api/src/WebClient.ts#L356-L358
     res.simulateWebAPIRequestError = (error: string, statusCode?: number) => {
     res.simulateWebAPIRequestError = (error: string, statusCode?: number) => {
-      return res.status(statusCode || 500).send({ error, errorCode: ErrorCode.RequestError });
+      return res
+        .status(statusCode || 500)
+        .send({ error, errorCode: ErrorCode.RequestError });
     };
     };
     // https://github.com/slackapi/node-slack-sdk/blob/7b95663a9ef31036367c066ccbf0021423278f40/packages/web-api/src/WebClient.ts#L197-L199
     // https://github.com/slackapi/node-slack-sdk/blob/7b95663a9ef31036367c066ccbf0021423278f40/packages/web-api/src/WebClient.ts#L197-L199
     res.simulateWebAPIPlatformError = (error: string, errorCode?: string) => {
     res.simulateWebAPIPlatformError = (error: string, errorCode?: string) => {
@@ -26,5 +28,4 @@ export class AddWebclientResponseToRes implements IMiddleware {
 
 
     next();
     next();
   }
   }
-
 }
 }

+ 6 - 6
apps/slackbot-proxy/src/middlewares/slack-to-growi/add-signing-secret-to-req.ts

@@ -1,14 +1,14 @@
 import type { RequestFromSlack } from '@growi/slack';
 import type { RequestFromSlack } from '@growi/slack';
-import {
-  type IMiddleware, Middleware, Next, Req, Res,
-} from '@tsed/common';
+import { type IMiddleware, Middleware, Next, Req, Res } from '@tsed/common';
 
 
 @Middleware()
 @Middleware()
 export class AddSigningSecretToReq implements IMiddleware {
 export class AddSigningSecretToReq implements IMiddleware {
-
-  use(@Req() req: Req & RequestFromSlack, @Res() res: Res, @Next() next: Next): void {
+  use(
+    @Req() req: Req & RequestFromSlack,
+    @Res() res: Res,
+    @Next() next: Next,
+  ): void {
     req.slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
     req.slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
     next();
     next();
   }
   }
-
 }
 }

+ 58 - 27
apps/slackbot-proxy/src/middlewares/slack-to-growi/authorizer.ts

@@ -1,7 +1,5 @@
 import { AuthorizeResult, InstallationQuery } from '@slack/oauth';
 import { AuthorizeResult, InstallationQuery } from '@slack/oauth';
-import {
-  IMiddleware, Inject, Middleware, Next, Req, Res,
-} from '@tsed/common';
+import { IMiddleware, Inject, Middleware, Next, Req, Res } from '@tsed/common';
 import Logger from 'bunyan';
 import Logger from 'bunyan';
 import createError from 'http-errors';
 import createError from 'http-errors';
 
 
@@ -12,10 +10,16 @@ import loggerFactory from '~/utils/logger';
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
 const logger = loggerFactory('@growi/slackbot-proxy:middlewares:authorizer');
 const logger = loggerFactory('@growi/slackbot-proxy:middlewares:authorizer');
 
 
-
-const getCommonMiddleware = (query:InstallationQuery<boolean>, installerService:InstallerService, logger:Logger) => {
-  return async(req: SlackOauthReq, res: Res, next: Next): Promise<void|Res> => {
-
+const getCommonMiddleware = (
+  query: InstallationQuery<boolean>,
+  installerService: InstallerService,
+  logger: Logger,
+) => {
+  return async (
+    req: SlackOauthReq,
+    res: Res,
+    next: Next,
+  ): Promise<void | Res> => {
     if (query.teamId == null && query.enterpriseId == null) {
     if (query.teamId == null && query.enterpriseId == null) {
       return next(createError(400, 'No installation found'));
       return next(createError(400, 'No installation found'));
     }
     }
@@ -25,10 +29,14 @@ const getCommonMiddleware = (query:InstallationQuery<boolean>, installerService:
       result = await installerService.installer.authorize(query);
       result = await installerService.installer.authorize(query);
 
 
       if (result.botToken == null) {
       if (result.botToken == null) {
-        return next(createError(403, `The installation for the team(${query.teamId || query.enterpriseId}) has no botToken`));
+        return next(
+          createError(
+            403,
+            `The installation for the team(${query.teamId || query.enterpriseId}) has no botToken`,
+          ),
+        );
       }
       }
-    }
-    catch (e) {
+    } catch (e) {
       logger.error(e.message);
       logger.error(e.message);
 
 
       return next(createError(500, e.message));
       return next(createError(500, e.message));
@@ -41,17 +49,22 @@ const getCommonMiddleware = (query:InstallationQuery<boolean>, installerService:
 };
 };
 @Middleware()
 @Middleware()
 export class AuthorizeCommandMiddleware implements IMiddleware {
 export class AuthorizeCommandMiddleware implements IMiddleware {
-
   private logger: Logger;
   private logger: Logger;
 
 
   constructor() {
   constructor() {
-    this.logger = loggerFactory('slackbot-proxy:middlewares:AuthorizeCommandMiddleware');
+    this.logger = loggerFactory(
+      'slackbot-proxy:middlewares:AuthorizeCommandMiddleware',
+    );
   }
   }
 
 
   @Inject()
   @Inject()
   installerService: InstallerService;
   installerService: InstallerService;
 
 
-  async use(@Req() req: SlackOauthReq, @Res() res: Res, @Next() next: Next): Promise<void|Res> {
+  async use(
+    @Req() req: SlackOauthReq,
+    @Res() res: Res,
+    @Next() next: Next,
+  ): Promise<void | Res> {
     const { body } = req;
     const { body } = req;
     const teamId = body.team_id;
     const teamId = body.team_id;
     const enterpriseId = body.enterprise_id;
     const enterpriseId = body.enterprise_id;
@@ -62,26 +75,33 @@ export class AuthorizeCommandMiddleware implements IMiddleware {
       isEnterpriseInstall,
       isEnterpriseInstall,
     };
     };
 
 
-    const commonMiddleware = getCommonMiddleware(query, this.installerService, this.logger);
+    const commonMiddleware = getCommonMiddleware(
+      query,
+      this.installerService,
+      this.logger,
+    );
     await commonMiddleware(req, res, next);
     await commonMiddleware(req, res, next);
   }
   }
-
 }
 }
 
 
 @Middleware()
 @Middleware()
 export class AuthorizeInteractionMiddleware implements IMiddleware {
 export class AuthorizeInteractionMiddleware implements IMiddleware {
-
   private logger: Logger;
   private logger: Logger;
 
 
   constructor() {
   constructor() {
-    this.logger = loggerFactory('slackbot-proxy:middlewares:AuthorizeInteractionMiddleware');
+    this.logger = loggerFactory(
+      'slackbot-proxy:middlewares:AuthorizeInteractionMiddleware',
+    );
   }
   }
 
 
   @Inject()
   @Inject()
-    installerService: InstallerService;
-
-  async use(@Req() req: SlackOauthReq, @Res() res:Res, @Next() next: Next): Promise<void|Res> {
+  installerService: InstallerService;
 
 
+  async use(
+    @Req() req: SlackOauthReq,
+    @Res() res: Res,
+    @Next() next: Next,
+  ): Promise<void | Res> {
     if (req.interactionPayload == null) {
     if (req.interactionPayload == null) {
       return next(createError(400, 'The request has no payload.'));
       return next(createError(400, 'The request has no payload.'));
     }
     }
@@ -99,24 +119,32 @@ export class AuthorizeInteractionMiddleware implements IMiddleware {
       isEnterpriseInstall,
       isEnterpriseInstall,
     };
     };
 
 
-    const commonMiddleware = getCommonMiddleware(query, this.installerService, this.logger);
+    const commonMiddleware = getCommonMiddleware(
+      query,
+      this.installerService,
+      this.logger,
+    );
     await commonMiddleware(req, res, next);
     await commonMiddleware(req, res, next);
   }
   }
-
 }
 }
 @Middleware()
 @Middleware()
 export class AuthorizeEventsMiddleware implements IMiddleware {
 export class AuthorizeEventsMiddleware implements IMiddleware {
-
   private logger: Logger;
   private logger: Logger;
 
 
   constructor() {
   constructor() {
-    this.logger = loggerFactory('slackbot-proxy:middlewares:AuthorizeEventsMiddleware');
+    this.logger = loggerFactory(
+      'slackbot-proxy:middlewares:AuthorizeEventsMiddleware',
+    );
   }
   }
 
 
   @Inject()
   @Inject()
   installerService: InstallerService;
   installerService: InstallerService;
 
 
-  async use(@Req() req: SlackOauthReq, @Res() res: Res, @Next() next: Next): Promise<void|Res> {
+  async use(
+    @Req() req: SlackOauthReq,
+    @Res() res: Res,
+    @Next() next: Next,
+  ): Promise<void | Res> {
     const { body } = req;
     const { body } = req;
     const teamId = body.team_id;
     const teamId = body.team_id;
     const enterpriseId = body.enterprise_id;
     const enterpriseId = body.enterprise_id;
@@ -127,8 +155,11 @@ export class AuthorizeEventsMiddleware implements IMiddleware {
       isEnterpriseInstall,
       isEnterpriseInstall,
     };
     };
 
 
-    const commonMiddleware = getCommonMiddleware(query, this.installerService, this.logger);
+    const commonMiddleware = getCommonMiddleware(
+      query,
+      this.installerService,
+      this.logger,
+    );
     await commonMiddleware(req, res, next);
     await commonMiddleware(req, res, next);
   }
   }
-
 }
 }

+ 4 - 9
apps/slackbot-proxy/src/middlewares/slack-to-growi/extract-growi-uri-from-req.ts

@@ -1,15 +1,11 @@
-import {
-  IMiddleware, Inject, Middleware, Next, Req, Res,
-} from '@tsed/common';
+import { IMiddleware, Inject, Middleware, Next, Req, Res } from '@tsed/common';
 
 
 import { SlackOauthReq } from '~/interfaces/slack-to-growi/slack-oauth-req';
 import { SlackOauthReq } from '~/interfaces/slack-to-growi/slack-oauth-req';
 import { ActionsBlockPayloadDelegator } from '~/services/growi-uri-injector/ActionsBlockPayloadDelegator';
 import { ActionsBlockPayloadDelegator } from '~/services/growi-uri-injector/ActionsBlockPayloadDelegator';
 import { ViewInteractionPayloadDelegator } from '~/services/growi-uri-injector/ViewInteractionPayloadDelegator';
 import { ViewInteractionPayloadDelegator } from '~/services/growi-uri-injector/ViewInteractionPayloadDelegator';
 
 
-
 @Middleware()
 @Middleware()
 export class ExtractGrowiUriFromReq implements IMiddleware {
 export class ExtractGrowiUriFromReq implements IMiddleware {
-
   @Inject()
   @Inject()
   viewInteractionPayloadDelegator: ViewInteractionPayloadDelegator;
   viewInteractionPayloadDelegator: ViewInteractionPayloadDelegator;
 
 
@@ -17,7 +13,6 @@ export class ExtractGrowiUriFromReq implements IMiddleware {
   actionsBlockPayloadDelegator: ActionsBlockPayloadDelegator;
   actionsBlockPayloadDelegator: ActionsBlockPayloadDelegator;
 
 
   use(@Req() req: SlackOauthReq, @Res() res: Res, @Next() next: Next): void {
   use(@Req() req: SlackOauthReq, @Res() res: Res, @Next() next: Next): void {
-
     // There is no payload in the request from slack
     // There is no payload in the request from slack
     if (req.interactionPayload == null) {
     if (req.interactionPayload == null) {
       return next();
       return next();
@@ -28,13 +23,13 @@ export class ExtractGrowiUriFromReq implements IMiddleware {
     if (this.viewInteractionPayloadDelegator.shouldHandleToExtract(payload)) {
     if (this.viewInteractionPayloadDelegator.shouldHandleToExtract(payload)) {
       const data = this.viewInteractionPayloadDelegator.extract(payload);
       const data = this.viewInteractionPayloadDelegator.extract(payload);
       req.growiUri = data.growiUri;
       req.growiUri = data.growiUri;
-    }
-    else if (this.actionsBlockPayloadDelegator.shouldHandleToExtract(payload)) {
+    } else if (
+      this.actionsBlockPayloadDelegator.shouldHandleToExtract(payload)
+    ) {
       const data = this.actionsBlockPayloadDelegator.extract(payload);
       const data = this.actionsBlockPayloadDelegator.extract(payload);
       req.growiUri = data.growiUri;
       req.growiUri = data.growiUri;
     }
     }
 
 
     return next();
     return next();
   }
   }
-
 }
 }

+ 5 - 9
apps/slackbot-proxy/src/middlewares/slack-to-growi/join-to-conversation.ts

@@ -1,21 +1,19 @@
 import { generateWebClient } from '@growi/slack/dist/utils/webclient-factory';
 import { generateWebClient } from '@growi/slack/dist/utils/webclient-factory';
-import {
-  IMiddleware, Middleware, Req,
-} from '@tsed/common';
+import { IMiddleware, Middleware, Req } from '@tsed/common';
 import Logger from 'bunyan';
 import Logger from 'bunyan';
 
 
 import { SlackOauthReq } from '~/interfaces/slack-to-growi/slack-oauth-req';
 import { SlackOauthReq } from '~/interfaces/slack-to-growi/slack-oauth-req';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
-const logger: Logger = loggerFactory('slackbot-proxy:middlewares:JoinToConversationsMiddleware');
-
+const logger: Logger = loggerFactory(
+  'slackbot-proxy:middlewares:JoinToConversationsMiddleware',
+);
 
 
 /**
 /**
  * This middleware should be processed after AuthorizeCommandMiddleware or AuthorizeInteractionMiddleware
  * This middleware should be processed after AuthorizeCommandMiddleware or AuthorizeInteractionMiddleware
  */
  */
 @Middleware()
 @Middleware()
 export class JoinToConversationMiddleware implements IMiddleware {
 export class JoinToConversationMiddleware implements IMiddleware {
-
   async use(@Req() req: SlackOauthReq): Promise<void> {
   async use(@Req() req: SlackOauthReq): Promise<void> {
     const { body, authorizeResult } = req;
     const { body, authorizeResult } = req;
 
 
@@ -24,10 +22,8 @@ export class JoinToConversationMiddleware implements IMiddleware {
 
 
     try {
     try {
       await client.conversations.join({ channel: body.channel_id });
       await client.conversations.join({ channel: body.channel_id });
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       logger.error(err);
     }
     }
   }
   }
-
 }
 }

+ 1 - 7
apps/slackbot-proxy/src/middlewares/slack-to-growi/parse-interaction-req.ts

@@ -1,14 +1,9 @@
 import type { RequestFromSlack } from '@growi/slack';
 import type { RequestFromSlack } from '@growi/slack';
-import {
-  type IMiddleware, Middleware, Next, Req,
-} from '@tsed/common';
-
+import { type IMiddleware, Middleware, Next, Req } from '@tsed/common';
 
 
 @Middleware()
 @Middleware()
 export class ParseInteractionPayloadMiddleare implements IMiddleware {
 export class ParseInteractionPayloadMiddleare implements IMiddleware {
-
   use(@Req() req: RequestFromSlack, @Next() next: Next): void {
   use(@Req() req: RequestFromSlack, @Next() next: Next): void {
-
     // There is no payload in the request from slack
     // There is no payload in the request from slack
     if (req.body.payload == null) {
     if (req.body.payload == null) {
       return next();
       return next();
@@ -18,5 +13,4 @@ export class ParseInteractionPayloadMiddleare implements IMiddleware {
 
 
     return next();
     return next();
   }
   }
-
 }
 }

+ 6 - 8
apps/slackbot-proxy/src/middlewares/slack-to-growi/url-verification.ts

@@ -1,15 +1,14 @@
-import {
-  IMiddleware, Middleware, Req, Res, Next,
-} from '@tsed/common';
+import { IMiddleware, Middleware, Next, Req, Res } from '@tsed/common';
 
 
 import { SlackOauthReq } from '~/interfaces/slack-to-growi/slack-oauth-req';
 import { SlackOauthReq } from '~/interfaces/slack-to-growi/slack-oauth-req';
 
 
-
 @Middleware()
 @Middleware()
 export class UrlVerificationMiddleware implements IMiddleware {
 export class UrlVerificationMiddleware implements IMiddleware {
-
-  async use(@Req() req: SlackOauthReq, @Res() res: Res, @Next() next: Next): Promise<void> {
-
+  async use(
+    @Req() req: SlackOauthReq,
+    @Res() res: Res,
+    @Next() next: Next,
+  ): Promise<void> {
     // eslint-disable-next-line max-len
     // eslint-disable-next-line max-len
     // see: https://api.slack.com/apis/connections/events-api#the-events-api__subscribing-to-event-types__events-api-request-urls__request-url-configuration--verification
     // see: https://api.slack.com/apis/connections/events-api#the-events-api__subscribing-to-event-types__events-api-request-urls__request-url-configuration--verification
     if (req.body.type === 'url_verification') {
     if (req.body.type === 'url_verification') {
@@ -19,5 +18,4 @@ export class UrlVerificationMiddleware implements IMiddleware {
 
 
     next();
     next();
   }
   }
-
 }
 }

+ 0 - 4
apps/slackbot-proxy/src/models/errors.ts

@@ -2,20 +2,16 @@ import ExtensibleCustomError from 'extensible-custom-error';
 import { HttpError } from 'http-errors';
 import { HttpError } from 'http-errors';
 
 
 export class InvalidUrlError extends ExtensibleCustomError {
 export class InvalidUrlError extends ExtensibleCustomError {
-
   constructor(url: string) {
   constructor(url: string) {
     super(`Invalid URL: ${url}`);
     super(`Invalid URL: ${url}`);
   }
   }
-
 }
 }
 
 
 export class CustomHttpError extends Error {
 export class CustomHttpError extends Error {
-
   httpError: HttpError;
   httpError: HttpError;
 
 
   constructor(httpError: HttpError) {
   constructor(httpError: HttpError) {
     super(httpError.message);
     super(httpError.message);
     this.httpError = httpError;
     this.httpError = httpError;
   }
   }
-
 }
 }

+ 4 - 6
apps/slackbot-proxy/src/repositories/installation.ts

@@ -1,17 +1,16 @@
-import {
-  Repository, EntityRepository,
-} from 'typeorm';
+import { EntityRepository, Repository } from 'typeorm';
 
 
 import { Installation } from '~/entities/installation';
 import { Installation } from '~/entities/installation';
 
 
 @EntityRepository(Installation)
 @EntityRepository(Installation)
 export class InstallationRepository extends Repository<Installation> {
 export class InstallationRepository extends Repository<Installation> {
-
   findByID(id: string): Promise<Installation | undefined> {
   findByID(id: string): Promise<Installation | undefined> {
     return this.findOne(id);
     return this.findOne(id);
   }
   }
 
 
-  async findByTeamIdOrEnterpriseId(teamIdOrEnterpriseId:string): Promise<Installation|undefined> {
+  async findByTeamIdOrEnterpriseId(
+    teamIdOrEnterpriseId: string,
+  ): Promise<Installation | undefined> {
     return this.findOne({
     return this.findOne({
       where: [
       where: [
         { teamId: teamIdOrEnterpriseId },
         { teamId: teamIdOrEnterpriseId },
@@ -19,5 +18,4 @@ export class InstallationRepository extends Repository<Installation> {
       ],
       ],
     });
     });
   }
   }
-
 }
 }

+ 2 - 6
apps/slackbot-proxy/src/repositories/order.ts

@@ -1,10 +1,6 @@
-import {
-  Repository, EntityRepository,
-} from 'typeorm';
+import { EntityRepository, Repository } from 'typeorm';
 
 
 import { Order } from '~/entities/order';
 import { Order } from '~/entities/order';
 
 
 @EntityRepository(Order)
 @EntityRepository(Order)
-export class OrderRepository extends Repository<Order> {
-
-}
+export class OrderRepository extends Repository<Order> {}

+ 2 - 6
apps/slackbot-proxy/src/repositories/relation.ts

@@ -1,14 +1,10 @@
-import {
-  Repository, EntityRepository,
-} from 'typeorm';
+import { EntityRepository, Repository } from 'typeorm';
 
 
 import { Relation } from '~/entities/relation';
 import { Relation } from '~/entities/relation';
 
 
 @EntityRepository(Relation)
 @EntityRepository(Relation)
 export class RelationRepository extends Repository<Relation> {
 export class RelationRepository extends Repository<Relation> {
-
   async findAllByGrowiUris(growiUris: string[]): Promise<Relation[]> {
   async findAllByGrowiUris(growiUris: string[]): Promise<Relation[]> {
-    return this.find({ where: growiUris.map(uri => ({ growiUri: uri })) });
+    return this.find({ where: growiUris.map((uri) => ({ growiUri: uri })) });
   }
   }
-
 }
 }

+ 5 - 6
apps/slackbot-proxy/src/repositories/system-information.ts

@@ -1,13 +1,13 @@
-import {
-  Repository, EntityRepository,
-} from 'typeorm';
+import { EntityRepository, Repository } from 'typeorm';
 
 
 import { SystemInformation } from '~/entities/system-information';
 import { SystemInformation } from '~/entities/system-information';
 
 
 @EntityRepository(SystemInformation)
 @EntityRepository(SystemInformation)
 export class SystemInformationRepository extends Repository<SystemInformation> {
 export class SystemInformationRepository extends Repository<SystemInformation> {
-
-  async createOrUpdateUniqueRecordWithVersion(systemInfo: SystemInformation | undefined, proxyVersion: string): Promise<void> {
+  async createOrUpdateUniqueRecordWithVersion(
+    systemInfo: SystemInformation | undefined,
+    proxyVersion: string,
+  ): Promise<void> {
     // update the version if it exists
     // update the version if it exists
     if (systemInfo != null) {
     if (systemInfo != null) {
       systemInfo.setVersion(proxyVersion);
       systemInfo.setVersion(proxyVersion);
@@ -19,5 +19,4 @@ export class SystemInformationRepository extends Repository<SystemInformation> {
     newSystemInfo.setVersion(proxyVersion);
     newSystemInfo.setVersion(proxyVersion);
     await this.save(newSystemInfo);
     await this.save(newSystemInfo);
   }
   }
-
 }
 }

+ 2 - 3
apps/slackbot-proxy/src/utils/logger/index.ts

@@ -7,11 +7,10 @@ import configForProd from '~/config/logger/config.prod';
 const isProduction = process.env.NODE_ENV === 'production';
 const isProduction = process.env.NODE_ENV === 'production';
 const config = isProduction ? configForProd : configForDev;
 const config = isProduction ? configForProd : configForDev;
 
 
-const loggerFactory = function(name: string): Logger {
-  return createLogger({
+const loggerFactory = (name: string): Logger =>
+  createLogger({
     name,
     name,
     config,
     config,
   });
   });
-};
 
 
 export default loggerFactory;
 export default loggerFactory;

+ 26 - 12
apps/slackbot-proxy/src/utils/welcome-message.ts

@@ -1,7 +1,10 @@
 import { markdownSectionBlock } from '@growi/slack/dist/utils/block-kit-builder';
 import { markdownSectionBlock } from '@growi/slack/dist/utils/block-kit-builder';
 import { ChatPostMessageResponse, WebClient } from '@slack/web-api';
 import { ChatPostMessageResponse, WebClient } from '@slack/web-api';
 
 
-export const postWelcomeMessageOnce = async(client: WebClient, channel: string): Promise<void|ChatPostMessageResponse> => {
+export const postWelcomeMessageOnce = async (
+  client: WebClient,
+  channel: string,
+): Promise<void | ChatPostMessageResponse> => {
   const history = await client.conversations.history({
   const history = await client.conversations.history({
     channel,
     channel,
     limit: 1,
     limit: 1,
@@ -15,24 +18,35 @@ export const postWelcomeMessageOnce = async(client: WebClient, channel: string):
   return client.chat.postMessage({
   return client.chat.postMessage({
     channel,
     channel,
     blocks: [
     blocks: [
-      markdownSectionBlock('Hi! This is GROWI bot.\n'
-        + 'You can invoke any feature with `/growi [command]` in any channel. Type `/growi help` to check the available features.'),
-      markdownSectionBlock('Looking for additional help? '
-        // eslint-disable-next-line max-len
-        + 'See <https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/official-bot-settings.html#official-bot-settings | Docs>.'),
+      markdownSectionBlock(
+        'Hi! This is GROWI bot.\n' +
+          'You can invoke any feature with `/growi [command]` in any channel. Type `/growi help` to check the available features.',
+      ),
+      markdownSectionBlock(
+        'Looking for additional help? ' +
+          // eslint-disable-next-line max-len
+          'See <https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/official-bot-settings.html#official-bot-settings | Docs>.',
+      ),
     ],
     ],
   });
   });
 };
 };
 
 
-export const postInstallSuccessMessage = async(client: WebClient, userId: string): Promise<ChatPostMessageResponse> => {
+export const postInstallSuccessMessage = async (
+  client: WebClient,
+  userId: string,
+): Promise<ChatPostMessageResponse> => {
   return client.chat.postMessage({
   return client.chat.postMessage({
     channel: userId,
     channel: userId,
     blocks: [
     blocks: [
-      markdownSectionBlock(':tada: You have successfully installed GROWI bot on this Slack workspace.\n'
-      + 'At first you do `/growi register` in the channel that you want to use.'),
-      markdownSectionBlock('Looking for additional help? '
-        // eslint-disable-next-line max-len
-        + 'See <https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/official-bot-settings.html#official-bot-settings | Docs>.'),
+      markdownSectionBlock(
+        ':tada: You have successfully installed GROWI bot on this Slack workspace.\n' +
+          'At first you do `/growi register` in the channel that you want to use.',
+      ),
+      markdownSectionBlock(
+        'Looking for additional help? ' +
+          // eslint-disable-next-line max-len
+          'See <https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/official-bot-settings.html#official-bot-settings | Docs>.',
+      ),
     ],
     ],
   });
   });
 };
 };

+ 2 - 4
apps/slackbot-proxy/tsconfig.json

@@ -14,7 +14,7 @@
       "@tsed/exceptions": ["./node_modules/@tsed/exceptions"],
       "@tsed/exceptions": ["./node_modules/@tsed/exceptions"],
       "@tsed/common": ["./node_modules/@tsed/common"],
       "@tsed/common": ["./node_modules/@tsed/common"],
       "@tsed/di": ["./node_modules/@tsed/di"],
       "@tsed/di": ["./node_modules/@tsed/di"],
-      "@tsed/logger": ["./node_modules/@tsed/logger"],
+      "@tsed/logger": ["./node_modules/@tsed/logger"]
     },
     },
 
 
     /* TODO: remove below flags for strict checking */
     /* TODO: remove below flags for strict checking */
@@ -32,9 +32,7 @@
       { "transform": "typescript-transform-paths", "afterDeclarations": true }
       { "transform": "typescript-transform-paths", "afterDeclarations": true }
     ]
     ]
   },
   },
-  "include": [
-    "src"
-  ],
+  "include": ["src"],
   "exclude": [
   "exclude": [
     "node_modules",
     "node_modules",
     "config",
     "config",

+ 3 - 2
biome.json

@@ -19,7 +19,8 @@
       ".stylelintrc.json",
       ".stylelintrc.json",
       "package.json",
       "package.json",
       "./apps/app/**",
       "./apps/app/**",
-      "./apps/slackbot-proxy/**",
+      "./apps/slackbot-proxy/src/public/bootstrap/**",
+      "./apps/slackbot-proxy/src/services/**",
       "./packages/editor/**",
       "./packages/editor/**",
       "./packages/pdf-converter-client/src/index.ts"
       "./packages/pdf-converter-client/src/index.ts"
     ]
     ]
@@ -47,7 +48,7 @@
   },
   },
   "overrides": [
   "overrides": [
     {
     {
-      "include": ["./apps/pdf-converter/**"],
+      "include": ["./apps/pdf-converter/**", "./apps/slackbot-proxy/**"],
       "linter": {
       "linter": {
         "rules": {
         "rules": {
           "style": {
           "style": {