From 02618b3e6a2ee3a897cadac31fec5b542bac5862 Mon Sep 17 00:00:00 2001 From: Weiko Date: Wed, 18 Sep 2024 18:43:45 +0200 Subject: [PATCH] Fix graphql query createMany resolver with nested relations (#7061) Looks like insert() does not return foreign keys. We could eventually call findMany after but it seems that's what save() is doing so I'm replacing insert with save. ```typescript /** * Flag to determine whether the entity that is being persisted * should be reloaded during the persistence operation. * * It will work only on databases which does not support RETURNING / OUTPUT statement. * Enabled by default. */ reload?: boolean; ``` Note: save() also does an upsert by default with no way to configure that so if we want to keep that behaviour we will need to add a check before ```typescript if (args.upsert) { const existingRecords = await repository.findBy({ id: Any(args.data.map((record) => record.id)), }); ... ``` --------- Co-authored-by: Charles Bochet --- .../graphql-query-runner.module.ts | 3 + .../graphql-query-runner.service.ts | 219 +++++++++++++++++- ...phql-query-create-many-resolver.service.ts | 58 ++++- ...raphql-query-find-many-resolver.service.ts | 9 +- ...graphql-query-find-one-resolver.service.ts | 4 +- .../workspace-query-runner.module.ts | 4 +- .../workspace-query-runner.service.ts | 91 -------- .../factories/create-many-resolver.factory.ts | 23 +- .../factories/create-one-resolver.factory.ts | 25 +- .../factories/find-many-resolver.factory.ts | 12 +- .../factories/find-one-resolver.factory.ts | 12 +- .../workspace-resolver-builder.module.ts | 7 +- .../factories/entity-schema-column.factory.ts | 1 + 13 files changed, 347 insertions(+), 121 deletions(-) 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 87d6c0255..96f15862e 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 @@ -1,8 +1,11 @@ import { Module } from '@nestjs/common'; import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.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'; @Module({ + imports: [WorkspaceQueryHookModule, WorkspaceQueryRunnerModule], providers: [GraphqlQueryRunnerService], exports: [GraphqlQueryRunnerService], }) diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service.ts index 40ea3b4b4..cd1337b45 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service.ts @@ -9,22 +9,49 @@ import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/inter import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { CreateManyResolverArgs, + CreateOneResolverArgs, DestroyOneResolverArgs, FindManyResolverArgs, FindOneResolverArgs, + ResolverArgsType, } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; +import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service'; import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service'; import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service'; import { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service'; +import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory'; +import { + CallWebhookJobsJob, + CallWebhookJobsJobData, + CallWebhookJobsJobOperation, +} from 'src/engine/api/graphql/workspace-query-runner/jobs/call-webhook-jobs.job'; +import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; +import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service'; +import { + WorkspaceQueryRunnerException, + WorkspaceQueryRunnerExceptionCode, +} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception'; +import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event'; +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 { LogExecutionTime } from 'src/engine/decorators/observability/log-execution-time.decorator'; +import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; 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 GraphqlQueryRunnerService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + private readonly workspaceQueryHookService: WorkspaceQueryHookService, + private readonly queryRunnerArgsFactory: QueryRunnerArgsFactory, + private readonly workspaceEventEmitter: WorkspaceEventEmitter, + @InjectMessageQueue(MessageQueue.webhookQueue) + private readonly messageQueueService: MessageQueueService, ) {} @LogExecutionTime() @@ -38,7 +65,30 @@ export class GraphqlQueryRunnerService { const graphqlQueryFindOneResolverService = new GraphqlQueryFindOneResolverService(this.twentyORMGlobalManager); - return graphqlQueryFindOneResolverService.findOne(args, options); + const { authContext, objectMetadataItem } = options; + + if (!args.filter || Object.keys(args.filter).length === 0) { + throw new WorkspaceQueryRunnerException( + 'Missing filter argument', + WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT, + ); + } + + const hookedArgs = + await this.workspaceQueryHookService.executePreQueryHooks( + authContext, + objectMetadataItem.nameSingular, + 'findOne', + args, + ); + + const computedArgs = (await this.queryRunnerArgsFactory.create( + hookedArgs, + options, + ResolverArgsType.FindOne, + )) as FindOneResolverArgs; + + return graphqlQueryFindOneResolverService.findOne(computedArgs, options); } @LogExecutionTime() @@ -53,7 +103,78 @@ export class GraphqlQueryRunnerService { const graphqlQueryFindManyResolverService = new GraphqlQueryFindManyResolverService(this.twentyORMGlobalManager); - return graphqlQueryFindManyResolverService.findMany(args, options); + const { authContext, objectMetadataItem } = options; + + const hookedArgs = + await this.workspaceQueryHookService.executePreQueryHooks( + authContext, + objectMetadataItem.nameSingular, + 'findMany', + args, + ); + + const computedArgs = (await this.queryRunnerArgsFactory.create( + hookedArgs, + options, + ResolverArgsType.FindMany, + )) as FindManyResolverArgs; + + return graphqlQueryFindManyResolverService.findMany(computedArgs, options); + } + + @LogExecutionTime() + async createOne( + args: CreateOneResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const graphqlQueryCreateManyResolverService = + new GraphqlQueryCreateManyResolverService(this.twentyORMGlobalManager); + + const { authContext, objectMetadataItem } = options; + + assertMutationNotOnRemoteObject(objectMetadataItem); + + if (args.data.id) { + assertIsValidUuid(args.data.id); + } + + const createManyArgs = { + data: [args.data], + upsert: args.upsert, + } as CreateManyResolverArgs; + + const hookedArgs = + await this.workspaceQueryHookService.executePreQueryHooks( + authContext, + objectMetadataItem.nameSingular, + 'createMany', + createManyArgs, + ); + + const computedArgs = (await this.queryRunnerArgsFactory.create( + hookedArgs, + options, + ResolverArgsType.CreateMany, + )) as CreateManyResolverArgs; + + const results = (await graphqlQueryCreateManyResolverService.createMany( + computedArgs, + options, + )) as ObjectRecord[]; + + await this.triggerWebhooks( + results, + CallWebhookJobsJobOperation.create, + options, + ); + + this.emitCreateEvents( + results, + authContext, + objectMetadataItem, + ); + + return results?.[0] as ObjectRecord; } @LogExecutionTime() @@ -64,7 +185,99 @@ export class GraphqlQueryRunnerService { const graphqlQueryCreateManyResolverService = new GraphqlQueryCreateManyResolverService(this.twentyORMGlobalManager); - return graphqlQueryCreateManyResolverService.createMany(args, options); + const { authContext, objectMetadataItem } = options; + + assertMutationNotOnRemoteObject(objectMetadataItem); + + args.data.forEach((record) => { + if (record?.id) { + assertIsValidUuid(record.id); + } + }); + + const hookedArgs = + await this.workspaceQueryHookService.executePreQueryHooks( + authContext, + objectMetadataItem.nameSingular, + 'createMany', + args, + ); + + const computedArgs = (await this.queryRunnerArgsFactory.create( + hookedArgs, + options, + ResolverArgsType.CreateMany, + )) as CreateManyResolverArgs; + + const results = (await graphqlQueryCreateManyResolverService.createMany( + computedArgs, + options, + )) as ObjectRecord[]; + + await this.workspaceQueryHookService.executePostQueryHooks( + authContext, + objectMetadataItem.nameSingular, + 'createMany', + results, + ); + + await this.triggerWebhooks( + results, + CallWebhookJobsJobOperation.create, + options, + ); + + this.emitCreateEvents( + results, + authContext, + objectMetadataItem, + ); + + return results; + } + + private emitCreateEvents( + records: BaseRecord[], + authContext: AuthContext, + objectMetadataItem: ObjectMetadataInterface, + ) { + this.workspaceEventEmitter.emit( + `${objectMetadataItem.nameSingular}.created`, + records.map( + (record) => + ({ + userId: authContext.user?.id, + recordId: record.id, + objectMetadata: objectMetadataItem, + properties: { + after: record, + }, + }) satisfies ObjectRecordCreateEvent, + ), + authContext.workspace.id, + ); + } + + private async triggerWebhooks( + jobsData: Record[] | undefined, + operation: CallWebhookJobsJobOperation, + options: WorkspaceQueryRunnerOptions, + ) { + if (!Array.isArray(jobsData)) { + return; + } + jobsData.forEach((jobData) => { + this.messageQueueService.add( + CallWebhookJobsJob.name, + { + record: jobData, + workspaceId: options.authContext.workspace.id, + operation, + objectMetadataItem: options.objectMetadataItem, + }, + { retryLimit: 3 }, + ); + }); } @LogExecutionTime() 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 34ab5e849..f66497a68 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 @@ -1,9 +1,14 @@ -import { InsertResult } from 'typeorm'; +import graphqlFields from 'graphql-fields'; +import { In, InsertResult } from 'typeorm'; import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; +import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; +import { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper'; +import { getObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util'; +import { generateObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; export class GraphqlQueryCreateManyResolverService { @@ -17,17 +22,58 @@ export class GraphqlQueryCreateManyResolverService { args: CreateManyResolverArgs>, options: WorkspaceQueryRunnerOptions, ): Promise { - const { authContext, objectMetadataItem } = options; + const { authContext, objectMetadataItem, objectMetadataCollection, info } = + options; const repository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( authContext.workspace.id, objectMetadataItem.nameSingular, ); - const insertResult: InsertResult = !args.upsert - ? await repository.insert(args.data) - : await repository.upsert(args.data, ['id']); + const objectMetadataMap = generateObjectMetadataMap( + objectMetadataCollection, + ); + const objectMetadata = getObjectMetadataOrThrow( + objectMetadataMap, + objectMetadataItem.nameSingular, + ); + const graphqlQueryParser = new GraphqlQueryParser( + objectMetadata.fields, + objectMetadataMap, + ); - return insertResult.generatedMaps as ObjectRecord[]; + const selectedFields = graphqlFields(info); + + const { select, relations } = graphqlQueryParser.parseSelectedFields( + objectMetadataItem, + selectedFields, + ); + + const objectRecords: InsertResult = !args.upsert + ? await repository.insert(args.data) + : await repository.upsert(args.data, { + conflictPaths: ['id'], + skipUpdateIfNoValuesChanged: true, + }); + + const upsertedRecords = await repository.find({ + where: { + id: In(objectRecords.generatedMaps.map((record) => record.id)), + }, + select, + relations, + }); + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionMapper(objectMetadataMap); + + return upsertedRecords.map((record: ObjectRecord) => + typeORMObjectRecordsParser.processRecord( + record, + objectMetadataItem.nameSingular, + 1, + 1, + ), + ); } } 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 ab00e0dfd..5a48d71b4 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 @@ -61,9 +61,11 @@ export class GraphqlQueryFindManyResolverService { objectMetadataMap, ); + const selectedFields = graphqlFields(info); + const { select, relations } = graphqlQueryParser.parseSelectedFields( objectMetadataItem, - graphqlFields(info), + selectedFields, ); const isForwardPagination = !isDefined(args.before); const order = graphqlQueryParser.parseOrder( @@ -84,7 +86,10 @@ export class GraphqlQueryFindManyResolverService { relations, take: limit + 1, }; - const totalCount = await repository.count({ where }); + + const totalCount = isDefined(selectedFields.totalCount) + ? await repository.count({ where }) + : 0; if (cursor) { applyRangeFilter(where, cursor, isForwardPagination); 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 4ccaae2d9..fc5740af1 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 @@ -51,9 +51,11 @@ export class GraphqlQueryFindOneResolverService { objectMetadataMap, ); + const selectedFields = graphqlFields(info); + const { select, relations } = graphqlQueryParser.parseSelectedFields( objectMetadataItem, - graphqlFields(info), + selectedFields, ); const where = graphqlQueryParser.parseFilter(args.filter ?? ({} as Filter)); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module.ts index 066886e98..5d1771aad 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module.ts @@ -1,7 +1,6 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { GraphqlQueryRunnerModule } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.module'; import { WorkspaceQueryBuilderModule } from 'src/engine/api/graphql/workspace-query-builder/workspace-query-builder.module'; import { RecordPositionBackfillCommand } from 'src/engine/api/graphql/workspace-query-runner/commands/0-20-record-position-backfill.command'; import { workspaceQueryRunnerFactories } from 'src/engine/api/graphql/workspace-query-runner/factories'; @@ -32,7 +31,6 @@ import { EntityEventsToDbListener } from './listeners/entity-events-to-db.listen AnalyticsModule, DuplicateModule, FileModule, - GraphqlQueryRunnerModule, FeatureFlagModule, ], providers: [ @@ -42,6 +40,6 @@ import { EntityEventsToDbListener } from './listeners/entity-events-to-db.listen TelemetryListener, RecordPositionBackfillCommand, ], - exports: [WorkspaceQueryRunnerService], + exports: [WorkspaceQueryRunnerService, ...workspaceQueryRunnerFactories], }) export class WorkspaceQueryRunnerModule {} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service.ts index b754b4094..26526cf1f 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service.ts @@ -6,7 +6,6 @@ import { DataSource, In } from 'typeorm'; import { Record as IRecord, RecordFilter, - RecordOrderBy, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; import { @@ -16,8 +15,6 @@ import { DeleteOneResolverArgs, DestroyManyResolverArgs, FindDuplicatesResolverArgs, - FindManyResolverArgs, - FindOneResolverArgs, ResolverArgsType, RestoreManyResolverArgs, UpdateManyResolverArgs, @@ -25,7 +22,6 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; -import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { WorkspaceQueryBuilderFactory } from 'src/engine/api/graphql/workspace-query-builder/workspace-query-builder.factory'; 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'; @@ -46,8 +42,6 @@ import { EnvironmentService } from 'src/engine/core-modules/environment/environm 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 { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; @@ -85,76 +79,8 @@ export class WorkspaceQueryRunnerService { private readonly workspaceQueryHookService: WorkspaceQueryHookService, private readonly environmentService: EnvironmentService, private readonly duplicateService: DuplicateService, - private readonly featureFlagService: FeatureFlagService, - private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} - async findMany< - Record extends IRecord = IRecord, - Filter extends RecordFilter = RecordFilter, - OrderBy extends RecordOrderBy = RecordOrderBy, - >( - args: FindManyResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise | undefined> { - const { authContext, objectMetadataItem } = options; - - console.log( - `running findMany for ${objectMetadataItem.nameSingular} on workspace ${authContext.workspace.id}`, - ); - const hookedArgs = - await this.workspaceQueryHookService.executePreQueryHooks( - authContext, - objectMetadataItem.nameSingular, - 'findMany', - args, - ); - - const computedArgs = (await this.queryRunnerArgsFactory.create( - hookedArgs, - options, - ResolverArgsType.FindMany, - )) as FindManyResolverArgs; - - return this.graphqlQueryRunnerService.findMany(computedArgs, options); - } - - async findOne< - Record extends IRecord = IRecord, - Filter extends RecordFilter = RecordFilter, - >( - args: FindOneResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise { - if (!args.filter || Object.keys(args.filter).length === 0) { - throw new WorkspaceQueryRunnerException( - 'Missing filter argument', - WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT, - ); - } - const { authContext, objectMetadataItem } = options; - - console.log( - `running findOne for ${objectMetadataItem.nameSingular} on workspace ${authContext.workspace.id}`, - ); - - const hookedArgs = - await this.workspaceQueryHookService.executePreQueryHooks( - authContext, - objectMetadataItem.nameSingular, - 'findOne', - args, - ); - - const computedArgs = (await this.queryRunnerArgsFactory.create( - hookedArgs, - options, - ResolverArgsType.FindOne, - )) as FindOneResolverArgs; - - return this.graphqlQueryRunnerService.findOne(computedArgs, options); - } - async findDuplicates( args: FindDuplicatesResolverArgs>, options: WorkspaceQueryRunnerOptions, @@ -233,16 +159,6 @@ export class WorkspaceQueryRunnerService { ): Promise { const { authContext, objectMetadataItem } = options; - console.log( - `running createMany for ${objectMetadataItem.nameSingular} on workspace ${authContext.workspace.id}`, - ); - - const isQueryRunnerTwentyORMEnabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, - authContext.workspace.id, - ); - assertMutationNotOnRemoteObject(objectMetadataItem); if (args.upsert) { @@ -269,13 +185,6 @@ export class WorkspaceQueryRunnerService { ResolverArgsType.CreateMany, )) as CreateManyResolverArgs; - if (isQueryRunnerTwentyORMEnabled) { - return (await this.graphqlQueryRunnerService.createMany( - computedArgs, - options, - )) as Record[]; - } - const query = await this.workspaceQueryBuilderFactory.createMany( computedArgs, options, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts index 467488192..872ab7906 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts @@ -7,8 +7,11 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; @Injectable() export class CreateManyResolverFactory @@ -18,6 +21,8 @@ export class CreateManyResolverFactory constructor( private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly featureFlagService: FeatureFlagService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -25,15 +30,27 @@ export class CreateManyResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, context, info) => { + return async (_source, args, _context, info) => { try { - return await this.workspaceQueryRunnerService.createMany(args, { + const options = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + }; + + const isQueryRunnerTwentyORMEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, + internalContext.authContext.workspace.id, + ); + + if (isQueryRunnerTwentyORMEnabled) { + return await this.graphqlQueryRunnerService.createMany(args, options); + } + + return await this.workspaceQueryRunnerService.createMany(args, options); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts index 24fdfe023..edf9206a1 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts @@ -7,8 +7,11 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; @Injectable() export class CreateOneResolverFactory @@ -18,6 +21,8 @@ export class CreateOneResolverFactory constructor( private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly featureFlagService: FeatureFlagService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -25,8 +30,26 @@ export class CreateOneResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, context, info) => { + return async (_source, args, _context, info) => { try { + const options = { + authContext: internalContext.authContext, + objectMetadataItem: internalContext.objectMetadataItem, + info, + fieldMetadataCollection: internalContext.fieldMetadataCollection, + objectMetadataCollection: internalContext.objectMetadataCollection, + }; + + const isQueryRunnerTwentyORMEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, + internalContext.authContext.workspace.id, + ); + + if (isQueryRunnerTwentyORMEnabled) { + return await this.graphqlQueryRunnerService.createOne(args, options); + } + return await this.workspaceQueryRunnerService.createOne(args, { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts index 76391b27e..2dd452a29 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts @@ -7,8 +7,8 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; -import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; @Injectable() export class FindManyResolverFactory @@ -17,7 +17,7 @@ export class FindManyResolverFactory public static methodName = 'findMany' as const; constructor( - private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -25,15 +25,17 @@ export class FindManyResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, context, info) => { + return async (_source, args, _context, info) => { try { - return await this.workspaceQueryRunnerService.findMany(args, { + const options = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + }; + + return await this.graphqlQueryRunnerService.findMany(args, options); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts index d6a679593..3dbbc2330 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts @@ -7,8 +7,8 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; -import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; @Injectable() export class FindOneResolverFactory @@ -17,7 +17,7 @@ export class FindOneResolverFactory public static methodName = 'findOne' as const; constructor( - private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -25,15 +25,17 @@ export class FindOneResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, context, info) => { + return async (_source, args, _context, info) => { try { - return await this.workspaceQueryRunnerService.findOne(args, { + const options = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + }; + + return await this.graphqlQueryRunnerService.findOne(args, options); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver-builder.module.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver-builder.module.ts index 93cf9e920..3aafdd792 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver-builder.module.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver-builder.module.ts @@ -2,13 +2,18 @@ import { Module } from '@nestjs/common'; import { GraphqlQueryRunnerModule } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.module'; import { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module'; +import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { WorkspaceResolverFactory } from './workspace-resolver.factory'; import { workspaceResolverBuilderFactories } from './factories/factories'; @Module({ - imports: [WorkspaceQueryRunnerModule, GraphqlQueryRunnerModule], + imports: [ + WorkspaceQueryRunnerModule, + GraphqlQueryRunnerModule, + FeatureFlagModule, + ], providers: [...workspaceResolverBuilderFactories, WorkspaceResolverFactory], exports: [WorkspaceResolverFactory], }) diff --git a/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-column.factory.ts b/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-column.factory.ts index a72fe08c7..db67298f1 100644 --- a/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-column.factory.ts +++ b/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-column.factory.ts @@ -79,6 +79,7 @@ export class EntitySchemaColumnFactory { nullable: fieldMetadata.isNullable, createDate: key === 'createdAt', updateDate: key === 'updatedAt', + deleteDate: key === 'deletedAt', array: fieldMetadata.type === FieldMetadataType.MULTI_SELECT, default: defaultValue, };