diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/workspace-pre-query-hook.config.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/workspace-pre-query-hook.config.ts index 0a5d8d9ca..b0bc2bc88 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/workspace-pre-query-hook.config.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/workspace-pre-query-hook.config.ts @@ -6,6 +6,8 @@ import { CalendarEventFindOnePreQueryHook } from 'src/modules/calendar/query-hoo import { BlocklistCreateManyPreQueryHook } from 'src/modules/connected-account/query-hooks/blocklist/blocklist-create-many.pre-query.hook'; import { BlocklistUpdateManyPreQueryHook } from 'src/modules/connected-account/query-hooks/blocklist/blocklist-update-many.pre-query.hook'; import { BlocklistUpdateOnePreQueryHook } from 'src/modules/connected-account/query-hooks/blocklist/blocklist-update-one.pre-query.hook'; +import { WorkspaceMemberDeleteOnePreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-delete-one.pre-query.hook'; +import { WorkspaceMemberDeleteManyPreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-delete-many.pre-query.hook'; // TODO: move to a decorator export const workspacePreQueryHooks: WorkspaceQueryHook = { @@ -22,4 +24,8 @@ export const workspacePreQueryHooks: WorkspaceQueryHook = { updateMany: [BlocklistUpdateManyPreQueryHook.name], updateOne: [BlocklistUpdateOnePreQueryHook.name], }, + workspaceMember: { + deleteOne: [WorkspaceMemberDeleteOnePreQueryHook.name], + deleteMany: [WorkspaceMemberDeleteManyPreQueryHook.name], + }, }; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/workspace-pre-query-hook.module.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/workspace-pre-query-hook.module.ts index fa718b6fe..7c367e7b2 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/workspace-pre-query-hook.module.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/workspace-pre-query-hook.module.ts @@ -4,12 +4,14 @@ import { MessagingQueryHookModule } from 'src/modules/messaging/query-hooks/mess import { WorkspacePreQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/workspace-pre-query-hook.service'; import { CalendarQueryHookModule } from 'src/modules/calendar/query-hooks/calendar-query-hook.module'; import { ConnectedAccountQueryHookModule } from 'src/modules/connected-account/query-hooks/connected-account-query-hook.module'; +import { WorkspaceMemberQueryHookModule } from 'src/modules/workspace-member/query-hooks/workspace-member-query-hook.module'; @Module({ imports: [ MessagingQueryHookModule, CalendarQueryHookModule, ConnectedAccountQueryHookModule, + WorkspaceMemberQueryHookModule, ], providers: [WorkspacePreQueryHookService], exports: [WorkspacePreQueryHookService], diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service.ts index 630c4a0f5..8fbee2a95 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service.ts @@ -436,6 +436,14 @@ export class WorkspaceQueryRunnerService { atMost: maximumRecordAffected, }); + await this.workspacePreQueryHookService.executePreHooks( + userId, + workspaceId, + objectMetadataItem.nameSingular, + 'deleteMany', + args, + ); + const result = await this.execute(query, workspaceId); const parsedResults = ( @@ -495,6 +503,14 @@ export class WorkspaceQueryRunnerService { ); // TODO END + await this.workspacePreQueryHookService.executePreHooks( + userId, + workspaceId, + objectMetadataItem.nameSingular, + 'deleteOne', + args, + ); + const result = await this.execute(query, workspaceId); const parsedResults = ( diff --git a/packages/twenty-server/src/engine/object-metadata-repository/metadata-to-repository.mapping.ts b/packages/twenty-server/src/engine/object-metadata-repository/metadata-to-repository.mapping.ts index fc5c95188..9974acead 100644 --- a/packages/twenty-server/src/engine/object-metadata-repository/metadata-to-repository.mapping.ts +++ b/packages/twenty-server/src/engine/object-metadata-repository/metadata-to-repository.mapping.ts @@ -14,6 +14,8 @@ import { MessageThreadRepository } from 'src/modules/messaging/repositories/mess import { MessageRepository } from 'src/modules/messaging/repositories/message.repository'; import { PersonRepository } from 'src/modules/person/repositories/person.repository'; import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository'; +import { AttachmentRepository } from 'src/modules/attachment/repositories/attachment.repository'; +import { CommentRepository } from 'src/modules/activity/repositories/comment.repository'; export const metadataToRepositoryMapping = { AuditLogObjectMetadata: AuditLogRepository, @@ -34,4 +36,6 @@ export const metadataToRepositoryMapping = { PersonObjectMetadata: PersonRepository, TimelineActivityObjectMetadata: TimelineActivityRepository, WorkspaceMemberObjectMetadata: WorkspaceMemberRepository, + AttachmentObjectMetadata: AttachmentRepository, + CommentObjectMetadata: CommentRepository, }; diff --git a/packages/twenty-server/src/engine/utils/global-exception-handler.util.ts b/packages/twenty-server/src/engine/utils/global-exception-handler.util.ts index 12f32656f..4a6dac8d8 100644 --- a/packages/twenty-server/src/engine/utils/global-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/utils/global-exception-handler.util.ts @@ -9,6 +9,7 @@ import { ValidationError, NotFoundError, ConflictError, + MethodNotAllowedError, } from 'src/engine/utils/graphql-errors.util'; import { ExceptionHandlerService } from 'src/engine/integrations/exception-handler/exception-handler.service'; @@ -17,6 +18,7 @@ const graphQLPredefinedExceptions = { 401: AuthenticationError, 403: ForbiddenError, 404: NotFoundError, + 405: MethodNotAllowedError, 409: ConflictError, }; diff --git a/packages/twenty-server/src/engine/utils/graphql-errors.util.ts b/packages/twenty-server/src/engine/utils/graphql-errors.util.ts index 069a03ee3..22733566d 100644 --- a/packages/twenty-server/src/engine/utils/graphql-errors.util.ts +++ b/packages/twenty-server/src/engine/utils/graphql-errors.util.ts @@ -142,6 +142,14 @@ export class NotFoundError extends BaseGraphQLError { } } +export class MethodNotAllowedError extends BaseGraphQLError { + constructor(message: string, extensions?: Record) { + super(message, 'METHOD_NOT_ALLOWED', extensions); + + Object.defineProperty(this, 'name', { value: 'MethodNotAllowedError' }); + } +} + export class ConflictError extends BaseGraphQLError { constructor(message: string, extensions?: Record) { super(message, 'CONFLICT', extensions); diff --git a/packages/twenty-server/src/modules/activity/repositories/comment.repository.ts b/packages/twenty-server/src/modules/activity/repositories/comment.repository.ts new file mode 100644 index 000000000..e6bf07b2b --- /dev/null +++ b/packages/twenty-server/src/modules/activity/repositories/comment.repository.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@nestjs/common'; + +import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; + +@Injectable() +export class CommentRepository { + constructor( + private readonly workspaceDataSourceService: WorkspaceDataSourceService, + ) {} + + async deleteByAuthorId(authorId: string, workspaceId: string): Promise { + const dataSourceSchema = + this.workspaceDataSourceService.getSchemaName(workspaceId); + + await this.workspaceDataSourceService.executeRawQuery( + `DELETE FROM ${dataSourceSchema}."comment" WHERE "authorId" = $1`, + [authorId], + workspaceId, + ); + } +} diff --git a/packages/twenty-server/src/modules/attachment/repositories/attachment.repository.ts b/packages/twenty-server/src/modules/attachment/repositories/attachment.repository.ts new file mode 100644 index 000000000..2d340d2cb --- /dev/null +++ b/packages/twenty-server/src/modules/attachment/repositories/attachment.repository.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@nestjs/common'; + +import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; + +@Injectable() +export class AttachmentRepository { + constructor( + private readonly workspaceDataSourceService: WorkspaceDataSourceService, + ) {} + + async deleteByAuthorId(authorId: string, workspaceId: string): Promise { + const dataSourceSchema = + this.workspaceDataSourceService.getSchemaName(workspaceId); + + await this.workspaceDataSourceService.executeRawQuery( + `DELETE FROM ${dataSourceSchema}."attachment" WHERE "authorId" = $1`, + [authorId], + workspaceId, + ); + } +} diff --git a/packages/twenty-server/src/modules/connected-account/query-hooks/blocklist/blocklist-update-many.pre-query.hook.ts b/packages/twenty-server/src/modules/connected-account/query-hooks/blocklist/blocklist-update-many.pre-query.hook.ts index b36ff3e96..1ce12a535 100644 --- a/packages/twenty-server/src/modules/connected-account/query-hooks/blocklist/blocklist-update-many.pre-query.hook.ts +++ b/packages/twenty-server/src/modules/connected-account/query-hooks/blocklist/blocklist-update-many.pre-query.hook.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, MethodNotAllowedException } from '@nestjs/common'; import { WorkspacePreQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/interfaces/workspace-pre-query-hook.interface'; @@ -7,6 +7,6 @@ export class BlocklistUpdateManyPreQueryHook implements WorkspacePreQueryHook { constructor() {} async execute(): Promise { - throw new Error('Method not implemented.'); + throw new MethodNotAllowedException('Method not allowed.'); } } diff --git a/packages/twenty-server/src/modules/messaging/query-hooks/message/message-find-one.pre-query-hook.ts b/packages/twenty-server/src/modules/messaging/query-hooks/message/message-find-one.pre-query-hook.ts index fb8585815..c5ef24158 100644 --- a/packages/twenty-server/src/modules/messaging/query-hooks/message/message-find-one.pre-query-hook.ts +++ b/packages/twenty-server/src/modules/messaging/query-hooks/message/message-find-one.pre-query-hook.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { BadRequestException, Injectable } from '@nestjs/common'; +import { Injectable, MethodNotAllowedException } from '@nestjs/common'; import { WorkspacePreQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/interfaces/workspace-pre-query-hook.interface'; import { FindOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; @@ -11,6 +11,6 @@ export class MessageFindOnePreQueryHook implements WorkspacePreQueryHook { _workspaceId: string, _payload: FindOneResolverArgs, ): Promise { - throw new BadRequestException('Method not implemented.'); + throw new MethodNotAllowedException('Method not allowed.'); } } diff --git a/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-delete-many.pre-query.hook.ts b/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-delete-many.pre-query.hook.ts new file mode 100644 index 000000000..e0650b7e2 --- /dev/null +++ b/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-delete-many.pre-query.hook.ts @@ -0,0 +1,14 @@ +import { Injectable, MethodNotAllowedException } from '@nestjs/common'; + +import { WorkspacePreQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/interfaces/workspace-pre-query-hook.interface'; + +@Injectable() +export class WorkspaceMemberDeleteManyPreQueryHook + implements WorkspacePreQueryHook +{ + constructor() {} + + async execute(): Promise { + throw new MethodNotAllowedException('Method not allowed.'); + } +} diff --git a/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-delete-one.pre-query.hook.ts b/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-delete-one.pre-query.hook.ts new file mode 100644 index 000000000..637686747 --- /dev/null +++ b/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-delete-one.pre-query.hook.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@nestjs/common'; + +import { WorkspacePreQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/interfaces/workspace-pre-query-hook.interface'; +import { DeleteOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; +import { CommentRepository } from 'src/modules/activity/repositories/comment.repository'; +import { CommentObjectMetadata } from 'src/modules/activity/standard-objects/comment.object-metadata'; +import { AttachmentRepository } from 'src/modules/attachment/repositories/attachment.repository'; +import { AttachmentObjectMetadata } from 'src/modules/attachment/standard-objects/attachment.object-metadata'; + +@Injectable() +export class WorkspaceMemberDeleteOnePreQueryHook + implements WorkspacePreQueryHook +{ + constructor( + @InjectObjectMetadataRepository(AttachmentObjectMetadata) + private readonly attachmentRepository: AttachmentRepository, + @InjectObjectMetadataRepository(CommentObjectMetadata) + private readonly commentRepository: CommentRepository, + ) {} + + // There is no need to validate the user's access to the workspace member since we don't have permission yet. + async execute( + userId: string, + workspaceId: string, + payload: DeleteOneResolverArgs, + ): Promise { + const workspaceMemberId = payload.id; + + await this.attachmentRepository.deleteByAuthorId( + workspaceMemberId, + workspaceId, + ); + + await this.commentRepository.deleteByAuthorId( + workspaceMemberId, + workspaceId, + ); + } +} diff --git a/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-query-hook.module.ts b/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-query-hook.module.ts new file mode 100644 index 000000000..ce3496541 --- /dev/null +++ b/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-query-hook.module.ts @@ -0,0 +1,27 @@ +import { Module } from '@nestjs/common'; + +import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; +import { CommentObjectMetadata } from 'src/modules/activity/standard-objects/comment.object-metadata'; +import { AttachmentObjectMetadata } from 'src/modules/attachment/standard-objects/attachment.object-metadata'; +import { WorkspaceMemberDeleteManyPreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-delete-many.pre-query.hook'; +import { WorkspaceMemberDeleteOnePreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-delete-one.pre-query.hook'; + +@Module({ + imports: [ + ObjectMetadataRepositoryModule.forFeature([ + AttachmentObjectMetadata, + CommentObjectMetadata, + ]), + ], + providers: [ + { + provide: WorkspaceMemberDeleteOnePreQueryHook.name, + useClass: WorkspaceMemberDeleteOnePreQueryHook, + }, + { + provide: WorkspaceMemberDeleteManyPreQueryHook.name, + useClass: WorkspaceMemberDeleteManyPreQueryHook, + }, + ], +}) +export class WorkspaceMemberQueryHookModule {}