Skip to content

Commit

Permalink
refactor: Add TableActionActivationService for activating table actions
Browse files Browse the repository at this point in the history
  • Loading branch information
Artuomka committed Jul 18, 2024
1 parent 0b3ca56 commit 1a6f6e7
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 280 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { OperationResultStatusEnum } from '../../../../enums/operation-result-st
import { Messages } from '../../../../exceptions/text/messages.js';
import { TableLogsService } from '../../../table-logs/table-logs.service.js';
import { LogOperationTypeEnum } from '../../../../enums/index.js';
import { activateTableAction } from '../../table-actions-module/utils/activate-table-action.util.js';
import { TableActionActivationService } from '../../table-actions-module/table-action-activation.service.js';

@Injectable()
export class ActivateActionsInEventUseCase
Expand All @@ -20,6 +20,7 @@ export class ActivateActionsInEventUseCase
@Inject(BaseType.GLOBAL_DB_CONTEXT)
protected _dbContext: IGlobalDatabaseContext,
private tableLogsService: TableLogsService,
private tableActionActivationService: TableActionActivationService,
) {
super();
}
Expand Down Expand Up @@ -54,14 +55,15 @@ export class ActivateActionsInEventUseCase
for (const action of foundActionsWithCustomEvents) {
let primaryKeyValuesArray = [];
try {
const { receivedOperationResult, receivedPrimaryKeysObj, location } = await activateTableAction(
action,
foundConnection,
request_body,
userId,
tableName,
null,
);
const { receivedOperationResult, receivedPrimaryKeysObj, location } =
await this.tableActionActivationService.activateTableAction(
action,
foundConnection,
request_body,
userId,
tableName,
null,
);
operationResult = receivedOperationResult;
primaryKeyValuesArray = receivedPrimaryKeysObj;
if (location) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
import { HttpException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { UserEntity } from '../../user/user.entity.js';
import { TableActionEntity } from './table-action.entity.js';
import { ConnectionEntity } from '../../connection/connection.entity.js';
import { TableActionEventEnum } from '../../../enums/table-action-event-enum.js';
import { TableActionMethodEnum } from '../../../enums/table-action-method-enum.js';
import { OperationResultStatusEnum } from '../../../enums/operation-result-status.enum.js';
import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js';
import { actionSlackPostMessage } from '../../../helpers/slack/action-slack-post-message.js';
import { Constants } from '../../../helpers/constants/constants.js';
import { getProcessVariable } from '../../../helpers/get-process-variable.js';
import { IMessage } from '../../email/email/email.interface.js';
import { sendEmailToUser } from '../../email/send-email.js';
import { Encryptor } from '../../../helpers/encryption/encryptor.js';
import axios from 'axios';
import PQueue from 'p-queue';

export type ActionActivationResult = {
location?: string;
receivedOperationResult: OperationResultStatusEnum;
receivedPrimaryKeysObj: Array<Record<string, unknown>>;
};

type UserInfoMessageData = {
userId: string;
email: string;
userName: string;
};

@Injectable()
export class TableActionActivationService {
constructor(
@InjectRepository(UserEntity)
private readonly userRepository: Repository<UserEntity>,
) {}

public async activateTableAction(
tableAction: TableActionEntity,
foundConnection: ConnectionEntity,
request_body: Array<Record<string, unknown>>,
userId: string,
tableName: string,
triggerOperation: TableActionEventEnum,
): Promise<ActionActivationResult> {
const foundUser = await this.userRepository.findOne({
where: { id: userId },
});
const userInfoMessageData: UserInfoMessageData = {
userId,
email: foundUser.email,
userName: foundUser.name,
};
switch (tableAction.method) {
case TableActionMethodEnum.URL:
return await this.activateHttpTableAction(
tableAction,
foundConnection,
request_body,
userInfoMessageData,
tableName,
);
case TableActionMethodEnum.SLACK:
return await this.activateSlackTableAction(
tableAction,
foundConnection,
request_body,
userInfoMessageData,
tableName,
triggerOperation,
);
case TableActionMethodEnum.EMAIL:
return await this.activateEmailTableAction(
tableAction,
foundConnection,
request_body,
userInfoMessageData,
tableName,
triggerOperation,
);
default:
throw new Error(`Method ${tableAction.method} is not supported`);
}
}

public async activateSlackTableAction(
tableAction: TableActionEntity,
foundConnection: ConnectionEntity,
request_body: Array<Record<string, unknown>>,
userInfo: UserInfoMessageData,
tableName: string,
triggerOperation?: TableActionEventEnum,
): Promise<ActionActivationResult> {
let operationResult = OperationResultStatusEnum.unknown;
const dataAccessObject = getDataAccessObject(foundConnection);
const tablePrimaryKeys = await dataAccessObject.getTablePrimaryColumns(tableName, null);
const primaryKeyValuesArray: Array<Record<string, unknown>> = [];
for (const primaryKeyInBody of request_body) {
for (const primaryKey of tablePrimaryKeys) {
const pKeysObj: Record<string, unknown> = {};
if (primaryKeyInBody.hasOwnProperty(primaryKey.column_name) && primaryKeyInBody[primaryKey.column_name]) {
pKeysObj[primaryKey.column_name] = primaryKeyInBody[primaryKey.column_name];
primaryKeyValuesArray.push(pKeysObj);
}
}
}
const slackMessage = this.generateMessageString(userInfo, triggerOperation, tableName, primaryKeyValuesArray);

try {
await actionSlackPostMessage(slackMessage, tableAction.slack_url);
operationResult = OperationResultStatusEnum.successfully;
} catch (e) {
operationResult = OperationResultStatusEnum.unsuccessfully;
}
return {
receivedOperationResult: operationResult,
receivedPrimaryKeysObj: primaryKeyValuesArray,
};
}

public async activateEmailTableAction(
tableAction: TableActionEntity,
foundConnection: ConnectionEntity,
request_body: Array<Record<string, unknown>>,
userInfo: UserInfoMessageData,
tableName: string,
triggerOperation: TableActionEventEnum,
): Promise<ActionActivationResult> {
let operationResult = OperationResultStatusEnum.unknown;
const dataAccessObject = getDataAccessObject(foundConnection);
const tablePrimaryKeys = await dataAccessObject.getTablePrimaryColumns(tableName, null);
const primaryKeyValuesArray: Array<Record<string, unknown>> = [];
for (const primaryKeyInBody of request_body) {
for (const primaryKey of tablePrimaryKeys) {
const pKeysObj: Record<string, unknown> = {};
if (primaryKeyInBody.hasOwnProperty(primaryKey.column_name) && primaryKeyInBody[primaryKey.column_name]) {
pKeysObj[primaryKey.column_name] = primaryKeyInBody[primaryKey.column_name];
primaryKeyValuesArray.push(pKeysObj);
}
}
}
const emailMessage = this.generateMessageString(userInfo, triggerOperation, tableName, primaryKeyValuesArray);

const emailFrom = getProcessVariable('EMAIL_FROM') || Constants.AUTOADMIN_SUPPORT_MAIL;

const queue = new PQueue({ concurrency: 2 });
try {
await Promise.all(
tableAction.emails.map((email) =>
queue.add(() => {
const letterContent: IMessage = {
from: emailFrom,
to: email,
subject: 'Rocketadmin action notification',
text: emailMessage,
html: emailMessage,
};
return sendEmailToUser(letterContent);
}),
),
);
operationResult = OperationResultStatusEnum.successfully;
return {
receivedOperationResult: operationResult,
receivedPrimaryKeysObj: primaryKeyValuesArray,
};
} catch (error) {
operationResult = OperationResultStatusEnum.unsuccessfully;
return {
receivedOperationResult: operationResult,
receivedPrimaryKeysObj: primaryKeyValuesArray,
};
}
}

public async activateHttpTableAction(
tableAction: TableActionEntity,
foundConnection: ConnectionEntity,
request_body: Array<Record<string, unknown>>,
userInfo: UserInfoMessageData,
tableName: string,
): Promise<ActionActivationResult> {
const { userId } = userInfo;
let operationResult = OperationResultStatusEnum.unknown;
const dataAccessObject = getDataAccessObject(foundConnection);
const tablePrimaryKeys = await dataAccessObject.getTablePrimaryColumns(tableName, null);
const primaryKeyValuesArray: Array<Record<string, unknown>> = [];
for (const primaryKeyInBody of request_body) {
for (const primaryKey of tablePrimaryKeys) {
const pKeysObj: Record<string, unknown> = {};
if (primaryKeyInBody.hasOwnProperty(primaryKey.column_name) && primaryKeyInBody[primaryKey.column_name]) {
pKeysObj[primaryKey.column_name] = primaryKeyInBody[primaryKey.column_name];
primaryKeyValuesArray.push(pKeysObj);
}
}
}
const dateString = new Date().toISOString();
const actionRequestBody = JSON.stringify({
$$_raUserId: userId,
primaryKeys: primaryKeyValuesArray,
$$_date: dateString,
$$_actionId: tableAction.id,
$$_tableName: tableName,
});
const autoadminSignatureHeader = Encryptor.hashDataHMACexternalKey(foundConnection.signing_key, actionRequestBody);
const result = await axios.post(tableAction.url, actionRequestBody, {
headers: { 'Rocketadmin-Signature': autoadminSignatureHeader, 'Content-Type': 'application/json' },
maxRedirects: 0,
validateStatus: function (status) {
return status <= 599;
},
});
const operationStatusCode = result.status;
if (operationStatusCode >= 200 && operationStatusCode < 300) {
operationResult = OperationResultStatusEnum.successfully;
return {
receivedOperationResult: operationResult,
receivedPrimaryKeysObj: primaryKeyValuesArray,
};
}
if (operationStatusCode >= 300 && operationStatusCode < 400) {
operationResult = OperationResultStatusEnum.successfully;
return {
receivedOperationResult: operationResult,
location: result?.headers?.location,
receivedPrimaryKeysObj: primaryKeyValuesArray,
};
}
if (operationStatusCode >= 400 && operationStatusCode <= 599) {
throw new HttpException(
{
message: result.data,
},
operationStatusCode,
);
}
return {
receivedOperationResult: operationResult,
receivedPrimaryKeysObj: primaryKeyValuesArray,
};
}

public async activateTableActions(
tableActions: Array<TableActionEntity>,
connection: ConnectionEntity,
request_body: Record<string, unknown>,
userId: string,
tableName: string,
triggerOperation: TableActionEventEnum,
): Promise<void> {
if (!tableActions.length) {
return;
}
try {
const queue = new PQueue({ concurrency: 2 });
await Promise.all(
tableActions.map((tableAction) =>
queue
.add(() =>
this.activateTableAction(tableAction, connection, [request_body], userId, tableName, triggerOperation),
)
.catch((error) => {
console.error('Error in activateTableActions', error);
}),
),
);
} catch (error) {
return;
}
}

private generateMessageString(
userInfo: UserInfoMessageData,
triggerOperation: TableActionEventEnum,
tableName: string,
primaryKeyValuesArray: Array<Record<string, unknown>>,
): string {
const { email, userId, userName } = userInfo;
return `User with id "${userId}", email "${email}", name "${userName}" ${
triggerOperation === TableActionEventEnum.ADD_ROW
? 'added'
: triggerOperation === TableActionEventEnum.UPDATE_ROW
? 'updated'
: triggerOperation === TableActionEventEnum.DELETE_ROW
? 'deleted'
: 'performed an action on'
} a row in table "${tableName}" with primary keys: ${JSON.stringify(primaryKeyValuesArray)}`;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
import { Global, MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthMiddleware } from '../../../authorization/index.js';
import { GlobalDatabaseContext } from '../../../common/application/global-database-context.js';
Expand All @@ -8,7 +8,9 @@ import { UserEntity } from '../../user/user.entity.js';
import { TableActionsController } from './table-action.controller.js';
import { TableActionEntity } from './table-action.entity.js';
import { ActivateTableActionsUseCase } from './use-cases/activate-table-actions.use.case.js';
import { TableActionActivationService } from './table-action-activation.service.js';

@Global()
@Module({
imports: [TypeOrmModule.forFeature([TableActionEntity, UserEntity, LogOutEntity])],
providers: [
Expand All @@ -20,7 +22,9 @@ import { ActivateTableActionsUseCase } from './use-cases/activate-table-actions.
provide: UseCaseType.ACTIVATE_TABLE_ACTIONS,
useClass: ActivateTableActionsUseCase,
},
TableActionActivationService,
],
exports: [TableActionActivationService],
controllers: [TableActionsController],
})
export class TableActionModule implements NestModule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { TableLogsService } from '../../../table-logs/table-logs.service.js';
import { ActivateTableActionsDS } from '../application/data-sctructures/activate-table-actions.ds.js';
import { ActivatedTableActionsDS } from '../application/data-sctructures/activated-table-action.ds.js';
import { IActivateTableActions } from './table-actions-use-cases.interface.js';
import { activateTableAction } from '../utils/activate-table-action.util.js';
import { TableActionActivationService } from '../table-action-activation.service.js';

@Injectable()
export class ActivateTableActionsUseCase
Expand All @@ -19,6 +19,7 @@ export class ActivateTableActionsUseCase
@Inject(BaseType.GLOBAL_DB_CONTEXT)
protected _dbContext: IGlobalDatabaseContext,
private tableLogsService: TableLogsService,
private tableActionActivationService: TableActionActivationService,
) {
super();
}
Expand All @@ -43,14 +44,15 @@ export class ActivateTableActionsUseCase

let primaryKeyValuesArray = [];
try {
const { receivedOperationResult, receivedPrimaryKeysObj, location } = await activateTableAction(
foundTableAction,
foundConnection,
request_body,
userId,
tableName,
null,
);
const { receivedOperationResult, receivedPrimaryKeysObj, location } =
await this.tableActionActivationService.activateTableAction(
foundTableAction,
foundConnection,
request_body,
userId,
tableName,
null,
);
operationResult = receivedOperationResult;
primaryKeyValuesArray = receivedPrimaryKeysObj;
if (location) {
Expand Down
Loading

0 comments on commit 1a6f6e7

Please sign in to comment.