fix workspace-member deletion with existing attachments/documents (#5232)
## Context We have a non-nullable constraint on authorId in attachments and documents, until we have soft-deletion we need to handle deletion of workspace-members and their attachments/documents. This PR introduces pre-hooks to deleteOne/deleteMany This is called when a user deletes a workspace-member from the members page Next: needs to be done on user level as well. This is called when users try to delete their own accounts. I've seen other issues such as re-creating a user with a previously used email failing.
This commit is contained in:
@ -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 { 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 { 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 { 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
|
// TODO: move to a decorator
|
||||||
export const workspacePreQueryHooks: WorkspaceQueryHook = {
|
export const workspacePreQueryHooks: WorkspaceQueryHook = {
|
||||||
@ -22,4 +24,8 @@ export const workspacePreQueryHooks: WorkspaceQueryHook = {
|
|||||||
updateMany: [BlocklistUpdateManyPreQueryHook.name],
|
updateMany: [BlocklistUpdateManyPreQueryHook.name],
|
||||||
updateOne: [BlocklistUpdateOnePreQueryHook.name],
|
updateOne: [BlocklistUpdateOnePreQueryHook.name],
|
||||||
},
|
},
|
||||||
|
workspaceMember: {
|
||||||
|
deleteOne: [WorkspaceMemberDeleteOnePreQueryHook.name],
|
||||||
|
deleteMany: [WorkspaceMemberDeleteManyPreQueryHook.name],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 { 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 { 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 { 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({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
MessagingQueryHookModule,
|
MessagingQueryHookModule,
|
||||||
CalendarQueryHookModule,
|
CalendarQueryHookModule,
|
||||||
ConnectedAccountQueryHookModule,
|
ConnectedAccountQueryHookModule,
|
||||||
|
WorkspaceMemberQueryHookModule,
|
||||||
],
|
],
|
||||||
providers: [WorkspacePreQueryHookService],
|
providers: [WorkspacePreQueryHookService],
|
||||||
exports: [WorkspacePreQueryHookService],
|
exports: [WorkspacePreQueryHookService],
|
||||||
|
|||||||
@ -436,6 +436,14 @@ export class WorkspaceQueryRunnerService {
|
|||||||
atMost: maximumRecordAffected,
|
atMost: maximumRecordAffected,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.workspacePreQueryHookService.executePreHooks(
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
objectMetadataItem.nameSingular,
|
||||||
|
'deleteMany',
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
|
||||||
const result = await this.execute(query, workspaceId);
|
const result = await this.execute(query, workspaceId);
|
||||||
|
|
||||||
const parsedResults = (
|
const parsedResults = (
|
||||||
@ -495,6 +503,14 @@ export class WorkspaceQueryRunnerService {
|
|||||||
);
|
);
|
||||||
// TODO END
|
// TODO END
|
||||||
|
|
||||||
|
await this.workspacePreQueryHookService.executePreHooks(
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
objectMetadataItem.nameSingular,
|
||||||
|
'deleteOne',
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
|
||||||
const result = await this.execute(query, workspaceId);
|
const result = await this.execute(query, workspaceId);
|
||||||
|
|
||||||
const parsedResults = (
|
const parsedResults = (
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import { MessageThreadRepository } from 'src/modules/messaging/repositories/mess
|
|||||||
import { MessageRepository } from 'src/modules/messaging/repositories/message.repository';
|
import { MessageRepository } from 'src/modules/messaging/repositories/message.repository';
|
||||||
import { PersonRepository } from 'src/modules/person/repositories/person.repository';
|
import { PersonRepository } from 'src/modules/person/repositories/person.repository';
|
||||||
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.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 = {
|
export const metadataToRepositoryMapping = {
|
||||||
AuditLogObjectMetadata: AuditLogRepository,
|
AuditLogObjectMetadata: AuditLogRepository,
|
||||||
@ -34,4 +36,6 @@ export const metadataToRepositoryMapping = {
|
|||||||
PersonObjectMetadata: PersonRepository,
|
PersonObjectMetadata: PersonRepository,
|
||||||
TimelineActivityObjectMetadata: TimelineActivityRepository,
|
TimelineActivityObjectMetadata: TimelineActivityRepository,
|
||||||
WorkspaceMemberObjectMetadata: WorkspaceMemberRepository,
|
WorkspaceMemberObjectMetadata: WorkspaceMemberRepository,
|
||||||
|
AttachmentObjectMetadata: AttachmentRepository,
|
||||||
|
CommentObjectMetadata: CommentRepository,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
ValidationError,
|
ValidationError,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
ConflictError,
|
ConflictError,
|
||||||
|
MethodNotAllowedError,
|
||||||
} from 'src/engine/utils/graphql-errors.util';
|
} from 'src/engine/utils/graphql-errors.util';
|
||||||
import { ExceptionHandlerService } from 'src/engine/integrations/exception-handler/exception-handler.service';
|
import { ExceptionHandlerService } from 'src/engine/integrations/exception-handler/exception-handler.service';
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ const graphQLPredefinedExceptions = {
|
|||||||
401: AuthenticationError,
|
401: AuthenticationError,
|
||||||
403: ForbiddenError,
|
403: ForbiddenError,
|
||||||
404: NotFoundError,
|
404: NotFoundError,
|
||||||
|
405: MethodNotAllowedError,
|
||||||
409: ConflictError,
|
409: ConflictError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -142,6 +142,14 @@ export class NotFoundError extends BaseGraphQLError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class MethodNotAllowedError extends BaseGraphQLError {
|
||||||
|
constructor(message: string, extensions?: Record<string, any>) {
|
||||||
|
super(message, 'METHOD_NOT_ALLOWED', extensions);
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'name', { value: 'MethodNotAllowedError' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class ConflictError extends BaseGraphQLError {
|
export class ConflictError extends BaseGraphQLError {
|
||||||
constructor(message: string, extensions?: Record<string, any>) {
|
constructor(message: string, extensions?: Record<string, any>) {
|
||||||
super(message, 'CONFLICT', extensions);
|
super(message, 'CONFLICT', extensions);
|
||||||
|
|||||||
@ -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<void> {
|
||||||
|
const dataSourceSchema =
|
||||||
|
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||||
|
|
||||||
|
await this.workspaceDataSourceService.executeRawQuery(
|
||||||
|
`DELETE FROM ${dataSourceSchema}."comment" WHERE "authorId" = $1`,
|
||||||
|
[authorId],
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<void> {
|
||||||
|
const dataSourceSchema =
|
||||||
|
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||||
|
|
||||||
|
await this.workspaceDataSourceService.executeRawQuery(
|
||||||
|
`DELETE FROM ${dataSourceSchema}."attachment" WHERE "authorId" = $1`,
|
||||||
|
[authorId],
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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';
|
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() {}
|
constructor() {}
|
||||||
|
|
||||||
async execute(): Promise<void> {
|
async execute(): Promise<void> {
|
||||||
throw new Error('Method not implemented.');
|
throw new MethodNotAllowedException('Method not allowed.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* 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 { 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';
|
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,
|
_workspaceId: string,
|
||||||
_payload: FindOneResolverArgs,
|
_payload: FindOneResolverArgs,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
throw new BadRequestException('Method not implemented.');
|
throw new MethodNotAllowedException('Method not allowed.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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<void> {
|
||||||
|
throw new MethodNotAllowedException('Method not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<void> {
|
||||||
|
const workspaceMemberId = payload.id;
|
||||||
|
|
||||||
|
await this.attachmentRepository.deleteByAuthorId(
|
||||||
|
workspaceMemberId,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.commentRepository.deleteByAuthorId(
|
||||||
|
workspaceMemberId,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 {}
|
||||||
Reference in New Issue
Block a user