Explorar o código

biome autofix for slackbot-proxy services

Futa Arai hai 8 meses
pai
achega
712a3bbddf

+ 17 - 9
apps/slackbot-proxy/src/services/InstallerService.ts

@@ -1,5 +1,7 @@
 import {
-  Installation as SlackInstallation, InstallationQuery, InstallProvider,
+  InstallationQuery,
+  InstallProvider,
+  Installation as SlackInstallation,
 } from '@slack/oauth';
 import { Inject, Service } from '@tsed/di';
 
@@ -8,7 +10,6 @@ import { InstallationRepository } from '~/repositories/installation';
 
 @Service()
 export class InstallerService {
-
   installer: InstallProvider;
 
   @Inject()
@@ -20,10 +21,14 @@ export class InstallerService {
     const stateSecret = process.env.SLACK_INSTALLPROVIDER_STATE_SECRET;
 
     if (clientId === undefined) {
-      throw new Error('The environment variable \'SLACK_CLIENT_ID\' must be defined.');
+      throw new Error(
+        "The environment variable 'SLACK_CLIENT_ID' must be defined.",
+      );
     }
     if (clientSecret === undefined) {
-      throw new Error('The environment variable \'SLACK_CLIENT_SECRET\' must be defined.');
+      throw new Error(
+        "The environment variable 'SLACK_CLIENT_SECRET' must be defined.",
+      );
     }
 
     const { repository } = this;
@@ -35,14 +40,18 @@ export class InstallerService {
       legacyStateVerification: true,
       installationStore: {
         // upsert
-        storeInstallation: async(slackInstallation: SlackInstallation<'v1' | 'v2', boolean>) => {
-          const teamIdOrEnterpriseId = slackInstallation.team?.id || slackInstallation.enterprise?.id;
+        storeInstallation: async (
+          slackInstallation: SlackInstallation<'v1' | 'v2', boolean>,
+        ) => {
+          const teamIdOrEnterpriseId =
+            slackInstallation.team?.id || slackInstallation.enterprise?.id;
 
           if (teamIdOrEnterpriseId == null) {
             throw new Error('teamId or enterpriseId is required.');
           }
 
-          const existedInstallation = await repository.findByTeamIdOrEnterpriseId(teamIdOrEnterpriseId);
+          const existedInstallation =
+            await repository.findByTeamIdOrEnterpriseId(teamIdOrEnterpriseId);
 
           if (existedInstallation != null) {
             existedInstallation.setData(slackInstallation);
@@ -55,7 +64,7 @@ export class InstallerService {
           await repository.save(installation);
           return;
         },
-        fetchInstallation: async(installQuery: InstallationQuery<boolean>) => {
+        fetchInstallation: async (installQuery: InstallationQuery<boolean>) => {
           const id = installQuery.enterpriseId || installQuery.teamId;
 
           // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -70,5 +79,4 @@ export class InstallerService {
       },
     });
   }
-
 }

+ 81 - 54
apps/slackbot-proxy/src/services/LinkSharedService.ts

@@ -1,4 +1,7 @@
-import { type GrowiEventProcessor, REQUEST_TIMEOUT_FOR_PTOG } from '@growi/slack';
+import {
+  type GrowiEventProcessor,
+  REQUEST_TIMEOUT_FOR_PTOG,
+} from '@growi/slack';
 import type { WebClient } from '@slack/web-api';
 import { Inject, Service } from '@tsed/di';
 import axios from 'axios';
@@ -11,41 +14,42 @@ import loggerFactory from '~/utils/logger';
 const logger = loggerFactory('slackbot-proxy:services:LinkSharedService');
 
 type LinkSharedEventLink = {
-  url: string,
-  domain: string,
-}
+  url: string;
+  domain: string;
+};
 
 // aliases
 type GrowiOrigin = string;
 type TokenPtoG = string;
 
 export type LinkSharedRequestEvent = {
-  channel: string,
+  channel: string;
 
   // eslint-disable-next-line camelcase
-  message_ts: string,
+  message_ts: string;
 
-  links: LinkSharedEventLink[],
-}
+  links: LinkSharedEventLink[];
+};
 
 type PrivateData = {
-  isPublic: false,
-  path: string,
-}
+  isPublic: false;
+  path: string;
+};
 
 type PublicData = {
-  isPublic: true,
-  path: string,
-  pageBody: string,
-  updatedAt: string,
-  commentCount: number,
-}
+  isPublic: true;
+  path: string;
+  pageBody: string;
+  updatedAt: string;
+  commentCount: number;
+};
 
 export type DataForLinkShared = PrivateData | PublicData;
 
 @Service()
