Skip to content

Commit

Permalink
fix workspace-member deletion with existing attachments/documents (tw…
Browse files Browse the repository at this point in the history
…entyhq#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.
  • Loading branch information
Weiko committed May 2, 2024
1 parent f9c19c8 commit fe758e1
Show file tree
Hide file tree
Showing 13 changed files with 166 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -22,4 +24,8 @@ export const workspacePreQueryHooks: WorkspaceQueryHook = {
updateMany: [BlocklistUpdateManyPreQueryHook.name],
updateOne: [BlocklistUpdateOnePreQueryHook.name],
},
workspaceMember: {
deleteOne: [WorkspaceMemberDeleteOnePreQueryHook.name],
deleteMany: [WorkspaceMemberDeleteManyPreQueryHook.name],
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand Down Expand Up @@ -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 = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -34,4 +36,6 @@ export const metadataToRepositoryMapping = {
PersonObjectMetadata: PersonRepository,
TimelineActivityObjectMetadata: TimelineActivityRepository,
WorkspaceMemberObjectMetadata: WorkspaceMemberRepository,
AttachmentObjectMetadata: AttachmentRepository,
CommentObjectMetadata: CommentRepository,
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -17,6 +18,7 @@ const graphQLPredefinedExceptions = {
401: AuthenticationError,
403: ForbiddenError,
404: NotFoundError,
405: MethodNotAllowedError,
409: ConflictError,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
constructor(message: string, extensions?: Record<string, any>) {
super(message, 'CONFLICT', extensions);
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
);
}
}
Original file line number Diff line number Diff line change
@@ -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,
);
}
}
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -7,6 +7,6 @@ export class BlocklistUpdateManyPreQueryHook implements WorkspacePreQueryHook {
constructor() {}

async execute(): Promise<void> {
throw new Error('Method not implemented.');
throw new MethodNotAllowedException('Method not allowed.');
}
}
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -11,6 +11,6 @@ export class MessageFindOnePreQueryHook implements WorkspacePreQueryHook {
_workspaceId: string,
_payload: FindOneResolverArgs,
): Promise<void> {
throw new BadRequestException('Method not implemented.');
throw new MethodNotAllowedException('Method not allowed.');
}
}
Original file line number Diff line number Diff line change
@@ -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.');
}
}
Original file line number Diff line number Diff line change
@@ -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,
);
}
}
Original file line number Diff line number Diff line change
@@ -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 {}

0 comments on commit fe758e1

Please sign in to comment.