|
|
@@ -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 };
|
|
|
}
|
|
|
-
|
|
|
}
|