From 2deac9448ed5bdd1a4b7af41d52cbc4fa2dff9a7 Mon Sep 17 00:00:00 2001 From: Weiko Date: Thu, 17 Jul 2025 18:07:28 +0200 Subject: [PATCH] Add db event emitter in twenty orm (#13167) ## Context Add an eventEmitter instance to twenty datasources so we can emit DB events. Add input and output formatting to twenty orm (formatData, formatResult) Those 2 elements simplified existing logic when we interact with the ORM, input will be formatted by the ORM so we can directly use field-like structure instead of column-like. The output will be formatted, for builder queries it will be in `result.generatedMaps` where `result.raw` preserves the previous column-like structure. Important change: We now have an authContext that we can pass when we get a repository, this will be used for the different events emitted in the ORM. We also removed the caching for repositories as it was not scaling well and not necessary imho Note: An upcoming PR should handle the onDelete: cascade behavior where we send DESTROY events in cascade when there is an onDelete: CASCADE on the FK. --------- Co-authored-by: Charles Bochet --- .../patches/typeorm+0.3.20.patch | 36 ++- .../graphql-query-runner.module.ts | 2 - .../process-nested-relations-v2.helper.ts | 15 +- .../interfaces/base-resolver-service.ts | 4 +- ...phql-query-create-many-resolver.service.ts | 92 +------ ...aphql-query-create-one-resolver.service.ts | 20 +- ...phql-query-delete-many-resolver.service.ts | 23 +- ...aphql-query-delete-one-resolver.service.ts | 22 +- ...hql-query-destroy-many-resolver.service.ts | 23 +- ...phql-query-destroy-one-resolver.service.ts | 25 +- ...-query-find-duplicates-resolver.service.ts | 23 +- ...raphql-query-find-many-resolver.service.ts | 11 +- ...graphql-query-find-one-resolver.service.ts | 11 +- ...hql-query-restore-many-resolver.service.ts | 23 +- ...phql-query-restore-one-resolver.service.ts | 23 +- ...phql-query-update-many-resolver.service.ts | 65 +---- ...aphql-query-update-one-resolver.service.ts | 56 +---- .../services/api-event-emitter.service.ts | 188 -------------- .../handlers/rest-api-create-many.handler.ts | 10 - .../handlers/rest-api-create-one.handler.ts | 10 - .../handlers/rest-api-delete-one.handler.ts | 9 - .../rest-api-find-duplicates.handler.ts | 9 +- .../handlers/rest-api-update-one.handler.ts | 11 - .../core/interfaces/rest-api-base.handler.ts | 3 - .../api/rest/core/rest-api-core.module.ts | 30 ++- .../utils/build-duplicate-conditions.utils.ts | 2 +- .../core-modules/ai/services/tool.service.ts | 95 ------- .../create-calendar-channel.service.ts | 28 --- .../create-connected-account.service.ts | 30 +-- .../create-message-channel.service.ts | 28 --- .../auth/services/google-apis.service.spec.ts | 7 - .../services/microsoft-apis.service.spec.ts | 7 - .../reset-calendar-channel.service.ts | 37 +-- .../services/reset-message-channel.service.ts | 34 +-- ...-connected-account-on-reconnect.service.ts | 36 +-- .../object-record-changed-values.spec.ts | 15 +- .../utils/object-record-changed-values.ts | 12 +- .../user-workspace.service.spec.ts | 49 ---- .../user-workspace/user-workspace.service.ts | 27 -- .../user/services/user.service.ts | 32 --- .../datasource/workspace.datasource.ts | 24 +- .../workspace-entity-manager.spec.ts | 58 ++++- .../workspace-entity-manager.ts | 237 +++++++++++++----- .../exceptions/twenty-orm.exception.ts | 2 + .../factories/workspace-datasource.factory.ts | 3 + .../workspace-internal-context.interface.ts | 2 + .../workspace.repository.spec.ts | 32 ++- .../workspace-delete-query-builder.ts | 82 +++++- .../workspace-insert-query-builder.ts | 106 +++++++- .../workspace-select-query-builder.ts | 131 +++++++++- .../workspace-soft-delete-query-builder.ts | 83 +++++- .../workspace-update-query-builder.ts | 119 ++++++++- .../repository/workspace.repository.ts | 106 +++----- ...object-metadata-from-entity-target.util.ts | 3 +- .../workspace-event-emitter.ts | 105 ++++++++ ...ected-account-delete-one.pre-query.hook.ts | 3 +- .../services/imap-smtp-caldav-apis.service.ts | 100 +------- .../__tests__/create-company.service.spec.ts | 7 - .../create-company-and-contact.service.ts | 45 +--- .../services/create-company.service.ts | 38 +-- .../match-participant.service.spec.ts | 40 ++- .../match-participant.service.ts | 8 +- .../services/imap-handle-error.service.ts | 34 --- ...d-enqueue-contact-creation.service.spec.ts | 7 - ...es-and-enqueue-contact-creation.service.ts | 31 +-- .../workflow-create-many.post-query.hook.ts | 35 +-- .../workflow-create-one.post-query.hook.ts | 37 +-- .../workflow-version.workspace-service.ts | 50 ---- .../create-record.workflow-action.ts | 18 -- .../delete-record.workflow-action.ts | 36 --- .../find-records.workflow-action.ts | 9 +- .../update-record.workflow-action.ts | 56 +---- .../workflow-run.workspace-service.ts | 96 +------ .../workflow-statuses-update.job.spec.ts | 24 -- .../jobs/workflow-statuses-update.job.ts | 77 +----- .../workflow-trigger.workspace-service.ts | 33 --- .../all-people-resolvers.integration-spec.ts | 4 +- .../agent/utils/agent-tool-test-utils.ts | 9 - yarn.lock | 4 +- 79 files changed, 1061 insertions(+), 2016 deletions(-) delete mode 100644 packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts rename packages/twenty-server/src/engine/twenty-orm/repository/{ => __tests__}/workspace.repository.spec.ts (95%) diff --git a/packages/twenty-server/patches/typeorm+0.3.20.patch b/packages/twenty-server/patches/typeorm+0.3.20.patch index f3730c15d..7c6be7a99 100644 --- a/packages/twenty-server/patches/typeorm+0.3.20.patch +++ b/packages/twenty-server/patches/typeorm+0.3.20.patch @@ -1,5 +1,5 @@ -diff --git a/node_modules/typeorm/common/PickKeysByType.d.ts b/node_modules/typeorm/common/PickKeysByType.d.ts -index 55ad347..1a8a184 100644 +diff --git a/common/PickKeysByType.d.ts b/common/PickKeysByType.d.ts +index 55ad347..4288c06 100644 --- a/common/PickKeysByType.d.ts +++ b/common/PickKeysByType.d.ts @@ -1,6 +1,6 @@ @@ -12,3 +12,35 @@ index 55ad347..1a8a184 100644 +export type PickKeysByType = string & { + [P in keyof T]: Exclude extends U ? P : never; +}[keyof T]; +diff --git a/query-builder/result/DeleteResult.d.ts b/query-builder/result/DeleteResult.d.ts +index 9c98830..d0578f9 100644 +--- a/query-builder/result/DeleteResult.d.ts ++++ b/query-builder/result/DeleteResult.d.ts +@@ -13,4 +13,9 @@ export declare class DeleteResult { + * Not all drivers support this + */ + affected?: number | null; ++ /** ++ * Generated values returned by a database. ++ * Has entity-like structure (not just column database name and values). ++ */ ++ generatedMaps: ObjectLiteral[]; + } +diff --git a/query-builder/result/DeleteResult.js b/query-builder/result/DeleteResult.js +index 6519c11..0bb344a 100644 +--- a/query-builder/result/DeleteResult.js ++++ b/query-builder/result/DeleteResult.js +@@ -5,6 +5,13 @@ exports.DeleteResult = void 0; + * Result object returned by DeleteQueryBuilder execution. + */ + class DeleteResult { ++ constructor() { ++ /** ++ * Generated values returned by a database. ++ * Has entity-like structure (not just column database name and values). ++ */ ++ this.generatedMaps = []; ++ } + static from(queryResult) { + const result = new this(); + result.raw = queryResult.records; diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.module.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.module.ts index 0a1861741..0561315d2 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.module.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.module.ts @@ -17,7 +17,6 @@ import { GraphqlQueryRestoreManyResolverService } from 'src/engine/api/graphql/g import { GraphqlQueryRestoreOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service'; import { GraphqlQueryUpdateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service'; import { GraphqlQueryUpdateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service'; -import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service'; import { WorkspaceQueryHookModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module'; import { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; @@ -49,7 +48,6 @@ const graphqlQueryResolvers = [ UserRoleModule, ], providers: [ - ApiEventEmitterService, ProcessNestedRelationsHelper, ProcessNestedRelationsV2Helper, ProcessAggregateHelper, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper.ts index 1557ba668..fb8c9258e 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper.ts @@ -20,7 +20,6 @@ import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-met import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util'; import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource'; import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util'; @Injectable() @@ -180,8 +179,6 @@ export class ProcessNestedRelationsV2Helper { : 'id', ids: relationIds, limit: limit * parentObjectRecords.length, - objectMetadataMaps, - targetObjectMetadata, aggregate, sourceFieldName, }); @@ -286,8 +283,6 @@ export class ProcessNestedRelationsV2Helper { column, ids, limit, - objectMetadataMaps, - targetObjectMetadata, aggregate, sourceFieldName, }: { @@ -297,8 +292,6 @@ export class ProcessNestedRelationsV2Helper { // eslint-disable-next-line @typescript-eslint/no-explicit-any ids: any[]; limit: number; - objectMetadataMaps: ObjectMetadataMaps; - targetObjectMetadata: ObjectMetadataItemWithFieldMaps; // eslint-disable-next-line @typescript-eslint/no-explicit-any aggregate: Record; sourceFieldName: string; @@ -359,13 +352,7 @@ export class ProcessNestedRelationsV2Helper { .take(limit) .getMany(); - const relationResults = formatResult( - result, - targetObjectMetadata, - objectMetadataMaps, - ); - - return { relationResults, relationAggregatedFieldsResult }; + return { relationResults: result, relationAggregatedFieldsResult }; } private assignRelationResults({ diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts index bf77b299e..27aaab4d3 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts @@ -19,7 +19,6 @@ import { OBJECTS_WITH_SETTINGS_PERMISSIONS_REQUIREMENTS } from 'src/engine/api/g import { GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser'; import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; -import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service'; import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory'; import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; @@ -66,8 +65,6 @@ export abstract class GraphqlQueryBaseResolverService< @Inject() protected readonly queryResultGettersFactory: QueryResultGettersFactory; @Inject() - protected readonly apiEventEmitterService: ApiEventEmitterService; - @Inject() protected readonly twentyORMGlobalManager: TwentyORMGlobalManager; @Inject() protected readonly processNestedRelationsHelper: ProcessNestedRelationsHelper; @@ -128,6 +125,7 @@ export abstract class GraphqlQueryBaseResolverService< objectMetadataItemWithFieldMaps.nameSingular, shouldBypassPermissionChecks, roleId, + authContext, ); const graphqlQueryParser = new GraphqlQueryParser( diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts index 115a9d9bf..a732d7e2d 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts @@ -19,15 +19,11 @@ import { import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select'; import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; -import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; -import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResolverService< @@ -48,7 +44,6 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol executionArgs, objectRecords, objectMetadataItemWithFieldMaps, - objectMetadataMaps, ); const shouldBypassPermissionChecks = executionArgs.isExecutedByApiKey; @@ -105,21 +100,15 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol await this.processRecordsToUpdate({ partialRecordsToUpdate: recordsToUpdate, - existingRecords, repository: executionArgs.repository, objectMetadataItemWithFieldMaps, - objectMetadataMaps: executionArgs.options.objectMetadataMaps, result, - authContext: executionArgs.options.authContext, }); await this.processRecordsToInsert({ recordsToInsert, repository: executionArgs.repository, result, - objectMetadataItemWithFieldMaps, - objectMetadataMaps: executionArgs.options.objectMetadataMaps, - authContext: executionArgs.options.authContext, }); return result; @@ -273,20 +262,14 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol private async processRecordsToUpdate({ partialRecordsToUpdate, - existingRecords, repository, objectMetadataItemWithFieldMaps, - objectMetadataMaps, result, - authContext, }: { partialRecordsToUpdate: Partial[]; - existingRecords: Partial[]; repository: WorkspaceRepository; objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps; - objectMetadataMaps: ObjectMetadataMaps; result: InsertResult; - authContext: AuthContext; }): Promise { for (const partialRecordToUpdate of partialRecordsToUpdate) { const recordId = partialRecordToUpdate.id as string; @@ -298,101 +281,38 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol objectMetadataItemWithFieldMaps, ); - const formattedPartialRecordToUpdate = formatData( + await repository.update( + recordId, partialRecordToUpdateWithoutCreatedByUpdate, - objectMetadataItemWithFieldMaps, ); - // TODO: we should align update and insert - // For insert, formating is done in the server - // While for update, formatting is done at the resolver level - await repository.update(recordId, formattedPartialRecordToUpdate); - result.identifiers.push({ id: recordId }); result.generatedMaps.push({ id: recordId }); - - const [updatedRecord] = await repository.find({ - where: { id: recordId }, - }); - - if (!isDefined(updatedRecord)) { - continue; - } - - const record = formatResult( - updatedRecord, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); - - const existingRecord = formatResult( - existingRecords.find((record) => record.id === recordId), - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); - - this.apiEventEmitterService.emitUpdateEvents({ - existingRecords: structuredClone([existingRecord]), - records: structuredClone([record]), - updatedFields: Object.keys(formattedPartialRecordToUpdate), - authContext, - objectMetadataItem: - getObjectMetadataFromObjectMetadataItemWithFieldMaps( - objectMetadataItemWithFieldMaps, - ), - }); } } private async processRecordsToInsert({ recordsToInsert, repository, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, result, - authContext, }: { recordsToInsert: Partial[]; repository: WorkspaceRepository; - objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps; - objectMetadataMaps: ObjectMetadataMaps; result: InsertResult; - authContext: AuthContext; }): Promise { - const formattedInsertedRecords: ObjectRecord[] = []; - if (recordsToInsert.length > 0) { const insertResult = await repository.insert(recordsToInsert); result.identifiers.push(...insertResult.identifiers); result.generatedMaps.push(...insertResult.generatedMaps); result.raw.push(...insertResult.raw); - - formattedInsertedRecords.push( - ...insertResult.raw.map((record: ObjectRecord) => - formatResult( - record, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ), - ), - ); } - - this.apiEventEmitterService.emitCreateEvents({ - records: structuredClone(formattedInsertedRecords), - authContext, - objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps( - objectMetadataItemWithFieldMaps, - ), - }); } private async fetchUpsertedRecords( executionArgs: GraphqlQueryResolverExecutionArgs, objectRecords: InsertResult, objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, - objectMetadataMaps: ObjectMetadataMaps, ): Promise { const queryBuilder = executionArgs.repository.createQueryBuilder( objectMetadataItemWithFieldMaps.nameSingular, @@ -404,7 +324,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol objectMetadataItemWithFieldMaps, }); - const nonFormattedUpsertedRecords = await queryBuilder + const upsertedRecords = await queryBuilder .setFindOptions({ select: columnsToSelect, }) @@ -414,11 +334,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol .take(QUERY_MAX_RECORDS) .getMany(); - return formatResult( - nonFormattedUpsertedRecords, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); + return upsertedRecords as ObjectRecord[]; } private async processNestedRelationsIfNeeded( diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service.ts index 25c41ab31..c85a71aec 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service.ts @@ -15,8 +15,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select'; import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolverService< @@ -48,7 +46,7 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv objectMetadataItemWithFieldMaps, }); - const nonFormattedUpsertedRecords = await queryBuilder + const upsertedRecords = (await queryBuilder .setFindOptions({ select: columnsToSelect, }) @@ -56,21 +54,7 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv id: In(objectRecords.generatedMaps.map((record) => record.id)), }) .take(QUERY_MAX_RECORDS) - .getMany(); - - const upsertedRecords = formatResult( - nonFormattedUpsertedRecords, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); - - this.apiEventEmitterService.emitCreateEvents({ - records: structuredClone(upsertedRecords), - authContext, - objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps( - objectMetadataItemWithFieldMaps, - ), - }); + .getMany()) as ObjectRecord[]; if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await this.processNestedRelationsHelper.processNestedRelations({ diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service.ts index 3d9d68889..5cae18c81 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service.ts @@ -14,8 +14,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return'; import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() @@ -52,30 +50,17 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol objectMetadataItemWithFieldMaps, }); - const nonFormattedDeletedObjectRecords = await queryBuilder + const deletedObjectRecords = await queryBuilder .softDelete() .returning(columnsToReturn) .execute(); - const formattedDeletedRecords = formatResult( - nonFormattedDeletedObjectRecords.raw, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); - - this.apiEventEmitterService.emitDeletedEvents({ - records: structuredClone(formattedDeletedRecords), - authContext, - objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps( - objectMetadataItemWithFieldMaps, - ), - }); - if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await this.processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps, - parentObjectRecords: formattedDeletedRecords, + parentObjectRecords: + deletedObjectRecords.generatedMaps as ObjectRecord[], relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, @@ -89,7 +74,7 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); - return formattedDeletedRecords.map((record: ObjectRecord) => + return deletedObjectRecords.generatedMaps.map((record: ObjectRecord) => typeORMObjectRecordsParser.processRecord({ objectRecord: record, objectName: objectMetadataItemWithFieldMaps.nameSingular, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service.ts index 1d395aaf1..e7bc9863c 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service.ts @@ -18,8 +18,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return'; import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolverService< @@ -44,34 +42,20 @@ export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolv objectMetadataItemWithFieldMaps, }); - const nonFormattedDeletedObjectRecords = await queryBuilder + const deletedObjectRecords = await queryBuilder .softDelete() .where({ id: executionArgs.args.id }) .returning(columnsToReturn) .execute(); - const formattedDeletedRecords = formatResult( - nonFormattedDeletedObjectRecords.raw, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); - - if (formattedDeletedRecords.length === 0) { + if (deletedObjectRecords.generatedMaps.length === 0) { throw new GraphqlQueryRunnerException( 'Record not found', GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, ); } - const deletedRecord = formattedDeletedRecords[0]; - - this.apiEventEmitterService.emitDeletedEvents({ - records: structuredClone(formattedDeletedRecords), - authContext, - objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps( - objectMetadataItemWithFieldMaps, - ), - }); + const deletedRecord = deletedObjectRecords.generatedMaps[0] as ObjectRecord; if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await this.processNestedRelationsHelper.processNestedRelations({ diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts index 7e107673f..7a353c123 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts @@ -12,8 +12,6 @@ import { DestroyManyResolverArgs } from 'src/engine/api/graphql/workspace-resolv import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() @@ -50,30 +48,17 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso objectMetadataItemWithFieldMaps, }); - const nonFormattedDeletedObjectRecords = await queryBuilder + const deletedObjectRecords = await queryBuilder .delete() .returning(columnsToReturn) .execute(); - const deletedRecords = formatResult( - nonFormattedDeletedObjectRecords.raw, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); - - this.apiEventEmitterService.emitDestroyEvents({ - records: structuredClone(deletedRecords), - authContext, - objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps( - objectMetadataItemWithFieldMaps, - ), - }); - if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await this.processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps, - parentObjectRecords: deletedRecords, + parentObjectRecords: + deletedObjectRecords.generatedMaps as ObjectRecord[], relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, @@ -87,7 +72,7 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); - return deletedRecords.map((record: ObjectRecord) => + return deletedObjectRecords.generatedMaps.map((record: ObjectRecord) => typeORMObjectRecordsParser.processRecord({ objectRecord: record, objectName: objectMetadataItemWithFieldMaps.nameSingular, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts index d218596c6..cdf3312c9 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts @@ -16,8 +16,6 @@ import { } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResolverService< @@ -42,38 +40,25 @@ export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResol objectMetadataItemWithFieldMaps, }); - const nonFormattedDeletedObjectRecords = await queryBuilder + const deletedObjectRecords = await queryBuilder .delete() .where({ id: executionArgs.args.id }) .returning(columnsToReturn) .execute(); - if (!nonFormattedDeletedObjectRecords.affected) { + if (!deletedObjectRecords.affected) { throw new GraphqlQueryRunnerException( 'Record not found', GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, ); } - const deletedRecords = formatResult( - nonFormattedDeletedObjectRecords.raw, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); - - this.apiEventEmitterService.emitDestroyEvents({ - records: structuredClone(deletedRecords), - authContext, - objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps( - objectMetadataItemWithFieldMaps, - ), - }); - if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await this.processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps, - parentObjectRecords: deletedRecords, + parentObjectRecords: + deletedObjectRecords.generatedMaps as ObjectRecord[], relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, @@ -88,7 +73,7 @@ export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResol new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); return typeORMObjectRecordsParser.processRecord({ - objectRecord: deletedRecords[0], + objectRecord: deletedObjectRecords.generatedMaps[0] as ObjectRecord, objectName: objectMetadataItemWithFieldMaps.nameSingular, take: 1, totalCount: 1, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts index e6207f928..c2767e9bc 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts @@ -24,8 +24,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select'; import { buildDuplicateConditions } from 'src/engine/api/utils/build-duplicate-conditions.utils'; import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util'; -import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseResolverService< @@ -67,20 +65,11 @@ export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseR let objectRecords: Partial[] = []; if (executionArgs.args.ids) { - const nonFormattedObjectRecords = (await existingRecordsQueryBuilder + objectRecords = (await existingRecordsQueryBuilder .where({ id: In(executionArgs.args.ids) }) .getMany()) as ObjectRecord[]; - - objectRecords = formatResult( - nonFormattedObjectRecords, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); } else if (executionArgs.args.data && !isEmpty(executionArgs.args.data)) { - objectRecords = formatData( - executionArgs.args.data, - objectMetadataItemWithFieldMaps, - ); + objectRecords = executionArgs.args.data; } const duplicateConnections: IConnection[] = await Promise.all( @@ -120,18 +109,12 @@ export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseR duplicateConditions, ); - const nonFormattedDuplicates = (await duplicateRecordsQueryBuilder + const duplicates = (await duplicateRecordsQueryBuilder .setFindOptions({ select: columnsToSelect, }) .getMany()) as ObjectRecord[]; - const duplicates = formatResult( - nonFormattedDuplicates, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); - return typeORMObjectRecordsParser.createConnection({ objectRecords: duplicates, objectName: objectMetadataItemWithFieldMaps.nameSingular, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts index 73592fcbf..14168dc5d 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts @@ -29,7 +29,6 @@ import { getPaginationInfo, } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util'; import { computeCursorArgFilter } from 'src/engine/api/utils/compute-cursor-arg-filter.utils'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolverService< @@ -127,18 +126,12 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve objectMetadataItemWithFieldMaps, }); - const nonFormattedObjectRecords = await queryBuilder + const objectRecords = (await queryBuilder .setFindOptions({ select: columnsToSelect, }) .take(limit + 1) - .getMany(); - - const objectRecords = formatResult( - nonFormattedObjectRecords, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); + .getMany()) as ObjectRecord[]; const { hasNextPage, hasPreviousPage } = getPaginationInfo( objectRecords, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts index 1cbe5c077..cb33eaa1f 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts @@ -23,7 +23,6 @@ import { WorkspaceQueryRunnerException, WorkspaceQueryRunnerExceptionCode, } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolverService< @@ -59,18 +58,12 @@ export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolver objectMetadataItemWithFieldMaps, }); - const nonFormattedObjectRecord = await queryBuilder + const objectRecord = await queryBuilder .setFindOptions({ select: columnsToSelect, }) .getOne(); - const objectRecord = formatResult( - nonFormattedObjectRecord, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); - if (!objectRecord) { throw new GraphqlQueryRunnerException( 'Record not found', @@ -78,7 +71,7 @@ export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolver ); } - const objectRecords = [objectRecord]; + const objectRecords = [objectRecord] as ObjectRecord[]; if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await this.processNestedRelationsHelper.processNestedRelations({ diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service.ts index a91057c45..272411f2a 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service.ts @@ -14,8 +14,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return'; import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() @@ -52,30 +50,17 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso objectMetadataItemWithFieldMaps, }); - const nonFormattedRestoredObjectRecords = await queryBuilder + const restoredObjectRecords = await queryBuilder .restore() .returning(columnsToReturn) .execute(); - const formattedRestoredRecords = formatResult( - nonFormattedRestoredObjectRecords.raw, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); - - this.apiEventEmitterService.emitRestoreEvents({ - records: structuredClone(formattedRestoredRecords), - authContext, - objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps( - objectMetadataItemWithFieldMaps, - ), - }); - if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await this.processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps, - parentObjectRecords: formattedRestoredRecords, + parentObjectRecords: + restoredObjectRecords.generatedMaps as ObjectRecord[], relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, @@ -89,7 +74,7 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); - return formattedRestoredRecords.map((record: ObjectRecord) => + return restoredObjectRecords.generatedMaps.map((record: ObjectRecord) => typeORMObjectRecordsParser.processRecord({ objectRecord: record, objectName: objectMetadataItemWithFieldMaps.nameSingular, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service.ts index 7381d486f..c1b6549fc 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service.ts @@ -18,8 +18,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return'; import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResolverService< @@ -44,34 +42,21 @@ export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResol objectMetadataItemWithFieldMaps, }); - const nonFormattedRestoredObjectRecords = await queryBuilder + const restoredObjectRecords = await queryBuilder .restore() .where({ id: executionArgs.args.id }) .returning(columnsToReturn) .execute(); - const formattedRestoredRecords = formatResult( - nonFormattedRestoredObjectRecords.raw, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); - - if (formattedRestoredRecords.length === 0) { + if (restoredObjectRecords.generatedMaps.length === 0) { throw new GraphqlQueryRunnerException( 'Record not found', GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, ); } - const restoredRecord = formattedRestoredRecords[0]; - - this.apiEventEmitterService.emitRestoreEvents({ - records: structuredClone(formattedRestoredRecords), - authContext, - objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps( - objectMetadataItemWithFieldMaps, - ), - }); + const restoredRecord = restoredObjectRecords + .generatedMaps[0] as ObjectRecord; if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await this.processNestedRelationsHelper.processNestedRelations({ diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts index f32c8efe1..be4655e46 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts @@ -1,6 +1,5 @@ import { Injectable } from '@nestjs/common'; -import isEmpty from 'lodash.isempty'; import { QUERY_MAX_RECORDS } from 'twenty-shared/constants'; import { @@ -11,17 +10,10 @@ import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/int import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; -import { - GraphqlQueryRunnerException, - GraphqlQueryRunnerExceptionCode, -} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return'; import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; -import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() @@ -41,29 +33,6 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol objectMetadataItemWithFieldMaps.nameSingular, ); - const existingRecordsBuilder = queryBuilder.clone(); - - executionArgs.graphqlQueryParser.applyFilterToBuilder( - existingRecordsBuilder, - objectMetadataItemWithFieldMaps.nameSingular, - executionArgs.args.filter, - ); - - const existingRecords = await existingRecordsBuilder.getMany(); - - const formattedExistingRecords = formatResult( - existingRecords, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); - - if (isEmpty(formattedExistingRecords)) { - throw new GraphqlQueryRunnerException( - 'Records not found', - GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, - ); - } - const tableName = computeTableName( objectMetadataItemWithFieldMaps.nameSingular, objectMetadataItemWithFieldMaps.isCustom, @@ -75,46 +44,24 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol executionArgs.args.filter, ); - const data = formatData( - executionArgs.args.data, - objectMetadataItemWithFieldMaps, - ); - const columnsToReturn = buildColumnsToReturn({ select: executionArgs.graphqlQuerySelectedFieldsResult.select, relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, objectMetadataItemWithFieldMaps, }); - const nonFormattedUpdatedObjectRecords = await queryBuilder - .update(data) + const updatedObjectRecords = await queryBuilder + .update() + .set(executionArgs.args.data) .returning(columnsToReturn) .execute(); - const formattedUpdatedRecords = formatResult( - nonFormattedUpdatedObjectRecords.raw, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); - - this.apiEventEmitterService.emitUpdateEvents({ - existingRecords: structuredClone(formattedExistingRecords), - records: structuredClone(formattedUpdatedRecords), - updatedFields: Object.keys(executionArgs.args.data), - authContext, - objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps( - objectMetadataItemWithFieldMaps, - ), - }); - if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await this.processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps, - parentObjectRecords: [ - ...formattedExistingRecords, - ...formattedUpdatedRecords, - ], + parentObjectRecords: + updatedObjectRecords.generatedMaps as ObjectRecord[], relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, @@ -128,7 +75,7 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); - return formattedUpdatedRecords.map((record: ObjectRecord) => + return updatedObjectRecords.generatedMaps.map((record: ObjectRecord) => typeORMObjectRecordsParser.processRecord({ objectRecord: record, objectName: objectMetadataItemWithFieldMaps.nameSingular, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts index b57c66cf7..b97330efe 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts @@ -1,6 +1,5 @@ import { Injectable } from '@nestjs/common'; -import isEmpty from 'lodash.isempty'; import { QUERY_MAX_RECORDS } from 'twenty-shared/constants'; import { @@ -19,9 +18,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return'; import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; -import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolverService< @@ -40,73 +36,33 @@ export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolv objectMetadataItemWithFieldMaps.nameSingular, ); - const data = formatData( - executionArgs.args.data, - objectMetadataItemWithFieldMaps, - ); - - const existingRecordBuilder = queryBuilder.clone(); - - const existingRecords = (await existingRecordBuilder - .where({ id: executionArgs.args.id }) - .getMany()) as ObjectRecord[]; - - const formattedExistingRecords = formatResult( - existingRecords, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); - - if (isEmpty(formattedExistingRecords)) { - throw new GraphqlQueryRunnerException( - 'Record not found', - GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, - ); - } - const columnsToReturn = buildColumnsToReturn({ select: executionArgs.graphqlQuerySelectedFieldsResult.select, relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, objectMetadataItemWithFieldMaps, }); - const nonFormattedUpdatedObjectRecords = await queryBuilder - .update(data) + const updatedObjectRecords = await queryBuilder + .update() + .set(executionArgs.args.data) .where({ id: executionArgs.args.id }) .returning(columnsToReturn) .execute(); - const formattedUpdatedRecords = formatResult( - nonFormattedUpdatedObjectRecords.raw, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); + const updatedRecord = updatedObjectRecords.generatedMaps[0] as ObjectRecord; - if (formattedUpdatedRecords.length === 0) { + if (!updatedRecord) { throw new GraphqlQueryRunnerException( 'Record not found', GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, ); } - const updatedRecord = formattedUpdatedRecords[0]; - const existingRecord = formattedExistingRecords[0]; - - this.apiEventEmitterService.emitUpdateEvents({ - existingRecords: structuredClone(formattedExistingRecords), - records: structuredClone(formattedUpdatedRecords), - updatedFields: Object.keys(executionArgs.args.data), - authContext, - objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps( - objectMetadataItemWithFieldMaps, - ), - }); - if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await this.processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps, - parentObjectRecords: [existingRecord, updatedRecord], + parentObjectRecords: [updatedRecord], relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts deleted file mode 100644 index 8cd0e192f..000000000 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; -import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; - -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; -import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; -import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values'; -import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; - -@Injectable() -export class ApiEventEmitterService { - constructor(private readonly workspaceEventEmitter: WorkspaceEventEmitter) {} - - public emitCreateEvents({ - records, - authContext, - objectMetadataItem, - }: { - records: T[]; - authContext: AuthContext; - objectMetadataItem: ObjectMetadataInterface; - }): void { - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: objectMetadataItem.nameSingular, - action: DatabaseEventAction.CREATED, - events: records.map((record) => ({ - userId: authContext.user?.id, - recordId: record.id, - objectMetadata: objectMetadataItem, - properties: { - before: null, - after: record, - }, - })), - workspaceId: authContext.workspace?.id, - }); - } - - public emitUpdateEvents({ - existingRecords, - records, - updatedFields, - authContext, - objectMetadataItem, - }: { - existingRecords: T[]; - records: T[]; - updatedFields: string[]; - authContext: AuthContext; - objectMetadataItem: ObjectMetadataInterface; - }): void { - const workspace = authContext.workspace; - - workspaceValidator.assertIsDefinedOrThrow(workspace); - - const mappedExistingRecords = existingRecords.reduce( - (acc, { id, ...record }) => ({ - ...acc, - [id]: record, - }), - {}, - ); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: objectMetadataItem.nameSingular, - action: DatabaseEventAction.UPDATED, - events: records.map((record) => { - // @ts-expect-error legacy noImplicitAny - const before = mappedExistingRecords[record.id]; - const after = record; - const diff = objectRecordChangedValues( - before, - after, - updatedFields, - objectMetadataItem, - ); - - return { - userId: authContext.user?.id, - recordId: record.id, - objectMetadata: objectMetadataItem, - properties: { - before, - after, - updatedFields, - diff, - }, - }; - }), - workspaceId: workspace.id, - }); - } - - public emitDeletedEvents({ - records, - authContext, - objectMetadataItem, - }: { - records: T[]; - authContext: AuthContext; - objectMetadataItem: ObjectMetadataInterface; - }): void { - const workspace = authContext.workspace; - - workspaceValidator.assertIsDefinedOrThrow(workspace); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: objectMetadataItem.nameSingular, - action: DatabaseEventAction.DELETED, - events: records.map((record) => { - return { - userId: authContext.user?.id, - recordId: record.id, - objectMetadata: objectMetadataItem, - properties: { - before: record, - after: null, - }, - }; - }), - workspaceId: workspace.id, - }); - } - - public emitRestoreEvents({ - records, - authContext, - objectMetadataItem, - }: { - records: T[]; - authContext: AuthContext; - objectMetadataItem: ObjectMetadataInterface; - }): void { - const workspace = authContext.workspace; - - workspaceValidator.assertIsDefinedOrThrow(workspace); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: objectMetadataItem.nameSingular, - action: DatabaseEventAction.RESTORED, - events: records.map((record) => { - return { - userId: authContext.user?.id, - recordId: record.id, - objectMetadata: objectMetadataItem, - properties: { - before: null, - after: record, - }, - }; - }), - workspaceId: workspace.id, - }); - } - - public emitDestroyEvents({ - records, - authContext, - objectMetadataItem, - }: { - records: T[]; - authContext: AuthContext; - objectMetadataItem: ObjectMetadataInterface; - }): void { - const workspace = authContext.workspace; - - workspaceValidator.assertIsDefinedOrThrow(workspace); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: objectMetadataItem.nameSingular, - action: DatabaseEventAction.DESTROYED, - events: records.map((record) => { - return { - userId: authContext.user?.id, - recordId: record.id, - objectMetadata: objectMetadataItem, - properties: { - before: record, - after: null, - }, - }; - }), - workspaceId: workspace.id, - }); - } -} diff --git a/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-create-many.handler.ts b/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-create-many.handler.ts index 4a0c98cda..66c0a678b 100644 --- a/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-create-many.handler.ts +++ b/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-create-many.handler.ts @@ -9,8 +9,6 @@ import { isDefined } from 'twenty-shared/utils'; import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; - @Injectable() export class RestApiCreateManyHandler extends RestApiBaseHandler { async handle(request: Request) { @@ -60,14 +58,6 @@ export class RestApiCreateManyHandler extends RestApiBaseHandler { const createdRecords = await repository.save(recordsToCreate); - this.apiEventEmitterService.emitCreateEvents({ - records: createdRecords, - authContext: this.getAuthContextFromRequest(request), - objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps( - objectMetadata.objectMetadataMapItem, - ), - }); - const records = await this.getRecord({ recordIds: createdRecords.map((record) => record.id), repository, diff --git a/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-create-one.handler.ts b/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-create-one.handler.ts index d69db19dc..209af3642 100644 --- a/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-create-one.handler.ts +++ b/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-create-one.handler.ts @@ -9,8 +9,6 @@ import { isDefined } from 'twenty-shared/utils'; import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; - @Injectable() export class RestApiCreateOneHandler extends RestApiBaseHandler { async handle(request: Request) { @@ -43,14 +41,6 @@ export class RestApiCreateOneHandler extends RestApiBaseHandler { const createdRecord = await repository.save(recordToCreate); - this.apiEventEmitterService.emitCreateEvents({ - records: [createdRecord], - authContext: this.getAuthContextFromRequest(request), - objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps( - objectMetadata.objectMetadataMapItem, - ), - }); - const records = await this.getRecord({ recordIds: [createdRecord.id], repository, diff --git a/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-delete-one.handler.ts b/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-delete-one.handler.ts index 0b48778df..c0d95d2bc 100644 --- a/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-delete-one.handler.ts +++ b/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-delete-one.handler.ts @@ -5,7 +5,6 @@ import { Request } from 'express'; import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler'; import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; @Injectable() export class RestApiDeleteOneHandler extends RestApiBaseHandler { @@ -24,14 +23,6 @@ export class RestApiDeleteOneHandler extends RestApiBaseHandler { await repository.delete(recordId); - this.apiEventEmitterService.emitDestroyEvents({ - records: [recordToDelete], - authContext: this.getAuthContextFromRequest(request), - objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps( - objectMetadata.objectMetadataMapItem, - ), - }); - return this.formatResult({ operation: 'delete', objectNameSingular: objectMetadata.objectMetadataMapItem.nameSingular, diff --git a/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-find-duplicates.handler.ts b/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-find-duplicates.handler.ts index 550b4b183..4b9e1cc42 100644 --- a/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-find-duplicates.handler.ts +++ b/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-find-duplicates.handler.ts @@ -11,7 +11,6 @@ import { } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler'; import { buildDuplicateConditions } from 'src/engine/api/utils/build-duplicate-conditions.utils'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() export class RestApiFindDuplicatesHandler extends RestApiBaseHandler { @@ -28,15 +27,9 @@ export class RestApiFindDuplicatesHandler extends RestApiBaseHandler { let objectRecords: Partial[] = []; if (request.body.ids) { - const nonFormattedObjectRecords = (await existingRecordsQueryBuilder + objectRecords = (await existingRecordsQueryBuilder .where({ id: In(request.body.ids) }) .getMany()) as ObjectRecord[]; - - objectRecords = formatResult( - nonFormattedObjectRecords, - objectMetadataItemWithFieldsMaps, - objectMetadata.objectMetadataMaps, - ); } else if (request.body.data && !isEmpty(request.body.data)) { objectRecords = request.body.data; } diff --git a/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-update-one.handler.ts b/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-update-one.handler.ts index 47cef77e8..ed6fac428 100644 --- a/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-update-one.handler.ts +++ b/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-update-one.handler.ts @@ -10,7 +10,6 @@ import { isDefined } from 'twenty-shared/utils'; import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler'; import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; @Injectable() export class RestApiUpdateOneHandler extends RestApiBaseHandler { @@ -38,16 +37,6 @@ export class RestApiUpdateOneHandler extends RestApiBaseHandler { ...overriddenBody, }); - this.apiEventEmitterService.emitUpdateEvents({ - existingRecords: [recordToUpdate], - records: [updatedRecord], - updatedFields: Object.keys(request.body), - authContext: this.getAuthContextFromRequest(request), - objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps( - objectMetadata.objectMetadataMapItem, - ), - }); - const records = await this.getRecord({ recordIds: [updatedRecord.id], repository, diff --git a/packages/twenty-server/src/engine/api/rest/core/interfaces/rest-api-base.handler.ts b/packages/twenty-server/src/engine/api/rest/core/interfaces/rest-api-base.handler.ts index 02cfdd6f7..ae050362f 100644 --- a/packages/twenty-server/src/engine/api/rest/core/interfaces/rest-api-base.handler.ts +++ b/packages/twenty-server/src/engine/api/rest/core/interfaces/rest-api-base.handler.ts @@ -12,7 +12,6 @@ import { } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; -import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service'; import { encodeCursor } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util'; import { CoreQueryBuilderFactory } from 'src/engine/api/rest/core/query-builder/core-query-builder.factory'; import { GetVariablesFactory } from 'src/engine/api/rest/core/query-builder/factories/get-variables.factory'; @@ -80,8 +79,6 @@ export abstract class RestApiBaseHandler { @Inject() protected readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService; @Inject() - protected readonly apiEventEmitterService: ApiEventEmitterService; - @Inject() protected readonly createdByFromAuthContextService: CreatedByFromAuthContextService; protected abstract handle( diff --git a/packages/twenty-server/src/engine/api/rest/core/rest-api-core.module.ts b/packages/twenty-server/src/engine/api/rest/core/rest-api-core.module.ts index c2950dad4..3a8d26b2b 100644 --- a/packages/twenty-server/src/engine/api/rest/core/rest-api-core.module.ts +++ b/packages/twenty-server/src/engine/api/rest/core/rest-api-core.module.ts @@ -1,25 +1,24 @@ -import { Module } from '@nestjs/common'; import { HttpModule } from '@nestjs/axios'; +import { Module } from '@nestjs/common'; -import { RestApiDeleteOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-delete-one.handler'; -import { RestApiCreateOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-create-one.handler'; -import { RestApiUpdateOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-update-one.handler'; -import { RestApiFindOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-find-one.handler'; -import { RestApiFindManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-find-many.handler'; -import { CoreQueryBuilderModule } from 'src/engine/api/rest/core/query-builder/core-query-builder.module'; -import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; -import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module'; -import { RecordTransformerModule } from 'src/engine/core-modules/record-transformer/record-transformer.module'; -import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module'; import { RestApiCoreController } from 'src/engine/api/rest/core/controllers/rest-api-core.controller'; +import { RestApiCreateManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-create-many.handler'; +import { RestApiCreateOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-create-one.handler'; +import { RestApiDeleteOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-delete-one.handler'; +import { RestApiFindDuplicatesHandler } from 'src/engine/api/rest/core/handlers/rest-api-find-duplicates.handler'; +import { RestApiFindManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-find-many.handler'; +import { RestApiFindOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-find-one.handler'; +import { RestApiUpdateOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-update-one.handler'; +import { CoreQueryBuilderModule } from 'src/engine/api/rest/core/query-builder/core-query-builder.module'; import { coreQueryBuilderFactories } from 'src/engine/api/rest/core/query-builder/factories/factories'; import { RestApiCoreService } from 'src/engine/api/rest/core/services/rest-api-core.service'; import { RestApiService } from 'src/engine/api/rest/rest-api.service'; -import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service'; -import { AuthModule } from 'src/engine/core-modules/auth/auth.module'; -import { RestApiCreateManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-create-many.handler'; -import { RestApiFindDuplicatesHandler } from 'src/engine/api/rest/core/handlers/rest-api-find-duplicates.handler'; import { ActorModule } from 'src/engine/core-modules/actor/actor.module'; +import { AuthModule } from 'src/engine/core-modules/auth/auth.module'; +import { RecordTransformerModule } from 'src/engine/core-modules/record-transformer/record-transformer.module'; +import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module'; +import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module'; +import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; const restApiCoreResolvers = [ RestApiDeleteOneHandler, @@ -46,7 +45,6 @@ const restApiCoreResolvers = [ providers: [ RestApiService, RestApiCoreService, - ApiEventEmitterService, ...coreQueryBuilderFactories, ...restApiCoreResolvers, ], diff --git a/packages/twenty-server/src/engine/api/utils/build-duplicate-conditions.utils.ts b/packages/twenty-server/src/engine/api/utils/build-duplicate-conditions.utils.ts index 6f9485c31..0472cc1a4 100644 --- a/packages/twenty-server/src/engine/api/utils/build-duplicate-conditions.utils.ts +++ b/packages/twenty-server/src/engine/api/utils/build-duplicate-conditions.utils.ts @@ -7,8 +7,8 @@ import { import { settings } from 'src/engine/constants/settings'; import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; -import { getCompositeFieldMetadataMap } from 'src/engine/twenty-orm/utils/format-result.util'; import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; +import { getCompositeFieldMetadataMap } from 'src/engine/twenty-orm/utils/format-result.util'; export const buildDuplicateConditions = ( objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, diff --git a/packages/twenty-server/src/engine/core-modules/ai/services/tool.service.ts b/packages/twenty-server/src/engine/core-modules/ai/services/tool.service.ts index 736c7fee5..5b1c9f35b 100644 --- a/packages/twenty-server/src/engine/core-modules/ai/services/tool.service.ts +++ b/packages/twenty-server/src/engine/core-modules/ai/services/tool.service.ts @@ -13,7 +13,6 @@ import { Not, } from 'typeorm'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { generateBulkDeleteToolSchema, generateFindOneToolSchema, @@ -25,13 +24,11 @@ import { isWorkflowRelatedObject } from 'src/engine/metadata-modules/agent/utils import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; @Injectable() export class ToolService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, private readonly objectMetadataService: ObjectMetadataService, protected readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService, ) {} @@ -378,13 +375,6 @@ export class ToolService { const createdRecord = await repository.save(parameters); - await this.emitDatabaseEvent({ - objectName, - action: DatabaseEventAction.CREATED, - records: [createdRecord], - workspaceId, - }); - return { success: true, record: createdRecord, @@ -449,14 +439,6 @@ export class ToolService { }; } - await this.emitDatabaseEvent({ - objectName, - action: DatabaseEventAction.UPDATED, - records: [updatedRecord], - workspaceId, - beforeRecords: [existingRecord], - }); - return { success: true, record: updatedRecord, @@ -509,13 +491,6 @@ export class ToolService { await repository.softDelete(id); - await this.emitDatabaseEvent({ - objectName, - action: DatabaseEventAction.DELETED, - records: [existingRecord], - workspaceId, - }); - return { success: true, message: `Successfully soft deleted ${objectName}`, @@ -567,13 +542,6 @@ export class ToolService { await repository.remove(existingRecord); - await this.emitDatabaseEvent({ - objectName, - action: DatabaseEventAction.DESTROYED, - records: [existingRecord], - workspaceId, - }); - return { success: true, message: `Successfully destroyed ${objectName}`, @@ -636,13 +604,6 @@ export class ToolService { await repository.softDelete({ id: { in: recordIds } }); - await this.emitDatabaseEvent({ - objectName, - action: DatabaseEventAction.DELETED, - records: existingRecords, - workspaceId, - }); - return { success: true, count: existingRecords.length, @@ -706,13 +667,6 @@ export class ToolService { await repository.delete({ id: { in: recordIds } }); - await this.emitDatabaseEvent({ - objectName, - action: DatabaseEventAction.DESTROYED, - records: existingRecords, - workspaceId, - }); - return { success: true, count: existingRecords.length, @@ -726,53 +680,4 @@ export class ToolService { }; } } - - private async emitDatabaseEvent({ - objectName, - action, - records, - workspaceId, - beforeRecords, - }: { - objectName: string; - action: DatabaseEventAction; - records: Record[]; - workspaceId: string; - beforeRecords?: Record[]; - }) { - const objectMetadata = - await this.objectMetadataService.findOneWithinWorkspace(workspaceId, { - where: { - nameSingular: objectName, - isActive: true, - }, - relations: ['fields'], - }); - - if (!objectMetadata) { - return; - } - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: objectName, - action, - events: records.map((record) => { - const beforeRecord = beforeRecords?.find((r) => r.id === record.id); - - return { - recordId: record.id as string, - objectMetadata, - properties: { - before: beforeRecord || undefined, - after: - action === DatabaseEventAction.DELETED || - action === DatabaseEventAction.DESTROYED - ? undefined - : (record as Record), - }, - }; - }), - workspaceId, - }); - } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/create-calendar-channel.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/create-calendar-channel.service.ts index 56b83af3a..537d9f42c 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/create-calendar-channel.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/create-calendar-channel.service.ts @@ -1,14 +1,9 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; import { v4 } from 'uuid'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { CalendarChannelVisibility, CalendarChannelWorkspaceEntity, @@ -26,9 +21,6 @@ export type CreateCalendarChannelInput = { export class CreateCalendarChannelService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, ) {} async createCalendarChannel( @@ -60,26 +52,6 @@ export class CreateCalendarChannelService { manager, ); - const calendarChannelMetadata = - await this.objectMetadataRepository.findOneOrFail({ - where: { nameSingular: 'calendarChannel', workspaceId }, - }); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'calendarChannel', - action: DatabaseEventAction.CREATED, - events: [ - { - recordId: newCalendarChannel.id, - objectMetadata: calendarChannelMetadata, - properties: { - after: newCalendarChannel, - }, - }, - ], - workspaceId, - }); - return newCalendarChannel.id; } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/create-connected-account.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/create-connected-account.service.ts index fc3207a5d..3edc9c544 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/create-connected-account.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/create-connected-account.service.ts @@ -1,14 +1,9 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; import { ConnectedAccountProvider } from 'twenty-shared/types'; -import { Repository } from 'typeorm'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; export type CreateConnectedAccountInput = { @@ -27,9 +22,6 @@ export type CreateConnectedAccountInput = { export class CreateConnectedAccountService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, ) {} async createConnectedAccount( @@ -53,7 +45,7 @@ export class CreateConnectedAccountService { 'connectedAccount', ); - const newConnectedAccount = await connectedAccountRepository.save( + await connectedAccountRepository.save( { id: connectedAccountId, handle, @@ -66,25 +58,5 @@ export class CreateConnectedAccountService { {}, manager, ); - - const connectedAccountMetadata = - await this.objectMetadataRepository.findOneOrFail({ - where: { nameSingular: 'connectedAccount', workspaceId }, - }); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'connectedAccount', - action: DatabaseEventAction.CREATED, - events: [ - { - recordId: newConnectedAccount.id, - objectMetadata: connectedAccountMetadata, - properties: { - after: newConnectedAccount, - }, - }, - ], - workspaceId, - }); } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/create-message-channel.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/create-message-channel.service.ts index 763d6fa79..c936f8b9c 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/create-message-channel.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/create-message-channel.service.ts @@ -1,14 +1,9 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; import { v4 } from 'uuid'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { MessageChannelSyncStatus, MessageChannelType, @@ -28,9 +23,6 @@ export type CreateMessageChannelInput = { export class CreateMessageChannelService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, ) {} async createMessageChannel( @@ -64,26 +56,6 @@ export class CreateMessageChannelService { manager, ); - const messageChannelMetadata = - await this.objectMetadataRepository.findOneOrFail({ - where: { nameSingular: 'messageChannel', workspaceId }, - }); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'messageChannel', - action: DatabaseEventAction.CREATED, - events: [ - { - recordId: newMessageChannel.id, - objectMetadata: messageChannelMetadata, - properties: { - after: newMessageChannel, - }, - }, - ], - workspaceId, - }); - return newMessageChannel.id; } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.spec.ts index ce03dafd2..a623c7c9a 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.spec.ts @@ -16,7 +16,6 @@ import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-q import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { CalendarChannelSyncStage, CalendarChannelVisibility, @@ -161,12 +160,6 @@ describe('GoogleAPIsService', () => { removeAccountToReconnect: jest.fn(), }, }, - { - provide: WorkspaceEventEmitter, - useValue: { - emitDatabaseBatchEvent: jest.fn(), - }, - }, { provide: getQueueToken(MessageQueue.messagingQueue), useValue: mockMessageQueueService, diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.spec.ts index 952b389f2..4fd88632e 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.spec.ts @@ -17,7 +17,6 @@ import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-q import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { CalendarChannelSyncStage, CalendarChannelVisibility, @@ -166,12 +165,6 @@ describe('MicrosoftAPIsService', () => { removeAccountToReconnect: jest.fn(), }, }, - { - provide: WorkspaceEventEmitter, - useValue: { - emitDatabaseBatchEvent: jest.fn(), - }, - }, { provide: getQueueToken(MessageQueue.messagingQueue), useValue: mockMessageQueueService, diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/reset-calendar-channel.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/reset-calendar-channel.service.ts index 8cc780514..215d9fa68 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/reset-calendar-channel.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/reset-calendar-channel.service.ts @@ -1,13 +1,7 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; - -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { CalendarChannelSyncStage, CalendarChannelWorkspaceEntity, @@ -23,9 +17,6 @@ export type ResetCalendarChannelsInput = { export class ResetCalendarChannelService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, ) {} async resetCalendarChannels( @@ -39,11 +30,7 @@ export class ResetCalendarChannelService { 'calendarChannel', ); - const calendarChannels = await calendarChannelRepository.find({ - where: { connectedAccountId }, - }); - - const calendarChannelUpdates = await calendarChannelRepository.update( + await calendarChannelRepository.update( { connectedAccountId, }, @@ -57,28 +44,6 @@ export class ResetCalendarChannelService { manager, ); - const calendarChannelMetadata = - await this.objectMetadataRepository.findOneOrFail({ - where: { nameSingular: 'calendarChannel', workspaceId }, - }); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'calendarChannel', - action: DatabaseEventAction.UPDATED, - events: calendarChannels.map((calendarChannel) => ({ - recordId: calendarChannel.id, - objectMetadata: calendarChannelMetadata, - properties: { - before: calendarChannel, - after: { - ...calendarChannel, - ...calendarChannelUpdates.raw[0], - }, - }, - })), - workspaceId, - }); - return; } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/reset-message-channel.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/reset-message-channel.service.ts index 0601fa116..d51d8d79a 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/reset-message-channel.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/reset-message-channel.service.ts @@ -1,13 +1,7 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; - -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { MessageChannelSyncStage, MessageChannelSyncStatus, @@ -24,9 +18,6 @@ export type ResetMessageChannelsInput = { export class ResetMessageChannelService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, ) {} async resetMessageChannels(input: ResetMessageChannelsInput): Promise { @@ -38,11 +29,7 @@ export class ResetMessageChannelService { 'messageChannel', ); - const messageChannels = await messageChannelRepository.find({ - where: { connectedAccountId }, - }); - - const messageChannelUpdates = await messageChannelRepository.update( + await messageChannelRepository.update( { connectedAccountId, }, @@ -55,25 +42,6 @@ export class ResetMessageChannelService { manager, ); - const messageChannelMetadata = - await this.objectMetadataRepository.findOneOrFail({ - where: { nameSingular: 'messageChannel', workspaceId }, - }); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'messageChannel', - action: DatabaseEventAction.UPDATED, - events: messageChannels.map((messageChannel) => ({ - recordId: messageChannel.id, - objectMetadata: messageChannelMetadata, - properties: { - before: messageChannel, - after: { ...messageChannel, ...messageChannelUpdates.raw[0] }, - }, - })), - workspaceId, - }); - return; } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service.ts index 55f2ea32e..76c89bd61 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service.ts @@ -1,13 +1,7 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; - -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; export type UpdateConnectedAccountOnReconnectInput = { @@ -24,9 +18,6 @@ export type UpdateConnectedAccountOnReconnectInput = { export class UpdateConnectedAccountOnReconnectService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, ) {} async updateConnectedAccountOnReconnect( @@ -38,7 +29,6 @@ export class UpdateConnectedAccountOnReconnectService { accessToken, refreshToken, scopes, - connectedAccount, manager, } = input; @@ -48,7 +38,7 @@ export class UpdateConnectedAccountOnReconnectService { 'connectedAccount', ); - const updatedConnectedAccount = await connectedAccountRepository.update( + await connectedAccountRepository.update( { id: connectedAccountId, }, @@ -60,29 +50,5 @@ export class UpdateConnectedAccountOnReconnectService { }, manager, ); - - const connectedAccountMetadata = - await this.objectMetadataRepository.findOneOrFail({ - where: { nameSingular: 'connectedAccount', workspaceId }, - }); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'connectedAccount', - action: DatabaseEventAction.UPDATED, - events: [ - { - recordId: connectedAccountId, - objectMetadata: connectedAccountMetadata, - properties: { - before: connectedAccount, - after: { - ...connectedAccount, - ...updatedConnectedAccount.raw[0], - }, - }, - }, - ], - workspaceId, - }); } } diff --git a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/__tests__/object-record-changed-values.spec.ts b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/__tests__/object-record-changed-values.spec.ts index 775fc64b1..32a2c01e3 100644 --- a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/__tests__/object-record-changed-values.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/__tests__/object-record-changed-values.spec.ts @@ -1,8 +1,7 @@ -import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; - import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; -const mockObjectMetadata: ObjectMetadataInterface = { +const mockObjectMetadata: ObjectMetadataItemWithFieldMaps = { id: '1', icon: 'Icon123', nameSingular: 'Object', @@ -12,14 +11,16 @@ const mockObjectMetadata: ObjectMetadataInterface = { description: 'Test object metadata', targetTableName: 'test_table', workspaceId: '1', - fields: [], - indexMetadatas: [], + fieldsById: {}, + fieldIdByName: {}, isSystem: false, isCustom: false, isActive: true, isRemote: false, isAuditLogged: true, isSearchable: true, + indexMetadatas: [], + fieldIdByJoinColumnName: {}, }; describe('objectRecordChangedValues', () => { @@ -38,7 +39,6 @@ describe('objectRecordChangedValues', () => { const result = objectRecordChangedValues( oldRecord, newRecord, - ['name'], mockObjectMetadata, ); @@ -60,7 +60,6 @@ describe('objectRecordChangedValues', () => { const result = objectRecordChangedValues( oldRecord, newRecord, - [], mockObjectMetadata, ); @@ -82,7 +81,6 @@ describe('objectRecordChangedValues', () => { const result = objectRecordChangedValues( oldRecord, newRecord, - ['name', 'value'], mockObjectMetadata, ); @@ -112,7 +110,6 @@ describe('objectRecordChangedValues', () => { const result = objectRecordChangedValues( oldRecord, newRecord, - ['name', 'config', 'status'], mockObjectMetadata, ); diff --git a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-values.ts b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-values.ts index 81f85186d..044c75f59 100644 --- a/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-values.ts +++ b/packages/twenty-server/src/engine/core-modules/event-emitter/utils/object-record-changed-values.ts @@ -2,23 +2,25 @@ import deepEqual from 'deep-equal'; import { FieldMetadataType } from 'twenty-shared/types'; import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; -import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; + +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; export const objectRecordChangedValues = ( oldRecord: Partial, newRecord: Partial, - updatedKeys: string[] | undefined, - objectMetadataItem: ObjectMetadataInterface, + objectMetadataItem: ObjectMetadataItemWithFieldMaps, ) => { return Object.keys(newRecord).reduce( (acc, key) => { - const field = objectMetadataItem.fields.find((f) => f.name === key); + const field = + objectMetadataItem.fieldsById[objectMetadataItem.fieldIdByName[key]]; + const oldRecordValue = oldRecord[key]; const newRecordValue = newRecord[key]; if ( key === 'updatedAt' || - !updatedKeys?.includes(key) || + key === 'searchVector' || field?.type === FieldMetadataType.RELATION || deepEqual(oldRecordValue, newRecordValue) ) { diff --git a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts index 977bd8f66..95277c2ee 100644 --- a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts @@ -6,7 +6,6 @@ import { DataSource, Repository } from 'typeorm'; import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; import { TypeORMService } from 'src/database/typeorm/typeorm.service'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { ApprovedAccessDomain } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.entity'; import { ApprovedAccessDomainService } from 'src/engine/core-modules/approved-access-domain/services/approved-access-domain.service'; import { AuthException } from 'src/engine/core-modules/auth/auth.exception'; @@ -28,17 +27,14 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; describe('UserWorkspaceService', () => { let service: UserWorkspaceService; let userWorkspaceRepository: Repository; let userRepository: Repository; - let objectMetadataRepository: Repository; let typeORMService: TypeORMService; let workspaceInvitationService: WorkspaceInvitationService; - let workspaceEventEmitter: WorkspaceEventEmitter; let approvedAccessDomainService: ApprovedAccessDomainService; let twentyORMGlobalManager: TwentyORMGlobalManager; let userRoleService: UserRoleService; @@ -92,13 +88,6 @@ describe('UserWorkspaceService', () => { findInvitationsByEmail: jest.fn(), }, }, - { - provide: WorkspaceEventEmitter, - useValue: { - emitCustomBatchEvent: jest.fn(), - emitDatabaseBatchEvent: jest.fn(), - }, - }, { provide: DomainManagerService, useValue: { @@ -155,16 +144,10 @@ describe('UserWorkspaceService', () => { getRepositoryToken(UserWorkspace, 'core'), ); userRepository = module.get(getRepositoryToken(User, 'core')); - objectMetadataRepository = module.get( - getRepositoryToken(ObjectMetadataEntity, 'core'), - ); typeORMService = module.get(TypeORMService); workspaceInvitationService = module.get( WorkspaceInvitationService, ); - workspaceEventEmitter = module.get( - WorkspaceEventEmitter, - ); approvedAccessDomainService = module.get( ApprovedAccessDomainService, ); @@ -329,9 +312,6 @@ describe('UserWorkspaceService', () => { userEmail: 'test@example.com', }, ]; - const objectMetadata = { - nameSingular: 'workspaceMember', - } as ObjectMetadataEntity; const workspaceMemberRepository = { insert: jest.fn(), find: jest.fn().mockResolvedValue(workspaceMember), @@ -344,13 +324,6 @@ describe('UserWorkspaceService', () => { .spyOn(mainDataSource, 'query') .mockResolvedValueOnce(undefined) .mockResolvedValueOnce(workspaceMember); - jest - .spyOn(objectMetadataRepository, 'findOneOrFail') - .mockResolvedValue(objectMetadata); - jest - .spyOn(workspaceEventEmitter, 'emitDatabaseBatchEvent') - .mockImplementation(); - jest .spyOn(twentyORMGlobalManager, 'getRepositoryForWorkspace') .mockResolvedValue(workspaceMemberRepository as any); @@ -372,28 +345,6 @@ describe('UserWorkspaceService', () => { locale: 'en', avatarUrl: 'userWorkspace-avatar-url', }); - expect(objectMetadataRepository.findOneOrFail).toHaveBeenCalledWith({ - where: { - nameSingular: 'workspaceMember', - workspaceId, - }, - }); - expect(workspaceEventEmitter.emitDatabaseBatchEvent).toHaveBeenCalledWith( - { - objectMetadataNameSingular: 'workspaceMember', - action: DatabaseEventAction.CREATED, - events: [ - { - recordId: workspaceMember[0].id, - objectMetadata, - properties: { - after: workspaceMember[0], - }, - }, - ], - workspaceId, - }, - ); }); }); diff --git a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts index cf870bc4d..823839dea 100644 --- a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts @@ -9,7 +9,6 @@ import { IsNull, Not, Repository } from 'typeorm'; import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception'; import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity'; import { ApprovedAccessDomainService } from 'src/engine/core-modules/approved-access-domain/services/approved-access-domain.service'; import { @@ -27,7 +26,6 @@ import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-in import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { PermissionsException, PermissionsExceptionCode, @@ -35,7 +33,6 @@ import { } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; import { assert } from 'src/utils/assert'; import { getDomainNameByEmail } from 'src/utils/get-domain-name-by-email'; @@ -46,10 +43,7 @@ export class UserWorkspaceService extends TypeOrmQueryService { private readonly userWorkspaceRepository: Repository, @InjectRepository(User, 'core') private readonly userRepository: Repository, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, private readonly workspaceInvitationService: WorkspaceInvitationService, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, private readonly domainManagerService: DomainManagerService, private readonly loginTokenService: LoginTokenService, private readonly approvedAccessDomainService: ApprovedAccessDomainService, @@ -124,27 +118,6 @@ export class UserWorkspaceService extends TypeOrmQueryService { workspaceMember?.length === 1, `Error while creating workspace member ${user.email} on workspace ${workspaceId}`, ); - const objectMetadata = await this.objectMetadataRepository.findOneOrFail({ - where: { - nameSingular: 'workspaceMember', - workspaceId, - }, - }); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'workspaceMember', - action: DatabaseEventAction.CREATED, - events: [ - { - recordId: workspaceMember[0].id, - objectMetadata, - properties: { - after: workspaceMember[0], - }, - }, - ], - workspaceId, - }); } async addUserToWorkspaceIfUserNotInWorkspace( diff --git a/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts b/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts index 1e2ffc73f..bc4fe62cc 100644 --- a/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts +++ b/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts @@ -6,17 +6,14 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace'; import { IsNull, Not, Repository } from 'typeorm'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { AuthException, AuthExceptionCode, } from 'src/engine/core-modules/auth/auth.exception'; -import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; import { User } from 'src/engine/core-modules/user/user.entity'; import { userValidator } from 'src/engine/core-modules/user/user.validate'; import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { PermissionsException, PermissionsExceptionCode, @@ -24,7 +21,6 @@ import { } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; // eslint-disable-next-line @nx/workspace-inject-workspace-repository @@ -32,13 +28,9 @@ export class UserService extends TypeOrmQueryService { constructor( @InjectRepository(User, 'core') private readonly userRepository: Repository, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, private readonly workspaceService: WorkspaceService, private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly userRoleService: UserRoleService, - private readonly userWorkspaceService: UserWorkspaceService, ) { super(userRepository); } @@ -157,38 +149,14 @@ export class UserService extends TypeOrmQueryService { workspaceId, workspaceMemberRepository, workspaceMembers, - workspaceMember, }) => { await workspaceMemberRepository.delete({ userId }); - const objectMetadata = - await this.objectMetadataRepository.findOneOrFail({ - where: { - nameSingular: 'workspaceMember', - workspaceId, - }, - }); - if (workspaceMembers.length === 1) { await this.workspaceService.deleteWorkspace(workspaceId); return; } - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'workspaceMember', - action: DatabaseEventAction.DELETED, - events: [ - { - recordId: workspaceMember.id, - objectMetadata, - properties: { - before: workspaceMember, - }, - }, - ], - workspaceId, - }); }, ), ); diff --git a/packages/twenty-server/src/engine/twenty-orm/datasource/workspace.datasource.ts b/packages/twenty-server/src/engine/twenty-orm/datasource/workspace.datasource.ts index 82e5d63ba..8fc5b94cc 100644 --- a/packages/twenty-server/src/engine/twenty-orm/datasource/workspace.datasource.ts +++ b/packages/twenty-server/src/engine/twenty-orm/datasource/workspace.datasource.ts @@ -15,6 +15,7 @@ import { EntityManagerFactory } from 'typeorm/entity-manager/EntityManagerFactor import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; +import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { PermissionsException, PermissionsExceptionCode, @@ -58,20 +59,29 @@ export class WorkspaceDataSource extends DataSource { target: EntityTarget, shouldBypassPermissionChecks = false, roleId?: string, + authContext?: AuthContext, ): WorkspaceRepository { if (shouldBypassPermissionChecks === true) { - return this.manager.getRepository(target, { - shouldBypassPermissionChecks: true, - }); + return this.manager.getRepository( + target, + { + shouldBypassPermissionChecks: true, + }, + authContext, + ); } if (roleId) { - return this.manager.getRepository(target, { - roleId, - }); + return this.manager.getRepository( + target, + { + roleId, + }, + authContext, + ); } - return this.manager.getRepository(target); + return this.manager.getRepository(target, undefined, authContext); } override createEntityManager( diff --git a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts index f6f06df7f..2f5b1836c 100644 --- a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts +++ b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts @@ -55,8 +55,64 @@ describe('WorkspaceEntityManager', () => { mockInternalContext = { workspaceId: 'test-workspace-id', objectMetadataMaps: { - idByNameSingular: {}, + byId: { + 'test-entity-id': { + id: 'test-entity-id', + nameSingular: 'test-entity', + namePlural: 'test-entities', + labelSingular: 'Test Entity', + labelPlural: 'Test Entities', + workspaceId: 'test-workspace-id', + icon: 'test-icon', + color: 'test-color', + isCustom: false, + isRemote: false, + isAuditLogged: false, + isSearchable: false, + isSystem: false, + isActive: true, + targetTableName: 'test_entity', + indexMetadatas: [], + fieldsById: { + 'field-id': { + id: 'field-id', + type: 'TEXT', + name: 'fieldName', + label: 'Field Name', + objectMetadataId: 'test-entity-id', + isNullable: true, + isLabelSyncedWithName: false, + createdAt: new Date(), + updatedAt: new Date(), + }, + }, + fieldIdByName: { fieldName: 'field-id' }, + fieldIdByJoinColumnName: {}, + }, + }, + idByNameSingular: { + 'test-entity': 'test-entity-id', + }, }, + featureFlagsMap: { + IS_AIRTABLE_INTEGRATION_ENABLED: false, + IS_POSTGRESQL_INTEGRATION_ENABLED: false, + IS_STRIPE_INTEGRATION_ENABLED: false, + IS_UNIQUE_INDEXES_ENABLED: false, + IS_JSON_FILTER_ENABLED: false, + IS_AI_ENABLED: false, + IS_IMAP_SMTP_CALDAV_ENABLED: false, + IS_MORPH_RELATION_ENABLED: false, + IS_WORKFLOW_FILTERING_ENABLED: false, + IS_RELATION_CONNECT_ENABLED: false, + IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED: false, + IS_FIELDS_PERMISSIONS_ENABLED: false, + }, + eventEmitterService: { + emitMutationEvent: jest.fn(), + emitDatabaseBatchEvent: jest.fn(), + emitCustomBatchEvent: jest.fn(), + } as any, } as WorkspaceInternalContext; mockDataSource = { diff --git a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.ts b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.ts index e16e2a833..6c43711e4 100644 --- a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.ts +++ b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.ts @@ -8,6 +8,7 @@ import { FindManyOptions, FindOneOptions, FindOptionsWhere, + In, InsertResult, ObjectId, ObjectLiteral, @@ -32,6 +33,8 @@ import { InstanceChecker } from 'typeorm/util/InstanceChecker'; import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; +import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; +import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { PermissionsException, PermissionsExceptionCode, @@ -51,6 +54,8 @@ import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/wo import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { computeRelationConnectQueryConfigs } from 'src/engine/twenty-orm/utils/compute-relation-connect-query-configs.util'; import { createSqlWhereTupleInClause } from 'src/engine/twenty-orm/utils/create-sql-where-tuple-in-clause.utils'; +import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util'; import { getRecordToConnectFields } from 'src/engine/twenty-orm/utils/get-record-to-connect-fields.util'; @@ -85,22 +90,10 @@ export class WorkspaceEntityManager extends EntityManager { shouldBypassPermissionChecks?: boolean; roleId?: string; }, + authContext?: AuthContext, ): WorkspaceRepository { const dataSource = this.connection; - const repositoryKey = this.getRepositoryKey({ - target, - dataSource, - roleId: permissionOptions?.roleId, - shouldBypassPermissionChecks: - permissionOptions?.shouldBypassPermissionChecks ?? false, - }); - const repoFromMap = this.repositories.get(repositoryKey); - - if (repoFromMap) { - return repoFromMap as WorkspaceRepository; - } - let objectPermissions = {}; if (permissionOptions?.roleId) { @@ -128,10 +121,9 @@ export class WorkspaceEntityManager extends EntityManager { this.queryRunner, objectPermissions, permissionOptions?.shouldBypassPermissionChecks, + authContext, ); - this.repositories.set(repositoryKey, newRepository); - return newRepository; } @@ -360,32 +352,6 @@ export class WorkspaceEntityManager extends EntityManager { .execute(); } - private getRepositoryKey({ - target, - dataSource, - roleId, - shouldBypassPermissionChecks, - }: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - target: EntityTarget; - dataSource: WorkspaceDataSource; - shouldBypassPermissionChecks: boolean; - roleId?: string; - }) { - const repositoryPrefix = dataSource.getMetadata(target).name; - const roleIdSuffix = roleId ? `_${roleId}` : ''; - const rolesPermissionsVersionSuffix = dataSource.rolesPermissionsVersion - ? `_${dataSource.rolesPermissionsVersion}` - : ''; - const featureFlagMapVersionSuffix = dataSource.featureFlagMapVersion - ? `_${dataSource.featureFlagMapVersion}` - : ''; - - return shouldBypassPermissionChecks - ? `${repositoryPrefix}_bypass${featureFlagMapVersionSuffix}` - : `${repositoryPrefix}${roleIdSuffix}${rolesPermissionsVersionSuffix}${featureFlagMapVersionSuffix}`; - } - validatePermissions( target: EntityTarget | Entity, operationType: OperationType, @@ -900,6 +866,13 @@ export class WorkspaceEntityManager extends EntityManager { entityLike: DeepPartial, permissionOptions?: PermissionOptions, ): Promise { + const objectMetadataItem = getObjectMetadataFromEntityTarget( + entityClass, + this.internalContext, + ); + + const formattedEntityLike = formatData(entityLike, objectMetadataItem); + const managerWithPermissionOptions = Object.assign( Object.create(Object.getPrototypeOf(this)), this, @@ -915,12 +888,16 @@ export class WorkspaceEntityManager extends EntityManager { new PlainObjectToDatabaseEntityTransformer(managerWithPermissionOptions); const transformedEntity = await plainObjectToDatabaseEntityTransformer.transform( - entityLike, + formattedEntityLike, metadata, ); if (transformedEntity) - return this.merge(entityClass, transformedEntity, entityLike) as Entity; + return this.merge( + entityClass, + transformedEntity, + formattedEntityLike, + ) as Entity; return undefined; } @@ -1075,17 +1052,81 @@ export class WorkspaceEntityManager extends EntityManager { const queryRunnerForEntityPersistExecutor = this.connection.createQueryRunnerForEntityPersistExecutor(); - return new EntityPersistExecutor( + const isEntityArray = Array.isArray(entity); + const entityTarget = + target ?? (isEntityArray ? entity[0]?.constructor : entity.constructor); + + const entityArray = isEntityArray ? entity : [entity]; + const entityIds = entityArray.map((e) => (e as { id: string }).id); + const beforeUpdate = await this.find( + entityTarget, + { + where: { id: In(entityIds) }, + }, + permissionOptions, + ); + + const beforeUpdateMapById = beforeUpdate.reduce( + (acc, e: ObjectLiteral) => { + acc[e.id] = e; + + return acc; + }, + {} as Record, + ); + + const objectMetadataItem = getObjectMetadataFromEntityTarget( + entityTarget, + this.internalContext, + ); + + const formattedEntityOrEntities = formatData( + entityArray, + objectMetadataItem, + ); + + const result = await new EntityPersistExecutor( this.connection, queryRunnerForEntityPersistExecutor, 'save', target, - entity as ObjectLiteral, + formattedEntityOrEntities as ObjectLiteral[], options as SaveOptions | (SaveOptions & { reload: false }), ) .execute() - .then(() => entity as Entity) + .then(() => formattedEntityOrEntities as Entity[]) .finally(() => queryRunnerForEntityPersistExecutor.release()); + + const resultArray = Array.isArray(result) ? result : [result]; + + const formattedResult = formatResult( + resultArray, + objectMetadataItem, + this.internalContext.objectMetadataMaps, + ); + + for (const entity of formattedResult) { + const isUpdate = beforeUpdateMapById[entity.id]; + + if (isUpdate) { + await this.internalContext.eventEmitterService.emitMutationEvent({ + action: DatabaseEventAction.UPDATED, + objectMetadataItem, + workspaceId: this.internalContext.workspaceId, + entities: [entity], + beforeEntities: beforeUpdateMapById[entity.id], + }); + } else { + await this.internalContext.eventEmitterService.emitMutationEvent({ + action: DatabaseEventAction.CREATED, + objectMetadataItem, + workspaceId: this.internalContext.workspaceId, + entities: [entity], + }); + } + } + + return isEntityArray ? formattedResult : formattedResult[0]; } override remove( @@ -1145,23 +1186,49 @@ export class WorkspaceEntityManager extends EntityManager { ? maybeOptionsOrMaybePermissionOptions : entityOrMaybeOptions; - if (Array.isArray(entity) && entity.length === 0) - return Promise.resolve(entity); + const isEntityArray = Array.isArray(entity); + + if (isEntityArray && entity.length === 0) return Promise.resolve(entity); const queryRunnerForEntityPersistExecutor = this.connection.createQueryRunnerForEntityPersistExecutor(); - return new EntityPersistExecutor( + const entityTarget = + target ?? (isEntityArray ? entity[0]?.constructor : entity.constructor); + + const objectMetadataItem = getObjectMetadataFromEntityTarget( + entityTarget, + this.internalContext, + ); + + const formattedEntity = formatData(entity, objectMetadataItem); + + const result = new EntityPersistExecutor( this.connection, queryRunnerForEntityPersistExecutor, 'remove', target as string | undefined, - entity as ObjectLiteral, + formattedEntity as ObjectLiteral, options as RemoveOptions, ) .execute() - .then(() => entity as Entity | Entity[]) + .then(() => formattedEntity as Entity | Entity[]) .finally(() => queryRunnerForEntityPersistExecutor.release()); + + const formattedResult = formatResult( + result, + objectMetadataItem, + this.internalContext.objectMetadataMaps, + ); + + await this.internalContext.eventEmitterService.emitMutationEvent({ + action: DatabaseEventAction.DESTROYED, + objectMetadataItem, + workspaceId: this.internalContext.workspaceId, + entities: formattedResult, + }); + + return isEntityArray ? formattedResult : formattedResult[0]; } override softRemove( @@ -1237,17 +1304,43 @@ export class WorkspaceEntityManager extends EntityManager { const queryRunnerForEntityPersistExecutor = this.connection.createQueryRunnerForEntityPersistExecutor(); - return new EntityPersistExecutor( + const isEntityArray = Array.isArray(entity); + const entityTarget = + target ?? (isEntityArray ? entity[0]?.constructor : entity.constructor); + + const objectMetadataItem = getObjectMetadataFromEntityTarget( + entityTarget, + this.internalContext, + ); + + const formattedEntity = formatData(entity, objectMetadataItem); + + const result = new EntityPersistExecutor( this.connection, queryRunnerForEntityPersistExecutor, 'soft-remove', target, - entity as ObjectLiteral, + formattedEntity as ObjectLiteral, options as SaveOptions, ) .execute() - .then(() => entity as Entity) + .then(() => formattedEntity as Entity) .finally(() => queryRunnerForEntityPersistExecutor.release()); + + const formattedResult = formatResult( + result, + objectMetadataItem, + this.internalContext.objectMetadataMaps, + ); + + await this.internalContext.eventEmitterService.emitMutationEvent({ + action: DatabaseEventAction.DELETED, + objectMetadataItem, + workspaceId: this.internalContext.workspaceId, + entities: formattedResult, + }); + + return isEntityArray ? formattedResult : formattedResult[0]; } override recover( @@ -1313,23 +1406,49 @@ export class WorkspaceEntityManager extends EntityManager { : entityOrEntitiesOrMaybeOptions; if (InstanceChecker.isEntitySchema(target)) target = target.options.name; - if (Array.isArray(entity) && entity.length === 0) - return Promise.resolve(entity); + const isEntityArray = Array.isArray(entity); + + if (isEntityArray && entity.length === 0) return Promise.resolve(entity); const queryRunnerForEntityPersistExecutor = this.connection.createQueryRunnerForEntityPersistExecutor(); - return new EntityPersistExecutor( + const entityTarget = + target ?? (isEntityArray ? entity[0]?.constructor : entity.constructor); + + const objectMetadataItem = getObjectMetadataFromEntityTarget( + entityTarget, + this.internalContext, + ); + + const formattedEntity = formatData(entity, objectMetadataItem); + + const result = new EntityPersistExecutor( this.connection, queryRunnerForEntityPersistExecutor, 'recover', target, - entity as ObjectLiteral, + formattedEntity as ObjectLiteral, options as SaveOptions, ) .execute() - .then(() => entity as Entity) + .then(() => formattedEntity as Entity) .finally(() => queryRunnerForEntityPersistExecutor.release()); + + const formattedResult = formatResult( + result, + objectMetadataItem, + this.internalContext.objectMetadataMaps, + ); + + await this.internalContext.eventEmitterService.emitMutationEvent({ + action: DatabaseEventAction.RESTORED, + objectMetadataItem, + workspaceId: this.internalContext.workspaceId, + entities: formattedResult, + }); + + return isEntityArray ? formattedResult : formattedResult[0]; } // Forbidden methods diff --git a/packages/twenty-server/src/engine/twenty-orm/exceptions/twenty-orm.exception.ts b/packages/twenty-server/src/engine/twenty-orm/exceptions/twenty-orm.exception.ts index d9e313240..681444377 100644 --- a/packages/twenty-server/src/engine/twenty-orm/exceptions/twenty-orm.exception.ts +++ b/packages/twenty-server/src/engine/twenty-orm/exceptions/twenty-orm.exception.ts @@ -21,4 +21,6 @@ export enum TwentyORMExceptionCode { CONNECT_RECORD_NOT_FOUND = 'CONNECT_RECORD_NOT_FOUND', CONNECT_NOT_ALLOWED = 'CONNECT_NOT_ALLOWED', CONNECT_UNIQUE_CONSTRAINT_ERROR = 'CONNECT_UNIQUE_CONSTRAINT_ERROR', + MISSING_MAIN_ALIAS_TARGET = 'MISSING_MAIN_ALIAS_TARGET', + METHOD_NOT_ALLOWED = 'METHOD_NOT_ALLOWED', } diff --git a/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts b/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts index 3b2e7a045..8b6f20d3c 100644 --- a/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts +++ b/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts @@ -27,6 +27,7 @@ import { PromiseMemoizer } from 'src/engine/twenty-orm/storage/promise-memoizer. import { CacheKey } from 'src/engine/twenty-orm/storage/types/cache-key.type'; import { getFromCacheWithRecompute } from 'src/engine/utils/get-data-from-cache-with-recompute.util'; import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; +import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; type CacheResult = { version: T; @@ -51,6 +52,7 @@ export class WorkspaceDatasourceFactory { private readonly workspaceFeatureFlagsMapCacheService: WorkspaceFeatureFlagsMapCacheService, @InjectRepository(Workspace, 'core') private readonly workspaceRepository: Repository, + private readonly workspaceEventEmitter: WorkspaceEventEmitter, ) {} private async conditionalDestroyDataSource( @@ -192,6 +194,7 @@ export class WorkspaceDatasourceFactory { workspaceId, objectMetadataMaps: cachedObjectMetadataMaps, featureFlagsMap: cachedFeatureFlagMap, + eventEmitterService: this.workspaceEventEmitter, }, { url: diff --git a/packages/twenty-server/src/engine/twenty-orm/interfaces/workspace-internal-context.interface.ts b/packages/twenty-server/src/engine/twenty-orm/interfaces/workspace-internal-context.interface.ts index 55042285d..3899b5c5e 100644 --- a/packages/twenty-server/src/engine/twenty-orm/interfaces/workspace-internal-context.interface.ts +++ b/packages/twenty-server/src/engine/twenty-orm/interfaces/workspace-internal-context.interface.ts @@ -1,8 +1,10 @@ import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; +import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; export interface WorkspaceInternalContext { workspaceId: string; objectMetadataMaps: ObjectMetadataMaps; featureFlagsMap: Record; + eventEmitterService: WorkspaceEventEmitter; } diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.spec.ts b/packages/twenty-server/src/engine/twenty-orm/repository/__tests__/workspace.repository.spec.ts similarity index 95% rename from packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.spec.ts rename to packages/twenty-server/src/engine/twenty-orm/repository/__tests__/workspace.repository.spec.ts index 7d9f5c565..0034295c9 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.spec.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/__tests__/workspace.repository.spec.ts @@ -97,24 +97,20 @@ describe('WorkspaceRepository', () => { id: 'test-metadata-id', nameSingular: 'test-entity', namePlural: 'test-entities', - fields: [], + fieldIdByName: { + id: 'test-field-id', + }, + fieldIdByJoinColumnName: {}, + fieldsById: { + 'test-field-id': { + id: 'test-field-id', + name: 'id', + type: 'string', + isNullable: false, + isUnique: true, + }, + }, }); - - jest.spyOn(repository as any, 'formatData').mockImplementation((data) => { - if (Array.isArray(data)) { - return data.map((item) => Object.assign({}, item)); - } - - return Object.assign({}, data); - }); - - jest.spyOn(repository as any, 'formatResult').mockImplementation((data) => { - if (Array.isArray(data)) { - return data.map((item) => Object.assign({}, item)); - } - - return Object.assign({}, data); - }); }); describe('Find Methods', () => { @@ -239,7 +235,7 @@ describe('WorkspaceRepository', () => { it('should delegate to workspaceEntityManager delete', async () => { const criteria: FindOptionsWhere = { id: 'test-id' }; - const expectedResult = { affected: 1, raw: [] }; + const expectedResult = { affected: 1, raw: [], generatedMaps: [] }; mockEntityManager.delete.mockResolvedValue(expectedResult); diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-delete-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-delete-query-builder.ts index 46dd60c2b..670e193aa 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-delete-query-builder.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-delete-query-builder.ts @@ -2,6 +2,7 @@ import { ObjectRecordsPermissions } from 'twenty-shared/types'; import { DeleteQueryBuilder, DeleteResult, + EntityTarget, InsertQueryBuilder, ObjectLiteral, } from 'typeorm'; @@ -9,10 +10,18 @@ import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; +import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; +import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { + TwentyORMException, + TwentyORMExceptionCode, +} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception'; import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils'; import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder'; import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; +import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util'; export class WorkspaceDeleteQueryBuilder< T extends ObjectLiteral, @@ -20,16 +29,19 @@ export class WorkspaceDeleteQueryBuilder< private objectRecordsPermissions: ObjectRecordsPermissions; private shouldBypassPermissionChecks: boolean; private internalContext: WorkspaceInternalContext; + private authContext?: AuthContext; constructor( queryBuilder: DeleteQueryBuilder, objectRecordsPermissions: ObjectRecordsPermissions, internalContext: WorkspaceInternalContext, shouldBypassPermissionChecks: boolean, + authContext?: AuthContext, ) { super(queryBuilder); this.objectRecordsPermissions = objectRecordsPermissions; this.internalContext = internalContext; this.shouldBypassPermissionChecks = shouldBypassPermissionChecks; + this.authContext = authContext; } override clone(): this { @@ -40,10 +52,11 @@ export class WorkspaceDeleteQueryBuilder< this.objectRecordsPermissions, this.internalContext, this.shouldBypassPermissionChecks, + this.authContext, ) as this; } - override execute(): Promise { + override async execute(): Promise { validateQueryIsPermittedOrThrow( this.expressionMap, this.objectRecordsPermissions, @@ -51,11 +64,54 @@ export class WorkspaceDeleteQueryBuilder< this.shouldBypassPermissionChecks, ); - return super.execute(); + const mainAliasTarget = this.getMainAliasTarget(); + + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); + + const result = await super.execute(); + + const formattedResult = formatResult( + result.raw, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + + await this.internalContext.eventEmitterService.emitMutationEvent({ + action: DatabaseEventAction.DESTROYED, + objectMetadataItem: objectMetadata, + workspaceId: this.internalContext.workspaceId, + entities: formattedResult, + authContext: this.authContext, + }); + + return { + raw: result.raw, + generatedMaps: formattedResult, + affected: result.affected, + }; + } + + private getMainAliasTarget(): EntityTarget { + const mainAliasTarget = this.expressionMap.mainAlias?.target; + + if (!mainAliasTarget) { + throw new TwentyORMException( + 'Main alias target is missing', + TwentyORMExceptionCode.MISSING_MAIN_ALIAS_TARGET, + ); + } + + return mainAliasTarget; } override select(): WorkspaceSelectQueryBuilder { - throw new Error('This builder cannot morph into a select builder'); + throw new TwentyORMException( + 'This builder cannot morph into a select builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); } override update(): WorkspaceUpdateQueryBuilder; @@ -67,18 +123,30 @@ export class WorkspaceDeleteQueryBuilder< override update( _updateSet?: QueryDeepPartialEntity, ): WorkspaceUpdateQueryBuilder { - throw new Error('This builder cannot morph into an update builder'); + throw new TwentyORMException( + 'This builder cannot morph into an update builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); } override insert(): InsertQueryBuilder { - throw new Error('This builder cannot morph into an insert builder'); + throw new TwentyORMException( + 'This builder cannot morph into an insert builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); } override softDelete(): WorkspaceSoftDeleteQueryBuilder { - throw new Error('This builder cannot morph into a soft delete builder'); + throw new TwentyORMException( + 'This builder cannot morph into a soft delete builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); } override restore(): WorkspaceSoftDeleteQueryBuilder { - throw new Error('This builder cannot morph into a soft delete builder'); + throw new TwentyORMException( + 'This builder cannot morph into a soft delete builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); } } diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts index 62b2f8928..7cf737d1e 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts @@ -1,13 +1,28 @@ import { ObjectRecordsPermissions } from 'twenty-shared/types'; -import { InsertQueryBuilder, ObjectLiteral } from 'typeorm'; +import { + EntityTarget, + InsertQueryBuilder, + InsertResult, + ObjectLiteral, +} from 'typeorm'; +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; +import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; +import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { + TwentyORMException, + TwentyORMExceptionCode, +} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception'; import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils'; import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder'; import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder'; import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder'; +import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; +import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util'; export class WorkspaceInsertQueryBuilder< T extends ObjectLiteral, @@ -15,17 +30,20 @@ export class WorkspaceInsertQueryBuilder< private objectRecordsPermissions: ObjectRecordsPermissions; private shouldBypassPermissionChecks: boolean; private internalContext: WorkspaceInternalContext; + private authContext?: AuthContext; constructor( queryBuilder: InsertQueryBuilder, objectRecordsPermissions: ObjectRecordsPermissions, internalContext: WorkspaceInternalContext, shouldBypassPermissionChecks: boolean, + authContext?: AuthContext, ) { super(queryBuilder); this.objectRecordsPermissions = objectRecordsPermissions; this.internalContext = internalContext; this.shouldBypassPermissionChecks = shouldBypassPermissionChecks; + this.authContext = authContext; } override clone(): this { @@ -36,11 +54,26 @@ export class WorkspaceInsertQueryBuilder< this.objectRecordsPermissions, this.internalContext, this.shouldBypassPermissionChecks, + this.authContext, ) as this; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - override execute(): Promise { + override values( + values: QueryDeepPartialEntity | QueryDeepPartialEntity[], + ): this { + const mainAliasTarget = this.getMainAliasTarget(); + + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); + + const formattedValues = formatData(values, objectMetadata); + + return super.values(formattedValues); + } + + override async execute(): Promise { validateQueryIsPermittedOrThrow( this.expressionMap, this.objectRecordsPermissions, @@ -48,26 +81,81 @@ export class WorkspaceInsertQueryBuilder< this.shouldBypassPermissionChecks, ); - return super.execute(); + const mainAliasTarget = this.getMainAliasTarget(); + + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); + + const result = await super.execute(); + + const formattedResult = formatResult( + result.raw, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + + await this.internalContext.eventEmitterService.emitMutationEvent({ + action: DatabaseEventAction.CREATED, + objectMetadataItem: objectMetadata, + workspaceId: this.internalContext.workspaceId, + entities: formattedResult, + authContext: this.authContext, + }); + + return { + raw: result.raw, + generatedMaps: formattedResult, + identifiers: result.identifiers, + }; + } + + private getMainAliasTarget(): EntityTarget { + const mainAliasTarget = this.expressionMap.mainAlias?.target; + + if (!mainAliasTarget) { + throw new TwentyORMException( + 'Main alias target is missing', + TwentyORMExceptionCode.MISSING_MAIN_ALIAS_TARGET, + ); + } + + return mainAliasTarget; } override select(): WorkspaceSelectQueryBuilder { - throw new Error('This builder cannot morph into a select builder'); + throw new TwentyORMException( + 'This builder cannot morph into a select builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); } override update(): WorkspaceUpdateQueryBuilder { - throw new Error('This builder cannot morph into an update builder'); + throw new TwentyORMException( + 'This builder cannot morph into an update builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); } override delete(): WorkspaceDeleteQueryBuilder { - throw new Error('This builder cannot morph into a delete builder'); + throw new TwentyORMException( + 'This builder cannot morph into a delete builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); } override softDelete(): WorkspaceSoftDeleteQueryBuilder { - throw new Error('This builder cannot morph into a soft delete builder'); + throw new TwentyORMException( + 'This builder cannot morph into a soft delete builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); } override restore(): WorkspaceSoftDeleteQueryBuilder { - throw new Error('This builder cannot morph into a soft delete builder'); + throw new TwentyORMException( + 'This builder cannot morph into a soft delete builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); } } diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts index 5273a1e3d..13a4c3f9b 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts @@ -1,18 +1,25 @@ import { ObjectRecordsPermissions } from 'twenty-shared/types'; -import { ObjectLiteral, SelectQueryBuilder } from 'typeorm'; +import { EntityTarget, ObjectLiteral, SelectQueryBuilder } from 'typeorm'; import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; +import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { PermissionsException, PermissionsExceptionCode, } from 'src/engine/metadata-modules/permissions/permissions.exception'; +import { + TwentyORMException, + TwentyORMExceptionCode, +} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception'; import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils'; import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder'; import { WorkspaceInsertQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-insert-query-builder'; import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder'; import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; +import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util'; export class WorkspaceSelectQueryBuilder< T extends ObjectLiteral, @@ -20,16 +27,19 @@ export class WorkspaceSelectQueryBuilder< objectRecordsPermissions: ObjectRecordsPermissions; shouldBypassPermissionChecks: boolean; internalContext: WorkspaceInternalContext; + authContext?: AuthContext; constructor( queryBuilder: SelectQueryBuilder, objectRecordsPermissions: ObjectRecordsPermissions, internalContext: WorkspaceInternalContext, shouldBypassPermissionChecks: boolean, + authContext?: AuthContext, ) { super(queryBuilder); this.objectRecordsPermissions = objectRecordsPermissions; this.internalContext = internalContext; this.shouldBypassPermissionChecks = shouldBypassPermissionChecks; + this.authContext = authContext; } getFindOptions() { @@ -44,19 +54,55 @@ export class WorkspaceSelectQueryBuilder< this.objectRecordsPermissions, this.internalContext, this.shouldBypassPermissionChecks, + this.authContext, ) as this; } - override execute(): Promise { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + override async execute(): Promise { this.validatePermissions(); - return super.execute(); + const mainAliasTarget = this.getMainAliasTarget(); + + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); + + const result = await super.execute(); + + const formattedResult = formatResult( + result, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + + return { + raw: result, + generatedMaps: formattedResult, + identifiers: result.identifiers, + }; } - override getMany(): Promise { + override async getMany(): Promise { this.validatePermissions(); - return super.getMany(); + const mainAliasTarget = this.getMainAliasTarget(); + + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); + + const result = await super.getMany(); + + const formattedResult = formatResult( + result, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + + return formattedResult; } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -73,16 +119,46 @@ export class WorkspaceSelectQueryBuilder< return super.getRawMany(); } - override getOne(): Promise { + override async getOne(): Promise { this.validatePermissions(); - return super.getOne(); + const mainAliasTarget = this.getMainAliasTarget(); + + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); + + const result = await super.getOne(); + + const formattedResult = formatResult( + result, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + + return formattedResult; } - override getOneOrFail(): Promise { + override async getOneOrFail(): Promise { this.validatePermissions(); - return super.getOneOrFail(); + const mainAliasTarget = this.getMainAliasTarget(); + + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); + + const result = await super.getOneOrFail(); + + const formattedResult = formatResult( + result, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + + return formattedResult[0]; } override getCount(): Promise { @@ -98,10 +174,25 @@ export class WorkspaceSelectQueryBuilder< ); } - override getManyAndCount(): Promise<[T[], number]> { + override async getManyAndCount(): Promise<[T[], number]> { this.validatePermissions(); - return super.getManyAndCount(); + const mainAliasTarget = this.getMainAliasTarget(); + + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); + + const [result, count] = await super.getManyAndCount(); + + const formattedResult = formatResult( + result, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + + return [formattedResult, count]; } override insert(): WorkspaceInsertQueryBuilder { @@ -112,6 +203,7 @@ export class WorkspaceSelectQueryBuilder< this.objectRecordsPermissions, this.internalContext, this.shouldBypassPermissionChecks, + this.authContext, ); } @@ -133,6 +225,7 @@ export class WorkspaceSelectQueryBuilder< this.objectRecordsPermissions, this.internalContext, this.shouldBypassPermissionChecks, + this.authContext, ); } @@ -144,6 +237,7 @@ export class WorkspaceSelectQueryBuilder< this.objectRecordsPermissions, this.internalContext, this.shouldBypassPermissionChecks, + this.authContext, ); } @@ -155,6 +249,7 @@ export class WorkspaceSelectQueryBuilder< this.objectRecordsPermissions, this.internalContext, this.shouldBypassPermissionChecks, + this.authContext, ); } @@ -166,6 +261,7 @@ export class WorkspaceSelectQueryBuilder< this.objectRecordsPermissions, this.internalContext, this.shouldBypassPermissionChecks, + this.authContext, ); } @@ -184,4 +280,17 @@ export class WorkspaceSelectQueryBuilder< this.shouldBypassPermissionChecks, ); } + + private getMainAliasTarget(): EntityTarget { + const mainAliasTarget = this.expressionMap.mainAlias?.target; + + if (!mainAliasTarget) { + throw new TwentyORMException( + 'Main alias target is missing', + TwentyORMExceptionCode.MISSING_MAIN_ALIAS_TARGET, + ); + } + + return mainAliasTarget; + } } diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-soft-delete-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-soft-delete-query-builder.ts index 4be700055..6b9c3543c 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-soft-delete-query-builder.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-soft-delete-query-builder.ts @@ -1,13 +1,26 @@ import { ObjectRecordsPermissions } from 'twenty-shared/types'; -import { InsertQueryBuilder, ObjectLiteral, UpdateResult } from 'typeorm'; +import { + EntityTarget, + InsertQueryBuilder, + ObjectLiteral, + UpdateResult, +} from 'typeorm'; import { SoftDeleteQueryBuilder } from 'typeorm/query-builder/SoftDeleteQueryBuilder'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; +import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; +import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { + TwentyORMException, + TwentyORMExceptionCode, +} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception'; import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils'; import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder'; import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; +import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util'; export class WorkspaceSoftDeleteQueryBuilder< T extends ObjectLiteral, @@ -15,17 +28,20 @@ export class WorkspaceSoftDeleteQueryBuilder< private objectRecordsPermissions: ObjectRecordsPermissions; private shouldBypassPermissionChecks: boolean; private internalContext: WorkspaceInternalContext; + private authContext?: AuthContext; constructor( queryBuilder: SoftDeleteQueryBuilder, objectRecordsPermissions: ObjectRecordsPermissions, internalContext: WorkspaceInternalContext, shouldBypassPermissionChecks: boolean, + authContext?: AuthContext, ) { super(queryBuilder); this.objectRecordsPermissions = objectRecordsPermissions; this.internalContext = internalContext; this.shouldBypassPermissionChecks = shouldBypassPermissionChecks; + this.authContext = authContext; } override clone(): this { @@ -36,10 +52,11 @@ export class WorkspaceSoftDeleteQueryBuilder< this.objectRecordsPermissions, this.internalContext, this.shouldBypassPermissionChecks, + this.authContext, ) as this; } - override execute(): Promise { + override async execute(): Promise { validateQueryIsPermittedOrThrow( this.expressionMap, this.objectRecordsPermissions, @@ -47,22 +64,74 @@ export class WorkspaceSoftDeleteQueryBuilder< this.shouldBypassPermissionChecks, ); - return super.execute(); + const mainAliasTarget = this.getMainAliasTarget(); + + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); + + const after = await super.execute(); + + const formattedAfter = formatResult( + after.raw, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + + await this.internalContext.eventEmitterService.emitMutationEvent({ + action: DatabaseEventAction.DELETED, + objectMetadataItem: objectMetadata, + workspaceId: this.internalContext.workspaceId, + entities: formattedAfter, + authContext: this.authContext, + }); + + return { + raw: after.raw, + generatedMaps: formattedAfter, + affected: after.affected, + }; } override select(): WorkspaceSelectQueryBuilder { - throw new Error('This builder cannot morph into a select builder'); + throw new TwentyORMException( + 'This builder cannot morph into a select builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); } override update(): WorkspaceUpdateQueryBuilder { - throw new Error('This builder cannot morph into an update builder'); + throw new TwentyORMException( + 'This builder cannot morph into an update builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); } override insert(): InsertQueryBuilder { - throw new Error('This builder cannot morph into an insert builder'); + throw new TwentyORMException( + 'This builder cannot morph into an insert builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); } override delete(): WorkspaceDeleteQueryBuilder { - throw new Error('This builder cannot morph into a delete builder'); + throw new TwentyORMException( + 'This builder cannot morph into a delete builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); + } + + private getMainAliasTarget(): EntityTarget { + const mainAliasTarget = this.expressionMap.mainAlias?.target; + + if (!mainAliasTarget) { + throw new TwentyORMException( + 'Main alias target is missing', + TwentyORMExceptionCode.MISSING_MAIN_ALIAS_TARGET, + ); + } + + return mainAliasTarget; } } diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-update-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-update-query-builder.ts index 558511269..439c26183 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-update-query-builder.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-update-query-builder.ts @@ -1,12 +1,27 @@ import { ObjectRecordsPermissions } from 'twenty-shared/types'; -import { ObjectLiteral, UpdateQueryBuilder, UpdateResult } from 'typeorm'; +import { + EntityTarget, + ObjectLiteral, + UpdateQueryBuilder, + UpdateResult, +} from 'typeorm'; +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; +import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; +import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { + TwentyORMException, + TwentyORMExceptionCode, +} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception'; import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils'; import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder'; import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder'; +import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; +import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util'; export class WorkspaceUpdateQueryBuilder< T extends ObjectLiteral, @@ -14,16 +29,19 @@ export class WorkspaceUpdateQueryBuilder< private objectRecordsPermissions: ObjectRecordsPermissions; private shouldBypassPermissionChecks: boolean; private internalContext: WorkspaceInternalContext; + private authContext?: AuthContext; constructor( queryBuilder: UpdateQueryBuilder, objectRecordsPermissions: ObjectRecordsPermissions, internalContext: WorkspaceInternalContext, shouldBypassPermissionChecks: boolean, + authContext?: AuthContext, ) { super(queryBuilder); this.objectRecordsPermissions = objectRecordsPermissions; this.internalContext = internalContext; this.shouldBypassPermissionChecks = shouldBypassPermissionChecks; + this.authContext = authContext; } override clone(): this { @@ -34,10 +52,11 @@ export class WorkspaceUpdateQueryBuilder< this.objectRecordsPermissions, this.internalContext, this.shouldBypassPermissionChecks, + this.authContext, ) as this; } - override execute(): Promise { + override async execute(): Promise { validateQueryIsPermittedOrThrow( this.expressionMap, this.objectRecordsPermissions, @@ -45,22 +64,108 @@ export class WorkspaceUpdateQueryBuilder< this.shouldBypassPermissionChecks, ); - return super.execute(); + const mainAliasTarget = this.getMainAliasTarget(); + + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); + + const beforeSelectQueryBuilder = new WorkspaceSelectQueryBuilder( + this as unknown as WorkspaceSelectQueryBuilder, + this.objectRecordsPermissions, + this.internalContext, + this.shouldBypassPermissionChecks, + this.authContext, + ); + + beforeSelectQueryBuilder.expressionMap.wheres = this.expressionMap.wheres; + beforeSelectQueryBuilder.expressionMap.aliases = this.expressionMap.aliases; + beforeSelectQueryBuilder.setParameters(this.getParameters()); + + const before = await beforeSelectQueryBuilder.getMany(); + + const formattedBefore = formatResult( + before, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + + const after = await super.execute(); + + const formattedAfter = formatResult( + after.raw, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + + await this.internalContext.eventEmitterService.emitMutationEvent({ + action: DatabaseEventAction.UPDATED, + objectMetadataItem: objectMetadata, + workspaceId: this.internalContext.workspaceId, + entities: formattedAfter, + beforeEntities: formattedBefore, + authContext: this.authContext, + }); + + return { + raw: after.raw, + generatedMaps: formattedAfter, + affected: after.affected, + }; + } + + override set(_values: QueryDeepPartialEntity): this { + const mainAliasTarget = this.getMainAliasTarget(); + + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); + + const formattedUpdateSet = formatData(_values, objectMetadata); + + return super.set(formattedUpdateSet); } override select(): WorkspaceSelectQueryBuilder { - throw new Error('This builder cannot morph into a select builder'); + throw new TwentyORMException( + 'This builder cannot morph into a select builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); } override delete(): WorkspaceDeleteQueryBuilder { - throw new Error('This builder cannot morph into a delete builder'); + throw new TwentyORMException( + 'This builder cannot morph into a delete builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); } override softDelete(): WorkspaceSoftDeleteQueryBuilder { - throw new Error('This builder cannot morph into a soft delete builder'); + throw new TwentyORMException( + 'This builder cannot morph into a soft delete builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); } override restore(): WorkspaceSoftDeleteQueryBuilder { - throw new Error('This builder cannot morph into a soft delete builder'); + throw new TwentyORMException( + 'This builder cannot morph into a soft delete builder', + TwentyORMExceptionCode.METHOD_NOT_ALLOWED, + ); + } + + private getMainAliasTarget(): EntityTarget { + const mainAliasTarget = this.expressionMap.mainAlias?.target; + + if (!mainAliasTarget) { + throw new TwentyORMException( + 'Main alias target is missing', + TwentyORMExceptionCode.MISSING_MAIN_ALIAS_TARGET, + ); + } + + return mainAliasTarget; } } diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts index bb8a51c4f..9c5c20ec0 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts @@ -22,16 +22,15 @@ import { UpsertOptions } from 'typeorm/repository/UpsertOptions'; import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; +import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { PermissionsException, PermissionsExceptionCode, } from 'src/engine/metadata-modules/permissions/permissions.exception'; -import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { QueryDeepPartialEntityWithRelationConnect } from 'src/engine/twenty-orm/entity-manager/types/query-deep-partial-entity-with-relation-connect.type'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util'; export class WorkspaceRepository< @@ -41,6 +40,7 @@ export class WorkspaceRepository< private shouldBypassPermissionChecks: boolean; private featureFlagMap: FeatureFlagMap; private objectRecordsPermissions?: ObjectRecordsPermissions; + private authContext?: AuthContext; declare manager: WorkspaceEntityManager; constructor( @@ -51,6 +51,7 @@ export class WorkspaceRepository< queryRunner?: QueryRunner, objectRecordsPermissions?: ObjectRecordsPermissions, shouldBypassPermissionChecks = false, + authContext?: AuthContext, ) { super(target, manager, queryRunner); this.internalContext = internalContext; @@ -58,6 +59,7 @@ export class WorkspaceRepository< this.objectRecordsPermissions = objectRecordsPermissions; this.shouldBypassPermissionChecks = shouldBypassPermissionChecks; this.manager = manager; + this.authContext = authContext; } override createQueryBuilder( @@ -78,6 +80,7 @@ export class WorkspaceRepository< this.objectRecordsPermissions, this.internalContext, this.shouldBypassPermissionChecks, + this.authContext, ); } @@ -99,9 +102,8 @@ export class WorkspaceRepository< computedOptions, permissionOptions, ); - const formattedResult = await this.formatResult(result); - return formattedResult; + return result; } override async findBy( @@ -119,9 +121,8 @@ export class WorkspaceRepository< computedOptions.where, permissionOptions, ); - const formattedResult = await this.formatResult(result); - return formattedResult; + return result; } override async findAndCount( @@ -139,9 +140,8 @@ export class WorkspaceRepository< computedOptions, permissionOptions, ); - const formattedResult = await this.formatResult(result); - return formattedResult; + return result; } override async findAndCountBy( @@ -159,9 +159,8 @@ export class WorkspaceRepository< computedOptions.where, permissionOptions, ); - const formattedResult = await this.formatResult(result); - return formattedResult; + return result; } override async findOne( @@ -179,9 +178,8 @@ export class WorkspaceRepository< computedOptions, permissionOptions, ); - const formattedResult = await this.formatResult(result); - return formattedResult; + return result; } override async findOneBy( @@ -199,9 +197,8 @@ export class WorkspaceRepository< computedOptions.where, permissionOptions, ); - const formattedResult = await this.formatResult(result); - return formattedResult; + return result; } override async findOneOrFail( @@ -219,9 +216,8 @@ export class WorkspaceRepository< computedOptions, permissionOptions, ); - const formattedResult = await this.formatResult(result); - return formattedResult; + return result; } override async findOneByOrFail( @@ -239,9 +235,8 @@ export class WorkspaceRepository< computedOptions.where, permissionOptions, ); - const formattedResult = await this.formatResult(result); - return formattedResult; + return result; } /** @@ -277,7 +272,6 @@ export class WorkspaceRepository< entityManager?: WorkspaceEntityManager, ): Promise { const manager = entityManager || this.manager; - const formattedEntityOrEntities = await this.formatData(entityOrEntities); let result: U | U[]; const permissionOptions = { @@ -286,25 +280,23 @@ export class WorkspaceRepository< }; // Needed because save method has multiple signature, otherwise we will need to do a type assertion - if (Array.isArray(formattedEntityOrEntities)) { + if (Array.isArray(entityOrEntities)) { result = await manager.save( this.target, - formattedEntityOrEntities, + entityOrEntities, options, permissionOptions, ); } else { result = await manager.save( this.target, - formattedEntityOrEntities, + entityOrEntities, options, permissionOptions, ); } - const formattedResult = await this.formatResult(result); - - return formattedResult; + return result; } /** @@ -328,21 +320,18 @@ export class WorkspaceRepository< entityManager?: WorkspaceEntityManager, ): Promise { const manager = entityManager || this.manager; - const formattedEntityOrEntities = await this.formatData(entityOrEntities); const permissionOptions = { shouldBypassPermissionChecks: this.shouldBypassPermissionChecks, objectRecordsPermissions: this.objectRecordsPermissions, }; const result = await manager.remove( this.target, - formattedEntityOrEntities, + entityOrEntities, options, permissionOptions, ); - const formattedResult = await this.formatResult(result); - - return formattedResult; + return result; } override async delete( @@ -402,7 +391,6 @@ export class WorkspaceRepository< entityManager?: WorkspaceEntityManager, ): Promise { const manager = entityManager || this.manager; - const formattedEntityOrEntities = await this.formatData(entityOrEntities); const permissionOptions = { shouldBypassPermissionChecks: this.shouldBypassPermissionChecks, objectRecordsPermissions: this.objectRecordsPermissions, @@ -410,25 +398,23 @@ export class WorkspaceRepository< let result: U | U[]; // Needed because save method has multiple signature, otherwise we will need to do a type assertion - if (Array.isArray(formattedEntityOrEntities)) { + if (Array.isArray(entityOrEntities)) { result = await manager.softRemove( this.target, - formattedEntityOrEntities, + entityOrEntities, options, permissionOptions, ); } else { result = await manager.softRemove( this.target, - formattedEntityOrEntities, + entityOrEntities, options, permissionOptions, ); } - const formattedResult = await this.formatResult(result); - - return formattedResult; + return result; } override async softDelete( @@ -491,7 +477,6 @@ export class WorkspaceRepository< entityManager?: WorkspaceEntityManager, ): Promise { const manager = entityManager || this.manager; - const formattedEntityOrEntities = await this.formatData(entityOrEntities); const permissionOptions = { shouldBypassPermissionChecks: this.shouldBypassPermissionChecks, objectRecordsPermissions: this.objectRecordsPermissions, @@ -499,25 +484,23 @@ export class WorkspaceRepository< let result: U | U[]; // Needed because save method has multiple signature, otherwise we will need to do a type assertion - if (Array.isArray(formattedEntityOrEntities)) { + if (Array.isArray(entityOrEntities)) { result = await manager.recover( this.target, - formattedEntityOrEntities, + entityOrEntities, options, permissionOptions, ); } else { result = await manager.recover( this.target, - formattedEntityOrEntities, + entityOrEntities, options, permissionOptions, ); } - const formattedResult = await this.formatResult(result); - - return formattedResult; + return result; } override async restore( @@ -558,23 +541,12 @@ export class WorkspaceRepository< ): Promise { const manager = entityManager || this.manager; - const formattedEntity = await this.formatData(entity); const permissionOptions = { shouldBypassPermissionChecks: this.shouldBypassPermissionChecks, objectRecordsPermissions: this.objectRecordsPermissions, }; - const result = await manager.insert( - this.target, - formattedEntity, - permissionOptions, - ); - const formattedResult = await this.formatResult(result.generatedMaps); - return { - raw: result.raw, - generatedMaps: formattedResult, - identifiers: result.identifiers, - }; + return manager.insert(this.target, entity, permissionOptions); } /** @@ -620,8 +592,6 @@ export class WorkspaceRepository< ): Promise { const manager = entityManager || this.manager; - const formattedEntityOrEntities = await this.formatData(entityOrEntities); - const permissionOptions = { shouldBypassPermissionChecks: this.shouldBypassPermissionChecks, objectRecordsPermissions: this.objectRecordsPermissions, @@ -629,16 +599,14 @@ export class WorkspaceRepository< const result = await manager.upsert( this.target, - formattedEntityOrEntities, + entityOrEntities, conflictPathsOrOptions, permissionOptions, ); - const formattedResult = await this.formatResult(result.generatedMaps); - return { raw: result.raw, - generatedMaps: formattedResult, + generatedMaps: result.generatedMaps, identifiers: result.identifiers, }; } @@ -862,13 +830,12 @@ export class WorkspaceRepository< entityManager?: WorkspaceEntityManager, ): Promise { const manager = entityManager || this.manager; - const formattedEntityLike = await this.formatData(entityLike); const permissionOptions = { shouldBypassPermissionChecks: this.shouldBypassPermissionChecks, objectRecordsPermissions: this.objectRecordsPermissions, }; - return manager.preload(this.target, formattedEntityLike, permissionOptions); + return manager.preload(this.target, entityLike, permissionOptions); } /** @@ -940,15 +907,4 @@ export class WorkspaceRepository< return formatData(data, objectMetadata) as T; } - - async formatResult( - data: T, - objectMetadata?: ObjectMetadataItemWithFieldMaps, - ): Promise { - objectMetadata ??= await this.getObjectMetadataFromTarget(); - - const objectMetadataMaps = this.internalContext.objectMetadataMaps; - - return formatResult(data, objectMetadata, objectMetadataMaps) as T; - } } diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util.ts index 10730c5f5..5d0f9b19d 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util.ts @@ -2,6 +2,7 @@ import { EntitySchema, EntityTarget, ObjectLiteral } from 'typeorm'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util'; import { TwentyORMException, @@ -12,7 +13,7 @@ import { WorkspaceEntitiesStorage } from 'src/engine/twenty-orm/storage/workspac export const getObjectMetadataFromEntityTarget = ( entityTarget: EntityTarget, internalContext: WorkspaceInternalContext, -) => { +): ObjectMetadataItemWithFieldMaps => { const objectMetadataName = typeof entityTarget === 'string' ? entityTarget diff --git a/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.ts b/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.ts index 3dc9ea0da..96b693f8f 100644 --- a/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.ts +++ b/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.ts @@ -1,12 +1,18 @@ import { Injectable } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; +import { ObjectLiteral } from 'typeorm'; + import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; +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 { 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 { ObjectRecordDiff } from 'src/engine/core-modules/event-emitter/types/object-record-diff'; import { ObjectRecordRestoreEvent } from 'src/engine/core-modules/event-emitter/types/object-record-restore.event'; import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event'; +import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { CustomEventName } from 'src/engine/workspace-event-emitter/types/custom-event-name.type'; type ActionEventMap = { @@ -21,6 +27,105 @@ type ActionEventMap = { export class WorkspaceEventEmitter { constructor(private readonly eventEmitter: EventEmitter2) {} + async emitMutationEvent({ + action, + objectMetadataItem, + workspaceId, + authContext, + entities, + beforeEntities, + }: { + action: DatabaseEventAction; + objectMetadataItem: ObjectMetadataItemWithFieldMaps; + workspaceId: string; + authContext?: AuthContext; + entities: T | T[]; + beforeEntities?: T | T[]; + }) { + const objectMetadataNameSingular = objectMetadataItem.nameSingular; + const fields = Object.values(objectMetadataItem.fieldsById ?? {}); + const entityArray = Array.isArray(entities) ? entities : [entities]; + let events: ( + | ObjectRecordCreateEvent + | ObjectRecordUpdateEvent + | ObjectRecordDeleteEvent + )[] = []; + + switch (action) { + case DatabaseEventAction.CREATED: + events = entityArray.map((after) => { + const event = new ObjectRecordCreateEvent(); + + event.userId = authContext?.user?.id; + event.recordId = after.id; + event.objectMetadata = { ...objectMetadataItem, fields }; + event.properties = { after }; + + return event; + }); + break; + case DatabaseEventAction.UPDATED: + events = entityArray.map((after, idx) => { + if (!beforeEntities) { + throw new Error('beforeEntities is required for UPDATED action'); + } + + const before = Array.isArray(beforeEntities) + ? beforeEntities?.[idx] + : beforeEntities; + + const diff = objectRecordChangedValues( + before, + after, + objectMetadataItem, + ) as Partial>; + + const updatedFields = Object.keys(diff); + + const event = new ObjectRecordUpdateEvent(); + + event.userId = authContext?.user?.id; + event.recordId = after.id; + event.objectMetadata = { ...objectMetadataItem, fields }; + event.properties = { + before, + after, + updatedFields, + diff, + }; + + return event; + }); + break; + case DatabaseEventAction.DELETED: + events = entityArray.map((before) => { + const event = new ObjectRecordDeleteEvent(); + + event.userId = authContext?.user?.id; + event.recordId = before.id; + event.objectMetadata = { ...objectMetadataItem, fields }; + event.properties = { before }; + + return event; + }); + break; + default: + return; + } + + if (!events.length) { + return; + } + + const eventName = `${objectMetadataNameSingular}.${action}`; + + this.eventEmitter.emit(eventName, { + name: eventName, + workspaceId, + events, + }); + } + public emitDatabaseBatchEvent>({ objectMetadataNameSingular, action, diff --git a/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts b/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts index 0dd550c5b..354c31349 100644 --- a/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts +++ b/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts @@ -8,11 +8,11 @@ import { DeleteOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; 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 { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; @WorkspaceQueryHook(`connectedAccount.destroyOne`) export class ConnectedAccountDeleteOnePreQueryHook @@ -52,6 +52,7 @@ export class ConnectedAccountDeleteOnePreQueryHook }, }); + // TODO: handle cascade events for delete this.workspaceEventEmitter.emitDatabaseBatchEvent({ objectMetadataNameSingular: 'messageChannel', action: DatabaseEventAction.DESTROYED, diff --git a/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts b/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts index 7166d0031..65d46b5c4 100644 --- a/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts +++ b/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts @@ -1,18 +1,14 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; import { ConnectedAccountProvider } from 'twenty-shared/types'; import { v4 } from 'uuid'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { EmailAccountConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto'; 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 { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { CalendarEventListFetchJob, CalendarEventListFetchJobData, @@ -42,9 +38,6 @@ export class ImapSmtpCalDavAPIService { private readonly messageQueueService: MessageQueueService, @InjectMessageQueue(MessageQueue.calendarQueue) private readonly calendarQueueService: MessageQueueService, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: WorkspaceRepository, ) {} async setupCompleteAccount(input: { @@ -97,7 +90,6 @@ export class ImapSmtpCalDavAPIService { await this.upsertConnectedAccount( input, accountId, - existingAccount, connectedAccountRepository, ); @@ -116,7 +108,6 @@ export class ImapSmtpCalDavAPIService { await this.enqueueSyncJobs( input, - accountId, workspaceId, createdMessageChannel, createdCalendarChannel, @@ -131,7 +122,6 @@ export class ImapSmtpCalDavAPIService { connectionParameters: EmailAccountConnectionParameters; }, accountId: string, - existingAccount: ConnectedAccountWorkspaceEntity | null, connectedAccountRepository: WorkspaceRepository, ) { const accountData = { @@ -142,48 +132,7 @@ export class ImapSmtpCalDavAPIService { accountOwnerId: input.workspaceMemberId, }; - const savedAccount = await connectedAccountRepository.save(accountData, {}); - - const connectedAccountMetadata = - await this.objectMetadataRepository.findOneOrFail({ - where: { - nameSingular: 'connectedAccount', - workspaceId: input.workspaceId, - }, - }); - - if (existingAccount) { - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'connectedAccount', - action: DatabaseEventAction.UPDATED, - events: [ - { - recordId: savedAccount.id, - objectMetadata: connectedAccountMetadata, - properties: { - before: existingAccount, - after: savedAccount, - }, - }, - ], - workspaceId: input.workspaceId, - }); - } else { - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'connectedAccount', - action: DatabaseEventAction.CREATED, - events: [ - { - recordId: savedAccount.id, - objectMetadata: connectedAccountMetadata, - properties: { - after: savedAccount, - }, - }, - ], - workspaceId: input.workspaceId, - }); - } + await connectedAccountRepository.save(accountData, {}); } private async setupMessageChannels( @@ -226,29 +175,6 @@ export class ImapSmtpCalDavAPIService { {}, ); - const messageChannelMetadata = - await this.objectMetadataRepository.findOneOrFail({ - where: { - nameSingular: 'messageChannel', - workspaceId: input.workspaceId, - }, - }); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'messageChannel', - action: DatabaseEventAction.CREATED, - events: [ - { - recordId: newMessageChannel.id, - objectMetadata: messageChannelMetadata, - properties: { - after: newMessageChannel, - }, - }, - ], - workspaceId: input.workspaceId, - }); - return shouldEnableSync ? newMessageChannel : null; } @@ -289,29 +215,6 @@ export class ImapSmtpCalDavAPIService { {}, ); - const calendarChannelMetadata = - await this.objectMetadataRepository.findOneOrFail({ - where: { - nameSingular: 'calendarChannel', - workspaceId: input.workspaceId, - }, - }); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'calendarChannel', - action: DatabaseEventAction.CREATED, - events: [ - { - recordId: newCalendarChannel.id, - objectMetadata: calendarChannelMetadata, - properties: { - after: newCalendarChannel, - }, - }, - ], - workspaceId: input.workspaceId, - }); - return newCalendarChannel; } @@ -322,7 +225,6 @@ export class ImapSmtpCalDavAPIService { input: { connectionParameters: EmailAccountConnectionParameters; }, - accountId: string, workspaceId: string, messageChannel: MessageChannelWorkspaceEntity | null, calendarChannel: CalendarChannelWorkspaceEntity | null, diff --git a/packages/twenty-server/src/modules/contact-creation-manager/services/__tests__/create-company.service.spec.ts b/packages/twenty-server/src/modules/contact-creation-manager/services/__tests__/create-company.service.spec.ts index 10abef3a8..153ca890b 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/services/__tests__/create-company.service.spec.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/services/__tests__/create-company.service.spec.ts @@ -6,7 +6,6 @@ import { ConnectedAccountProvider } from 'twenty-shared/types'; import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; import { CompanyToCreate, @@ -103,12 +102,6 @@ describe('CreateCompanyService', () => { .mockResolvedValue(mockCompanyRepository), }, }, - { - provide: WorkspaceEventEmitter, - useValue: { - emitDatabaseBatchEvent: jest.fn(), - }, - }, { provide: getRepositoryToken(ObjectMetadataEntity, 'core'), useValue: { diff --git a/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts b/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts index 9cc38920d..08df8d923 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts @@ -1,18 +1,13 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; import { isNonEmptyString } from '@sniptt/guards'; import chunk from 'lodash.chunk'; import compact from 'lodash.compact'; -import { DeepPartial, Repository } from 'typeorm'; +import { DeepPartial } from 'typeorm'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; -import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { CONTACTS_CREATION_BATCH_SIZE } from 'src/modules/contact-creation-manager/constants/contacts-creation-batch-size.constant'; import { CreateCompanyService } from 'src/modules/contact-creation-manager/services/create-company.service'; @@ -31,9 +26,6 @@ export class CreateCompanyAndContactService { constructor( private readonly createContactService: CreateContactService, private readonly createCompaniesService: CreateCompanyService, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly exceptionHandlerService: ExceptionHandlerService, ) {} @@ -85,14 +77,10 @@ export class CreateCompanyAndContactService { emails: uniqueHandles, }); - const rawAlreadyCreatedContacts = await queryBuilder + const alreadyCreatedContacts = await queryBuilder .orderBy('person.createdAt', 'ASC') .getMany(); - const alreadyCreatedContacts = await personRepository.formatResult( - rawAlreadyCreatedContacts, - ); - const alreadyCreatedContactEmails: string[] = alreadyCreatedContacts?.reduce((acc, { emails }) => { const currentContactEmails: string[] = []; @@ -190,19 +178,6 @@ export class CreateCompanyAndContactService { CONTACTS_CREATION_BATCH_SIZE, ); - // TODO: Remove this when events are emitted directly inside TwentyORM - - const objectMetadata = await this.objectMetadataRepository.findOne({ - where: { - standardId: STANDARD_OBJECT_IDS.person, - workspaceId, - }, - }); - - if (!objectMetadata) { - throw new Error('Object metadata not found'); - } - // In some jobs the accountOwner is not populated if (!connectedAccount.accountOwner) { const workspaceMemberRepository = @@ -228,26 +203,12 @@ export class CreateCompanyAndContactService { for (const contactsBatch of contactsBatches) { try { - const createdPeople = await this.createCompaniesAndPeople( + await this.createCompaniesAndPeople( connectedAccount, contactsBatch, workspaceId, source, ); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'person', - action: DatabaseEventAction.CREATED, - events: createdPeople.map((createdPerson) => ({ - // Fix ' as string': TypeORM typing issue... id is always returned when using save - recordId: createdPerson.id as string, - objectMetadata, - properties: { - after: createdPerson, - }, - })), - workspaceId, - }); } catch (error) { this.exceptionHandlerService.captureExceptions([error], { workspace: { diff --git a/packages/twenty-server/src/modules/contact-creation-manager/services/create-company.service.ts b/packages/twenty-server/src/modules/contact-creation-manager/services/create-company.service.ts index d45152c59..6b2a3811c 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/services/create-company.service.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/services/create-company.service.ts @@ -1,20 +1,15 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; import axios, { AxiosInstance } from 'axios'; import uniqBy from 'lodash.uniqby'; import { TWENTY_COMPANIES_BASE_URL } from 'twenty-shared/constants'; import { ConnectedAccountProvider } from 'twenty-shared/types'; import { lowercaseUrlOriginAndRemoveTrailingSlash } from 'twenty-shared/utils'; -import { DeepPartial, ILike, Repository } from 'typeorm'; +import { DeepPartial, ILike } from 'typeorm'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; -import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity'; import { extractDomainFromLink } from 'src/modules/contact-creation-manager/utils/extract-domain-from-link.util'; import { getCompanyNameFromDomainName } from 'src/modules/contact-creation-manager/utils/get-company-name-from-domain-name.util'; @@ -34,12 +29,7 @@ export type CompanyToCreate = { export class CreateCompanyService { private readonly httpService: AxiosInstance; - constructor( - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, - ) { + constructor(private readonly twentyORMGlobalManager: TwentyORMGlobalManager) { this.httpService = axios.create({ baseURL: TWENTY_COMPANIES_BASE_URL, }); @@ -55,17 +45,6 @@ export class CreateCompanyService { return {}; } - const objectMetadata = await this.objectMetadataRepository.findOne({ - where: { - standardId: STANDARD_OBJECT_IDS.company, - workspaceId, - }, - }); - - if (!objectMetadata) { - throw new Error('Object metadata not found'); - } - const companyRepository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspaceId, @@ -124,19 +103,6 @@ export class CreateCompanyService { // Create new companies const createdCompanies = await companyRepository.save(newCompaniesData); - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'company', - action: DatabaseEventAction.CREATED, - events: createdCompanies.map((createdCompany) => ({ - recordId: createdCompany.id, - objectMetadata, - properties: { - after: createdCompany, - }, - })), - workspaceId, - }); - const createdCompanyIdsMap = this.createCompanyMap(createdCompanies); return { diff --git a/packages/twenty-server/src/modules/match-participant/match-participant.service.spec.ts b/packages/twenty-server/src/modules/match-participant/match-participant.service.spec.ts index e6ed79abe..5d2d62d92 100644 --- a/packages/twenty-server/src/modules/match-participant/match-participant.service.spec.ts +++ b/packages/twenty-server/src/modules/match-participant/match-participant.service.spec.ts @@ -20,18 +20,15 @@ describe('MatchParticipantService', () => { find: jest.Mock; update: jest.Mock; createQueryBuilder: jest.Mock; - formatResult: jest.Mock; }; let mockCalendarEventParticipantRepository: { find: jest.Mock; update: jest.Mock; createQueryBuilder: jest.Mock; - formatResult: jest.Mock; }; let mockPersonRepository: { find: jest.Mock; createQueryBuilder: jest.Mock; - formatResult: jest.Mock; }; let mockWorkspaceMemberRepository: { find: jest.Mock; @@ -53,7 +50,6 @@ describe('MatchParticipantService', () => { getMany: jest.fn(), withDeleted: jest.fn().mockReturnThis(), }), - formatResult: jest.fn(), }; mockCalendarEventParticipantRepository = { @@ -68,7 +64,6 @@ describe('MatchParticipantService', () => { getMany: jest.fn(), withDeleted: jest.fn().mockReturnThis(), }), - formatResult: jest.fn(), }; mockPersonRepository = { @@ -82,7 +77,6 @@ describe('MatchParticipantService', () => { getMany: jest.fn(), withDeleted: jest.fn().mockReturnThis(), }), - formatResult: jest.fn(), }; mockWorkspaceMemberRepository = { @@ -204,7 +198,7 @@ describe('MatchParticipantService', () => { }; mockPersonRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder); - mockPersonRepository.formatResult.mockResolvedValue(mockPeople); + mockQueryBuilder.getMany.mockResolvedValue(mockPeople); mockWorkspaceMemberRepository.find.mockResolvedValue( mockWorkspaceMembers, ); @@ -316,7 +310,18 @@ describe('MatchParticipantService', () => { }); it('should handle participants with no matching people or workspace members', async () => { - mockPersonRepository.formatResult.mockResolvedValue([]); + const mockQueryBuilder = { + select: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + andWhere: jest.fn().mockReturnThis(), + orWhere: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockReturnThis(), + getMany: jest.fn().mockResolvedValue([]), + withDeleted: jest.fn().mockReturnThis(), + }; + + mockPersonRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder); + mockQueryBuilder.getMany.mockResolvedValue([]); mockWorkspaceMemberRepository.find.mockResolvedValue([]); await service.matchParticipants({ @@ -545,7 +550,18 @@ describe('MatchParticipantService', () => { affected: 1, }); mockMessageParticipantRepository.find.mockResolvedValue([]); - mockPersonRepository.formatResult.mockResolvedValue([]); + const mockQueryBuilder = { + select: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + andWhere: jest.fn().mockReturnThis(), + orWhere: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockReturnThis(), + getMany: jest.fn().mockResolvedValue([]), + withDeleted: jest.fn().mockReturnThis(), + }; + + mockPersonRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder); + mockQueryBuilder.getMany.mockResolvedValue([]); }); describe('person unmatching', () => { @@ -590,9 +606,7 @@ describe('MatchParticipantService', () => { mockPersonRepository.createQueryBuilder.mockReturnValue( mockQueryBuilder, ); - mockPersonRepository.formatResult.mockResolvedValue( - mockAlternativePeople, - ); + mockQueryBuilder.getMany.mockResolvedValue(mockAlternativePeople); const rematchedParticipants = [ { @@ -655,7 +669,7 @@ describe('MatchParticipantService', () => { mockPersonRepository.createQueryBuilder.mockReturnValue( mockQueryBuilder, ); - mockPersonRepository.formatResult.mockResolvedValue([]); + mockQueryBuilder.getMany.mockResolvedValue([]); await service.unmatchParticipants({ handle: 'test-1@example.com', diff --git a/packages/twenty-server/src/modules/match-participant/match-participant.service.ts b/packages/twenty-server/src/modules/match-participant/match-participant.service.ts index 01a97625c..d8affc7d0 100644 --- a/packages/twenty-server/src/modules/match-participant/match-participant.service.ts +++ b/packages/twenty-server/src/modules/match-participant/match-participant.service.ts @@ -82,12 +82,10 @@ export class MatchParticipantService< emails: uniqueParticipantsHandles, }); - const rawPeople = await queryBuilder + const people = await queryBuilder .orderBy('person.createdAt', 'ASC') .getMany(); - const people = await personRepository.formatResult(rawPeople); - const workspaceMemberRepository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspaceId, @@ -192,12 +190,10 @@ export class MatchParticipantService< excludePersonIds: [personId], }); - const rawPeople = await queryBuilder + const peopleToMatch = await queryBuilder .orderBy('person.createdAt', 'ASC') .getMany(); - const peopleToMatch = await personRepository.formatResult(rawPeople); - if (peopleToMatch.length > 0) { const bestMatch = findPersonByPrimaryOrAdditionalEmail({ people: peopleToMatch, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-handle-error.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-handle-error.service.ts index 49e322f76..049de2119 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-handle-error.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-handle-error.service.ts @@ -1,9 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { MessageChannelSyncStatus, MessageChannelWorkspaceEntity, @@ -18,7 +15,6 @@ export class ImapHandleErrorService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, ) {} async handleError( @@ -38,42 +34,12 @@ export class ImapHandleErrorService { 'messageChannel', ); - const messageChannel = await messageChannelRepository.findOneOrFail({ - where: { id: messageChannelId }, - }); - await messageChannelRepository.update( { id: messageChannelId }, { syncStatus: MessageChannelSyncStatus.FAILED_UNKNOWN, }, ); - - const dataSource = - await this.twentyORMGlobalManager.getDataSourceForWorkspace({ - workspaceId, - }); - const messageChannelMetadata = await dataSource - .getRepository(ObjectMetadataEntity) - .findOneOrFail({ - where: { nameSingular: 'messageChannel', workspaceId }, - }); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'messageChannel', - action: DatabaseEventAction.UPDATED, - events: [ - { - recordId: messageChannelId, - objectMetadata: messageChannelMetadata, - properties: { - before: { syncStatus: messageChannel.syncStatus }, - after: { syncStatus: MessageChannelSyncStatus.FAILED_UNKNOWN }, - }, - }, - ], - workspaceId, - }); } catch (handleErrorError) { this.logger.error( `Error handling IMAP error: ${handleErrorError.message}`, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts index 52ec1c4a8..4e009db23 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts @@ -7,7 +7,6 @@ import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-q import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { CreateCompanyAndContactJob } from 'src/modules/contact-creation-manager/jobs/create-company-and-contact.job'; import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum'; @@ -104,12 +103,6 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => { add: jest.fn().mockResolvedValue(undefined), }, }, - { - provide: WorkspaceEventEmitter, - useValue: { - emitDatabaseBatchEvent: jest.fn().mockResolvedValue(undefined), - }, - }, { provide: getRepositoryToken(ObjectMetadataEntity, 'core'), useValue: { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts index b88068139..68acc3c7d 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts @@ -1,17 +1,11 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; - -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; 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 { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { CreateCompanyAndContactJob, @@ -39,9 +33,6 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService { private readonly messageService: MessagingMessageService, private readonly messageParticipantService: MessagingMessageParticipantService, private readonly twentyORMManager: TwentyORMManager, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, ) {} async saveMessagesAndEnqueueContactCreation( @@ -122,27 +113,7 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService { }, ); - const { participantsWithMessageId, createdMessages } = - createdMessagesWithParticipants; - - const messageMetadata = await this.objectMetadataRepository.findOneOrFail({ - where: { nameSingular: 'message', workspaceId }, - }); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'message', - action: DatabaseEventAction.CREATED, - events: createdMessages.map((message) => { - return { - recordId: message.id ?? '', - objectMetadata: messageMetadata, - properties: { - after: message, - }, - }; - }), - workspaceId, - }); + const { participantsWithMessageId } = createdMessagesWithParticipants; if (messageChannel.isContactAutoCreationEnabled) { const contactsToCreate = participantsWithMessageId.filter( diff --git a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook.ts b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook.ts index 87e02ad15..647ad07c1 100644 --- a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook.ts +++ b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook.ts @@ -1,23 +1,16 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Repository } from 'typeorm'; - import { WorkspacePostQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkflowVersionStatus, WorkflowVersionWorkspaceEntity, } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; -import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; @WorkspaceQueryHook({ key: `workflow.createMany`, @@ -28,9 +21,6 @@ export class WorkflowCreateManyPostQueryHook { constructor( private readonly twentyORMManager: TwentyORMManager, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, private readonly recordPositionService: RecordPositionService, ) {} @@ -71,28 +61,5 @@ export class WorkflowCreateManyPostQueryHook return workflowVersionRepository.save(workflowVersion); }), ); - - const objectMetadata = await this.objectMetadataRepository.findOneOrFail({ - where: { - nameSingular: 'workflowVersion', - workspaceId: workspace.id, - }, - }); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'workflowVersion', - action: DatabaseEventAction.CREATED, - events: workflowVersionsToCreate.map((workflowVersionToCreate) => { - return { - userId: authContext.user?.id, - recordId: workflowVersionToCreate.id, - objectMetadata, - properties: { - after: workflowVersionToCreate, - }, - }; - }), - workspaceId: workspace.id, - }); } } diff --git a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook.ts b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook.ts index ee6b7b490..fc5911d9b 100644 --- a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook.ts +++ b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook.ts @@ -1,23 +1,16 @@ -import { InjectRepository } from '@nestjs/typeorm'; - -import { Repository } from 'typeorm'; - import { WorkspacePostQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkflowVersionStatus, WorkflowVersionWorkspaceEntity, } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; -import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; @WorkspaceQueryHook({ key: `workflow.createOne`, @@ -28,9 +21,6 @@ export class WorkflowCreateOnePostQueryHook { constructor( private readonly twentyORMManager: TwentyORMManager, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, private readonly recordPositionService: RecordPositionService, ) {} @@ -59,7 +49,7 @@ export class WorkflowCreateOnePostQueryHook workspaceId: workspace.id, }); - const workflowVersionToCreate = await workflowVersionRepository.create({ + const workflowVersionToCreate = workflowVersionRepository.create({ workflowId: workflow.id, status: WorkflowVersionStatus.DRAFT, name: 'v1', @@ -67,28 +57,5 @@ export class WorkflowCreateOnePostQueryHook }); await workflowVersionRepository.save(workflowVersionToCreate); - - const objectMetadata = await this.objectMetadataRepository.findOneOrFail({ - where: { - nameSingular: 'workflowVersion', - workspaceId: workspace.id, - }, - }); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'workflowVersion', - action: DatabaseEventAction.CREATED, - events: [ - { - userId: authContext.user?.id, - recordId: workflowVersionToCreate.id, - objectMetadata, - properties: { - after: workflowVersionToCreate, - }, - }, - ], - workspaceId: workspace.id, - }); } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service.ts index 0c23af39c..da6e7ba58 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service.ts @@ -1,14 +1,9 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; import { isDefined } from 'twenty-shared/utils'; -import { Repository } from 'typeorm'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkflowVersionStepException, WorkflowVersionStepExceptionCode, @@ -28,9 +23,6 @@ export class WorkflowVersionWorkspaceService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly workflowVersionStepWorkspaceService: WorkflowVersionStepWorkspaceService, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, private readonly recordPositionService: RecordPositionService, ) {} @@ -96,11 +88,6 @@ export class WorkflowVersionWorkspaceService { status: WorkflowVersionStatus.DRAFT, position, }); - - await this.emitWorkflowVersionCreationEvent({ - workflowVersion: draftWorkflowVersion, - workspaceId, - }); } assertWorkflowVersionIsDraft(draftWorkflowVersion); @@ -125,41 +112,4 @@ export class WorkflowVersionWorkspaceService { return draftWorkflowVersion.id; } - - private async emitWorkflowVersionCreationEvent({ - workflowVersion, - workspaceId, - }: { - workflowVersion: WorkflowVersionWorkspaceEntity; - workspaceId: string; - }) { - const objectMetadata = await this.objectMetadataRepository.findOne({ - where: { - nameSingular: 'workflowVersion', - workspaceId, - }, - }); - - if (!objectMetadata) { - throw new WorkflowVersionStepException( - 'Object metadata not found', - WorkflowVersionStepExceptionCode.FAILURE, - ); - } - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'workflowVersion', - action: DatabaseEventAction.CREATED, - events: [ - { - recordId: workflowVersion.id, - objectMetadata, - properties: { - after: workflowVersion, - }, - }, - ], - workspaceId, - }); - } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts index 0e9c168c8..3235327bd 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts @@ -6,14 +6,12 @@ import { Repository } from 'typeorm'; import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service'; import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service'; import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { WorkflowStepExecutorException, @@ -35,7 +33,6 @@ export class CreateRecordWorkflowAction implements WorkflowAction { private readonly twentyORMGlobalManager: TwentyORMGlobalManager, @InjectRepository(ObjectMetadataEntity, 'core') private readonly objectMetadataRepository: Repository, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, private readonly recordPositionService: RecordPositionService, private readonly recordInputTransformerService: RecordInputTransformerService, @@ -130,21 +127,6 @@ export class CreateRecordWorkflowAction implements WorkflowAction { }, }); - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: workflowActionInput.objectName, - action: DatabaseEventAction.CREATED, - events: [ - { - recordId: objectRecord.id, - objectMetadata, - properties: { - after: objectRecord, - }, - }, - ], - workspaceId, - }); - return { result: objectRecord, }; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action.ts index 401a07fa0..a2784d2b5 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action.ts @@ -1,17 +1,12 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; import { isDefined } from 'class-validator'; import { isValidUuid } from 'twenty-shared/utils'; -import { Repository } from 'typeorm'; import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkflowStepExecutorException, WorkflowStepExecutorExceptionCode, @@ -30,9 +25,6 @@ import { WorkflowDeleteRecordActionInput } from 'src/modules/workflow/workflow-e export class DeleteRecordWorkflowAction implements WorkflowAction { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, ) {} @@ -88,19 +80,6 @@ export class DeleteRecordWorkflowAction implements WorkflowAction { { shouldBypassPermissionChecks: true }, ); - const objectMetadata = await this.objectMetadataRepository.findOne({ - where: { - nameSingular: workflowActionInput.objectName, - }, - }); - - if (!objectMetadata) { - throw new RecordCRUDActionException( - 'Failed to delete: Object metadata not found', - RecordCRUDActionExceptionCode.INVALID_REQUEST, - ); - } - const objectRecord = await repository.findOne({ where: { id: workflowActionInput.objectRecordId, @@ -116,21 +95,6 @@ export class DeleteRecordWorkflowAction implements WorkflowAction { await repository.softDelete(workflowActionInput.objectRecordId); - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: workflowActionInput.objectName, - action: DatabaseEventAction.DELETED, - events: [ - { - recordId: objectRecord.id, - objectMetadata, - properties: { - before: objectRecord, - }, - }, - ], - workspaceId, - }); - return { result: objectRecord, }; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action.ts index 11d07a034..bfc18eadd 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action.ts @@ -17,7 +17,6 @@ import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-met import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { WorkflowStepExecutorException, @@ -147,15 +146,9 @@ export class FindRecordsWorkflowAction implements WorkflowAction { false, ); - const nonFormattedObjectRecords = await withOrderByQueryBuilder + return withOrderByQueryBuilder .take(workflowActionInput.limit ?? QUERY_MAX_RECORDS) .getMany(); - - return formatResult( - nonFormattedObjectRecords, - objectMetadataItemWithFieldsMaps, - objectMetadataMaps, - ); } private async getTotalCount( diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts index 39ea5dd5b..838d1b60a 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts @@ -1,20 +1,13 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; import deepEqual from 'deep-equal'; import { isDefined, isValidUuid } from 'twenty-shared/utils'; -import { Repository } from 'typeorm'; import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; -import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values'; import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { WorkflowStepExecutorException, @@ -35,9 +28,6 @@ export class UpdateRecordWorkflowAction implements WorkflowAction { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService, private readonly recordInputTransformerService: RecordInputTransformerService, ) {} @@ -95,20 +85,6 @@ export class UpdateRecordWorkflowAction implements WorkflowAction { { shouldBypassPermissionChecks: true }, ); - const objectMetadata = await this.objectMetadataRepository.findOne({ - where: { - nameSingular: workflowActionInput.objectName, - }, - relations: ['fields'], - }); - - if (!objectMetadata) { - throw new RecordCRUDActionException( - 'Failed to update: Object metadata not found', - RecordCRUDActionExceptionCode.INVALID_REQUEST, - ); - } - const previousObjectRecord = await repository.findOne({ where: { id: workflowActionInput.objectRecordId, @@ -153,11 +129,6 @@ export class UpdateRecordWorkflowAction implements WorkflowAction { objectMetadataMapItem: objectMetadataItemWithFieldsMaps, }); - const objectRecordFormatted = formatData( - transformedObjectRecord, - objectMetadataItemWithFieldsMaps, - ); - const updatedObjectRecord = { ...previousObjectRecord, ...objectRecordWithFilteredFields, @@ -165,32 +136,7 @@ export class UpdateRecordWorkflowAction implements WorkflowAction { if (!deepEqual(updatedObjectRecord, previousObjectRecord)) { await repository.update(workflowActionInput.objectRecordId, { - ...objectRecordFormatted, - }); - - const diff = objectRecordChangedValues( - previousObjectRecord, - updatedObjectRecord, - workflowActionInput.fieldsToUpdate, - objectMetadata, - ); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: workflowActionInput.objectName, - action: DatabaseEventAction.UPDATED, - events: [ - { - recordId: previousObjectRecord.id, - objectMetadata, - properties: { - before: previousObjectRecord, - after: updatedObjectRecord, - updatedFields: workflowActionInput.fieldsToUpdate, - diff, - }, - }, - ], - workspaceId, + ...transformedObjectRecord, }); } diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service.ts index 6460ad446..9b999f73e 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service.ts @@ -1,36 +1,30 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { v4 } from 'uuid'; import { isDefined } from 'twenty-shared/utils'; -import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; import { StepStatus } from 'twenty-shared/workflow'; +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; +import { v4 } from 'uuid'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; -import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values'; +import { WithLock } from 'src/engine/core-modules/cache-lock/with-lock.decorator'; import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type'; import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service'; import { ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { StepOutput, WorkflowRunState, WorkflowRunStatus, WorkflowRunWorkspaceEntity, } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; +import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; import { WorkflowRunException, WorkflowRunExceptionCode, } from 'src/modules/workflow/workflow-runner/exceptions/workflow-run.exception'; -import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; -import { WithLock } from 'src/engine/core-modules/cache-lock/with-lock.decorator'; @Injectable() export class WorkflowRunWorkspaceService { @@ -38,9 +32,6 @@ export class WorkflowRunWorkspaceService { private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService, private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, private readonly recordPositionService: RecordPositionService, private readonly metricsService: MetricsService, ) {} @@ -413,76 +404,6 @@ export class WorkflowRunWorkspaceService { return workflowRun; } - private async emitWorkflowRunUpdatedEvent({ - workflowRunBefore, - updatedFields, - }: { - workflowRunBefore: WorkflowRunWorkspaceEntity; - updatedFields: string[]; - }) { - const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId; - - if (!workspaceId) { - return; - } - - const objectMetadata = await this.objectMetadataRepository.findOne({ - where: { - nameSingular: 'workflowRun', - workspaceId, - }, - relations: ['fields'], - }); - - if (!objectMetadata) { - throw new WorkflowRunException( - 'Object metadata not found', - WorkflowRunExceptionCode.FAILURE, - ); - } - - const workflowRunRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'workflowRun', - { shouldBypassPermissionChecks: true }, - ); - - const workflowRunAfter = await workflowRunRepository.findOneBy({ - id: workflowRunBefore.id, - }); - - if (!workflowRunAfter) { - throw new WorkflowRunException( - 'WorkflowRun not found', - WorkflowRunExceptionCode.FAILURE, - ); - } - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'workflowRun', - action: DatabaseEventAction.UPDATED, - events: [ - { - recordId: workflowRunBefore.id, - objectMetadata, - properties: { - after: workflowRunAfter, - before: workflowRunBefore, - updatedFields, - diff: objectRecordChangedValues( - workflowRunBefore, - workflowRunAfter, - updatedFields, - objectMetadata, - ), - }, - }, - ], - workspaceId, - }); - } - private getInitState( workflowVersion: WorkflowVersionWorkspaceEntity, ): WorkflowRunState | undefined { @@ -538,14 +459,5 @@ export class WorkflowRunWorkspaceService { } await workflowRunRepository.update(workflowRunToUpdate.id, partialUpdate); - - const updatedFields = Object.keys(partialUpdate); - - if (updatedFields.length > 0) { - await this.emitWorkflowRunUpdatedEvent({ - workflowRunBefore: workflowRunToUpdate, - updatedFields: updatedFields, - }); - } } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-status/jobs/__tests__/workflow-statuses-update.job.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-status/jobs/__tests__/workflow-statuses-update.job.spec.ts index 767b73066..14e821f14 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-status/jobs/__tests__/workflow-statuses-update.job.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-status/jobs/__tests__/workflow-statuses-update.job.spec.ts @@ -5,7 +5,6 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; import { WorkflowStatus } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; import { @@ -54,10 +53,6 @@ describe('WorkflowStatusesUpdate', () => { findOneOrFail: jest.fn(), }; - const mockWorkspaceEventEmitter = { - emitDatabaseBatchEvent: jest.fn(), - }; - beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -70,10 +65,6 @@ describe('WorkflowStatusesUpdate', () => { provide: ServerlessFunctionService, useValue: mockServerlessFunctionService, }, - { - provide: WorkspaceEventEmitter, - useValue: mockWorkspaceEventEmitter, - }, { provide: getRepositoryToken(ObjectMetadataEntity, 'core'), useValue: { @@ -125,9 +116,6 @@ describe('WorkflowStatusesUpdate', () => { expect(mockWorkflowRepository.findOneOrFail).toHaveBeenCalledTimes(1); expect(mockWorkflowRepository.update).toHaveBeenCalledTimes(0); - expect( - mockWorkspaceEventEmitter.emitDatabaseBatchEvent, - ).toHaveBeenCalledTimes(0); }); it('when no draft yet, update statuses', async () => { @@ -155,9 +143,6 @@ describe('WorkflowStatusesUpdate', () => { { id: '1' }, { statuses: [WorkflowStatus.DRAFT, WorkflowStatus.ACTIVE] }, ); - expect( - mockWorkspaceEventEmitter.emitDatabaseBatchEvent, - ).toHaveBeenCalledTimes(1); }); }); @@ -202,9 +187,6 @@ describe('WorkflowStatusesUpdate', () => { mockWorkflowVersionRepository.findOneOrFail, ).toHaveBeenCalledTimes(1); expect(mockWorkflowRepository.update).toHaveBeenCalledTimes(0); - expect( - mockWorkspaceEventEmitter.emitDatabaseBatchEvent, - ).toHaveBeenCalledTimes(0); }); test('when WorkflowVersionStatus.DRAFT to WorkflowVersionStatus.ACTIVE, should activate and publish serverless functions', async () => { @@ -286,9 +268,6 @@ describe('WorkflowStatusesUpdate', () => { { id: '1' }, { statuses: [WorkflowStatus.ACTIVE] }, ); - expect( - mockWorkspaceEventEmitter.emitDatabaseBatchEvent, - ).toHaveBeenCalledTimes(1); }); }); @@ -338,9 +317,6 @@ describe('WorkflowStatusesUpdate', () => { { id: '1' }, { statuses: [] }, ); - expect( - mockWorkspaceEventEmitter.emitDatabaseBatchEvent, - ).toHaveBeenCalledTimes(1); }); }); }); diff --git a/packages/twenty-server/src/modules/workflow/workflow-status/jobs/workflow-statuses-update.job.ts b/packages/twenty-server/src/modules/workflow/workflow-status/jobs/workflow-statuses-update.job.ts index 9e89675be..abc0244c6 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-status/jobs/workflow-statuses-update.job.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-status/jobs/workflow-statuses-update.job.ts @@ -1,20 +1,15 @@ import { Logger, Scope } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; import isEqual from 'lodash.isequal'; import { isDefined } from 'twenty-shared/utils'; -import { In, Repository } from 'typeorm'; +import { In } from 'typeorm'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkflowVersionStatus, WorkflowVersionWorkspaceEntity, @@ -72,22 +67,10 @@ export class WorkflowStatusesUpdateJob { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly serverlessFunctionService: ServerlessFunctionService, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, - @InjectRepository(ObjectMetadataEntity, 'core') - protected readonly objectMetadataRepository: Repository, - @InjectRepository(ServerlessFunctionEntity, 'core') - private readonly serverlessFunctionRepository: Repository, ) {} @Process(WorkflowStatusesUpdateJob.name) async handle(event: WorkflowVersionBatchEvent): Promise { - const workflowObjectMetadata = - await this.objectMetadataRepository.findOneOrFail({ - where: { - nameSingular: 'workflow', - }, - }); - switch (event.type) { case WorkflowVersionEventType.CREATE: case WorkflowVersionEventType.DELETE: @@ -95,7 +78,6 @@ export class WorkflowStatusesUpdateJob { event.workflowIds.map((workflowId) => this.handleWorkflowVersionCreatedOrDeleted({ workflowId, - workflowObjectMetadata, workspaceId: event.workspaceId, }), ), @@ -106,7 +88,6 @@ export class WorkflowStatusesUpdateJob { event.statusUpdates.map((statusUpdate) => this.handleWorkflowVersionStatusUpdated({ statusUpdate, - workflowObjectMetadata, workspaceId: event.workspaceId, }), ), @@ -119,11 +100,9 @@ export class WorkflowStatusesUpdateJob { private async handleWorkflowVersionCreatedOrDeleted({ workflowId, - workflowObjectMetadata, workspaceId, }: { workflowId: string; - workflowObjectMetadata: ObjectMetadataEntity; workspaceId: string; }): Promise { const workflowRepository = @@ -163,13 +142,6 @@ export class WorkflowStatusesUpdateJob { statuses: newWorkflowStatuses, }, ); - - this.emitWorkflowStatusUpdatedEvent({ - currentWorkflow: previousWorkflow, - workflowObjectMetadata, - newWorkflowStatuses, - workspaceId, - }); } private async handlePublishServerlessFunction({ @@ -222,11 +194,9 @@ export class WorkflowStatusesUpdateJob { private async handleWorkflowVersionStatusUpdated({ statusUpdate, - workflowObjectMetadata, workspaceId, }: { statusUpdate: WorkflowVersionStatusUpdate; - workflowObjectMetadata: ObjectMetadataEntity; workspaceId: string; }): Promise { const workflowRepository = @@ -277,51 +247,6 @@ export class WorkflowStatusesUpdateJob { statuses: newWorkflowStatuses, }, ); - - this.emitWorkflowStatusUpdatedEvent({ - currentWorkflow: workflow, - workflowObjectMetadata, - newWorkflowStatuses, - workspaceId, - }); - } - - private emitWorkflowStatusUpdatedEvent({ - currentWorkflow, - workflowObjectMetadata, - newWorkflowStatuses, - workspaceId, - }: { - currentWorkflow: WorkflowWorkspaceEntity; - workflowObjectMetadata: ObjectMetadataEntity; - newWorkflowStatuses: WorkflowStatus[]; - workspaceId: string; - }) { - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: workflowObjectMetadata.nameSingular, - action: DatabaseEventAction.UPDATED, - events: [ - { - recordId: currentWorkflow.id, - objectMetadata: workflowObjectMetadata, - properties: { - before: currentWorkflow, - after: { - ...currentWorkflow, - statuses: newWorkflowStatuses, - }, - updatedFields: ['statuses'], - diff: { - statuses: { - before: currentWorkflow.statuses, - after: newWorkflowStatuses, - }, - }, - }, - }, - ], - workspaceId, - }); } private async getWorkflowStatuses({ diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts index 964f13e0a..f8477f98f 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts @@ -1,12 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; import { t } from '@lingui/core/macro'; -import { Repository } from 'typeorm'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; @@ -43,8 +39,6 @@ export class WorkflowTriggerWorkspaceService { private readonly workflowRunnerWorkspaceService: WorkflowRunnerWorkspaceService, private readonly automatedTriggerWorkspaceService: AutomatedTriggerWorkspaceService, private readonly workspaceEventEmitter: WorkspaceEventEmitter, - @InjectRepository(ObjectMetadataEntity, 'core') - private readonly objectMetadataRepository: Repository, ) {} private getWorkspaceId() { @@ -412,33 +406,6 @@ export class WorkflowTriggerWorkspaceService { newStatus: WorkflowVersionStatus, workspaceId: string, ) { - const objectMetadata = await this.objectMetadataRepository.findOneOrFail({ - where: { - nameSingular: 'workflowVersion', - workspaceId, - }, - }); - - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'workflowVersion', - action: DatabaseEventAction.UPDATED, - events: [ - { - recordId: workflowVersion.id, - objectMetadata, - properties: { - before: workflowVersion, - after: { ...workflowVersion, status: newStatus }, - updatedFields: ['status'], - diff: { - status: { before: workflowVersion.status, after: newStatus }, - }, - }, - }, - ], - workspaceId, - }); - this.workspaceEventEmitter.emitCustomBatchEvent( WORKFLOW_VERSION_STATUS_UPDATED, [ diff --git a/packages/twenty-server/test/integration/graphql/suites/all-people-resolvers.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/all-people-resolvers.integration-spec.ts index 37db06c72..a4c1b1e38 100644 --- a/packages/twenty-server/test/integration/graphql/suites/all-people-resolvers.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/all-people-resolvers.integration-spec.ts @@ -1,9 +1,9 @@ +import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants'; import { TEST_PERSON_1_ID, TEST_PERSON_2_ID, TEST_PERSON_3_ID, } from 'test/integration/constants/test-person-ids.constants'; -import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants'; import { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-operation-factory.util'; import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util'; import { deleteManyOperationFactory } from 'test/integration/graphql/utils/delete-many-operation-factory.util'; @@ -15,8 +15,8 @@ import { findOneOperationFactory } from 'test/integration/graphql/utils/find-one import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; import { updateManyOperationFactory } from 'test/integration/graphql/utils/update-many-operation-factory.util'; import { updateOneOperationFactory } from 'test/integration/graphql/utils/update-one-operation-factory.util'; -import { generateRecordName } from 'test/integration/utils/generate-record-name'; import { deleteAllRecords } from 'test/integration/utils/delete-all-records'; +import { generateRecordName } from 'test/integration/utils/generate-record-name'; describe('people resolvers (integration)', () => { beforeAll(async () => { diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/utils/agent-tool-test-utils.ts b/packages/twenty-server/test/integration/metadata/suites/agent/utils/agent-tool-test-utils.ts index b05ed6937..9dc8dab6f 100644 --- a/packages/twenty-server/test/integration/metadata/suites/agent/utils/agent-tool-test-utils.ts +++ b/packages/twenty-server/test/integration/metadata/suites/agent/utils/agent-tool-test-utils.ts @@ -12,7 +12,6 @@ import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metada import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; export interface AgentToolTestContext { module: TestingModule; @@ -64,14 +63,6 @@ export const createAgentToolTestModule = getRepositoryForWorkspace: jest.fn(), }, }, - { - provide: WorkspaceEventEmitter, - useValue: { - emit: jest.fn(), - emitDatabaseBatchEvent: jest.fn(), - emitCustomBatchEvent: jest.fn(), - }, - }, { provide: WorkspacePermissionsCacheService, useValue: { diff --git a/yarn.lock b/yarn.lock index 0038e488f..f05bc7ef5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -57751,7 +57751,7 @@ __metadata: "typeorm@patch:typeorm@0.3.20#./patches/typeorm+0.3.20.patch::locator=twenty-server%40workspace%3Apackages%2Ftwenty-server": version: 0.3.20 - resolution: "typeorm@patch:typeorm@npm%3A0.3.20#./patches/typeorm+0.3.20.patch::version=0.3.20&hash=d5f43a&locator=twenty-server%40workspace%3Apackages%2Ftwenty-server" + resolution: "typeorm@patch:typeorm@npm%3A0.3.20#./patches/typeorm+0.3.20.patch::version=0.3.20&hash=e61204&locator=twenty-server%40workspace%3Apackages%2Ftwenty-server" dependencies: "@sqltools/formatter": "npm:^1.2.5" app-root-path: "npm:^3.1.0" @@ -57825,7 +57825,7 @@ __metadata: typeorm: cli.js typeorm-ts-node-commonjs: cli-ts-node-commonjs.js typeorm-ts-node-esm: cli-ts-node-esm.js - checksum: 10c0/001ec2c0dc385226c9ba0ea6058b45f1c0e264a9adaac760dc7894b4a8dc25398334d3b35d94257088b39be0516a3e5b583e3a1687635d594a086abe09d6a888 + checksum: 10c0/2593c3ddf6b243c6be13e3d9deebad1e9ecfcbdda8bb518b7fb633077e2e20b99c1873cdc0848df8ba44c9bfb22ecfe345937aa82702a9c857ecd73b32de8373 languageName: node linkType: hard