UnregisterService.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import axios from 'axios';
  2. import { Inject, Service } from '@tsed/di';
  3. import { MultiStaticSelect } from '@slack/web-api';
  4. import {
  5. actionsBlock, buttonElement, getInteractionIdRegexpFromCommandName,
  6. GrowiCommand, GrowiCommandProcessor, GrowiInteractionProcessor,
  7. inputBlock, InteractionHandledResult, markdownSectionBlock, respond, InteractionPayloadAccessor, replaceOriginal,
  8. } from '@growi/slack';
  9. import { AuthorizeResult } from '@slack/oauth';
  10. import { DeleteResult } from 'typeorm';
  11. import { RelationRepository } from '~/repositories/relation';
  12. import { Installation } from '~/entities/installation';
  13. import { InstallationRepository } from '~/repositories/installation';
  14. import loggerFactory from '~/utils/logger';
  15. const logger = loggerFactory('slackbot-proxy:services:UnregisterService');
  16. @Service()
  17. export class UnregisterService implements GrowiCommandProcessor, GrowiInteractionProcessor<void> {
  18. @Inject()
  19. relationRepository: RelationRepository;
  20. @Inject()
  21. installationRepository: InstallationRepository;
  22. shouldHandleCommand(growiCommand: GrowiCommand): boolean {
  23. return growiCommand.growiCommandType === 'unregister';
  24. }
  25. async processCommand(growiCommand: GrowiCommand, authorizeResult: AuthorizeResult): Promise<void> {
  26. // get growi urls
  27. const installationId = authorizeResult.enterpriseId || authorizeResult.teamId;
  28. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  29. const installation = await this.installationRepository.findByTeamIdOrEnterpriseId(installationId!);
  30. const relations = await this.relationRepository.createQueryBuilder('relation')
  31. .where('relation.installationId = :id', { id: installation?.id })
  32. .leftJoinAndSelect('relation.installation', 'installation')
  33. .getMany();
  34. if (relations.length === 0) {
  35. await respond(growiCommand.responseUrl, {
  36. text: 'No GROWI found to unregister.',
  37. blocks: [
  38. markdownSectionBlock('You haven\'t registered any GROWI to this workspace.'),
  39. markdownSectionBlock('Send `/growi register` to register.'),
  40. ],
  41. });
  42. return;
  43. }
  44. const staticSelectElement: MultiStaticSelect = {
  45. action_id: 'unregister:selectedGrowiUris',
  46. type: 'multi_static_select',
  47. placeholder: {
  48. type: 'plain_text',
  49. text: 'Select GROWI URLs to unregister',
  50. },
  51. options: relations.map((relation) => {
  52. return {
  53. text: {
  54. type: 'plain_text',
  55. text: relation.growiUri,
  56. },
  57. value: relation.growiUri,
  58. };
  59. }),
  60. };
  61. await respond(growiCommand.responseUrl, {
  62. text: 'Select GROWI URLs to unregister.',
  63. blocks: [
  64. inputBlock(staticSelectElement, 'growiUris', 'GROWI URL to unregister'),
  65. actionsBlock(
  66. buttonElement({ text: 'Cancel', actionId: 'unregister:cancel', value: JSON.stringify({}) }),
  67. buttonElement({
  68. text: 'Unregister', actionId: 'unregister:unregister', style: 'danger', value: JSON.stringify({}),
  69. }),
  70. ),
  71. ],
  72. });
  73. }
  74. // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  75. shouldHandleInteraction(interactionPayloadAccessor: InteractionPayloadAccessor): boolean {
  76. const { actionId, callbackId } = interactionPayloadAccessor.getActionIdAndCallbackIdFromPayLoad();
  77. const registerRegexp: RegExp = getInteractionIdRegexpFromCommandName('unregister');
  78. return registerRegexp.test(actionId) || registerRegexp.test(callbackId);
  79. }
  80. async processInteraction(
  81. // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  82. authorizeResult: AuthorizeResult, interactionPayload: any, interactionPayloadAccessor: InteractionPayloadAccessor,
  83. ): Promise<InteractionHandledResult<void>> {
  84. const interactionHandledResult: InteractionHandledResult<void> = {
  85. isTerminated: false,
  86. };
  87. if (!this.shouldHandleInteraction(interactionPayloadAccessor)) return interactionHandledResult;
  88. const { actionId } = interactionPayloadAccessor.getActionIdAndCallbackIdFromPayLoad();
  89. switch (actionId) {
  90. case 'unregister:unregister':
  91. interactionHandledResult.result = await this.handleUnregisterInteraction(authorizeResult, interactionPayload, interactionPayloadAccessor);
  92. break;
  93. case 'unregister:cancel':
  94. interactionHandledResult.result = await this.handleUnregisterCancelInteraction(interactionPayloadAccessor);
  95. break;
  96. case 'unregister:selectedGrowiUris':
  97. break;
  98. default:
  99. logger.error('This unregister interaction is not implemented.');
  100. break;
  101. }
  102. interactionHandledResult.isTerminated = true;
  103. return interactionHandledResult as InteractionHandledResult<void>;
  104. }
  105. // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  106. async handleUnregisterInteraction(
  107. // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  108. authorizeResult: AuthorizeResult, interactionPayload: any, interactionPayloadAccessor: InteractionPayloadAccessor,
  109. ):Promise<void> {
  110. const responseUrl = interactionPayloadAccessor.getResponseUrl();
  111. const selectedOptions = interactionPayloadAccessor.getStateValues()?.growiUris?.['unregister:selectedGrowiUris']?.selected_options;
  112. if (!Array.isArray(selectedOptions)) {
  113. logger.error('Unregisteration failed: Mulformed object was detected\n');
  114. await respond(responseUrl, {
  115. text: 'Unregistration failed',
  116. blocks: [
  117. markdownSectionBlock('Error occurred while unregistering GROWI.'),
  118. ],
  119. });
  120. return;
  121. }
  122. const growiUris = selectedOptions.map(selectedOption => selectedOption.value);
  123. const installationId = authorizeResult.enterpriseId || authorizeResult.teamId;
  124. let installation: Installation | undefined;
  125. try {
  126. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  127. installation = await this.installationRepository.findByTeamIdOrEnterpriseId(installationId!);
  128. }
  129. catch (err) {
  130. logger.error('Unregisteration failed:\n', err);
  131. await respond(responseUrl, {
  132. text: 'Unregistration failed',
  133. blocks: [
  134. markdownSectionBlock('Error occurred while unregistering GROWI.'),
  135. ],
  136. });
  137. return;
  138. }
  139. let deleteResult: DeleteResult;
  140. try {
  141. deleteResult = await this.relationRepository.createQueryBuilder('relation')
  142. .where('relation.growiUri IN (:uris)', { uris: growiUris })
  143. .andWhere('relation.installationId = :installationId', { installationId: installation?.id })
  144. .delete()
  145. .execute();
  146. }
  147. catch (err) {
  148. logger.error('Unregisteration failed\n', err);
  149. await respond(responseUrl, {
  150. text: 'Unregistration failed',
  151. blocks: [
  152. markdownSectionBlock('Error occurred while unregistering GROWI.'),
  153. ],
  154. });
  155. return;
  156. }
  157. await replaceOriginal(responseUrl, {
  158. text: 'Unregistration completed',
  159. blocks: [
  160. markdownSectionBlock(`Unregistered *${deleteResult.affected}* GROWI from this workspace.`),
  161. ],
  162. });
  163. return;
  164. }
  165. // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  166. async handleUnregisterCancelInteraction(interactionPayloadAccessor: InteractionPayloadAccessor): Promise<void> {
  167. await axios.post(interactionPayloadAccessor.getResponseUrl(), {
  168. delete_original: true,
  169. });
  170. }
  171. }