-export class LinkSharedService implements GrowiEventProcessor<LinkSharedRequestEvent> {
-
+export class LinkSharedService
+  implements GrowiEventProcessor<LinkSharedRequestEvent>
+{
   @Inject()
   relationRepository: RelationRepository;
 
@@ -53,21 +57,33 @@ export class LinkSharedService implements GrowiEventProcessor<LinkSharedRequestE
     return eventType === 'link_shared';
   }
 
-  async processEvent(client: WebClient, event: LinkSharedRequestEvent): Promise<void> {
+  async processEvent(
+    client: WebClient,
+    event: LinkSharedRequestEvent,
+  ): Promise<void> {
     const { links } = event;
 
-    const origins: string[] = links.map((link: LinkSharedEventLink) => (new URL(link.url)).origin);
-    const originToTokenPtoGMap: Map<GrowiOrigin, TokenPtoG> = await this.generateOriginToTokenPtoGMapFromOrigins(origins); // get tokenPtoG at once
+    const origins: string[] = links.map(
+      (link: LinkSharedEventLink) => new URL(link.url).origin,
+    );
+    const originToTokenPtoGMap: Map<GrowiOrigin, TokenPtoG> =
+      await this.generateOriginToTokenPtoGMapFromOrigins(origins); // get tokenPtoG at once
 
     // forward to GROWI
-    const result = await this.forwardToEachGrowiOrigin(origins, event, originToTokenPtoGMap);
+    const result = await this.forwardToEachGrowiOrigin(
+      origins,
+      event,
+      originToTokenPtoGMap,
+    );
 
     // log error
     this.logErrorRejectedResults(result);
   }
 
   // generate Map<GrowiOrigin, TokenPtoG>
-  async generateOriginToTokenPtoGMapFromOrigins(origins: GrowiOrigin[]): Promise<Map<GrowiOrigin, TokenPtoG>> {
+  async generateOriginToTokenPtoGMapFromOrigins(
+    origins: GrowiOrigin[],
+  ): Promise<Map<GrowiOrigin, TokenPtoG>> {
     const originToTokenPtoGMap: Map<GrowiOrigin, TokenPtoG> = new Map();
 
     // get relations using origins at once
@@ -82,48 +98,59 @@ export class LinkSharedService implements GrowiEventProcessor<LinkSharedRequestE
   }
 
   async forwardToEachGrowiOrigin(
-      origins: string[], event: LinkSharedRequestEvent, originToTokenPtoGMap: Map<GrowiOrigin, TokenPtoG>,
+    origins: string[],
+    event: LinkSharedRequestEvent,
+    originToTokenPtoGMap: Map<GrowiOrigin, TokenPtoG>,
   ): Promise<PromiseSettledResult<void>[]> {
-    return Promise.allSettled(origins.map(async(origin) => {
-      const requestBody = {
-        growiBotEvent: {
-          eventType: 'link_shared',
-          event,
-        },
-        data: {
-          origin,
-        },
-      };
-      try {
-        // ensure tokenPtoG exists
-        const tokenPtoG = originToTokenPtoGMap.get(origin);
-        if (tokenPtoG == null) throw new Error('tokenPtoG is null');
-
-        const url = new URL('/_api/v3/slack-integration/proxied/events', origin);
-
-        await axios.post(url.toString(),
-          requestBody,
-          {
+    return Promise.allSettled(
+      origins.map(async (origin) => {
+        const requestBody = {
+          growiBotEvent: {
+            eventType: 'link_shared',
+            event,
+          },
+          data: {
+            origin,
+          },
+        };
+        try {
+          // ensure tokenPtoG exists
+          const tokenPtoG = originToTokenPtoGMap.get(origin);
+          if (tokenPtoG == null) throw new Error('tokenPtoG is null');
+
+          const url = new URL(
+            '/_api/v3/slack-integration/proxied/events',
+            origin,
+          );
+
+          await axios.post(url.toString(), requestBody, {
             headers: {
               'x-growi-ptog-tokens': tokenPtoG,
             },
             timeout: REQUEST_TIMEOUT_FOR_PTOG,
           });
-      }
-      catch (err) {
-        logger.error(`Error occurred while request to growi (origin=${origin}):`, err);
-        throw err;
-      }
-    }));
+        } catch (err) {
+          logger.error(
+            `Error occurred while request to growi (origin=${origin}):`,
+            err,
+          );
+          throw err;
+        }
+      }),
+    );
   }
 
   // Promise util method to output rejected results
   private logErrorRejectedResults<T>(results: PromiseSettledResult<T>[]): void {
-    const rejectedResults: PromiseRejectedResult[] = results.filter((result): result is PromiseRejectedResult => result.status === 'rejected');
+    const rejectedResults: PromiseRejectedResult[] = results.filter(
+      (result): result is PromiseRejectedResult => result.status === 'rejected',
+    );
 
     rejectedResults.forEach((rejected, i) => {
-      logger.error(`Error occurred (count: ${i}): `, rejected.reason.toString());
+      logger.error(
+        `Error occurred (count: ${i}): `,
+        rejected.reason.toString(),
+      );
     });
   }
-
 }

+ 150 - 55
apps/slackbot-proxy/src/services/RegisterService.ts

@@ -1,19 +1,27 @@
 import type {
-  GrowiCommand, GrowiCommandProcessor, GrowiInteractionProcessor, InteractionHandledResult,
+  GrowiCommand,
+  GrowiCommandProcessor,
+  GrowiInteractionProcessor,
+  InteractionHandledResult,
 } from '@growi/slack';
 import {
-  markdownSectionBlock, markdownHeaderBlock, inputSectionBlock, inputBlock,
+  inputBlock,
+  inputSectionBlock,
+  markdownHeaderBlock,
+  markdownSectionBlock,
 } from '@growi/slack/dist/utils/block-kit-builder';
 import { InteractionPayloadAccessor } from '@growi/slack/dist/utils/interaction-payload-accessor';
 import { getInteractionIdRegexpFromCommandName } from '@growi/slack/dist/utils/payload-interaction-id-helpers';
 import { respond } from '@growi/slack/dist/utils/response-url';
 import { AuthorizeResult } from '@slack/oauth';
 import {
-  WebClient, LogLevel, Block, ConversationsSelect,
+  Block,
+  ConversationsSelect,
+  LogLevel,
+  WebClient,
 } from '@slack/web-api';
 import { Inject, Service } from '@tsed/di';
 
-
 import { InstallationRepository } from '~/repositories/installation';
 import { OrderRepository } from '~/repositories/order';
 import loggerFactory from '~/utils/logger';
@@ -27,14 +35,17 @@ const isOfficialMode = process.env.OFFICIAL_MODE === 'true';
 
 export type RegisterCommandBody = {
   // eslint-disable-next-line camelcase
-  trigger_id: string,
+  trigger_id: string;
   // eslint-disable-next-line camelcase
-  channel_name: string,
-}
+  channel_name: string;
+};
 
 @Service()
-export class RegisterService implements GrowiCommandProcessor<RegisterCommandBody>, GrowiInteractionProcessor<void> {
-
+export class RegisterService
+  implements
+    GrowiCommandProcessor<RegisterCommandBody>,
+    GrowiInteractionProcessor<void>
+{
   @Inject()
   orderRepository: OrderRepository;
 
@@ -45,10 +56,16 @@ export class RegisterService implements GrowiCommandProcessor<RegisterCommandBod
     return growiCommand.growiCommandType === 'register';
   }
 
-  async processCommand(growiCommand: GrowiCommand, authorizeResult: AuthorizeResult, context: RegisterCommandBody): Promise<void> {
+  async processCommand(
+    growiCommand: GrowiCommand,
+    authorizeResult: AuthorizeResult,
+    context: RegisterCommandBody,
+  ): Promise<void> {
     const { botToken } = authorizeResult;
 
-    const client = new WebClient(botToken, { logLevel: isProduction ? LogLevel.DEBUG : LogLevel.INFO });
+    const client = new WebClient(botToken, {
+      logLevel: isProduction ? LogLevel.DEBUG : LogLevel.INFO,
+    });
 
     const conversationsSelectElement: ConversationsSelect = {
       action_id: 'conversation',
@@ -76,52 +93,84 @@ export class RegisterService implements GrowiCommandProcessor<RegisterCommandBod
         private_metadata: JSON.stringify({ channel: context.channel_name }),
 
         blocks: [
-          inputBlock(conversationsSelectElement, 'conversation', 'Channel to which you want to add'),
-          inputSectionBlock('growiUrl', 'GROWI domain', 'contents_input', false, 'https://example.com'),
-          inputSectionBlock('tokenPtoG', 'Access Token Proxy to GROWI', 'contents_input', false, 'jBMZvpk.....'),
-          inputSectionBlock('tokenGtoP', 'Access Token GROWI to Proxy', 'contents_input', false, 'sdg15av.....'),
+          inputBlock(
+            conversationsSelectElement,
+            'conversation',
+            'Channel to which you want to add',
+          ),
+          inputSectionBlock(
+            'growiUrl',
+            'GROWI domain',
+            'contents_input',
+            false,
+            'https://example.com',
+          ),
+          inputSectionBlock(
+            'tokenPtoG',
+            'Access Token Proxy to GROWI',
+            'contents_input',
+            false,
+            'jBMZvpk.....',
+          ),
+          inputSectionBlock(
+            'tokenGtoP',
+            'Access Token GROWI to Proxy',
+            'contents_input',
+            false,
+            'sdg15av.....',
+          ),
         ],
       },
     });
   }
 
   // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-  shouldHandleInteraction(interactionPayloadAccessor: InteractionPayloadAccessor): boolean {
-    const { actionId, callbackId } = interactionPayloadAccessor.getActionIdAndCallbackIdFromPayLoad();
-    const registerRegexp: RegExp = getInteractionIdRegexpFromCommandName('register');
+  shouldHandleInteraction(
+    interactionPayloadAccessor: InteractionPayloadAccessor,
+  ): boolean {
+    const { actionId, callbackId } =
+      interactionPayloadAccessor.getActionIdAndCallbackIdFromPayLoad();
+    const registerRegexp: RegExp =
+      getInteractionIdRegexpFromCommandName('register');
     return registerRegexp.test(actionId) || registerRegexp.test(callbackId);
   }
 
   async processInteraction(
-      // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-      authorizeResult: AuthorizeResult, interactionPayload: any, interactionPayloadAccessor: InteractionPayloadAccessor,
+    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+    authorizeResult: AuthorizeResult,
+    interactionPayload: any,
+    interactionPayloadAccessor: InteractionPayloadAccessor,
   ): Promise<InteractionHandledResult<void>> {
     const interactionHandledResult: InteractionHandledResult<void> = {
       isTerminated: false,
     };
-    if (!this.shouldHandleInteraction(interactionPayloadAccessor)) return interactionHandledResult;
-
-    interactionHandledResult.result = await this.handleRegisterInteraction(authorizeResult, interactionPayload, interactionPayloadAccessor);
+    if (!this.shouldHandleInteraction(interactionPayloadAccessor))
+      return interactionHandledResult;
+
+    interactionHandledResult.result = await this.handleRegisterInteraction(
+      authorizeResult,
+      interactionPayload,
+      interactionPayloadAccessor,
+    );
     interactionHandledResult.isTerminated = true;
 
     return interactionHandledResult as InteractionHandledResult<void>;
   }
 
   async handleRegisterInteraction(
-      // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-      authorizeResult: AuthorizeResult, interactionPayload: any, interactionPayloadAccessor: InteractionPayloadAccessor,
+    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+    authorizeResult: AuthorizeResult,
+    interactionPayload: any,
+    interactionPayloadAccessor: InteractionPayloadAccessor,
   ): Promise<void> {
     try {
       await this.insertOrderRecord(authorizeResult, interactionPayloadAccessor);
-    }
-    catch (err) {
+    } catch (err) {
       if (err instanceof InvalidUrlError) {
         logger.error('Failed to register:\n', err);
         await respond(interactionPayloadAccessor.getResponseUrl(), {
           text: 'Invalid URL',
-          blocks: [
-            markdownSectionBlock('Please enter a valid URL'),
-          ],
+          blocks: [markdownSectionBlock('Please enter a valid URL')],
         });
         return;
       }
@@ -133,8 +182,9 @@ export class RegisterService implements GrowiCommandProcessor<RegisterCommandBod
   }
 
   async insertOrderRecord(
-      // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-      authorizeResult: AuthorizeResult, interactionPayloadAccessor: InteractionPayloadAccessor,
+    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+    authorizeResult: AuthorizeResult,
+    interactionPayloadAccessor: InteractionPayloadAccessor,
   ): Promise<void> {
     const inputValues = interactionPayloadAccessor.getStateValues();
     const growiUrl = inputValues.growiUrl.contents_input.value;
@@ -144,62 +194,107 @@ export class RegisterService implements GrowiCommandProcessor<RegisterCommandBod
     try {
       // eslint-disable-next-line @typescript-eslint/no-unused-vars
       const url = new URL(growiUrl);
-    }
-    catch (error) {
+    } catch (error) {
       throw new InvalidUrlError(growiUrl);
     }
 
-    const installationId = authorizeResult.enterpriseId || authorizeResult.teamId;
+    const installationId =
+      authorizeResult.enterpriseId || authorizeResult.teamId;
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const installation = await this.installationRepository.findByTeamIdOrEnterpriseId(installationId!);
+    const installation =
+      await this.installationRepository.findByTeamIdOrEnterpriseId(
+        installationId!,
+      );
 
     this.orderRepository.save({
-      installation, growiUrl, tokenPtoG, tokenGtoP,
+      installation,
+      growiUrl,
+      tokenPtoG,
+      tokenGtoP,
     });
   }
 
   async notifyServerUriToSlack(
-      // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-      interactionPayloadAccessor: InteractionPayloadAccessor,
+    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+    interactionPayloadAccessor: InteractionPayloadAccessor,
   ): Promise<void> {
-
     const serverUri = process.env.SERVER_URI;
     const responseUrl = interactionPayloadAccessor.getResponseUrl();
 
     const blocks: Block[] = [];
 
     if (isOfficialMode) {
-      blocks.push(markdownHeaderBlock(':white_check_mark: 1. Install Official bot to Slack'));
-      blocks.push(markdownHeaderBlock(':white_check_mark: 2. Register for GROWI Official Bot Proxy Service'));
-      blocks.push(markdownSectionBlock('The request has been successfully accepted. However, registration has *NOT been completed* yet.'));
+      blocks.push(
+        markdownHeaderBlock(
+          ':white_check_mark: 1. Install Official bot to Slack',
+        ),
+      );
+      blocks.push(
+        markdownHeaderBlock(
+          ':white_check_mark: 2. Register for GROWI Official Bot Proxy Service',
+        ),
+      );
+      blocks.push(
+        markdownSectionBlock(
+          'The request has been successfully accepted. However, registration has *NOT been completed* yet.',
+        ),
+      );
       blocks.push(markdownHeaderBlock(':arrow_right: 3. Test Connection'));
-      blocks.push(markdownSectionBlock('*Test Connection* to complete the registration in your GROWI.'));
-      blocks.push(markdownHeaderBlock(':white_large_square: 4. (Opt) Manage Permission'));
-      blocks.push(markdownSectionBlock('Modify permission settings if you need.'));
+      blocks.push(
+        markdownSectionBlock(
+          '*Test Connection* to complete the registration in your GROWI.',
+        ),
+      );
+      blocks.push(
+        markdownHeaderBlock(':white_large_square: 4. (Opt) Manage Permission'),
+      );
+      blocks.push(
+        markdownSectionBlock('Modify permission settings if you need.'),
+      );
       await respond(responseUrl, {
         text: 'Proxy URL',
         blocks,
       });
       return;
-
     }
 
     blocks.push(markdownHeaderBlock(':white_check_mark: 1. Create Bot'));
-    blocks.push(markdownHeaderBlock(':white_check_mark: 2. Install bot to Slack'));
-    blocks.push(markdownHeaderBlock(':white_check_mark: 3. Register for your GROWI Custom Bot Proxy'));
-    blocks.push(markdownSectionBlock('The request has been successfully accepted. However, registration has *NOT been completed* yet.'));
+    blocks.push(
+      markdownHeaderBlock(':white_check_mark: 2. Install bot to Slack'),
+    );
+    blocks.push(
+      markdownHeaderBlock(
+        ':white_check_mark: 3. Register for your GROWI Custom Bot Proxy',
+      ),
+    );
+    blocks.push(
+      markdownSectionBlock(
+        'The request has been successfully accepted. However, registration has *NOT been completed* yet.',
+      ),
+    );
     blocks.push(markdownHeaderBlock(':arrow_right: 4. Set Proxy URL on GROWI'));
-    blocks.push(markdownSectionBlock('Please enter and update the following Proxy URL to slack bot setting form in your GROWI'));
+    blocks.push(
+      markdownSectionBlock(
+        'Please enter and update the following Proxy URL to slack bot setting form in your GROWI',
+      ),
+    );
     blocks.push(markdownSectionBlock(`Proxy URL: ${serverUri}`));
     blocks.push(markdownHeaderBlock(':arrow_right: 5. Test Connection'));
-    blocks.push(markdownSectionBlock('And *Test Connection* to complete the registration in your GROWI.'));
-    blocks.push(markdownHeaderBlock(':white_large_square: 6. (Opt) Manage Permission'));
-    blocks.push(markdownSectionBlock('Modify permission settings if you need.'));
+    blocks.push(
+      markdownSectionBlock(
+        'And *Test Connection* to complete the registration in your GROWI.',
+      ),
+    );
+    blocks.push(
+      markdownHeaderBlock(':white_large_square: 6. (Opt) Manage Permission'),
+    );
+    blocks.push(
+      markdownSectionBlock('Modify permission settings if you need.'),
+    );
     await respond(responseUrl, {
       text: 'Proxy URL',
       blocks,
     });
     return;
   }
-
 }

+ 120 - 62
apps/slackbot-proxy/src/services/RelationsService.ts

@@ -1,43 +1,50 @@
-import { REQUEST_TIMEOUT_FOR_PTOG, type IChannelOptionalId } from '@growi/slack';
+import {
+  type IChannelOptionalId,
+  REQUEST_TIMEOUT_FOR_PTOG,
+} from '@growi/slack';
 import { getSupportedGrowiActionsRegExp } from '@growi/slack/dist/utils/get-supported-growi-actions-regexps';
 import { permissionParser } from '@growi/slack/dist/utils/permission-parser';
 import { Inject, Service } from '@tsed/di';
 import axios from 'axios';
 import { addHours } from 'date-fns/addHours';
 
-import { Relation, PermissionSettingsInterface } from '~/entities/relation';
+import { PermissionSettingsInterface, Relation } from '~/entities/relation';
 import { RelationRepository } from '~/repositories/relation';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('slackbot-proxy:services:RelationsService');
 
 type CheckPermissionForInteractionsResults = {
-  allowedRelations:Relation[],
-  disallowedGrowiUrls:Set<string>,
-  commandName:string,
-  rejectedResults:PromiseRejectedResult[]
-}
+  allowedRelations: Relation[];
+  disallowedGrowiUrls: Set<string>;
+  commandName: string;
+  rejectedResults: PromiseRejectedResult[];
+};
 
 type CheckEachRelationResult = {
-  allowedRelation:Relation|null,
-  disallowedGrowiUrl:string|null,
-  eachRelationCommandName:string,
-}
-
+  allowedRelation: Relation | null;
+  disallowedGrowiUrl: string | null;
+  eachRelationCommandName: string;
+};
 
 @Service()
 export class RelationsService {
-
   @Inject()
   relationRepository: RelationRepository;
 
   async resetAllExpiredAtCommands(): Promise<void> {
-    await this.relationRepository.update({}, { expiredAtCommands: new Date('2000-01-01') });
+    await this.relationRepository.update(
+      {},
+      { expiredAtCommands: new Date('2000-01-01') },
+    );
   }
 
-  private async getSupportedGrowiCommands(relation:Relation):Promise<any> {
+  private async getSupportedGrowiCommands(relation: Relation): Promise<any> {
     // generate API URL
-    const url = new URL('/_api/v3/slack-integration/supported-commands', relation.growiUri);
+    const url = new URL(
+      '/_api/v3/slack-integration/supported-commands',
+      relation.growiUri,
+    );
     return axios.get(url.toString(), {
       headers: {
         'x-growi-ptog-tokens': relation.tokenPtoG,
@@ -46,16 +53,23 @@ export class RelationsService {
     });
   }
 
-  private async syncSupportedGrowiCommands(relation:Relation): Promise<Relation> {
+  private async syncSupportedGrowiCommands(
+    relation: Relation,
+  ): Promise<Relation> {
     const res = await this.getSupportedGrowiCommands(relation);
 
     // support both of v4.4.x and v4.5.x
     // see: https://redmine.weseek.co.jp/issues/82985
-    const { permissionsForBroadcastUseCommands, permissionsForSingleUseCommands } = res.data.data ?? res.data;
+    const {
+      permissionsForBroadcastUseCommands,
+      permissionsForSingleUseCommands,
+    } = res.data.data ?? res.data;
 
     if (relation !== null) {
-      relation.permissionsForBroadcastUseCommands = permissionsForBroadcastUseCommands;
-      relation.permissionsForSingleUseCommands = permissionsForSingleUseCommands;
+      relation.permissionsForBroadcastUseCommands =
+        permissionsForBroadcastUseCommands;
+      relation.permissionsForSingleUseCommands =
+        permissionsForSingleUseCommands;
       relation.expiredAtCommands = addHours(new Date(), 48);
       return this.relationRepository.save(relation);
     }
@@ -65,8 +79,11 @@ export class RelationsService {
   private async syncRelation(relation: Relation): Promise<Relation> {
     // TODO use assert (relation != null)
 
-    const isDataNull = relation.permissionsForBroadcastUseCommands == null || relation.permissionsForBroadcastUseCommands == null;
-    const distanceMillisecondsToExpiredAt = relation.getDistanceInMillisecondsToExpiredAt(new Date());
+    const isDataNull =
+      relation.permissionsForBroadcastUseCommands == null ||
+      relation.permissionsForBroadcastUseCommands == null;
+    const distanceMillisecondsToExpiredAt =
+      relation.getDistanceInMillisecondsToExpiredAt(new Date());
     const isExpired = distanceMillisecondsToExpiredAt < 0;
 
     if (isDataNull || isExpired) {
@@ -74,14 +91,19 @@ export class RelationsService {
     }
 
     // 24 hours
-    const isLimitUnder24Hours = distanceMillisecondsToExpiredAt < 24 * 60 * 60 * 1000;
+    const isLimitUnder24Hours =
+      distanceMillisecondsToExpiredAt < 24 * 60 * 60 * 1000;
     if (isLimitUnder24Hours) {
       this.syncSupportedGrowiCommands(relation);
     }
     return relation;
   }
 
-  private isPermitted(permissionSettings: PermissionSettingsInterface, growiCommandType: string, channel: IChannelOptionalId): boolean {
+  private isPermitted(
+    permissionSettings: PermissionSettingsInterface,
+    growiCommandType: string,
+    channel: IChannelOptionalId,
+  ): boolean {
     // TODO assert (permissionSettings != null)
 
     const permissionForCommand = permissionSettings[growiCommandType];
@@ -89,7 +111,11 @@ export class RelationsService {
     return permissionParser(permissionForCommand, channel);
   }
 
-  async isPermissionsForSingleUseCommands(relation: Relation, growiCommandType: string, channel: IChannelOptionalId): Promise<boolean> {
+  async isPermissionsForSingleUseCommands(
+    relation: Relation,
+    growiCommandType: string,
+    channel: IChannelOptionalId,
+  ): Promise<boolean> {
     // TODO assert (relation != null)
     if (relation == null) {
       return false;
@@ -99,18 +125,25 @@ export class RelationsService {
 
     try {
       relationToEval = await this.syncRelation(relation);
-    }
-    catch (err) {
+    } catch (err) {
       logger.error('failed to sync', err);
       return false;
     }
 
     // TODO assert (relationToEval.permissionsForSingleUseCommands != null) because syncRelation success
 
-    return this.isPermitted(relationToEval.permissionsForSingleUseCommands, growiCommandType, channel);
+    return this.isPermitted(
+      relationToEval.permissionsForSingleUseCommands,
+      growiCommandType,
+      channel,
+    );
   }
 
-  async isPermissionsUseBroadcastCommands(relation: Relation, growiCommandType: string, channel: IChannelOptionalId):Promise<boolean> {
+  async isPermissionsUseBroadcastCommands(
+    relation: Relation,
+    growiCommandType: string,
+    channel: IChannelOptionalId,
+  ): Promise<boolean> {
     // TODO assert (relation != null)
     if (relation == null) {
       return false;
@@ -120,58 +153,82 @@ export class RelationsService {
 
     try {
       relationToEval = await this.syncRelation(relation);
-    }
-    catch (err) {
+    } catch (err) {
       logger.error('failed to sync', err);
       return false;
     }
 
     // TODO assert (relationToEval.permissionsForSingleUseCommands != null) because syncRelation success
 
-    return this.isPermitted(relationToEval.permissionsForBroadcastUseCommands, growiCommandType, channel);
+    return this.isPermitted(
+      relationToEval.permissionsForBroadcastUseCommands,
+      growiCommandType,
+      channel,
+    );
   }
 
   async checkPermissionForInteractions(
-      relations: Relation[], actionId: string, callbackId: string, channel: IChannelOptionalId,
-  ):Promise<CheckPermissionForInteractionsResults> {
-
-    const allowedRelations:Relation[] = [];
-    const disallowedGrowiUrls:Set<string> = new Set();
+    relations: Relation[],
+    actionId: string,
+    callbackId: string,
+    channel: IChannelOptionalId,
+  ): Promise<CheckPermissionForInteractionsResults> {
+    const allowedRelations: Relation[] = [];
+    const disallowedGrowiUrls: Set<string> = new Set();
     let commandName = '';
 
-    const results = await Promise.allSettled(relations.map((relation) => {
-      const relationResult = this.checkEachRelation(relation, actionId, callbackId, channel);
-      const { allowedRelation, disallowedGrowiUrl, eachRelationCommandName } = relationResult;
-
-      if (allowedRelation != null) {
-        allowedRelations.push(allowedRelation);
-      }
-      if (disallowedGrowiUrl != null) {
-        disallowedGrowiUrls.add(disallowedGrowiUrl);
-      }
-      commandName = eachRelationCommandName;
-      return relationResult;
-    }));
+    const results = await Promise.allSettled(
+      relations.map((relation) => {
+        const relationResult = this.checkEachRelation(
+          relation,
+          actionId,
+          callbackId,
+          channel,
+        );
+        const { allowedRelation, disallowedGrowiUrl, eachRelationCommandName } =
+          relationResult;
+
+        if (allowedRelation != null) {
+          allowedRelations.push(allowedRelation);
+        }
+        if (disallowedGrowiUrl != null) {
+          disallowedGrowiUrls.add(disallowedGrowiUrl);
+        }
+        commandName = eachRelationCommandName;
+        return relationResult;
+      }),
+    );
 
     // Pick up only a relation which status is "rejected" in results. Like bellow
-    const rejectedResults: PromiseRejectedResult[] = results.filter((result): result is PromiseRejectedResult => result.status === 'rejected');
+    const rejectedResults: PromiseRejectedResult[] = results.filter(
+      (result): result is PromiseRejectedResult => result.status === 'rejected',
+    );
 
     return {
-      allowedRelations, disallowedGrowiUrls, commandName, rejectedResults,
+      allowedRelations,
+      disallowedGrowiUrls,
+      commandName,
+      rejectedResults,
     };
   }
 
-  checkEachRelation(relation:Relation, actionId:string, callbackId:string, channel: IChannelOptionalId): CheckEachRelationResult {
-    let allowedRelation:Relation|null = null;
-    let disallowedGrowiUrl:string|null = null;
+  checkEachRelation(
+    relation: Relation,
+    actionId: string,
+    callbackId: string,
+    channel: IChannelOptionalId,
+  ): CheckEachRelationResult {
+    let allowedRelation: Relation | null = null;
+    let disallowedGrowiUrl: string | null = null;
     let eachRelationCommandName = '';
 
-    let permissionForInteractions:boolean|string[];
+    let permissionForInteractions: boolean | string[];
     const singleUse = Object.keys(relation.permissionsForSingleUseCommands);
-    const broadCastUse = Object.keys(relation.permissionsForBroadcastUseCommands);
-
-    [...singleUse, ...broadCastUse].forEach(async(tempCommandName) => {
+    const broadCastUse = Object.keys(
+      relation.permissionsForBroadcastUseCommands,
+    );
 
+    [...singleUse, ...broadCastUse].forEach(async (tempCommandName) => {
       // ex. search OR search:handlerName
       const commandRegExp = getSupportedGrowiActionsRegExp(tempCommandName);
       // skip this forEach loop if the requested command is not in permissionsForBroadcastUseCommands and permissionsForSingleUseCommands
@@ -182,10 +239,12 @@ export class RelationsService {
       eachRelationCommandName = tempCommandName;
 
       // case: singleUse
-      permissionForInteractions = relation.permissionsForSingleUseCommands[tempCommandName];
+      permissionForInteractions =
+        relation.permissionsForSingleUseCommands[tempCommandName];
       // case: broadcastUse
       if (permissionForInteractions == null) {
-        permissionForInteractions = relation.permissionsForBroadcastUseCommands[tempCommandName];
+        permissionForInteractions =
+          relation.permissionsForBroadcastUseCommands[tempCommandName];
       }
 
       if (permissionForInteractions === true) {
@@ -213,5 +272,4 @@ export class RelationsService {
 
     return { allowedRelation, disallowedGrowiUrl, eachRelationCommandName };
   }
-
 }

+ 89 - 49
apps/slackbot-proxy/src/services/SelectGrowiService.ts

@@ -1,6 +1,7 @@
-
 import type {
-  GrowiCommand, GrowiCommandProcessor, GrowiInteractionProcessor,
+  GrowiCommand,
+  GrowiCommandProcessor,
+  GrowiInteractionProcessor,
   InteractionHandledResult,
 } from '@growi/slack';
 import { markdownSectionBlock } from '@growi/slack/dist/utils/block-kit-builder';
@@ -19,39 +20,45 @@ import loggerFactory from '~/utils/logger';
 const logger = loggerFactory('slackbot-proxy:services:UnregisterService');
 
 export type SelectGrowiCommandBody = {
-  growiUrisForSingleUse: string[],
-}
+  growiUrisForSingleUse: string[];
+};
 
 type SelectValue = {
-  growiCommand: GrowiCommand,
-  growiUri: any,
-}
+  growiCommand: GrowiCommand;
+  growiUri: any;
+};
 
 type SendCommandBody = {
   // eslint-disable-next-line camelcase
-  trigger_id: string,
+  trigger_id: string;
   // eslint-disable-next-line camelcase
-  channel_id: string,
+  channel_id: string;
   // eslint-disable-next-line camelcase
-  channel_name: string,
-}
+  channel_name: string;
+};
 
 export type SelectedGrowiInformation = {
-  relation: Relation,
-  growiCommand: GrowiCommand,
-  sendCommandBody: SendCommandBody,
-}
+  relation: Relation;
+  growiCommand: GrowiCommand;
+  sendCommandBody: SendCommandBody;
+};
 
 @Service()
-export class SelectGrowiService implements GrowiCommandProcessor<SelectGrowiCommandBody | null>, GrowiInteractionProcessor<SelectedGrowiInformation> {
-
+export class SelectGrowiService
+  implements
+    GrowiCommandProcessor<SelectGrowiCommandBody | null>,
+    GrowiInteractionProcessor<SelectedGrowiInformation>
+{
   @Inject()
   relationRepository: RelationRepository;
 
   @Inject()
   installationRepository: InstallationRepository;
 
-  private generateGrowiSelectValue(growiCommand: GrowiCommand, growiUri: string): SelectValue {
+  private generateGrowiSelectValue(
+    growiCommand: GrowiCommand,
+    growiUri: string,
+  ): SelectValue {
     return {
       growiCommand,
       growiUri,
@@ -64,13 +71,15 @@ export class SelectGrowiService implements GrowiCommandProcessor<SelectGrowiComm
   }
 
   async processCommand(
-      growiCommand: GrowiCommand, authorizeResult: AuthorizeResult, context: SelectGrowiCommandBody,
+    growiCommand: GrowiCommand,
+    authorizeResult: AuthorizeResult,
+    context: SelectGrowiCommandBody,
   ): Promise<void> {
     const growiUrls = context.growiUrisForSingleUse;
 
     const chooseSection = growiUrls.map((growiUri) => {
       const value = this.generateGrowiSelectValue(growiCommand, growiUri);
-      return ({
+      return {
         type: 'section',
         text: {
           type: 'mrkdwn',
@@ -85,7 +94,7 @@ export class SelectGrowiService implements GrowiCommandProcessor<SelectGrowiComm
           },
           value: JSON.stringify(value),
         },
-      });
+      };
     });
 
     return respond(growiCommand.responseUrl, {
@@ -112,26 +121,37 @@ export class SelectGrowiService implements GrowiCommandProcessor<SelectGrowiComm
         ...chooseSection,
       ],
     });
-
   }
 
   // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-  shouldHandleInteraction(interactionPayloadAccessor: InteractionPayloadAccessor): boolean {
-    const { actionId, callbackId } = interactionPayloadAccessor.getActionIdAndCallbackIdFromPayLoad();
-    const registerRegexp: RegExp = getInteractionIdRegexpFromCommandName('select_growi');
+  shouldHandleInteraction(
+    interactionPayloadAccessor: InteractionPayloadAccessor,
+  ): boolean {
+    const { actionId, callbackId } =
+      interactionPayloadAccessor.getActionIdAndCallbackIdFromPayLoad();
+    const registerRegexp: RegExp =
+      getInteractionIdRegexpFromCommandName('select_growi');
     return registerRegexp.test(actionId) || registerRegexp.test(callbackId);
   }
 
   async processInteraction(
-      // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-      authorizeResult: AuthorizeResult, interactionPayload: any, interactionPayloadAccessor: InteractionPayloadAccessor,
+    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+    authorizeResult: AuthorizeResult,
+    interactionPayload: any,
+    interactionPayloadAccessor: InteractionPayloadAccessor,
   ): Promise<InteractionHandledResult<SelectedGrowiInformation>> {
-    const interactionHandledResult: InteractionHandledResult<SelectedGrowiInformation> = {
-      isTerminated: false,
-    };
-    if (!this.shouldHandleInteraction(interactionPayloadAccessor)) return interactionHandledResult;
+    const interactionHandledResult: InteractionHandledResult<SelectedGrowiInformation> =
+      {
+        isTerminated: false,
+      };
+    if (!this.shouldHandleInteraction(interactionPayloadAccessor))
+      return interactionHandledResult;
 
-    const selectGrowiInformation = await this.handleSelectInteraction(authorizeResult, interactionPayload, interactionPayloadAccessor);
+    const selectGrowiInformation = await this.handleSelectInteraction(
+      authorizeResult,
+      interactionPayload,
+      interactionPayloadAccessor,
+    );
     if (selectGrowiInformation != null) {
       interactionHandledResult.result = selectGrowiInformation;
     }
@@ -142,31 +162,40 @@ export class SelectGrowiService implements GrowiCommandProcessor<SelectGrowiComm
 
   // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
   async handleSelectInteraction(
-      // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-      authorizeResult: AuthorizeResult, interactionPayload: any, interactionPayloadAccessor: InteractionPayloadAccessor,
+    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+    authorizeResult: AuthorizeResult,
+    interactionPayload: any,
+    interactionPayloadAccessor: InteractionPayloadAccessor,
   ): Promise<SelectedGrowiInformation | null> {
     const responseUrl = interactionPayloadAccessor.getResponseUrl();
 
     const selectGrowiValue = interactionPayloadAccessor.firstAction()?.value;
     if (selectGrowiValue == null) {
-      logger.error('GROWI command failed: The first action element must have the value parameter.');
+      logger.error(
+        'GROWI command failed: The first action element must have the value parameter.',
+      );
       await respond(responseUrl, {
         text: 'GROWI command failed',
         blocks: [
-          markdownSectionBlock('Error occurred while processing GROWI command.'),
+          markdownSectionBlock(
+            'Error occurred while processing GROWI command.',
+          ),
         ],
       });
       return null;
     }
     const { growiUri, growiCommand } = JSON.parse(selectGrowiValue);
 
-
     if (growiCommand == null) {
-      logger.error('GROWI command failed: The first action value must have growiCommand parameter.');
+      logger.error(
+        'GROWI command failed: The first action value must have growiCommand parameter.',
+      );
       await respond(responseUrl, {
         text: 'GROWI command failed',
         blocks: [
-          markdownSectionBlock('Error occurred while processing GROWI command.'),
+          markdownSectionBlock(
+            'Error occurred while processing GROWI command.',
+          ),
         ],
       });
       return null;
@@ -175,28 +204,36 @@ export class SelectGrowiService implements GrowiCommandProcessor<SelectGrowiComm
     await replaceOriginal(responseUrl, {
       text: `Accepted ${growiCommand.growiCommandType} command.`,
       blocks: [
-        markdownSectionBlock(`Forwarding your request *"/growi ${growiCommand.growiCommandType}"* on GROWI to ${growiUri} ...`),
+        markdownSectionBlock(
+          `Forwarding your request *"/growi ${growiCommand.growiCommandType}"* on GROWI to ${growiUri} ...`,
+        ),
       ],
     });
 
-    const installationId = authorizeResult.enterpriseId || authorizeResult.teamId;
+    const installationId =
+      authorizeResult.enterpriseId || authorizeResult.teamId;
     let installation: Installation | undefined;
     try {
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      installation = await this.installationRepository.findByTeamIdOrEnterpriseId(installationId!);
-    }
-    catch (err) {
+      installation =
+        await this.installationRepository.findByTeamIdOrEnterpriseId(
+          installationId!,
+        );
+    } catch (err) {
       logger.error('GROWI command failed: No installation found.\n', err);
       await respond(responseUrl, {
         text: 'GROWI command failed',
         blocks: [
-          markdownSectionBlock('Error occurred while processing GROWI command.'),
+          markdownSectionBlock(
+            'Error occurred while processing GROWI command.',
+          ),
         ],
       });
       return null;
     }
 
-    const relation = await this.relationRepository.createQueryBuilder('relation')
+    const relation = await this.relationRepository
+      .createQueryBuilder('relation')
       .where('relation.growiUri =:growiUri', { growiUri })
       .andWhere('relation.installationId = :id', { id: installation?.id })
       .leftJoinAndSelect('relation.installation', 'installation')
@@ -207,7 +244,9 @@ export class SelectGrowiService implements GrowiCommandProcessor<SelectGrowiComm
       await respond(responseUrl, {
         text: 'GROWI command failed',
         blocks: [
-          markdownSectionBlock('Error occurred while processing GROWI command.'),
+          markdownSectionBlock(
+            'Error occurred while processing GROWI command.',
+          ),
         ],
       });
       return null;
@@ -220,7 +259,9 @@ export class SelectGrowiService implements GrowiCommandProcessor<SelectGrowiComm
       await respond(responseUrl, {
         text: 'GROWI command failed',
         blocks: [
-          markdownSectionBlock('Error occurred while processing GROWI command.'),
+          markdownSectionBlock(
+            'Error occurred while processing GROWI command.',
+          ),
         ],
       });
       return null;
@@ -237,5 +278,4 @@ export class SelectGrowiService implements GrowiCommandProcessor<SelectGrowiComm
       sendCommandBody,
     };
   }
-
 }

+ 9 - 5
apps/slackbot-proxy/src/services/SystemInformationService.ts

@@ -7,11 +7,12 @@ import loggerFactory from '~/utils/logger';
 
 import { RelationsService } from './RelationsService';
 
-const logger = loggerFactory('slackbot-proxy:services:SystemInformationService');
+const logger = loggerFactory(
+  'slackbot-proxy:services:SystemInformationService',
+);
 
 @Service()
 export class SystemInformationService {
-
   @Inject()
   private readonly repository: SystemInformationRepository;
 
@@ -31,17 +32,20 @@ export class SystemInformationService {
     const proxyVersion = readPkgUpResult?.packageJson.version;
     if (proxyVersion == null) return logger.error('version is null');
 
-    const systemInfo: SystemInformation | undefined = await this.repository.findOne();
+    const systemInfo: SystemInformation | undefined =
+      await this.repository.findOne();
 
     // return if the version didn't change
     if (systemInfo != null && systemInfo.version === proxyVersion) {
       return;
     }
 
-    await this.repository.createOrUpdateUniqueRecordWithVersion(systemInfo, proxyVersion);
+    await this.repository.createOrUpdateUniqueRecordWithVersion(
+      systemInfo,
+      proxyVersion,
+    );
 
     // make relations expired
     await this.relationsService.resetAllExpiredAtCommands();
   }
-
 }

+ 92 - 37
apps/slackbot-proxy/src/services/UnregisterService.ts

@@ -1,12 +1,18 @@
 import type {
-  GrowiCommand, GrowiCommandProcessor, GrowiInteractionProcessor, InteractionHandledResult,
+  GrowiCommand,
+  GrowiCommandProcessor,
+  GrowiInteractionProcessor,
+  InteractionHandledResult,
 } from '@growi/slack';
 import {
-  inputBlock, markdownSectionBlock, actionsBlock, buttonElement,
+  actionsBlock,
+  buttonElement,
+  inputBlock,
+  markdownSectionBlock,
 } from '@growi/slack/dist/utils/block-kit-builder';
 import { InteractionPayloadAccessor } from '@growi/slack/dist/utils/interaction-payload-accessor';
 import { getInteractionIdRegexpFromCommandName } from '@growi/slack/dist/utils/payload-interaction-id-helpers';
-import { respond, replaceOriginal } from '@growi/slack/dist/utils/response-url';
+import { replaceOriginal, respond } from '@growi/slack/dist/utils/response-url';
 import { AuthorizeResult } from '@slack/oauth';
 import { MultiStaticSelect } from '@slack/web-api';
 import { Inject, Service } from '@tsed/di';
@@ -21,8 +27,9 @@ import loggerFactory from '~/utils/logger';
 const logger = loggerFactory('slackbot-proxy:services:UnregisterService');
 
 @Service()
-export class UnregisterService implements GrowiCommandProcessor, GrowiInteractionProcessor<void> {
-
+export class UnregisterService
+  implements GrowiCommandProcessor, GrowiInteractionProcessor<void>
+{
   @Inject()
   relationRepository: RelationRepository;
 
@@ -33,12 +40,20 @@ export class UnregisterService implements GrowiCommandProcessor, GrowiInteractio
     return growiCommand.growiCommandType === 'unregister';
   }
 
-  async processCommand(growiCommand: GrowiCommand, authorizeResult: AuthorizeResult): Promise<void> {
+  async processCommand(
+    growiCommand: GrowiCommand,
+    authorizeResult: AuthorizeResult,
+  ): Promise<void> {
     // get growi urls
-    const installationId = authorizeResult.enterpriseId || authorizeResult.teamId;
+    const installationId =
+      authorizeResult.enterpriseId || authorizeResult.teamId;
     // 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 })
       .leftJoinAndSelect('relation.installation', 'installation')
       .getMany();
@@ -47,7 +62,9 @@ export class UnregisterService implements GrowiCommandProcessor, GrowiInteractio
       await respond(growiCommand.responseUrl, {
         text: 'No GROWI found to unregister.',
         blocks: [
-          markdownSectionBlock('You haven\'t registered any GROWI to this workspace.'),
+          markdownSectionBlock(
+            "You haven't registered any GROWI to this workspace.",
+          ),
           markdownSectionBlock('Send `/growi register` to register.'),
         ],
       });
@@ -77,9 +94,16 @@ export class UnregisterService implements GrowiCommandProcessor, GrowiInteractio
       blocks: [
         inputBlock(staticSelectElement, 'growiUris', 'GROWI URL to unregister'),
         actionsBlock(
-          buttonElement({ text: 'Cancel', actionId: 'unregister:cancel', value: JSON.stringify({}) }),
           buttonElement({
-            text: 'Unregister', actionId: 'unregister:unregister', style: 'danger', value: JSON.stringify({}),
+            text: 'Cancel',
+            actionId: 'unregister:cancel',
+            value: JSON.stringify({}),
+          }),
+          buttonElement({
+            text: 'Unregister',
+            actionId: 'unregister:unregister',
+            style: 'danger',
+            value: JSON.stringify({}),
           }),
         ),
       ],
@@ -87,29 +111,45 @@ export class UnregisterService implements GrowiCommandProcessor, GrowiInteractio
   }
 
   // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-  shouldHandleInteraction(interactionPayloadAccessor: InteractionPayloadAccessor): boolean {
-    const { actionId, callbackId } = interactionPayloadAccessor.getActionIdAndCallbackIdFromPayLoad();
-    const registerRegexp: RegExp = getInteractionIdRegexpFromCommandName('unregister');
+  shouldHandleInteraction(
+    interactionPayloadAccessor: InteractionPayloadAccessor,
+  ): boolean {
+    const { actionId, callbackId } =
+      interactionPayloadAccessor.getActionIdAndCallbackIdFromPayLoad();
+    const registerRegexp: RegExp =
+      getInteractionIdRegexpFromCommandName('unregister');
     return registerRegexp.test(actionId) || registerRegexp.test(callbackId);
   }
 
   async processInteraction(
-      // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-      authorizeResult: AuthorizeResult, interactionPayload: any, interactionPayloadAccessor: InteractionPayloadAccessor,
+    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+    authorizeResult: AuthorizeResult,
+    interactionPayload: any,
+    interactionPayloadAccessor: InteractionPayloadAccessor,
   ): Promise<InteractionHandledResult<void>> {
     const interactionHandledResult: InteractionHandledResult<void> = {
       isTerminated: false,
     };
-    if (!this.shouldHandleInteraction(interactionPayloadAccessor)) return interactionHandledResult;
+    if (!this.shouldHandleInteraction(interactionPayloadAccessor))
+      return interactionHandledResult;
 
-    const { actionId } = interactionPayloadAccessor.getActionIdAndCallbackIdFromPayLoad();
+    const { actionId } =
+      interactionPayloadAccessor.getActionIdAndCallbackIdFromPayLoad();
 
     switch (actionId) {
       case 'unregister:unregister':
-        interactionHandledResult.result = await this.handleUnregisterInteraction(authorizeResult, interactionPayload, interactionPayloadAccessor);
+        interactionHandledResult.result =
+          await this.handleUnregisterInteraction(
+            authorizeResult,
+            interactionPayload,
+            interactionPayloadAccessor,
+          );
         break;
       case 'unregister:cancel':
-        interactionHandledResult.result = await this.handleUnregisterCancelInteraction(interactionPayloadAccessor);
+        interactionHandledResult.result =
+          await this.handleUnregisterCancelInteraction(
+            interactionPayloadAccessor,
+          );
         break;
       case 'unregister:selectedGrowiUris':
         break;
@@ -124,12 +164,17 @@ export class UnregisterService implements GrowiCommandProcessor, GrowiInteractio
 
   // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
   async handleUnregisterInteraction(
-      // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-      authorizeResult: AuthorizeResult, interactionPayload: any, interactionPayloadAccessor: InteractionPayloadAccessor,
-  ):Promise<void> {
+    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+    authorizeResult: AuthorizeResult,
+    interactionPayload: any,
+    interactionPayloadAccessor: InteractionPayloadAccessor,
+  ): Promise<void> {
     const responseUrl = interactionPayloadAccessor.getResponseUrl();
 
-    const selectedOptions = interactionPayloadAccessor.getStateValues()?.growiUris?.['unregister:selectedGrowiUris']?.selected_options;
+    const selectedOptions =
+      interactionPayloadAccessor.getStateValues()?.growiUris?.[
+        'unregister:selectedGrowiUris'
+      ]?.selected_options;
     if (!Array.isArray(selectedOptions)) {
       logger.error('Unregisteration failed: Mulformed object was detected\n');
       await respond(responseUrl, {
@@ -140,15 +185,20 @@ export class UnregisterService implements GrowiCommandProcessor, GrowiInteractio
       });
       return;
     }
-    const growiUris = selectedOptions.map(selectedOption => selectedOption.value);
+    const growiUris = selectedOptions.map(
+      (selectedOption) => selectedOption.value,
+    );
 
-    const installationId = authorizeResult.enterpriseId || authorizeResult.teamId;
+    const installationId =
+      authorizeResult.enterpriseId || authorizeResult.teamId;
     let installation: Installation | undefined;
     try {
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      installation = await this.installationRepository.findByTeamIdOrEnterpriseId(installationId!);
-    }
-    catch (err) {
+      installation =
+        await this.installationRepository.findByTeamIdOrEnterpriseId(
+          installationId!,
+        );
+    } catch (err) {
       logger.error('Unregisteration failed:\n', err);
       await respond(responseUrl, {
         text: 'Unregistration failed',
@@ -161,13 +211,15 @@ export class UnregisterService implements GrowiCommandProcessor, GrowiInteractio
 
     let deleteResult: DeleteResult;
     try {
-      deleteResult = await this.relationRepository.createQueryBuilder('relation')
+      deleteResult = await this.relationRepository
+        .createQueryBuilder('relation')
         .where('relation.growiUri IN (:uris)', { uris: growiUris })
-        .andWhere('relation.installationId = :installationId', { installationId: installation?.id })
+        .andWhere('relation.installationId = :installationId', {
+          installationId: installation?.id,
+        })
         .delete()
         .execute();
-    }
-    catch (err) {
+    } catch (err) {
       logger.error('Unregisteration failed\n', err);
       await respond(responseUrl, {
         text: 'Unregistration failed',
@@ -181,17 +233,20 @@ export class UnregisterService implements GrowiCommandProcessor, GrowiInteractio
     await replaceOriginal(responseUrl, {
       text: 'Unregistration completed',
       blocks: [
-        markdownSectionBlock(`Unregistered *${deleteResult.affected}* GROWI from this workspace.`),
+        markdownSectionBlock(
+          `Unregistered *${deleteResult.affected}* GROWI from this workspace.`,
+        ),
       ],
     });
     return;
   }
 
   // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-  async handleUnregisterCancelInteraction(interactionPayloadAccessor: InteractionPayloadAccessor): Promise<void> {
+  async handleUnregisterCancelInteraction(
+    interactionPayloadAccessor: InteractionPayloadAccessor,
+  ): Promise<void> {
     await axios.post(interactionPayloadAccessor.getResponseUrl(), {
       delete_original: true,
     });
   }
-
 }

+ 28 - 14
apps/slackbot-proxy/src/services/growi-uri-injector/ActionsBlockPayloadDelegator.ts

@@ -1,33 +1,42 @@
 import { Inject, OnInit, Service } from '@tsed/di';
 
 import {
-  GrowiUriInjector, GrowiUriWithOriginalData, TypedBlock,
+  GrowiUriInjector,
+  GrowiUriWithOriginalData,
+  TypedBlock,
 } from '~/interfaces/growi-uri-injector';
 
 import { ButtonActionPayloadDelegator } from './block-elements/ButtonActionPayloadDelegator';
 import { CheckboxesActionPayloadDelegator } from './block-elements/CheckboxesActionPayloadDelegator';
 
-
 // see: https://api.slack.com/reference/block-kit/blocks
 type BlockElement = TypedBlock & {
-  elements: (TypedBlock & any)[],
-}
+  elements: (TypedBlock & any)[];
+};
 
 // see: https://api.slack.com/reference/interaction-payloads/block-actions
 type BlockActionsPayload = TypedBlock & {
-  actions: TypedBlock[],
-}
+  actions: TypedBlock[];
+};
 
 @Service()
-export class ActionsBlockPayloadDelegator implements GrowiUriInjector<any, BlockElement[], any, BlockActionsPayload>, OnInit {
-
+export class ActionsBlockPayloadDelegator
+  implements
+    GrowiUriInjector<any, BlockElement[], any, BlockActionsPayload>,
+    OnInit
+{
   @Inject()
   buttonActionPayloadDelegator: ButtonActionPayloadDelegator;
 
   @Inject()
   checkboxesActionPayloadDelegator: CheckboxesActionPayloadDelegator;
 
-  private childDelegators: GrowiUriInjector<TypedBlock[], any, TypedBlock, any>[] = [];
+  private childDelegators: GrowiUriInjector<
+    TypedBlock[],
+    any,
+    TypedBlock,
+    any
+  >[] = [];
 
   $onInit(): void | Promise<any> {
     this.childDelegators.push(
@@ -38,16 +47,22 @@ export class ActionsBlockPayloadDelegator implements GrowiUriInjector<any, Block
 
   // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
   shouldHandleToInject(data: any): data is BlockElement[] {
-    const actionsBlocks = data.filter(blockElement => blockElement.type === 'actions');
+    const actionsBlocks = data.filter(
+      (blockElement) => blockElement.type === 'actions',
+    );
     return actionsBlocks.length > 0;
   }
 
   inject(data: BlockElement[], growiUri: string): void {
-    const actionsBlocks = data.filter(blockElement => blockElement.type === 'actions');
+    const actionsBlocks = data.filter(
+      (blockElement) => blockElement.type === 'actions',
+    );
 
     // collect elements
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const elements = actionsBlocks.flatMap(actionBlock => actionBlock.elements!);
+    const elements = actionsBlocks.flatMap(
+      (actionBlock) => actionBlock.elements!,
+    );
 
     this.childDelegators.forEach((delegator) => {
       if (delegator.shouldHandleToInject(elements)) {
@@ -64,7 +79,7 @@ export class ActionsBlockPayloadDelegator implements GrowiUriInjector<any, Block
 
     const action = data.actions[0];
     return this.childDelegators
-      .map(delegator => delegator.shouldHandleToExtract(action))
+      .map((delegator) => delegator.shouldHandleToExtract(action))
       .includes(true);
   }
 
@@ -82,5 +97,4 @@ export class ActionsBlockPayloadDelegator implements GrowiUriInjector<any, Block
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     return growiUriWithOriginalData!;
   }
-
 }

+ 34 - 13
apps/slackbot-proxy/src/services/growi-uri-injector/SectionBlockPayloadDelegator.ts

@@ -1,33 +1,47 @@
 import { Inject, OnInit, Service } from '@tsed/di';
 
 import {
-  GrowiUriInjector, GrowiUriWithOriginalData, TypedBlock,
+  GrowiUriInjector,
+  GrowiUriWithOriginalData,
+  TypedBlock,
 } from '~/interfaces/growi-uri-injector';
 
 import { ButtonActionPayloadDelegator } from './block-elements/ButtonActionPayloadDelegator';
 import { CheckboxesActionPayloadDelegator } from './block-elements/CheckboxesActionPayloadDelegator';
 
-
 // see: https://api.slack.com/reference/block-kit/blocks#section
 type SectionWithAccessoryElement = TypedBlock & {
-  accessory: TypedBlock & any,
-}
+  accessory: TypedBlock & any;
+};
 
 // see: https://api.slack.com/reference/interaction-payloads/block-actions
 type BlockActionsPayload = TypedBlock & {
-  actions: TypedBlock[],
-}
+  actions: TypedBlock[];
+};
 
 @Service()
-export class SectionBlockPayloadDelegator implements GrowiUriInjector<any, SectionWithAccessoryElement[], any, BlockActionsPayload>, OnInit {
-
+export class SectionBlockPayloadDelegator
+  implements
+    GrowiUriInjector<
+      any,
+      SectionWithAccessoryElement[],
+      any,
+      BlockActionsPayload
+    >,
+    OnInit
+{
   @Inject()
   buttonActionPayloadDelegator: ButtonActionPayloadDelegator;
 
   @Inject()
   checkboxesActionPayloadDelegator: CheckboxesActionPayloadDelegator;
 
-  private childDelegators: GrowiUriInjector<TypedBlock[], any, TypedBlock, any>[] = [];
+  private childDelegators: GrowiUriInjector<
+    TypedBlock[],
+    any,
+    TypedBlock,
+    any
+  >[] = [];
 
   $onInit(): void | Promise<any> {
     this.childDelegators.push(
@@ -38,16 +52,24 @@ export class SectionBlockPayloadDelegator implements GrowiUriInjector<any, Secti
 
   // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
   shouldHandleToInject(data: any): data is SectionWithAccessoryElement[] {
-    const sectionBlocks = data.filter(blockElement => blockElement.type === 'section' && blockElement.accessory != null);
+    const sectionBlocks = data.filter(
+      (blockElement) =>
+        blockElement.type === 'section' && blockElement.accessory != null,
+    );
     return sectionBlocks.length > 0;
   }
 
   inject(data: SectionWithAccessoryElement[], growiUri: string): void {
-    const sectionBlocks = data.filter(blockElement => blockElement.type === 'section' && blockElement.accessory != null);
+    const sectionBlocks = data.filter(
+      (blockElement) =>
+        blockElement.type === 'section' && blockElement.accessory != null,
+    );
 
     // collect elements
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const accessories = sectionBlocks.flatMap(sectionBlock => sectionBlock.accessory);
+    const accessories = sectionBlocks.flatMap(
+      (sectionBlock) => sectionBlock.accessory,
+    );
 
     this.childDelegators.forEach((delegator) => {
       if (delegator.shouldHandleToInject(accessories)) {
@@ -65,5 +87,4 @@ export class SectionBlockPayloadDelegator implements GrowiUriInjector<any, Secti
   extract(data: BlockActionsPayload): GrowiUriWithOriginalData {
     throw new Error('No need to implement. Use ActionsBlockPayloadDelegator');
   }
-
 }

+ 18 - 14
apps/slackbot-proxy/src/services/growi-uri-injector/ViewInteractionPayloadDelegator.ts

@@ -1,30 +1,34 @@
 import { Service } from '@tsed/di';
 
 import {
-  GrowiUriInjector, GrowiUriWithOriginalData, isGrowiUriWithOriginalData, TypedBlock,
+  GrowiUriInjector,
+  GrowiUriWithOriginalData,
+  isGrowiUriWithOriginalData,
+  TypedBlock,
 } from '~/interfaces/growi-uri-injector';
 
 // see: https://api.slack.com/reference/interaction-payloads/views
 type ViewElement = TypedBlock & {
-  'private_metadata'?: any,
-}
+  private_metadata?: any;
+};
 
 // see: https://api.slack.com/reference/interaction-payloads/views
 type ViewInteractionPayload = TypedBlock & {
   view: {
-    'private_metadata'?: any,
-  },
-}
+    private_metadata?: any;
+  };
+};
 
 @Service()
-export class ViewInteractionPayloadDelegator implements GrowiUriInjector<any, ViewElement, any, ViewInteractionPayload> {
-
+export class ViewInteractionPayloadDelegator
+  implements GrowiUriInjector<any, ViewElement, any, ViewInteractionPayload>
+{
   // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
   shouldHandleToInject(data: any): data is ViewElement {
     return data.type != null && data.private_metadata != null;
   }
 
-  inject(data: ViewElement, growiUri :string): void {
+  inject(data: ViewElement, growiUri: string): void {
     const originalData = data.private_metadata;
 
     const urlWithOrgData: GrowiUriWithOriginalData = { growiUri, originalData };
@@ -45,19 +49,19 @@ export class ViewInteractionPayloadDelegator implements GrowiUriInjector<any, Vi
     try {
       const restoredData: any = JSON.parse(view.private_metadata);
       return isGrowiUriWithOriginalData(restoredData);
-    }
-    // when parsing failed
-    catch (err) {
+    } catch (err) {
+      // when parsing failed
       return false;
     }
   }
 
   extract(data: ViewInteractionPayload): GrowiUriWithOriginalData {
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const restoredData: GrowiUriWithOriginalData = JSON.parse(data.view.private_metadata!); // private_metadata must not be null at this moment
+    const restoredData: GrowiUriWithOriginalData = JSON.parse(
+      data.view.private_metadata!,
+    ); // private_metadata must not be null at this moment
     data.view.private_metadata = restoredData.originalData;
 
     return restoredData;
   }
-
 }

+ 35 - 19
apps/slackbot-proxy/src/services/growi-uri-injector/block-elements/ButtonActionPayloadDelegator.ts

@@ -1,32 +1,48 @@
 import { Service } from '@tsed/di';
 
-import { GrowiUriWithOriginalData, GrowiUriInjector, TypedBlock } from '~/interfaces/growi-uri-injector';
-
+import {
+  GrowiUriInjector,
+  GrowiUriWithOriginalData,
+  TypedBlock,
+} from '~/interfaces/growi-uri-injector';
 
 type ButtonElement = TypedBlock & {
-  value: string,
-}
+  value: string;
+};
 
 type ButtonActionPayload = TypedBlock & {
-  value: string,
-}
+  value: string;
+};
 
 @Service()
-export class ButtonActionPayloadDelegator implements GrowiUriInjector<TypedBlock[], ButtonElement[], TypedBlock, ButtonActionPayload> {
-
+export class ButtonActionPayloadDelegator
+  implements
+    GrowiUriInjector<
+      TypedBlock[],
+      ButtonElement[],
+      TypedBlock,
+      ButtonActionPayload
+    >
+{
   shouldHandleToInject(elements: TypedBlock[]): elements is ButtonElement[] {
-    const buttonElements = elements.filter(element => element.type === 'button');
+    const buttonElements = elements.filter(
+      (element) => element.type === 'button',
+    );
     return buttonElements.length > 0;
   }
 
   inject(elements: ButtonElement[], growiUri: string): void {
-    const buttonElements = elements.filter(blockElement => blockElement.type === 'button');
-
-    buttonElements
-      .forEach((element) => {
-        const urlWithOrgData: GrowiUriWithOriginalData = { growiUri, originalData: element.value };
-        element.value = JSON.stringify(urlWithOrgData);
-      });
+    const buttonElements = elements.filter(
+      (blockElement) => blockElement.type === 'button',
+    );
+
+    buttonElements.forEach((element) => {
+      const urlWithOrgData: GrowiUriWithOriginalData = {
+        growiUri,
+        originalData: element.value,
+      };
+      element.value = JSON.stringify(urlWithOrgData);
+    });
   }
 
   shouldHandleToExtract(action: TypedBlock): action is ButtonActionPayload {
@@ -34,7 +50,9 @@ export class ButtonActionPayloadDelegator implements GrowiUriInjector<TypedBlock
   }
 
   extract(action: ButtonActionPayload): GrowiUriWithOriginalData {
-    const restoredData: GrowiUriWithOriginalData = JSON.parse(action.value || '{}');
+    const restoredData: GrowiUriWithOriginalData = JSON.parse(
+      action.value || '{}',
+    );
 
     if (restoredData.originalData != null) {
       action.value = restoredData.originalData;
@@ -42,6 +60,4 @@ export class ButtonActionPayloadDelegator implements GrowiUriInjector<TypedBlock
 
     return restoredData;
   }
-
-
 }

+ 34 - 16
apps/slackbot-proxy/src/services/growi-uri-injector/block-elements/CheckboxesActionPayloadDelegator.ts

@@ -1,30 +1,49 @@
 import { Service } from '@tsed/di';
 
-import { GrowiUriWithOriginalData, GrowiUriInjector, TypedBlock } from '~/interfaces/growi-uri-injector';
-
+import {
+  GrowiUriInjector,
+  GrowiUriWithOriginalData,
+  TypedBlock,
+} from '~/interfaces/growi-uri-injector';
 
 type CheckboxesElement = TypedBlock & {
-  options: { value: string }[],
-}
+  options: { value: string }[];
+};
 
 type CheckboxesActionPayload = TypedBlock & {
-  'selected_options': { value: string }[],
-}
+  selected_options: { value: string }[];
+};
 
 @Service()
-export class CheckboxesActionPayloadDelegator implements GrowiUriInjector<TypedBlock[], CheckboxesElement[], TypedBlock, CheckboxesActionPayload> {
-
-  shouldHandleToInject(elements: TypedBlock[]): elements is CheckboxesElement[] {
-    const buttonElements = elements.filter(element => element.type === 'checkboxes');
+export class CheckboxesActionPayloadDelegator
+  implements
+    GrowiUriInjector<
+      TypedBlock[],
+      CheckboxesElement[],
+      TypedBlock,
+      CheckboxesActionPayload
+    >
+{
+  shouldHandleToInject(
+    elements: TypedBlock[],
+  ): elements is CheckboxesElement[] {
+    const buttonElements = elements.filter(
+      (element) => element.type === 'checkboxes',
+    );
     return buttonElements.length > 0;
   }
 
   inject(elements: CheckboxesElement[], growiUri: string): void {
-    const cbElements = elements.filter(blockElement => blockElement.type === 'checkboxes');
+    const cbElements = elements.filter(
+      (blockElement) => blockElement.type === 'checkboxes',
+    );
 
     cbElements.forEach((element) => {
       element.options.forEach((option) => {
-        const urlWithOrgData: GrowiUriWithOriginalData = { growiUri, originalData: option.value };
+        const urlWithOrgData: GrowiUriWithOriginalData = {
+          growiUri,
+          originalData: option.value,
+        };
         option.value = JSON.stringify(urlWithOrgData);
       });
     });
@@ -32,11 +51,11 @@ export class CheckboxesActionPayloadDelegator implements GrowiUriInjector<TypedB
 
   shouldHandleToExtract(action: TypedBlock): action is CheckboxesActionPayload {
     return (
-      action.type === 'checkboxes'
-      && (action as CheckboxesActionPayload).selected_options != null
+      action.type === 'checkboxes' &&
+      (action as CheckboxesActionPayload).selected_options != null &&
       // ...Unsolved problem...
       // slackbot-proxy can't determine growiUri when selected_options is empty -- 2021.07.12 Yuki Takei
-      && (action as CheckboxesActionPayload).selected_options.length > 0
+      (action as CheckboxesActionPayload).selected_options.length > 0
     );
   }
 
@@ -54,5 +73,4 @@ export class CheckboxesActionPayloadDelegator implements GrowiUriInjector<TypedB
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     return oneOfRestoredData!;
   }
-
 }

+ 0 - 1
biome.json

@@ -21,7 +21,6 @@
       "!**/package.json",
       "!apps/app/**",
       "!apps/slackbot-proxy/src/public/bootstrap/**",
-      "!apps/slackbot-proxy/src/services/**",
       "!packages/editor/**",
       "!packages/pdf-converter-client/src/index.ts"
     ]