diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory.ts index ca2506d18aa9..e3ada8ac7cea 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory.ts @@ -8,6 +8,7 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service'; +import { GraphqlQueryDestroyManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service'; import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service'; import { GraphqlQueryFindDuplicatesResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service'; import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service'; @@ -37,6 +38,8 @@ export class GraphqlQueryResolverFactory { return this.moduleRef.get(GraphqlQueryCreateManyResolverService); case 'destroyOne': return this.moduleRef.get(GraphqlQueryDestroyOneResolverService); + case 'destroyMany': + return this.moduleRef.get(GraphqlQueryDestroyManyResolverService); case 'updateOne': case 'deleteOne': return this.moduleRef.get(GraphqlQueryUpdateOneResolverService); 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 8c689ff8a979..642e11c81b1f 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 @@ -3,6 +3,7 @@ import { Module } from '@nestjs/common'; import { GraphqlQueryResolverFactory } from 'src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory'; import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service'; +import { GraphqlQueryDestroyManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service'; import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service'; import { GraphqlQueryFindDuplicatesResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service'; import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service'; @@ -16,14 +17,15 @@ import { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-que import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; const graphqlQueryResolvers = [ - GraphqlQueryFindOneResolverService, - GraphqlQueryFindManyResolverService, - GraphqlQueryFindDuplicatesResolverService, GraphqlQueryCreateManyResolverService, + GraphqlQueryDestroyManyResolverService, GraphqlQueryDestroyOneResolverService, - GraphqlQueryUpdateOneResolverService, - GraphqlQueryUpdateManyResolverService, + GraphqlQueryFindDuplicatesResolverService, + GraphqlQueryFindManyResolverService, + GraphqlQueryFindOneResolverService, GraphqlQuerySearchResolverService, + GraphqlQueryUpdateManyResolverService, + GraphqlQueryUpdateOneResolverService, ]; @Module({ 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 4e3475927ecd..d3d47daed171 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 @@ -13,6 +13,7 @@ import { CreateOneResolverArgs, DeleteManyResolverArgs, DeleteOneResolverArgs, + DestroyManyResolverArgs, DestroyOneResolverArgs, FindDuplicatesResolverArgs, FindManyResolverArgs, @@ -285,6 +286,25 @@ export class GraphqlQueryRunnerService { return result; } + @LogExecutionTime() + async destroyMany( + args: DestroyManyResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const result = await this.executeQuery< + DestroyManyResolverArgs, + ObjectRecord[] + >('destroyMany', args, options); + + this.apiEventEmitterService.emitDestroyEvents( + result, + options.authContext, + options.objectMetadataItem, + ); + + return result; + } + @LogExecutionTime() public async restoreMany( args: RestoreManyResolverArgs, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts index 5ccff3af114d..54220315345a 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts @@ -27,25 +27,34 @@ export class ObjectRecordsToGraphqlConnectionHelper { this.objectMetadataMap = objectMetadataMap; } - public createConnection( - objectRecords: ObjectRecord[], - objectName: string, - take: number, - totalCount: number, - order: RecordOrderBy | undefined, - hasNextPage: boolean, - hasPreviousPage: boolean, + public createConnection({ + objectRecords, + objectName, + take, + totalCount, + order, + hasNextPage, + hasPreviousPage, depth = 0, - ): IConnection { + }: { + objectRecords: ObjectRecord[]; + objectName: string; + take: number; + totalCount: number; + order?: RecordOrderBy; + hasNextPage: boolean; + hasPreviousPage: boolean; + depth?: number; + }): IConnection { const edges = (objectRecords ?? []).map((objectRecord) => ({ - node: this.processRecord( + node: this.processRecord({ objectRecord, objectName, take, totalCount, order, depth, - ), + }), cursor: encodeCursor(objectRecord, order), })); @@ -61,14 +70,21 @@ export class ObjectRecordsToGraphqlConnectionHelper { }; } - public processRecord>( - objectRecord: T, - objectName: string, - take: number, - totalCount: number, - order?: RecordOrderBy, + public processRecord>({ + objectRecord, + objectName, + take, + totalCount, + order, depth = 0, - ): T { + }: { + objectRecord: T; + objectName: string; + take: number; + totalCount: number; + order?: RecordOrderBy; + depth?: number; + }): T { if (depth >= CONNECTION_MAX_DEPTH) { throw new GraphqlQueryRunnerException( `Maximum depth of ${CONNECTION_MAX_DEPTH} reached`, @@ -97,27 +113,31 @@ export class ObjectRecordsToGraphqlConnectionHelper { if (isRelationFieldMetadataType(fieldMetadata.type)) { if (Array.isArray(value)) { - processedObjectRecord[key] = this.createConnection( - value, - getRelationObjectMetadata(fieldMetadata, this.objectMetadataMap) - .nameSingular, + processedObjectRecord[key] = this.createConnection({ + objectRecords: value, + objectName: getRelationObjectMetadata( + fieldMetadata, + this.objectMetadataMap, + ).nameSingular, take, - value.length, + totalCount: value.length, order, - false, - false, - depth + 1, - ); + hasNextPage: false, + hasPreviousPage: false, + depth: depth + 1, + }); } else if (isPlainObject(value)) { - processedObjectRecord[key] = this.processRecord( - value, - getRelationObjectMetadata(fieldMetadata, this.objectMetadataMap) - .nameSingular, + processedObjectRecord[key] = this.processRecord({ + objectRecord: value, + objectName: getRelationObjectMetadata( + fieldMetadata, + this.objectMetadataMap, + ).nameSingular, take, totalCount, order, - depth + 1, - ); + depth: depth + 1, + }); } } else if (isCompositeFieldMetadataType(fieldMetadata.type)) { processedObjectRecord[key] = this.processCompositeField( 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 aa8a81ce85a6..d5ee25e9be6d 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 @@ -93,12 +93,12 @@ export class GraphqlQueryCreateManyResolverService new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); return upsertedRecords.map((record: ObjectRecord) => - typeORMObjectRecordsParser.processRecord( - record, - objectMetadataMapItem.nameSingular, - 1, - 1, - ), + typeORMObjectRecordsParser.processRecord({ + objectRecord: record, + objectName: objectMetadataMapItem.nameSingular, + take: 1, + totalCount: 1, + }), ); } 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 new file mode 100644 index 000000000000..04ceddf9ac9d --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts @@ -0,0 +1,108 @@ +import { Injectable } from '@nestjs/common'; + +import graphqlFields from 'graphql-fields'; + +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; +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 { DestroyManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant'; +import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; +import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; + +@Injectable() +export class GraphqlQueryDestroyManyResolverService + implements ResolverService +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} + + async resolve( + args: DestroyManyResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const { authContext, objectMetadataMapItem, objectMetadataMap, info } = + options; + const dataSource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace( + authContext.workspace.id, + ); + + const repository = dataSource.getRepository( + objectMetadataMapItem.nameSingular, + ); + + const graphqlQueryParser = new GraphqlQueryParser( + objectMetadataMapItem.fields, + objectMetadataMap, + ); + + const selectedFields = graphqlFields(info); + + const { relations } = graphqlQueryParser.parseSelectedFields( + objectMetadataMapItem, + selectedFields, + ); + + const queryBuilder = repository.createQueryBuilder( + objectMetadataMapItem.nameSingular, + ); + + const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( + queryBuilder, + objectMetadataMapItem.nameSingular, + args.filter, + ); + + const nonFormattedDeletedObjectRecords = await withFilterQueryBuilder + .delete() + .returning('*') + .execute(); + + const deletedRecords = formatResult( + nonFormattedDeletedObjectRecords.raw, + objectMetadataMapItem, + objectMetadataMap, + ); + + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); + + if (relations) { + await processNestedRelationsHelper.processNestedRelations( + objectMetadataMap, + objectMetadataMapItem, + deletedRecords, + relations, + QUERY_MAX_RECORDS, + authContext, + dataSource, + ); + } + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + + return deletedRecords.map((record: ObjectRecord) => + typeORMObjectRecordsParser.processRecord({ + objectRecord: record, + objectName: objectMetadataMapItem.nameSingular, + take: 1, + totalCount: 1, + }), + ); + } + + async validate( + args: DestroyManyResolverArgs, + _options: WorkspaceQueryRunnerOptions, + ): Promise { + if (!args.filter) { + throw new Error('Filter is required'); + } + } +} 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 3540dcbf9559..483222a49050 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 @@ -1,14 +1,20 @@ import { Injectable } from '@nestjs/common'; +import graphqlFields from 'graphql-fields'; + import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; 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 { DestroyOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; +import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant'; import { GraphqlQueryRunnerException, GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; +import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; +import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @@ -24,19 +30,43 @@ export class GraphqlQueryDestroyOneResolverService args: DestroyOneResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise { - const { authContext, objectMetadataMapItem, objectMetadataMap } = options; - const repository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( + const { authContext, objectMetadataMapItem, objectMetadataMap, info } = + options; + const dataSource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace( authContext.workspace.id, - objectMetadataMapItem.nameSingular, ); - const nonFormattedRecordBeforeDeletion = await repository.findOne({ - where: { id: args.id }, - withDeleted: true, - }); + const repository = dataSource.getRepository( + objectMetadataMapItem.nameSingular, + ); + + const graphqlQueryParser = new GraphqlQueryParser( + objectMetadataMapItem.fields, + objectMetadataMap, + ); + + const selectedFields = graphqlFields(info); + + const { relations } = graphqlQueryParser.parseSelectedFields( + objectMetadataMapItem, + selectedFields, + ); - if (!nonFormattedRecordBeforeDeletion) { + const queryBuilder = repository.createQueryBuilder( + objectMetadataMapItem.nameSingular, + ); + + const nonFormattedDeletedObjectRecords = await queryBuilder + .where({ + id: args.id, + }) + .take(1) + .delete() + .returning('*') + .execute(); + + if (!nonFormattedDeletedObjectRecords.affected) { throw new GraphqlQueryRunnerException( 'Record not found', GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, @@ -44,14 +74,34 @@ export class GraphqlQueryDestroyOneResolverService } const recordBeforeDeletion = formatResult( - [nonFormattedRecordBeforeDeletion], + nonFormattedDeletedObjectRecords.raw, objectMetadataMapItem, objectMetadataMap, )[0]; - await repository.delete(args.id); + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); - return recordBeforeDeletion as ObjectRecord; + if (relations) { + await processNestedRelationsHelper.processNestedRelations( + objectMetadataMap, + objectMetadataMapItem, + [recordBeforeDeletion], + relations, + QUERY_MAX_RECORDS, + authContext, + dataSource, + ); + } + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + + return typeORMObjectRecordsParser.processRecord({ + objectRecord: recordBeforeDeletion, + objectName: objectMetadataMapItem.nameSingular, + take: 1, + totalCount: 1, + }); } async validate( 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 00561933043d..d3bc72fa8220 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 @@ -88,15 +88,15 @@ export class GraphqlQueryFindDuplicatesResolverService ); if (isEmpty(duplicateConditions)) { - return typeORMObjectRecordsParser.createConnection( - [], - objectMetadataMapItem.nameSingular, - 0, - 0, - [{ id: OrderByDirection.AscNullsFirst }], - false, - false, - ); + return typeORMObjectRecordsParser.createConnection({ + objectRecords: [], + objectName: objectMetadataMapItem.nameSingular, + take: 0, + totalCount: 0, + order: [{ id: OrderByDirection.AscNullsFirst }], + hasNextPage: false, + hasPreviousPage: false, + }); } const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( @@ -114,15 +114,15 @@ export class GraphqlQueryFindDuplicatesResolverService objectMetadataMap, ); - return typeORMObjectRecordsParser.createConnection( - duplicates, - objectMetadataMapItem.nameSingular, - duplicates.length, - duplicates.length, - [{ id: OrderByDirection.AscNullsFirst }], - false, - false, - ); + return typeORMObjectRecordsParser.createConnection({ + objectRecords: duplicates, + objectName: objectMetadataMapItem.nameSingular, + take: duplicates.length, + totalCount: duplicates.length, + order: [{ id: OrderByDirection.AscNullsFirst }], + hasNextPage: false, + hasPreviousPage: false, + }); }), ); 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 85fdd3948274..9411c5502103 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 @@ -176,15 +176,15 @@ export class GraphqlQueryFindManyResolverService const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); - const result = typeORMObjectRecordsParser.createConnection( + const result = typeORMObjectRecordsParser.createConnection({ objectRecords, - objectMetadataMapItem.nameSingular, - limit, + objectName: objectMetadataMapItem.nameSingular, + take: limit, totalCount, - orderByWithIdCondition, + order: orderByWithIdCondition, hasNextPage, hasPreviousPage, - ); + }); return result; } 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 164f30b68664..42c8daae8079 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 @@ -113,12 +113,12 @@ export class GraphqlQueryFindOneResolverService const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); - return typeORMObjectRecordsParser.processRecord( - objectRecords[0], - objectMetadataMapItem.nameSingular, - 1, - 1, - ) as ObjectRecord; + return typeORMObjectRecordsParser.processRecord({ + objectRecord: objectRecords[0], + objectName: objectMetadataMapItem.nameSingular, + take: 1, + totalCount: 1, + }) as ObjectRecord; } async validate( diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts index fe9d86f6cd84..e6f3273b9c09 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts @@ -44,15 +44,15 @@ export class GraphqlQuerySearchResolverService new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); if (!args.searchInput) { - return typeORMObjectRecordsParser.createConnection( - [], - objectMetadataItem.nameSingular, - 0, - 0, - [{ id: OrderByDirection.AscNullsFirst }], - false, - false, - ); + return typeORMObjectRecordsParser.createConnection({ + objectRecords: [], + objectName: objectMetadataItem.nameSingular, + take: 0, + totalCount: 0, + order: [{ id: OrderByDirection.AscNullsFirst }], + hasNextPage: false, + hasPreviousPage: false, + }); } const searchTerms = this.formatSearchTerms(args.searchInput); @@ -76,15 +76,15 @@ export class GraphqlQuerySearchResolverService const totalCount = await repository.count(); const order = undefined; - return typeORMObjectRecordsParser.createConnection( - objectRecords ?? [], - objectMetadataItem.nameSingular, - limit, + return typeORMObjectRecordsParser.createConnection({ + objectRecords: objectRecords ?? [], + objectName: objectMetadataItem.nameSingular, + take: limit, totalCount, order, - false, - false, - ); + hasNextPage: false, + hasPreviousPage: false, + }); } private formatSearchTerms(searchTerm: string) { 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 d8854223794b..270e6fd8196f 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 @@ -65,15 +65,13 @@ export class GraphqlQueryUpdateManyResolverService const data = formatData(args.data, objectMetadataMapItem); - const result = await withFilterQueryBuilder + const nonFormattedUpdatedObjectRecords = await withFilterQueryBuilder .update(data) .returning('*') .execute(); - const nonFormattedUpdatedObjectRecords = result.raw; - const updatedRecords = formatResult( - nonFormattedUpdatedObjectRecords, + nonFormattedUpdatedObjectRecords.raw, objectMetadataMapItem, objectMetadataMap, ); @@ -96,12 +94,12 @@ export class GraphqlQueryUpdateManyResolverService new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); return updatedRecords.map((record: ObjectRecord) => - typeORMObjectRecordsParser.processRecord( - record, - objectMetadataMapItem.nameSingular, - 1, - 1, - ), + typeORMObjectRecordsParser.processRecord({ + objectRecord: record, + objectName: objectMetadataMapItem.nameSingular, + take: 1, + totalCount: 1, + }), ); } @@ -110,6 +108,10 @@ export class GraphqlQueryUpdateManyResolverService options: WorkspaceQueryRunnerOptions, ): Promise { assertMutationNotOnRemoteObject(options.objectMetadataMapItem); - args.filter?.id?.in?.forEach((id: string) => assertIsValidUuid(id)); + if (!args.filter) { + throw new Error('Filter is required'); + } + + args.filter.id?.in?.forEach((id: string) => assertIsValidUuid(id)); } } 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 6fc4e1a72c58..8fe4396d2413 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 @@ -103,12 +103,12 @@ export class GraphqlQueryUpdateOneResolverService const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); - return typeORMObjectRecordsParser.processRecord( - updatedRecord, - objectMetadataMapItem.nameSingular, - 1, - 1, - ); + return typeORMObjectRecordsParser.processRecord({ + objectRecord: updatedRecord, + objectName: objectMetadataMapItem.nameSingular, + take: 1, + totalCount: 1, + }); } async validate( diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts index 2e6cf835effa..e90b93309c6f 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts @@ -8,8 +8,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 DestroyManyResolverFactory @@ -19,6 +22,8 @@ export class DestroyManyResolverFactory constructor( private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly featureFlagService: FeatureFlagService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -38,6 +43,19 @@ export class DestroyManyResolverFactory objectMetadataMapItem: internalContext.objectMetadataMapItem, }; + const isQueryRunnerTwentyORMEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, + internalContext.authContext.workspace.id, + ); + + if (isQueryRunnerTwentyORMEnabled) { + return await this.graphqlQueryRunnerService.destroyMany( + args, + options, + ); + } + return await this.workspaceQueryRunnerService.destroyMany( args, options,