From 1590751131f2647da81b41fe545b396c4fe5bdb4 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Tue, 28 Oct 2025 17:13:39 +0100 Subject: [PATCH] Set formatted before instead of after (#15401) Soft deletion events use before in event properties. But we set the after instead, that only contains a few data. Fixes https://github.com/twentyhq/twenty/issues/15120 Debugging raised an additional issue. UpdateMany, RestoreMany and SoftDeleteMany were broken for custom objects. The `where` clause was using "_objectName.id" instead of "objectName.id" during select. That's why we need to use different where clause between the select of the existing value and the actual mutation. --------- Co-authored-by: Charles Bochet --- ...phql-query-delete-many-resolver.service.ts | 8 +--- ...hql-query-restore-many-resolver.service.ts | 8 +--- ...phql-query-update-many-resolver.service.ts | 8 +--- .../workspace-soft-delete-query-builder.ts | 40 ++++++++++++++++++- .../workspace-update-query-builder.ts | 14 +++++++ 5 files changed, 55 insertions(+), 23 deletions(-) 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 6332200f60..8f76c2e286 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,7 +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 { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResolverService< @@ -31,14 +30,9 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol objectMetadataItemWithFieldMaps.nameSingular, ); - const tableName = computeTableName( - objectMetadataItemWithFieldMaps.nameSingular, - objectMetadataItemWithFieldMaps.isCustom, - ); - executionArgs.graphqlQueryParser.applyFilterToBuilder( queryBuilder, - tableName, + objectMetadataItemWithFieldMaps.nameSingular, executionArgs.args.filter, ); 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 851036a77b..38c88ee284 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,7 +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 { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseResolverService< @@ -31,14 +30,9 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso objectMetadataItemWithFieldMaps.nameSingular, ); - const tableName = computeTableName( - objectMetadataItemWithFieldMaps.nameSingular, - objectMetadataItemWithFieldMaps.isCustom, - ); - executionArgs.graphqlQueryParser.applyFilterToBuilder( queryBuilder, - tableName, + objectMetadataItemWithFieldMaps.nameSingular, executionArgs.args.filter, ); 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 9354667daa..111eeb3f7b 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 @@ -14,7 +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 { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResolverService< @@ -31,14 +30,9 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol objectMetadataItemWithFieldMaps.nameSingular, ); - const tableName = computeTableName( - objectMetadataItemWithFieldMaps.nameSingular, - objectMetadataItemWithFieldMaps.isCustom, - ); - executionArgs.graphqlQueryParser.applyFilterToBuilder( queryBuilder, - tableName, + objectMetadataItemWithFieldMaps.nameSingular, executionArgs.args.filter, ); 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 fc3889adbf..5c48080eb2 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 @@ -6,6 +6,7 @@ import { type UpdateResult, } from 'typeorm'; import { SoftDeleteQueryBuilder } from 'typeorm/query-builder/SoftDeleteQueryBuilder'; +import { type WhereClause } from 'typeorm/query-builder/WhereClause'; import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface'; import { type WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; @@ -19,11 +20,13 @@ import { } from 'src/engine/twenty-orm/exceptions/twenty-orm.exception'; import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils'; import { type WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder'; -import { type WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; +import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; import { type WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder'; +import { applyTableAliasOnWhereCondition } from 'src/engine/twenty-orm/utils/apply-table-alias-on-where-condition'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { formatTwentyOrmEventToDatabaseBatchEvent } from 'src/engine/twenty-orm/utils/format-twenty-orm-event-to-database-batch-event.util'; import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util'; +import { computeTableName } from 'src/engine/utils/compute-table-name.util'; export class WorkspaceSoftDeleteQueryBuilder< T extends ObjectLiteral, @@ -78,6 +81,33 @@ export class WorkspaceSoftDeleteQueryBuilder< this.internalContext, ); + const eventSelectQueryBuilder = new WorkspaceSelectQueryBuilder( + this as unknown as WorkspaceSelectQueryBuilder, + this.objectRecordsPermissions, + this.internalContext, + true, + this.authContext, + this.featureFlagMap, + ); + + const tableName = computeTableName( + objectMetadata.nameSingular, + objectMetadata.isCustom, + ); + + eventSelectQueryBuilder.expressionMap.wheres = this.expressionMap.wheres; + eventSelectQueryBuilder.expressionMap.aliases = + this.expressionMap.aliases; + eventSelectQueryBuilder.setParameters(this.getParameters()); + + const before = await eventSelectQueryBuilder.getMany(); + + this.expressionMap.wheres = applyTableAliasOnWhereCondition({ + condition: this.expressionMap.wheres, + tableName, + aliasName: objectMetadata.nameSingular, + }) as WhereClause[]; + const after = await super.execute(); const formattedAfter = formatResult( @@ -86,12 +116,18 @@ export class WorkspaceSoftDeleteQueryBuilder< this.internalContext.objectMetadataMaps, ); + const formattedBefore = formatResult( + before, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + this.internalContext.eventEmitterService.emitDatabaseBatchEvent( formatTwentyOrmEventToDatabaseBatchEvent({ action: DatabaseEventAction.DELETED, objectMetadataItem: objectMetadata, workspaceId: this.internalContext.workspaceId, - entities: formattedAfter, + entities: formattedBefore, authContext: this.authContext, }), ); 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 a450133d4e..3375d8ba78 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 @@ -7,6 +7,7 @@ import { type UpdateResult, } from 'typeorm'; import { type QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; +import { type WhereClause } from 'typeorm/query-builder/WhereClause'; import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface'; import { type WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; @@ -26,10 +27,12 @@ import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repositor import { type WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder'; import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; import { type WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder'; +import { applyTableAliasOnWhereCondition } from 'src/engine/twenty-orm/utils/apply-table-alias-on-where-condition'; import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { formatTwentyOrmEventToDatabaseBatchEvent } from 'src/engine/twenty-orm/utils/format-twenty-orm-event-to-database-batch-event.util'; import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util'; +import { computeTableName } from 'src/engine/utils/compute-table-name.util'; export class WorkspaceUpdateQueryBuilder< T extends ObjectLiteral, @@ -109,6 +112,11 @@ export class WorkspaceUpdateQueryBuilder< this.featureFlagMap, ); + const tableName = computeTableName( + objectMetadata.nameSingular, + objectMetadata.isCustom, + ); + eventSelectQueryBuilder.expressionMap.wheres = this.expressionMap.wheres; eventSelectQueryBuilder.expressionMap.aliases = this.expressionMap.aliases; @@ -116,6 +124,12 @@ export class WorkspaceUpdateQueryBuilder< const before = await eventSelectQueryBuilder.getMany(); + this.expressionMap.wheres = applyTableAliasOnWhereCondition({ + condition: this.expressionMap.wheres, + tableName, + aliasName: objectMetadata.nameSingular, + }) as WhereClause[]; + const nestedRelationQueryBuilder = new WorkspaceSelectQueryBuilder( this as unknown as WorkspaceSelectQueryBuilder, this.objectRecordsPermissions,