From 0474744d9068bd4744d28746cbb3eb15ed3e1648 Mon Sep 17 00:00:00 2001 From: Bieber Date: Thu, 15 Aug 2024 15:08:08 +0700 Subject: [PATCH] feat: controller standard event (#826) * feat: controller stardard event * chore: cancel in progress --- .github/workflows/docker-push.yml | 4 ++ .../decorators/event-emitter.interceptor.ts | 62 +++++++++++++++++++ .../src/event-emitter/events/event.enum.ts | 4 ++ .../open-api/record-open-api.controller.ts | 19 +++++- .../record/open-api/record-open-api.module.ts | 3 +- .../open-api/record-undo-redo-service.ts | 20 ++++++ 6 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 apps/nestjs-backend/src/event-emitter/decorators/event-emitter.interceptor.ts create mode 100644 apps/nestjs-backend/src/features/record/open-api/record-undo-redo-service.ts diff --git a/.github/workflows/docker-push.yml b/.github/workflows/docker-push.yml index c0768cb79..82e3bf861 100644 --- a/.github/workflows/docker-push.yml +++ b/.github/workflows/docker-push.yml @@ -1,5 +1,9 @@ name: Build and Push to Docker Registry +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + on: push: branches: diff --git a/apps/nestjs-backend/src/event-emitter/decorators/event-emitter.interceptor.ts b/apps/nestjs-backend/src/event-emitter/decorators/event-emitter.interceptor.ts new file mode 100644 index 000000000..0922e6fc6 --- /dev/null +++ b/apps/nestjs-backend/src/event-emitter/decorators/event-emitter.interceptor.ts @@ -0,0 +1,62 @@ +import type { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; +import { Injectable, SetMetadata } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import type { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import { EventEmitterService } from '../event-emitter.service'; + +@Injectable() +export class EventEmitterInterceptor implements NestInterceptor { + constructor( + private reflector: Reflector, + private eventEmitterService: EventEmitterService + ) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const eventMetadata = this.reflector.get<{ eventName: string; paramNames?: string[] }>( + 'eventMetadata', + context.getHandler() + ); + + if (!eventMetadata) { + return next.handle(); + } + + const { eventName, paramNames } = eventMetadata; + const req = context.switchToHttp().getRequest(); + + return next.handle().pipe( + tap((result) => { + const payload: { result: unknown; params: Record } = { + result, + params: {}, + }; + + if (paramNames && paramNames.length > 0) { + paramNames.forEach((paramName) => { + if (req.params && paramName in req.params) { + payload.params[paramName] = req.params[paramName]; + } else if (req.body && paramName in req.body) { + payload.params[paramName] = req.body[paramName]; + } else if (req.query && paramName in req.query) { + payload.params[paramName] = req.query[paramName]; + } + }); + } else { + // If no specific param names are provided, include all available params + payload.params = { + ...req.params, + ...req.query, + ...req.body, + }; + } + + this.eventEmitterService.emitAsync(eventName, payload); + }) + ); + } +} + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const EmitEvent = (eventName: string, paramNames?: string[]) => + SetMetadata('eventMetadata', { eventName, paramNames }); diff --git a/apps/nestjs-backend/src/event-emitter/events/event.enum.ts b/apps/nestjs-backend/src/event-emitter/events/event.enum.ts index f1e642fba..815d30f44 100644 --- a/apps/nestjs-backend/src/event-emitter/events/event.enum.ts +++ b/apps/nestjs-backend/src/event-emitter/events/event.enum.ts @@ -27,6 +27,10 @@ export enum Events { TABLE_VIEW_DELETE = 'table.view.delete', TABLE_VIEW_UPDATE = 'table.view.update', + CONTROLLER_RECORDS_CREATE = 'controller.records.create', + CONTROLLER_RECORDS_UPDATE = 'controller.records.update', + CONTROLLER_RECORDS_DELETE = 'controller.records.delete', + TABLE_USER_RENAME_COMPLETE = 'table.user.rename.complete', SHARED_VIEW_CREATE = 'shared.view.create', diff --git a/apps/nestjs-backend/src/features/record/open-api/record-open-api.controller.ts b/apps/nestjs-backend/src/features/record/open-api/record-open-api.controller.ts index 11313cdd7..cd4e19159 100644 --- a/apps/nestjs-backend/src/features/record/open-api/record-open-api.controller.ts +++ b/apps/nestjs-backend/src/features/record/open-api/record-open-api.controller.ts @@ -1,5 +1,15 @@ /* eslint-disable sonarjs/no-duplicate-string */ -import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common'; +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, + Query, + UseInterceptors, +} from '@nestjs/common'; import type { ICreateRecordsVo, IRecord, IRecordsVo } from '@teable/openapi'; import { createRecordsRoSchema, @@ -15,6 +25,11 @@ import { getRecordHistoryQuerySchema, IGetRecordHistoryQuery, } from '@teable/openapi'; +import { + EmitEvent, + EventEmitterInterceptor, +} from '../../../event-emitter/decorators/event-emitter.interceptor'; +import { Events } from '../../../event-emitter/events'; import { ZodValidationPipe } from '../../../zod.validation.pipe'; import { Permissions } from '../../auth/decorators/permissions.decorator'; import { RecordService } from '../record.service'; @@ -22,6 +37,7 @@ import { RecordOpenApiService } from './record-open-api.service'; import { TqlPipe } from './tql.pipe'; @Controller('api/table/:tableId/record') +@UseInterceptors(EventEmitterInterceptor) export class RecordOpenApiController { constructor( private readonly recordService: RecordService, @@ -78,6 +94,7 @@ export class RecordOpenApiController { @Permissions('record|create') @Post() + @EmitEvent(Events.CONTROLLER_RECORDS_CREATE) async createRecords( @Param('tableId') tableId: string, @Body(new ZodValidationPipe(createRecordsRoSchema)) createRecordsRo: ICreateRecordsRo diff --git a/apps/nestjs-backend/src/features/record/open-api/record-open-api.module.ts b/apps/nestjs-backend/src/features/record/open-api/record-open-api.module.ts index aa3102496..92232958c 100644 --- a/apps/nestjs-backend/src/features/record/open-api/record-open-api.module.ts +++ b/apps/nestjs-backend/src/features/record/open-api/record-open-api.module.ts @@ -8,6 +8,7 @@ import { RecordCalculateModule } from '../record-calculate/record-calculate.modu import { RecordModule } from '../record.module'; import { RecordOpenApiController } from './record-open-api.controller'; import { RecordOpenApiService } from './record-open-api.service'; +import { RecordUndoRedoService } from './record-undo-redo-service'; @Module({ imports: [ @@ -20,7 +21,7 @@ import { RecordOpenApiService } from './record-open-api.service'; ViewOpenApiModule, ], controllers: [RecordOpenApiController], - providers: [RecordOpenApiService], + providers: [RecordOpenApiService, RecordUndoRedoService], exports: [RecordOpenApiService], }) export class RecordOpenApiModule {} diff --git a/apps/nestjs-backend/src/features/record/open-api/record-undo-redo-service.ts b/apps/nestjs-backend/src/features/record/open-api/record-undo-redo-service.ts new file mode 100644 index 000000000..b2c1b8597 --- /dev/null +++ b/apps/nestjs-backend/src/features/record/open-api/record-undo-redo-service.ts @@ -0,0 +1,20 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { PrismaService } from '@teable/db-main-prisma'; +import type { ICreateRecordsRo, IRecordsVo } from '@teable/openapi'; +import { Events } from '../../../event-emitter/events'; + +@Injectable() +export class RecordUndoRedoService { + private readonly logger = new Logger(RecordUndoRedoService.name); + + constructor(private readonly prismaService: PrismaService) {} + + @OnEvent(Events.CONTROLLER_RECORDS_CREATE) + createRecords(payload: { + params: { tableId: string; createRecordsRo: ICreateRecordsRo }; + result: IRecordsVo; + }) { + this.logger.log('record created'); + } +}