7154 deleted event is not emitted when calling destroyone (#7159)

Closes #7154
This commit is contained in:
Raphaël Bosi
2024-09-27 15:52:04 +02:00
committed by GitHub
parent ca906bbf6b
commit c9c2f32922
12 changed files with 128 additions and 15 deletions

View File

@ -35,6 +35,7 @@ import {
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception'; } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event'; import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
@ -284,10 +285,94 @@ export class GraphqlQueryRunnerService {
async destroyOne<ObjectRecord extends IRecord = IRecord>( async destroyOne<ObjectRecord extends IRecord = IRecord>(
args: DestroyOneResolverArgs, args: DestroyOneResolverArgs,
options: WorkspaceQueryRunnerOptions, options: WorkspaceQueryRunnerOptions,
): Promise<ObjectRecord> { ): Promise<ObjectRecord | undefined> {
const graphqlQueryDestroyOneResolverService = const graphqlQueryDestroyOneResolverService =
new GraphqlQueryDestroyOneResolverService(this.twentyORMGlobalManager); new GraphqlQueryDestroyOneResolverService(this.twentyORMGlobalManager);
return graphqlQueryDestroyOneResolverService.destroyOne(args, options); const { authContext, objectMetadataItem } = options;
assertMutationNotOnRemoteObject(objectMetadataItem);
assertIsValidUuid(args.id);
const hookedArgs =
await this.workspaceQueryHookService.executePreQueryHooks(
authContext,
objectMetadataItem.nameSingular,
'destroyOne',
args,
);
const computedArgs = (await this.queryRunnerArgsFactory.create(
hookedArgs,
options,
ResolverArgsType.DestroyOne,
)) as DestroyOneResolverArgs;
const result = (await graphqlQueryDestroyOneResolverService.destroyOne(
computedArgs,
options,
)) as ObjectRecord;
await this.workspaceQueryHookService.executePostQueryHooks(
authContext,
objectMetadataItem.nameSingular,
'destroyOne',
[result],
);
await this.triggerWebhooks<IRecord>(
[result],
CallWebhookJobsJobOperation.destroy,
options,
);
this.emitDestroyEvents<IRecord>([result], authContext, objectMetadataItem);
return result;
}
private emitDestroyEvents<BaseRecord extends IRecord = IRecord>(
records: BaseRecord[],
authContext: AuthContext,
objectMetadataItem: ObjectMetadataInterface,
) {
this.workspaceEventEmitter.emit(
`${objectMetadataItem.nameSingular}.destroyed`,
records.map((record) => {
return {
userId: authContext.user?.id,
recordId: record.id,
objectMetadata: objectMetadataItem,
properties: {
before: this.removeNestedProperties(record),
},
} satisfies ObjectRecordDeleteEvent<any>;
}),
authContext.workspace.id,
);
}
private removeNestedProperties<Record extends IRecord = IRecord>(
record: Record,
) {
if (!record) {
return;
}
const sanitizedRecord = {};
for (const [key, value] of Object.entries(record)) {
if (value && typeof value === 'object' && value['edges']) {
continue;
}
if (key === '__typename') {
continue;
}
sanitizedRecord[key] = value;
}
return sanitizedRecord;
} }
} }

View File

@ -20,6 +20,7 @@ export enum CallWebhookJobsJobOperation {
create = 'create', create = 'create',
update = 'update', update = 'update',
delete = 'delete', delete = 'delete',
destroy = 'destroy',
} }
export type CallWebhookJobsJobData = { export type CallWebhookJobsJobData = {

View File

@ -49,6 +49,13 @@ export class EntityEventsToDbListener {
return this.handle(payload); return this.handle(payload);
} }
@OnEvent('*.destroyed')
async handleDestroy(
payload: WorkspaceEventBatch<ObjectRecordUpdateEvent<any>>,
) {
return this.handle(payload);
}
private async handle(payload: WorkspaceEventBatch<ObjectRecordBaseEvent>) { private async handle(payload: WorkspaceEventBatch<ObjectRecordBaseEvent>) {
const filteredEvents = payload.events.filter( const filteredEvents = payload.events.filter(
(event) => event.objectMetadata?.isAuditLogged, (event) => event.objectMetadata?.isAuditLogged,

View File

@ -4,6 +4,7 @@ import {
DeleteManyResolverArgs, DeleteManyResolverArgs,
DeleteOneResolverArgs, DeleteOneResolverArgs,
DestroyManyResolverArgs, DestroyManyResolverArgs,
DestroyOneResolverArgs,
FindDuplicatesResolverArgs, FindDuplicatesResolverArgs,
FindManyResolverArgs, FindManyResolverArgs,
FindOneResolverArgs, FindOneResolverArgs,
@ -39,4 +40,6 @@ export type WorkspacePreQueryHookPayload<T> = T extends 'createMany'
? RestoreManyResolverArgs ? RestoreManyResolverArgs
: T extends 'destroyMany' : T extends 'destroyMany'
? DestroyManyResolverArgs ? DestroyManyResolverArgs
: never; : T extends 'destroyOne'
? DestroyOneResolverArgs
: never;

View File

@ -22,6 +22,7 @@ export enum ResolverArgsType {
DeleteMany = 'DeleteMany', DeleteMany = 'DeleteMany',
RestoreMany = 'RestoreMany', RestoreMany = 'RestoreMany',
DestroyMany = 'DestroyMany', DestroyMany = 'DestroyMany',
DestroyOne = 'DestroyOne',
} }
export interface FindManyResolverArgs< export interface FindManyResolverArgs<

View File

@ -0,0 +1,7 @@
import { ObjectRecordBaseEvent } from 'src/engine/core-modules/event-emitter/types/object-record.base.event';
export class ObjectRecordDestroyEvent<T> extends ObjectRecordBaseEvent {
properties: {
before: T;
};
}

View File

@ -19,8 +19,8 @@ export class CalendarEventCleanerConnectedAccountListener {
private readonly calendarQueueService: MessageQueueService, private readonly calendarQueueService: MessageQueueService,
) {} ) {}
@OnEvent('connectedAccount.deleted') @OnEvent('connectedAccount.destroyed')
async handleDeletedEvent( async handleDestroyedEvent(
payload: WorkspaceEventBatch< payload: WorkspaceEventBatch<
ObjectRecordDeleteEvent<ConnectedAccountWorkspaceEntity> ObjectRecordDeleteEvent<ConnectedAccountWorkspaceEntity>
>, >,

View File

@ -15,8 +15,8 @@ export class ConnectedAccountListener {
private readonly accountsToReconnectService: AccountsToReconnectService, private readonly accountsToReconnectService: AccountsToReconnectService,
) {} ) {}
@OnEvent('connectedAccount.deleted') @OnEvent('connectedAccount.destroyed')
async handleDeletedEvent( async handleDestroyedEvent(
payload: WorkspaceEventBatch< payload: WorkspaceEventBatch<
ObjectRecordDeleteEvent<ConnectedAccountWorkspaceEntity> ObjectRecordDeleteEvent<ConnectedAccountWorkspaceEntity>
>, >,

View File

@ -8,7 +8,7 @@ import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
@WorkspaceQueryHook(`connectedAccount.deleteOne`) @WorkspaceQueryHook(`connectedAccount.destroyOne`)
export class ConnectedAccountDeleteOnePreQueryHook export class ConnectedAccountDeleteOnePreQueryHook
implements WorkspaceQueryHookInstance implements WorkspaceQueryHookInstance
{ {
@ -34,7 +34,7 @@ export class ConnectedAccountDeleteOnePreQueryHook
}); });
this.workspaceEventEmitter.emit( this.workspaceEventEmitter.emit(
'messageChannel.deleted', 'messageChannel.destroyed',
messageChannels.map( messageChannels.map(
(messageChannel) => (messageChannel) =>
({ ({

View File

@ -19,8 +19,8 @@ export class MessagingMessageCleanerConnectedAccountListener {
private readonly messageQueueService: MessageQueueService, private readonly messageQueueService: MessageQueueService,
) {} ) {}
@OnEvent('connectedAccount.deleted') @OnEvent('connectedAccount.destroyed')
async handleDeletedEvent( async handleDestroyedEvent(
payload: WorkspaceEventBatch< payload: WorkspaceEventBatch<
ObjectRecordDeleteEvent<ConnectedAccountWorkspaceEntity> ObjectRecordDeleteEvent<ConnectedAccountWorkspaceEntity>
>, >,

View File

@ -19,8 +19,8 @@ export class MessagingMessageImportManagerMessageChannelListener {
private readonly messageQueueService: MessageQueueService, private readonly messageQueueService: MessageQueueService,
) {} ) {}
@OnEvent('messageChannel.deleted') @OnEvent('messageChannel.destroyed')
async handleDeletedEvent( async handleDestroyedEvent(
payload: WorkspaceEventBatch< payload: WorkspaceEventBatch<
ObjectRecordDeleteEvent<MessageChannelWorkspaceEntity> ObjectRecordDeleteEvent<MessageChannelWorkspaceEntity>
>, >,

View File

@ -1,11 +1,12 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter'; import { OnEvent } from '@nestjs/event-emitter';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event'; import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event'; import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
import { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event';
import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event'; import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
@ -49,11 +50,19 @@ export class DatabaseEventTriggerListener {
await this.handleEvent(payload); await this.handleEvent(payload);
} }
@OnEvent('*.destroyed')
async handleObjectRecordDestroyEvent(
payload: WorkspaceEventBatch<ObjectRecordDestroyEvent<any>>,
) {
await this.handleEvent(payload);
}
private async handleEvent( private async handleEvent(
payload: WorkspaceEventBatch< payload: WorkspaceEventBatch<
| ObjectRecordCreateEvent<any> | ObjectRecordCreateEvent<any>
| ObjectRecordUpdateEvent<any> | ObjectRecordUpdateEvent<any>
| ObjectRecordDeleteEvent<any> | ObjectRecordDeleteEvent<any>
| ObjectRecordDestroyEvent<any>
>, >,
) { ) {
const workspaceId = payload.workspaceId; const workspaceId = payload.workspaceId;