diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/enums/database-event-action.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/enums/database-event-action.ts index dfe45b33a..ed5d1705b 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/enums/database-event-action.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/enums/database-event-action.ts @@ -3,4 +3,5 @@ export enum DatabaseEventAction { UPDATED = 'updated', DELETED = 'deleted', DESTROYED = 'destroyed', + RESTORED = 'restored', } 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 deleted file mode 100644 index e3ada8ac7..000000000 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { ModuleRef } from '@nestjs/core'; - -import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; -import { - ResolverArgs, - WorkspaceResolverBuilderMethodNames, -} 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'; -import { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service'; -import { GraphqlQuerySearchResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-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'; - -@Injectable() -export class GraphqlQueryResolverFactory { - constructor(private moduleRef: ModuleRef) {} - - public getResolver( - operationName: WorkspaceResolverBuilderMethodNames, - ): ResolverService { - switch (operationName) { - case 'findOne': - return this.moduleRef.get(GraphqlQueryFindOneResolverService); - case 'findMany': - return this.moduleRef.get(GraphqlQueryFindManyResolverService); - case 'findDuplicates': - return this.moduleRef.get(GraphqlQueryFindDuplicatesResolverService); - case 'search': - return this.moduleRef.get(GraphqlQuerySearchResolverService); - case 'createOne': - case 'createMany': - 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); - case 'updateMany': - case 'deleteMany': - case 'restoreMany': - return this.moduleRef.get(GraphqlQueryUpdateManyResolverService); - default: - throw new Error(`Unsupported operation: ${operationName}`); - } - } -} 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 642e11c81..a3384e52a 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,13 +1,16 @@ 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 { GraphqlQueryCreateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service'; +import { GraphqlQueryDeleteManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service'; +import { GraphqlQueryDeleteOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-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'; import { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service'; +import { GraphqlQueryRestoreManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service'; +import { GraphqlQueryRestoreOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service'; import { GraphqlQuerySearchResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-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'; @@ -18,11 +21,16 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature- const graphqlQueryResolvers = [ GraphqlQueryCreateManyResolverService, + GraphqlQueryCreateOneResolverService, + GraphqlQueryDeleteManyResolverService, + GraphqlQueryDeleteOneResolverService, GraphqlQueryDestroyManyResolverService, GraphqlQueryDestroyOneResolverService, GraphqlQueryFindDuplicatesResolverService, GraphqlQueryFindManyResolverService, GraphqlQueryFindOneResolverService, + GraphqlQueryRestoreManyResolverService, + GraphqlQueryRestoreOneResolverService, GraphqlQuerySearchResolverService, GraphqlQueryUpdateManyResolverService, GraphqlQueryUpdateOneResolverService, @@ -34,12 +42,7 @@ const graphqlQueryResolvers = [ WorkspaceQueryRunnerModule, FeatureFlagModule, ], - providers: [ - GraphqlQueryRunnerService, - GraphqlQueryResolverFactory, - ApiEventEmitterService, - ...graphqlQueryResolvers, - ], - exports: [GraphqlQueryRunnerService], + providers: [ApiEventEmitterService, ...graphqlQueryResolvers], + exports: [...graphqlQueryResolvers], }) export class GraphqlQueryRunnerModule {} 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 deleted file mode 100644 index b79137ab8..000000000 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service.ts +++ /dev/null @@ -1,362 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { - ObjectRecord, - ObjectRecordFilter, - ObjectRecordOrderBy, -} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; -import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; -import { IEdge } from 'src/engine/api/graphql/workspace-query-runner/interfaces/edge.interface'; -import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; -import { - CreateManyResolverArgs, - CreateOneResolverArgs, - DeleteManyResolverArgs, - DeleteOneResolverArgs, - DestroyManyResolverArgs, - DestroyOneResolverArgs, - FindDuplicatesResolverArgs, - FindManyResolverArgs, - FindOneResolverArgs, - ResolverArgs, - ResolverArgsType, - RestoreManyResolverArgs, - SearchResolverArgs, - UpdateManyResolverArgs, - UpdateOneResolverArgs, - WorkspaceResolverBuilderMethodNames, -} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; - -import { GraphqlQueryResolverFactory } from 'src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory'; -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 { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service'; -import { LogExecutionTime } from 'src/engine/decorators/observability/log-execution-time.decorator'; -import { capitalize } from 'src/utils/capitalize'; - -@Injectable() -export class GraphqlQueryRunnerService { - constructor( - private readonly workspaceQueryHookService: WorkspaceQueryHookService, - private readonly queryRunnerArgsFactory: QueryRunnerArgsFactory, - private readonly queryResultGettersFactory: QueryResultGettersFactory, - private readonly graphqlQueryResolverFactory: GraphqlQueryResolverFactory, - private readonly apiEventEmitterService: ApiEventEmitterService, - ) {} - - /** QUERIES */ - - @LogExecutionTime() - async findOne( - args: FindOneResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise { - return this.executeQuery, T>( - 'findOne', - args, - options, - ); - } - - @LogExecutionTime() - async findMany< - T extends ObjectRecord, - Filter extends ObjectRecordFilter, - OrderBy extends ObjectRecordOrderBy, - >( - args: FindManyResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise>> { - return this.executeQuery< - FindManyResolverArgs, - IConnection> - >('findMany', args, options); - } - - @LogExecutionTime() - async findDuplicates( - args: FindDuplicatesResolverArgs>, - options: WorkspaceQueryRunnerOptions, - ): Promise[]> { - return this.executeQuery< - FindDuplicatesResolverArgs>, - IConnection[] - >('findDuplicates', args, options); - } - - @LogExecutionTime() - async search( - args: SearchResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise> { - return this.executeQuery>( - 'search', - args, - options, - ); - } - - /** MUTATIONS */ - - @LogExecutionTime() - async createOne( - args: CreateOneResolverArgs>, - options: WorkspaceQueryRunnerOptions, - ): Promise { - const results = await this.executeQuery< - CreateManyResolverArgs>, - T[] - >('createMany', { data: [args.data], upsert: args.upsert }, options); - - // TODO: emitCreateEvents should be moved to the ORM layer - if (results) { - this.apiEventEmitterService.emitCreateEvents( - results, - options.authContext, - options.objectMetadataItemWithFieldMaps, - ); - } - - return results[0]; - } - - @LogExecutionTime() - async createMany( - args: CreateManyResolverArgs>, - options: WorkspaceQueryRunnerOptions, - ): Promise { - const results = await this.executeQuery< - CreateManyResolverArgs>, - T[] - >('createMany', args, options); - - if (results) { - this.apiEventEmitterService.emitCreateEvents( - results, - options.authContext, - options.objectMetadataItemWithFieldMaps, - ); - } - - return results; - } - - @LogExecutionTime() - public async updateOne( - args: UpdateOneResolverArgs>, - options: WorkspaceQueryRunnerOptions, - ): Promise { - const existingRecord = await this.executeQuery( - 'findOne', - { - filter: { id: { eq: args.id } }, - }, - options, - ); - - const result = await this.executeQuery< - UpdateOneResolverArgs>, - T - >('updateOne', args, options); - - this.apiEventEmitterService.emitUpdateEvents( - [existingRecord], - [result], - Object.keys(args.data), - options.authContext, - options.objectMetadataItemWithFieldMaps, - ); - - return result; - } - - @LogExecutionTime() - public async updateMany( - args: UpdateManyResolverArgs>, - options: WorkspaceQueryRunnerOptions, - ): Promise { - const existingRecords = await this.executeQuery< - FindManyResolverArgs, - IConnection> - >( - 'findMany', - { - filter: args.filter, - }, - options, - ); - - const result = await this.executeQuery< - UpdateManyResolverArgs>, - T[] - >('updateMany', args, options); - - this.apiEventEmitterService.emitUpdateEvents( - existingRecords.edges.map((edge) => edge.node), - result, - Object.keys(args.data), - options.authContext, - options.objectMetadataItemWithFieldMaps, - ); - - return result; - } - - @LogExecutionTime() - public async deleteOne( - args: DeleteOneResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise { - const result = await this.executeQuery< - UpdateOneResolverArgs>, - T - >( - 'deleteOne', - { - id: args.id, - data: { deletedAt: new Date() } as Partial, - }, - options, - ); - - this.apiEventEmitterService.emitDeletedEvents( - [result], - options.authContext, - options.objectMetadataItemWithFieldMaps, - ); - - return result; - } - - @LogExecutionTime() - public async deleteMany( - args: DeleteManyResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise { - const result = await this.executeQuery< - UpdateManyResolverArgs>, - T[] - >( - 'deleteMany', - { - filter: args.filter, - - data: { deletedAt: new Date() } as Partial, - }, - options, - ); - - this.apiEventEmitterService.emitDeletedEvents( - result, - options.authContext, - options.objectMetadataItemWithFieldMaps, - ); - - return result; - } - - @LogExecutionTime() - async destroyOne( - args: DestroyOneResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise { - const result = await this.executeQuery( - 'destroyOne', - args, - options, - ); - - this.apiEventEmitterService.emitDestroyEvents( - [result], - options.authContext, - options.objectMetadataItemWithFieldMaps, - ); - - return result; - } - - @LogExecutionTime() - async destroyMany( - args: DestroyManyResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise { - const result = await this.executeQuery( - 'destroyMany', - args, - options, - ); - - this.apiEventEmitterService.emitDestroyEvents( - result, - options.authContext, - options.objectMetadataItemWithFieldMaps, - ); - - return result; - } - - @LogExecutionTime() - public async restoreMany( - args: RestoreManyResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise { - return await this.executeQuery>, T>( - 'restoreMany', - { - filter: args.filter, - data: { deletedAt: null } as Partial, - }, - options, - ); - } - - private async executeQuery( - operationName: WorkspaceResolverBuilderMethodNames, - args: Input, - options: WorkspaceQueryRunnerOptions, - ): Promise { - const { authContext, objectMetadataItemWithFieldMaps } = options; - - const resolver = - this.graphqlQueryResolverFactory.getResolver(operationName); - - await resolver.validate(args, options); - - const hookedArgs = - await this.workspaceQueryHookService.executePreQueryHooks( - authContext, - objectMetadataItemWithFieldMaps.nameSingular, - operationName, - args, - ); - - const computedArgs = await this.queryRunnerArgsFactory.create( - hookedArgs, - options, - ResolverArgsType[capitalize(operationName)], - ); - - const results = await resolver.resolve(computedArgs as Input, options); - - const resultWithGetters = await this.queryResultGettersFactory.create( - results, - objectMetadataItemWithFieldMaps, - authContext.workspace.id, - options.objectMetadataMaps, - ); - - const resultWithGettersArray = Array.isArray(resultWithGetters) - ? resultWithGetters - : [resultWithGetters]; - - await this.workspaceQueryHookService.executePostQueryHooks( - authContext, - objectMetadataItemWithFieldMaps.nameSingular, - operationName, - resultWithGettersArray, - ); - - return resultWithGetters; - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts index add94baa3..708c79af8 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts @@ -403,7 +403,7 @@ export class ProcessNestedRelationsHelper { .take(limit) .getMany(); - const relationResults = formatResult( + const relationResults = formatResult( result, referenceObjectMetadata, objectMetadataMaps, 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 new file mode 100644 index 000000000..2a69fa359 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts @@ -0,0 +1,140 @@ +import { Inject, Injectable } from '@nestjs/common'; + +import graphqlFields from 'graphql-fields'; +import { DataSource, ObjectLiteral } from 'typeorm'; + +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; +import { IEdge } from 'src/engine/api/graphql/workspace-query-runner/interfaces/edge.interface'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; +import { + ResolverArgs, + ResolverArgsType, + WorkspaceResolverBuilderMethodNames, +} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +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 { 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 { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service'; +import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { capitalize } from 'src/utils/capitalize'; + +export type GraphqlQueryResolverExecutionArgs = { + args: Input; + options: WorkspaceQueryRunnerOptions; + dataSource: DataSource; + repository: WorkspaceRepository; + graphqlQueryParser: GraphqlQueryParser; + graphqlQuerySelectedFieldsResult: GraphqlQuerySelectedFieldsResult; +}; + +@Injectable() +export abstract class GraphqlQueryBaseResolverService< + Input extends ResolverArgs, + Response extends + | ObjectRecord + | ObjectRecord[] + | IConnection> + | IConnection>[], +> { + @Inject() + protected readonly workspaceQueryHookService: WorkspaceQueryHookService; + @Inject() + protected readonly queryRunnerArgsFactory: QueryRunnerArgsFactory; + @Inject() + protected readonly queryResultGettersFactory: QueryResultGettersFactory; + @Inject() + protected readonly apiEventEmitterService: ApiEventEmitterService; + @Inject() + protected readonly twentyORMGlobalManager: TwentyORMGlobalManager; + + public async execute( + args: Input, + options: WorkspaceQueryRunnerOptions, + operationName: WorkspaceResolverBuilderMethodNames, + ): Promise { + const { authContext, objectMetadataItemWithFieldMaps } = options; + + await this.validate(args, options); + + const hookedArgs = + await this.workspaceQueryHookService.executePreQueryHooks( + authContext, + objectMetadataItemWithFieldMaps.nameSingular, + operationName, + args, + ); + + const computedArgs = (await this.queryRunnerArgsFactory.create( + hookedArgs, + options, + ResolverArgsType[capitalize(operationName)], + )) as Input; + + const dataSource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace( + authContext.workspace.id, + ); + + const repository = dataSource.getRepository( + objectMetadataItemWithFieldMaps.nameSingular, + ); + + const graphqlQueryParser = new GraphqlQueryParser( + objectMetadataItemWithFieldMaps.fieldsByName, + options.objectMetadataMaps, + ); + + const selectedFields = graphqlFields(options.info); + + const graphqlQuerySelectedFieldsResult = + graphqlQueryParser.parseSelectedFields( + objectMetadataItemWithFieldMaps, + selectedFields, + ); + + const graphqlQueryResolverExecutionArgs = { + args: computedArgs, + options, + dataSource, + repository, + graphqlQueryParser, + graphqlQuerySelectedFieldsResult, + }; + + const results = await this.resolve(graphqlQueryResolverExecutionArgs); + + const resultWithGetters = await this.queryResultGettersFactory.create( + results, + objectMetadataItemWithFieldMaps, + authContext.workspace.id, + options.objectMetadataMaps, + ); + + const resultWithGettersArray = Array.isArray(resultWithGetters) + ? resultWithGetters + : [resultWithGetters]; + + await this.workspaceQueryHookService.executePostQueryHooks( + authContext, + objectMetadataItemWithFieldMaps.nameSingular, + operationName, + resultWithGettersArray, + ); + + return resultWithGetters; + } + + protected abstract resolve( + executionArgs: GraphqlQueryResolverExecutionArgs, + ): Promise; + + protected abstract validate( + args: Input, + options: WorkspaceQueryRunnerOptions, + ): Promise; +} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface.ts deleted file mode 100644 index f88691647..000000000 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; - -export interface ResolverService { - resolve: ( - args: ResolverArgs, - options: WorkspaceQueryRunnerOptions, - ) => Promise; - validate: ( - args: ResolverArgs, - options: WorkspaceQueryRunnerOptions, - ) => Promise; -} 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 19bc28cf0..070cc21a7 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,103 +1,81 @@ import { Injectable } from '@nestjs/common'; -import graphqlFields from 'graphql-fields'; import { In, InsertResult } from 'typeorm'; -import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; +import { + GraphqlQueryBaseResolverService, + GraphqlQueryResolverExecutionArgs, +} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service'; import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-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 { 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 { 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 { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() -export class GraphqlQueryCreateManyResolverService - implements ResolverService -{ - constructor( - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) {} +export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResolverService< + CreateManyResolverArgs, + ObjectRecord[] +> { + async resolve( + executionArgs: GraphqlQueryResolverExecutionArgs, + ): Promise { + const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } = + executionArgs.options; - async resolve( - args: CreateManyResolverArgs>, - options: WorkspaceQueryRunnerOptions, - ): Promise { - const { - authContext, - info, - objectMetadataMaps, - objectMetadataItemWithFieldMaps, - } = options; - const dataSource = - await this.twentyORMGlobalManager.getDataSourceForWorkspace( - authContext.workspace.id, - ); - - const repository = dataSource.getRepository( - objectMetadataItemWithFieldMaps.nameSingular, - ); - - const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataItemWithFieldMaps.fieldsByName, - objectMetadataMaps, - ); - - const selectedFields = graphqlFields(info); - - const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataItemWithFieldMaps, - selectedFields, - ); - - const objectRecords: InsertResult = !args.upsert - ? await repository.insert(args.data) - : await repository.upsert(args.data, { + const objectRecords: InsertResult = !executionArgs.args.upsert + ? await executionArgs.repository.insert(executionArgs.args.data) + : await executionArgs.repository.upsert(executionArgs.args.data, { conflictPaths: ['id'], skipUpdateIfNoValuesChanged: true, }); - const queryBuilder = repository.createQueryBuilder( + const queryBuilder = executionArgs.repository.createQueryBuilder( objectMetadataItemWithFieldMaps.nameSingular, ); - const nonFormattedUpsertedRecords = (await queryBuilder + const nonFormattedUpsertedRecords = await queryBuilder .where({ id: In(objectRecords.generatedMaps.map((record) => record.id)), }) .take(QUERY_MAX_RECORDS) - .getMany()) as ObjectRecord[]; + .getMany(); - const upsertedRecords = formatResult( + const upsertedRecords = formatResult( nonFormattedUpsertedRecords, objectMetadataItemWithFieldMaps, objectMetadataMaps, ); + this.apiEventEmitterService.emitCreateEvents( + upsertedRecords, + authContext, + objectMetadataItemWithFieldMaps, + ); + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); - if (relations) { + if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps, parentObjectRecords: upsertedRecords, - relations, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, - dataSource, + dataSource: executionArgs.dataSource, }); } const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); - return upsertedRecords.map((record: T) => + return upsertedRecords.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-create-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service.ts new file mode 100644 index 000000000..bce181745 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service.ts @@ -0,0 +1,98 @@ +import { Injectable } from '@nestjs/common'; + +import { In, InsertResult } from 'typeorm'; + +import { + GraphqlQueryBaseResolverService, + GraphqlQueryResolverExecutionArgs, +} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service'; +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; +import { CreateOneResolverArgs } 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 { 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 { 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 { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; + +@Injectable() +export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolverService< + CreateOneResolverArgs, + ObjectRecord +> { + async resolve( + executionArgs: GraphqlQueryResolverExecutionArgs, + ): Promise { + const { authContext, objectMetadataMaps, objectMetadataItemWithFieldMaps } = + executionArgs.options; + + const objectRecords: InsertResult = !executionArgs.args.upsert + ? await executionArgs.repository.insert(executionArgs.args.data) + : await executionArgs.repository.upsert(executionArgs.args.data, { + conflictPaths: ['id'], + skipUpdateIfNoValuesChanged: true, + }); + + const queryBuilder = executionArgs.repository.createQueryBuilder( + objectMetadataItemWithFieldMaps.nameSingular, + ); + + const nonFormattedUpsertedRecords = await queryBuilder + .where({ + id: In(objectRecords.generatedMaps.map((record) => record.id)), + }) + .take(QUERY_MAX_RECORDS) + .getMany(); + + const upsertedRecords = formatResult( + nonFormattedUpsertedRecords, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, + ); + + this.apiEventEmitterService.emitCreateEvents( + upsertedRecords, + authContext, + objectMetadataItemWithFieldMaps, + ); + + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); + + if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { + await processNestedRelationsHelper.processNestedRelations({ + objectMetadataMaps, + parentObjectMetadataItem: objectMetadataItemWithFieldMaps, + parentObjectRecords: upsertedRecords, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + limit: QUERY_MAX_RECORDS, + authContext, + dataSource: executionArgs.dataSource, + }); + } + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); + + return typeORMObjectRecordsParser.processRecord({ + objectRecord: upsertedRecords[0], + objectName: objectMetadataItemWithFieldMaps.nameSingular, + take: 1, + totalCount: 1, + }); + } + + async; + + async validate( + args: CreateOneResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps); + + if (args.data?.id) { + assertIsValidUuid(args.data.id); + } + } +} 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 new file mode 100644 index 000000000..377d7d6a6 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service.ts @@ -0,0 +1,100 @@ +import { Injectable } from '@nestjs/common'; + +import { + GraphqlQueryBaseResolverService, + GraphqlQueryResolverExecutionArgs, +} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service'; +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; +import { DeleteManyResolverArgs } 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 { 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 { 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 { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; +import { computeTableName } from 'src/engine/utils/compute-table-name.util'; + +@Injectable() +export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResolverService< + DeleteManyResolverArgs, + ObjectRecord[] +> { + async resolve( + executionArgs: GraphqlQueryResolverExecutionArgs, + ): Promise { + const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } = + executionArgs.options; + + const queryBuilder = executionArgs.repository.createQueryBuilder( + objectMetadataItemWithFieldMaps.nameSingular, + ); + + const tableName = computeTableName( + objectMetadataItemWithFieldMaps.nameSingular, + objectMetadataItemWithFieldMaps.isCustom, + ); + + executionArgs.graphqlQueryParser.applyFilterToBuilder( + queryBuilder, + tableName, + executionArgs.args.filter, + ); + + const nonFormattedDeletedObjectRecords = await queryBuilder + .softDelete() + .returning('*') + .execute(); + + const formattedDeletedRecords = formatResult( + nonFormattedDeletedObjectRecords.raw, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, + ); + + this.apiEventEmitterService.emitDeletedEvents( + formattedDeletedRecords, + authContext, + objectMetadataItemWithFieldMaps, + ); + + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); + + if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { + await processNestedRelationsHelper.processNestedRelations({ + objectMetadataMaps, + parentObjectMetadataItem: objectMetadataItemWithFieldMaps, + parentObjectRecords: formattedDeletedRecords, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + limit: QUERY_MAX_RECORDS, + authContext, + dataSource: executionArgs.dataSource, + }); + } + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); + + return formattedDeletedRecords.map((record: ObjectRecord) => + typeORMObjectRecordsParser.processRecord({ + objectRecord: record, + objectName: objectMetadataItemWithFieldMaps.nameSingular, + take: 1, + totalCount: 1, + }), + ); + } + + async validate( + args: DeleteManyResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise { + assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps); + 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-delete-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service.ts new file mode 100644 index 000000000..e7c183e02 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@nestjs/common'; + +import { + GraphqlQueryBaseResolverService, + GraphqlQueryResolverExecutionArgs, +} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service'; +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; +import { DeleteOneResolverArgs } 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 { 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 { 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 { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; + +@Injectable() +export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolverService< + DeleteOneResolverArgs, + ObjectRecord +> { + async resolve( + executionArgs: GraphqlQueryResolverExecutionArgs, + ): Promise { + const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } = + executionArgs.options; + + const queryBuilder = executionArgs.repository.createQueryBuilder( + objectMetadataItemWithFieldMaps.nameSingular, + ); + + const nonFormattedDeletedObjectRecords = await queryBuilder + .where({ id: executionArgs.args.id }) + .softDelete() + .returning('*') + .execute(); + + const formattedDeletedRecords = formatResult( + nonFormattedDeletedObjectRecords.raw, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, + ); + + this.apiEventEmitterService.emitDeletedEvents( + formattedDeletedRecords, + authContext, + objectMetadataItemWithFieldMaps, + ); + + if (formattedDeletedRecords.length === 0) { + throw new GraphqlQueryRunnerException( + 'Record not found', + GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, + ); + } + + const deletedRecord = formattedDeletedRecords[0]; + + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); + + if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { + await processNestedRelationsHelper.processNestedRelations({ + objectMetadataMaps, + parentObjectMetadataItem: objectMetadataItemWithFieldMaps, + parentObjectRecords: [deletedRecord], + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + limit: QUERY_MAX_RECORDS, + authContext, + dataSource: executionArgs.dataSource, + }); + } + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); + + return typeORMObjectRecordsParser.processRecord({ + objectRecord: deletedRecord, + objectName: objectMetadataItemWithFieldMaps.nameSingular, + take: 1, + totalCount: 1, + }); + } + + async validate( + args: DeleteOneResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise { + assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps); + assertIsValidUuid(args.id); + } +} 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 8b4176d26..abd5acc6d 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 @@ -1,98 +1,80 @@ 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 { + GraphqlQueryBaseResolverService, + GraphqlQueryResolverExecutionArgs, +} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service'; import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-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'; +import { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() -export class GraphqlQueryDestroyManyResolverService - implements ResolverService -{ - constructor( - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) {} +export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseResolverService< + DestroyManyResolverArgs, + ObjectRecord[] +> { + async resolve( + executionArgs: GraphqlQueryResolverExecutionArgs, + ): Promise { + const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } = + executionArgs.options; - async resolve( - args: DestroyManyResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise { - const { - authContext, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - info, - } = options; - - const dataSource = - await this.twentyORMGlobalManager.getDataSourceForWorkspace( - authContext.workspace.id, - ); - - const repository = dataSource.getRepository( + const queryBuilder = executionArgs.repository.createQueryBuilder( objectMetadataItemWithFieldMaps.nameSingular, ); - const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataItemWithFieldMaps.fieldsByName, - objectMetadataMaps, - ); - - const selectedFields = graphqlFields(info); - - const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataItemWithFieldMaps, - selectedFields, - ); - - const queryBuilder = repository.createQueryBuilder( + const tableName = computeTableName( objectMetadataItemWithFieldMaps.nameSingular, + objectMetadataItemWithFieldMaps.isCustom, ); - const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( + executionArgs.graphqlQueryParser.applyFilterToBuilder( queryBuilder, - objectMetadataItemWithFieldMaps.nameSingular, - args.filter, + tableName, + executionArgs.args.filter, ); - const nonFormattedDeletedObjectRecords = await withFilterQueryBuilder + const nonFormattedDeletedObjectRecords = await queryBuilder .delete() .returning('*') .execute(); - const deletedRecords = formatResult( + const deletedRecords = formatResult( nonFormattedDeletedObjectRecords.raw, objectMetadataItemWithFieldMaps, objectMetadataMaps, ); + this.apiEventEmitterService.emitDestroyEvents( + deletedRecords, + authContext, + objectMetadataItemWithFieldMaps, + ); + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); - if (relations) { + if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps, parentObjectRecords: deletedRecords, - relations, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, - dataSource, + dataSource: executionArgs.dataSource, }); } const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); - return deletedRecords.map((record: T) => + return deletedRecords.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 044370a07..c477263d0 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,8 +1,9 @@ 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 { + GraphqlQueryBaseResolverService, + GraphqlQueryResolverExecutionArgs, +} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service'; import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-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'; @@ -12,59 +13,34 @@ 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'; +import { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() -export class GraphqlQueryDestroyOneResolverService - implements ResolverService -{ - constructor( - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) {} +export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResolverService< + DestroyOneResolverArgs, + ObjectRecord +> { + async resolve( + executionArgs: GraphqlQueryResolverExecutionArgs, + ): Promise { + const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } = + executionArgs.options; - async resolve( - args: DestroyOneResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise { - const { - authContext, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - info, - } = options; - - const dataSource = - await this.twentyORMGlobalManager.getDataSourceForWorkspace( - authContext.workspace.id, - ); - - const repository = dataSource.getRepository( + const queryBuilder = executionArgs.repository.createQueryBuilder( objectMetadataItemWithFieldMaps.nameSingular, ); - const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataItemWithFieldMaps.fieldsByName, - objectMetadataMaps, - ); - - const selectedFields = graphqlFields(info); - - const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataItemWithFieldMaps, - selectedFields, - ); - - const queryBuilder = repository.createQueryBuilder( + const tableName = computeTableName( objectMetadataItemWithFieldMaps.nameSingular, + objectMetadataItemWithFieldMaps.isCustom, ); const nonFormattedDeletedObjectRecords = await queryBuilder - .where(`"${objectMetadataItemWithFieldMaps.nameSingular}".id = :id`, { - id: args.id, + .where(`"${tableName}".id = :id`, { + id: executionArgs.args.id, }) .take(1) .delete() @@ -78,23 +54,29 @@ export class GraphqlQueryDestroyOneResolverService ); } - const recordBeforeDeletion = formatResult( + const deletedRecords = formatResult( nonFormattedDeletedObjectRecords.raw, objectMetadataItemWithFieldMaps, objectMetadataMaps, - )[0]; + ); + + this.apiEventEmitterService.emitDestroyEvents( + deletedRecords, + authContext, + objectMetadataItemWithFieldMaps, + ); const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); - if (relations) { + if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps, - parentObjectRecords: [recordBeforeDeletion], - relations, + parentObjectRecords: deletedRecords, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, - dataSource, + dataSource: executionArgs.dataSource, }); } @@ -102,7 +84,7 @@ export class GraphqlQueryDestroyOneResolverService new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); return typeORMObjectRecordsParser.processRecord({ - objectRecord: recordBeforeDeletion, + objectRecord: deletedRecords[0], 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 d4b616d75..13f65996e 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 @@ -3,7 +3,10 @@ import { Injectable } from '@nestjs/common'; import isEmpty from 'lodash.isempty'; import { In } from 'typeorm'; -import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; +import { + GraphqlQueryBaseResolverService, + GraphqlQueryResolverExecutionArgs, +} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service'; import { ObjectRecord, ObjectRecordFilter, @@ -23,39 +26,25 @@ import { settings } from 'src/engine/constants/settings'; import { DUPLICATE_CRITERIA_COLLECTION } from 'src/engine/core-modules/duplicate/constants/duplicate-criteria.constants'; 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 { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; 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() -export class GraphqlQueryFindDuplicatesResolverService - implements - ResolverService[]> -{ - constructor( - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) {} +export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseResolverService< + FindDuplicatesResolverArgs, + IConnection[] +> { + async resolve( + executionArgs: GraphqlQueryResolverExecutionArgs, + ): Promise[]> { + const { objectMetadataItemWithFieldMaps, objectMetadataMaps } = + executionArgs.options; - async resolve( - args: FindDuplicatesResolverArgs>, - options: WorkspaceQueryRunnerOptions, - ): Promise[]> { - const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } = - options; - - const dataSource = - await this.twentyORMGlobalManager.getDataSourceForWorkspace( - authContext.workspace.id, + const existingRecordsQueryBuilder = + executionArgs.repository.createQueryBuilder( + objectMetadataItemWithFieldMaps.nameSingular, ); - const repository = dataSource.getRepository( - objectMetadataItemWithFieldMaps.nameSingular, - ); - const existingRecordsQueryBuilder = repository.createQueryBuilder( - objectMetadataItemWithFieldMaps.nameSingular, - ); - const duplicateRecordsQueryBuilder = repository.createQueryBuilder( - objectMetadataItemWithFieldMaps.nameSingular, - ); const objectMetadataItemWithFieldsMaps = getObjectMetadataMapItemByNameSingular( @@ -78,23 +67,26 @@ export class GraphqlQueryFindDuplicatesResolverService const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); - let objectRecords: Partial[] = []; + let objectRecords: Partial[] = []; - if (args.ids) { + if (executionArgs.args.ids) { const nonFormattedObjectRecords = (await existingRecordsQueryBuilder - .where({ id: In(args.ids) }) - .getMany()) as T[]; + .where({ id: In(executionArgs.args.ids) }) + .getMany()) as ObjectRecord[]; objectRecords = formatResult( nonFormattedObjectRecords, objectMetadataItemWithFieldMaps, objectMetadataMaps, ); - } else if (args.data && !isEmpty(args.data)) { - objectRecords = formatData(args.data, objectMetadataItemWithFieldMaps); + } else if (executionArgs.args.data && !isEmpty(executionArgs.args.data)) { + objectRecords = formatData( + executionArgs.args.data, + objectMetadataItemWithFieldMaps, + ); } - const duplicateConnections: IConnection[] = await Promise.all( + const duplicateConnections: IConnection[] = await Promise.all( objectRecords.map(async (record) => { const duplicateConditions = this.buildDuplicateConditions( objectMetadataItemWithFieldMaps, @@ -114,16 +106,26 @@ export class GraphqlQueryFindDuplicatesResolverService }); } - const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( - duplicateRecordsQueryBuilder, + const duplicateRecordsQueryBuilder = + executionArgs.repository.createQueryBuilder( + objectMetadataItemWithFieldMaps.nameSingular, + ); + + const tableName = computeTableName( objectMetadataItemWithFieldMaps.nameSingular, + objectMetadataItemWithFieldMaps.isCustom, + ); + + graphqlQueryParser.applyFilterToBuilder( + duplicateRecordsQueryBuilder, + tableName, duplicateConditions, ); const nonFormattedDuplicates = - (await withFilterQueryBuilder.getMany()) as T[]; + (await duplicateRecordsQueryBuilder.getMany()) as ObjectRecord[]; - const duplicates = formatResult( + const duplicates = formatResult( nonFormattedDuplicates, objectMetadataItemWithFieldMaps, objectMetadataMaps, 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 6557b2052..dcb529fdd 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 @@ -1,8 +1,9 @@ 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 { + GraphqlQueryBaseResolverService, + GraphqlQueryResolverExecutionArgs, +} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service'; import { ObjectRecord, ObjectRecordFilter, @@ -18,8 +19,6 @@ import { GraphqlQueryRunnerException, GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; -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 { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper'; import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; @@ -30,86 +29,58 @@ import { } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util'; 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 { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; +import { computeTableName } from 'src/engine/utils/compute-table-name.util'; import { isDefined } from 'src/utils/is-defined'; @Injectable() -export class GraphqlQueryFindManyResolverService - implements ResolverService> -{ - constructor( - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly featureFlagService: FeatureFlagService, - ) {} +export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolverService< + FindManyResolverArgs, + IConnection +> { + constructor(private readonly featureFlagService: FeatureFlagService) { + super(); + } - async resolve< - T extends ObjectRecord = ObjectRecord, - Filter extends ObjectRecordFilter = ObjectRecordFilter, - OrderBy extends ObjectRecordOrderBy = ObjectRecordOrderBy, - >( - args: FindManyResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise> { - const { - authContext, - objectMetadataItemWithFieldMaps, - info, - objectMetadataMaps, - } = options; + async resolve( + executionArgs: GraphqlQueryResolverExecutionArgs, + ): Promise> { + const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } = + executionArgs.options; - const dataSource = - await this.twentyORMGlobalManager.getDataSourceForWorkspace( - authContext.workspace.id, - ); - - const repository = dataSource.getRepository( + const queryBuilder = executionArgs.repository.createQueryBuilder( objectMetadataItemWithFieldMaps.nameSingular, ); - const queryBuilder = repository.createQueryBuilder( + const aggregateQueryBuilder = queryBuilder.clone(); + + let appliedFilters = + executionArgs.args.filter ?? ({} as ObjectRecordFilter); + + const tableName = computeTableName( objectMetadataItemWithFieldMaps.nameSingular, + objectMetadataItemWithFieldMaps.isCustom, ); - const aggregateQueryBuilder = repository.createQueryBuilder( - objectMetadataItemWithFieldMaps.nameSingular, + executionArgs.graphqlQueryParser.applyFilterToBuilder( + aggregateQueryBuilder, + tableName, + appliedFilters, ); - const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataItemWithFieldMaps.fieldsByName, - objectMetadataMaps, + executionArgs.graphqlQueryParser.applyDeletedAtToBuilder( + aggregateQueryBuilder, + appliedFilters, ); - const withFilterAggregateQueryBuilder = - graphqlQueryParser.applyFilterToBuilder( - aggregateQueryBuilder, - objectMetadataItemWithFieldMaps.nameSingular, - args.filter ?? ({} as Filter), - ); - - const selectedFields = graphqlFields(info); - - const graphqlQuerySelectedFieldsResult: GraphqlQuerySelectedFieldsResult = - graphqlQueryParser.parseSelectedFields( - objectMetadataItemWithFieldMaps, - selectedFields, - ); - const isForwardPagination = !isDefined(args.before); - - const withDeletedAggregateQueryBuilder = - graphqlQueryParser.applyDeletedAtToBuilder( - withFilterAggregateQueryBuilder, - args.filter ?? ({} as Filter), - ); - - const cursor = getCursor(args); - - let appliedFilters = args.filter ?? ({} as Filter); - const orderByWithIdCondition = [ - ...(args.orderBy ?? []), + ...(executionArgs.args.orderBy ?? []), { id: OrderByDirection.AscNullsFirst }, - ] as OrderBy; + ] as ObjectRecordOrderBy; + + const isForwardPagination = !isDefined(executionArgs.args.before); + + const cursor = getCursor(executionArgs.args); if (cursor) { const cursorArgFilter = computeCursorArgFilter( @@ -119,29 +90,29 @@ export class GraphqlQueryFindManyResolverService isForwardPagination, ); - appliedFilters = (args.filter + appliedFilters = (executionArgs.args.filter ? { - and: [args.filter, { or: cursorArgFilter }], + and: [executionArgs.args.filter, { or: cursorArgFilter }], } - : { or: cursorArgFilter }) as unknown as Filter; + : { or: cursorArgFilter }) as unknown as ObjectRecordFilter; } - const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( + executionArgs.graphqlQueryParser.applyFilterToBuilder( queryBuilder, - objectMetadataItemWithFieldMaps.nameSingular, + tableName, appliedFilters, ); - const withOrderByQueryBuilder = graphqlQueryParser.applyOrderToBuilder( - withFilterQueryBuilder, + executionArgs.graphqlQueryParser.applyOrderToBuilder( + queryBuilder, orderByWithIdCondition, - objectMetadataItemWithFieldMaps.nameSingular, + tableName, isForwardPagination, ); - const withDeletedQueryBuilder = graphqlQueryParser.applyDeletedAtToBuilder( - withOrderByQueryBuilder, - args.filter ?? ({} as Filter), + executionArgs.graphqlQueryParser.applyDeletedAtToBuilder( + queryBuilder, + appliedFilters, ); const isAggregationsEnabled = @@ -151,25 +122,28 @@ export class GraphqlQueryFindManyResolverService ); if (!isAggregationsEnabled) { - graphqlQuerySelectedFieldsResult.aggregate = { - totalCount: graphqlQuerySelectedFieldsResult.aggregate.totalCount, + executionArgs.graphqlQuerySelectedFieldsResult.aggregate = { + totalCount: + executionArgs.graphqlQuerySelectedFieldsResult.aggregate.totalCount, }; } const processAggregateHelper = new ProcessAggregateHelper(); processAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder({ - selectedAggregatedFields: graphqlQuerySelectedFieldsResult.aggregate, - queryBuilder: withDeletedAggregateQueryBuilder, + selectedAggregatedFields: + executionArgs.graphqlQuerySelectedFieldsResult.aggregate, + queryBuilder: aggregateQueryBuilder, }); - const limit = args.first ?? args.last ?? QUERY_MAX_RECORDS; + const limit = + executionArgs.args.first ?? executionArgs.args.last ?? QUERY_MAX_RECORDS; - const nonFormattedObjectRecords = await withDeletedQueryBuilder + const nonFormattedObjectRecords = await queryBuilder .take(limit + 1) .getMany(); - const objectRecords = formatResult( + const objectRecords = formatResult( nonFormattedObjectRecords, objectMetadataItemWithFieldMaps, objectMetadataMaps, @@ -186,21 +160,21 @@ export class GraphqlQueryFindManyResolverService } const parentObjectRecordsAggregatedValues = - await withDeletedAggregateQueryBuilder.getRawOne(); + await aggregateQueryBuilder.getRawOne(); const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); - if (graphqlQuerySelectedFieldsResult.relations) { + if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps, parentObjectRecords: objectRecords, parentObjectRecordsAggregatedValues, - relations: graphqlQuerySelectedFieldsResult.relations, - aggregate: graphqlQuerySelectedFieldsResult.aggregate, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + aggregate: executionArgs.graphqlQuerySelectedFieldsResult.aggregate, limit, authContext, - dataSource, + dataSource: executionArgs.dataSource, }); } @@ -210,7 +184,8 @@ export class GraphqlQueryFindManyResolverService return typeORMObjectRecordsParser.createConnection({ objectRecords, objectRecordsAggregatedValues: parentObjectRecordsAggregatedValues, - selectedAggregatedFields: graphqlQuerySelectedFieldsResult.aggregate, + selectedAggregatedFields: + executionArgs.graphqlQuerySelectedFieldsResult.aggregate, objectName: objectMetadataItemWithFieldMaps.nameSingular, take: limit, totalCount: parentObjectRecordsAggregatedValues?.totalCount, 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 bcd076a17..8f11c354f 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 @@ -1,8 +1,9 @@ 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 { + GraphqlQueryBaseResolverService, + GraphqlQueryResolverExecutionArgs, +} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service'; import { ObjectRecord, ObjectRecordFilter, @@ -15,77 +16,49 @@ 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 { WorkspaceQueryRunnerException, WorkspaceQueryRunnerExceptionCode, } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; +import { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() -export class GraphqlQueryFindOneResolverService - implements ResolverService -{ - constructor( - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) {} +export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolverService< + FindOneResolverArgs, + ObjectRecord +> { + async resolve( + executionArgs: GraphqlQueryResolverExecutionArgs, + ): Promise { + const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } = + executionArgs.options; - async resolve< - T extends ObjectRecord = ObjectRecord, - Filter extends ObjectRecordFilter = ObjectRecordFilter, - >( - args: FindOneResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise { - const { - authContext, - objectMetadataItemWithFieldMaps, - info, - objectMetadataMaps, - } = options; - - const dataSource = - await this.twentyORMGlobalManager.getDataSourceForWorkspace( - authContext.workspace.id, - ); - - const repository = dataSource.getRepository( + const queryBuilder = executionArgs.repository.createQueryBuilder( objectMetadataItemWithFieldMaps.nameSingular, ); - const queryBuilder = repository.createQueryBuilder( + const tableName = computeTableName( objectMetadataItemWithFieldMaps.nameSingular, + objectMetadataItemWithFieldMaps.isCustom, ); - const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataItemWithFieldMaps.fieldsByName, - objectMetadataMaps, - ); - - const selectedFields = graphqlFields(info); - - const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataItemWithFieldMaps, - selectedFields, - ); - - const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( + executionArgs.graphqlQueryParser.applyFilterToBuilder( queryBuilder, - objectMetadataItemWithFieldMaps.nameSingular, - args.filter ?? ({} as Filter), + tableName, + executionArgs.args.filter ?? ({} as ObjectRecordFilter), ); - const withDeletedQueryBuilder = graphqlQueryParser.applyDeletedAtToBuilder( - withFilterQueryBuilder, - args.filter ?? ({} as Filter), + executionArgs.graphqlQueryParser.applyDeletedAtToBuilder( + queryBuilder, + executionArgs.args.filter ?? ({} as ObjectRecordFilter), ); - const nonFormattedObjectRecord = await withDeletedQueryBuilder.getOne(); + const nonFormattedObjectRecord = await queryBuilder.getOne(); - const objectRecord = formatResult( + const objectRecord = formatResult( nonFormattedObjectRecord, objectMetadataItemWithFieldMaps, objectMetadataMaps, @@ -102,15 +75,15 @@ export class GraphqlQueryFindOneResolverService const objectRecords = [objectRecord]; - if (relations) { + if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps, parentObjectRecords: objectRecords, - relations, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, - dataSource, + dataSource: executionArgs.dataSource, }); } @@ -122,11 +95,11 @@ export class GraphqlQueryFindOneResolverService objectName: objectMetadataItemWithFieldMaps.nameSingular, take: 1, totalCount: 1, - }) as T; + }) as ObjectRecord; } - async validate( - args: FindOneResolverArgs, + async validate( + args: FindOneResolverArgs, _options: WorkspaceQueryRunnerOptions, ): Promise { if (!args.filter || Object.keys(args.filter).length === 0) { 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 new file mode 100644 index 000000000..8ed96cf1c --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service.ts @@ -0,0 +1,100 @@ +import { Injectable } from '@nestjs/common'; + +import { + GraphqlQueryBaseResolverService, + GraphqlQueryResolverExecutionArgs, +} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service'; +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; +import { RestoreManyResolverArgs } 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 { 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 { 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 { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; +import { computeTableName } from 'src/engine/utils/compute-table-name.util'; + +@Injectable() +export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseResolverService< + RestoreManyResolverArgs, + ObjectRecord[] +> { + async resolve( + executionArgs: GraphqlQueryResolverExecutionArgs, + ): Promise { + const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } = + executionArgs.options; + + const queryBuilder = executionArgs.repository.createQueryBuilder( + objectMetadataItemWithFieldMaps.nameSingular, + ); + + const tableName = computeTableName( + objectMetadataItemWithFieldMaps.nameSingular, + objectMetadataItemWithFieldMaps.isCustom, + ); + + executionArgs.graphqlQueryParser.applyFilterToBuilder( + queryBuilder, + tableName, + executionArgs.args.filter, + ); + + const nonFormattedRestoredObjectRecords = await queryBuilder + .restore() + .returning('*') + .execute(); + + const formattedRestoredRecords = formatResult( + nonFormattedRestoredObjectRecords.raw, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, + ); + + this.apiEventEmitterService.emitRestoreEvents( + formattedRestoredRecords, + authContext, + objectMetadataItemWithFieldMaps, + ); + + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); + + if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { + await processNestedRelationsHelper.processNestedRelations({ + objectMetadataMaps, + parentObjectMetadataItem: objectMetadataItemWithFieldMaps, + parentObjectRecords: formattedRestoredRecords, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + limit: QUERY_MAX_RECORDS, + authContext, + dataSource: executionArgs.dataSource, + }); + } + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); + + return formattedRestoredRecords.map((record: ObjectRecord) => + typeORMObjectRecordsParser.processRecord({ + objectRecord: record, + objectName: objectMetadataItemWithFieldMaps.nameSingular, + take: 1, + totalCount: 1, + }), + ); + } + + async validate( + args: RestoreManyResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise { + assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps); + 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-restore-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service.ts new file mode 100644 index 000000000..d596f7e14 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@nestjs/common'; + +import { + GraphqlQueryBaseResolverService, + GraphqlQueryResolverExecutionArgs, +} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service'; +import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; +import { RestoreOneResolverArgs } 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 { 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 { 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 { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; + +@Injectable() +export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResolverService< + RestoreOneResolverArgs, + ObjectRecord +> { + async resolve( + executionArgs: GraphqlQueryResolverExecutionArgs, + ): Promise { + const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } = + executionArgs.options; + + const queryBuilder = executionArgs.repository.createQueryBuilder( + objectMetadataItemWithFieldMaps.nameSingular, + ); + + const nonFormattedRestoredObjectRecords = await queryBuilder + .where({ id: executionArgs.args.id }) + .restore() + .returning('*') + .execute(); + + const formattedRestoredRecords = formatResult( + nonFormattedRestoredObjectRecords.raw, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, + ); + + this.apiEventEmitterService.emitRestoreEvents( + formattedRestoredRecords, + authContext, + objectMetadataItemWithFieldMaps, + ); + + if (formattedRestoredRecords.length === 0) { + throw new GraphqlQueryRunnerException( + 'Record not found', + GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, + ); + } + + const restoredRecord = formattedRestoredRecords[0]; + + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); + + if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { + await processNestedRelationsHelper.processNestedRelations({ + objectMetadataMaps, + parentObjectMetadataItem: objectMetadataItemWithFieldMaps, + parentObjectRecords: [restoredRecord], + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + limit: QUERY_MAX_RECORDS, + authContext, + dataSource: executionArgs.dataSource, + }); + } + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); + + return typeORMObjectRecordsParser.processRecord({ + objectRecord: restoredRecord, + objectName: objectMetadataItemWithFieldMaps.nameSingular, + take: 1, + totalCount: 1, + }); + } + + async validate( + args: RestoreOneResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise { + assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps); + assertIsValidUuid(args.id); + } +} 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 84f6c3ad7..b607e8667 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 @@ -1,9 +1,11 @@ import { Injectable } from '@nestjs/common'; -import graphqlFields from 'graphql-fields'; import { Brackets } from 'typeorm'; -import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; +import { + GraphqlQueryBaseResolverService, + GraphqlQueryResolverExecutionArgs, +} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service'; import { ObjectRecord, ObjectRecordFilter, @@ -14,46 +16,28 @@ import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-qu import { SearchResolverArgs } 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 { 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 { 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 { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; +import { computeTableName } from 'src/engine/utils/compute-table-name.util'; import { isDefined } from 'src/utils/is-defined'; @Injectable() -export class GraphqlQuerySearchResolverService - implements ResolverService> -{ - constructor( - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) {} - - async resolve< - T extends ObjectRecord = ObjectRecord, - Filter extends ObjectRecordFilter = ObjectRecordFilter, - >( - args: SearchResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise> { - const { - authContext, - objectMetadataMaps, - objectMetadataItemWithFieldMaps, - info, - } = options; - - const repository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - authContext.workspace.id, - objectMetadataItemWithFieldMaps.nameSingular, - ); +export class GraphqlQuerySearchResolverService extends GraphqlQueryBaseResolverService< + SearchResolverArgs, + IConnection +> { + async resolve( + executionArgs: GraphqlQueryResolverExecutionArgs, + ): Promise> { + const { authContext, objectMetadataMaps, objectMetadataItemWithFieldMaps } = + executionArgs.options; const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); - if (!isDefined(args.searchInput)) { + if (!isDefined(executionArgs.args.searchInput)) { return typeORMObjectRecordsParser.createConnection({ objectRecords: [], objectName: objectMetadataItemWithFieldMaps.nameSingular, @@ -64,26 +48,36 @@ export class GraphqlQuerySearchResolverService hasPreviousPage: false, }); } - const searchTerms = this.formatSearchTerms(args.searchInput, 'and'); - const searchTermsOr = this.formatSearchTerms(args.searchInput, 'or'); - const limit = args?.limit ?? QUERY_MAX_RECORDS; + const searchTerms = this.formatSearchTerms( + executionArgs.args.searchInput, + 'and', + ); + const searchTermsOr = this.formatSearchTerms( + executionArgs.args.searchInput, + 'or', + ); - const queryBuilder = repository.createQueryBuilder( + const limit = executionArgs.args?.limit ?? QUERY_MAX_RECORDS; + + const queryBuilder = executionArgs.repository.createQueryBuilder( objectMetadataItemWithFieldMaps.nameSingular, ); - const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataItemWithFieldMaps.fieldsByName, - objectMetadataMaps, + + const tableName = computeTableName( + objectMetadataItemWithFieldMaps.nameSingular, + objectMetadataItemWithFieldMaps.isCustom, ); - const queryBuilderWithFilter = graphqlQueryParser.applyFilterToBuilder( + executionArgs.graphqlQueryParser.applyFilterToBuilder( queryBuilder, - objectMetadataItemWithFieldMaps.nameSingular, - args.filter ?? ({} as Filter), + tableName, + executionArgs.args.filter ?? ({} as ObjectRecordFilter), ); - const resultsWithTsVector = (await queryBuilderWithFilter + const countQueryBuilder = queryBuilder.clone(); + + const resultsWithTsVector = (await queryBuilder .andWhere( new Brackets((qb) => { qb.where( @@ -110,40 +104,33 @@ export class GraphqlQuerySearchResolverService .setParameter('searchTerms', searchTerms) .setParameter('searchTermsOr', searchTermsOr) .take(limit) - .getMany()) as T[]; + .getMany()) as ObjectRecord[]; - const objectRecords = await repository.formatResult(resultsWithTsVector); + const objectRecords = formatResult( + resultsWithTsVector, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, + ); - const selectedFields = graphqlFields(info); - - const graphqlQuerySelectedFieldsResult: GraphqlQuerySelectedFieldsResult = - graphqlQueryParser.parseSelectedFields( - objectMetadataItemWithFieldMaps, - selectedFields, - ); - - const totalCount = isDefined(selectedFields.totalCount) - ? await queryBuilderWithFilter.getCount() + const totalCount = isDefined( + executionArgs.graphqlQuerySelectedFieldsResult.aggregate.totalCount, + ) + ? await countQueryBuilder.getCount() : 0; const order = undefined; const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); - const dataSource = - await this.twentyORMGlobalManager.getDataSourceForWorkspace( - authContext.workspace.id, - ); - - if (graphqlQuerySelectedFieldsResult.relations) { + if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps, parentObjectRecords: objectRecords, - relations: graphqlQuerySelectedFieldsResult.relations, - aggregate: graphqlQuerySelectedFieldsResult.aggregate, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + aggregate: executionArgs.graphqlQuerySelectedFieldsResult.aggregate, limit, authContext, - dataSource, + dataSource: executionArgs.dataSource, }); } 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 461940be3..1f194f18d 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,64 +1,34 @@ 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 { + GraphqlQueryBaseResolverService, + GraphqlQueryResolverExecutionArgs, +} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service'; import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; 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 { 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 { 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 { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; 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() -export class GraphqlQueryUpdateManyResolverService - implements ResolverService -{ - constructor( - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) {} +export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResolverService< + UpdateManyResolverArgs, + ObjectRecord[] +> { + async resolve( + executionArgs: GraphqlQueryResolverExecutionArgs, + ): Promise { + const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } = + executionArgs.options; - async resolve( - args: UpdateManyResolverArgs>, - options: WorkspaceQueryRunnerOptions, - ): Promise { - const { - authContext, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - info, - } = options; - - const dataSource = - await this.twentyORMGlobalManager.getDataSourceForWorkspace( - authContext.workspace.id, - ); - - const repository = dataSource.getRepository( - objectMetadataItemWithFieldMaps.nameSingular, - ); - - const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataItemWithFieldMaps.fieldsByName, - objectMetadataMaps, - ); - - const selectedFields = graphqlFields(info); - - const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataItemWithFieldMaps, - selectedFields, - ); - - const queryBuilder = repository.createQueryBuilder( + const queryBuilder = executionArgs.repository.createQueryBuilder( objectMetadataItemWithFieldMaps.nameSingular, ); @@ -67,43 +37,64 @@ export class GraphqlQueryUpdateManyResolverService objectMetadataItemWithFieldMaps.isCustom, ); - const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( + executionArgs.graphqlQueryParser.applyFilterToBuilder( queryBuilder, tableName, - args.filter, + executionArgs.args.filter, ); - const data = formatData(args.data, objectMetadataItemWithFieldMaps); + const existingRecordsBuilder = queryBuilder.clone(); - const nonFormattedUpdatedObjectRecords = await withFilterQueryBuilder + const existingRecords = await existingRecordsBuilder.getMany(); + + const formattedExistingRecords = formatResult( + existingRecords, + objectMetadataItemWithFieldMaps, + objectMetadataMaps, + ); + + const data = formatData( + executionArgs.args.data, + objectMetadataItemWithFieldMaps, + ); + + const nonFormattedUpdatedObjectRecords = await queryBuilder .update(data) .returning('*') .execute(); - const updatedRecords = formatResult( + const formattedUpdatedRecords = formatResult( nonFormattedUpdatedObjectRecords.raw, objectMetadataItemWithFieldMaps, objectMetadataMaps, ); + this.apiEventEmitterService.emitUpdateEvents( + formattedExistingRecords, + formattedUpdatedRecords, + Object.keys(executionArgs.args.data), + authContext, + objectMetadataItemWithFieldMaps, + ); + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); - if (relations) { + if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps, - parentObjectRecords: updatedRecords, - relations, + parentObjectRecords: formattedUpdatedRecords, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, - dataSource, + dataSource: executionArgs.dataSource, }); } const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); - return updatedRecords.map((record: T) => + return formattedUpdatedRecords.map((record: ObjectRecord) => typeORMObjectRecordsParser.processRecord({ objectRecord: record, objectName: objectMetadataItemWithFieldMaps.nameSingular, @@ -113,8 +104,8 @@ export class GraphqlQueryUpdateManyResolverService ); } - async validate( - args: UpdateManyResolverArgs>, + async validate( + args: UpdateManyResolverArgs>, options: WorkspaceQueryRunnerOptions, ): Promise { assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps); 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 6475e6488..4feea3166 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,8 +1,9 @@ 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 { + GraphqlQueryBaseResolverService, + GraphqlQueryResolverExecutionArgs, +} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service'; import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; @@ -12,102 +13,92 @@ 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 { 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 { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; 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 - implements ResolverService -{ - constructor( - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - ) {} +export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolverService< + UpdateOneResolverArgs, + ObjectRecord +> { + async resolve( + executionArgs: GraphqlQueryResolverExecutionArgs, + ): Promise { + const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } = + executionArgs.options; - async resolve( - args: UpdateOneResolverArgs>, - options: WorkspaceQueryRunnerOptions, - ): Promise { - const { - authContext, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - info, - } = options; - - const dataSource = - await this.twentyORMGlobalManager.getDataSourceForWorkspace( - authContext.workspace.id, - ); - - const repository = dataSource.getRepository( + const queryBuilder = executionArgs.repository.createQueryBuilder( objectMetadataItemWithFieldMaps.nameSingular, ); - const graphqlQueryParser = new GraphqlQueryParser( - objectMetadataItemWithFieldMaps.fieldsByName, + 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, ); - const selectedFields = graphqlFields(info); - - const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataItemWithFieldMaps, - selectedFields, - ); - - const queryBuilder = repository.createQueryBuilder( - objectMetadataItemWithFieldMaps.nameSingular, - ); - - const data = formatData(args.data, objectMetadataItemWithFieldMaps); - - const result = await queryBuilder + const nonFormattedUpdatedObjectRecords = await queryBuilder .update(data) - .where({ id: args.id }) + .where({ id: executionArgs.args.id }) .returning('*') .execute(); - const nonFormattedUpdatedObjectRecords = result.raw; - - const updatedRecords = formatResult( - nonFormattedUpdatedObjectRecords, + const formattedUpdatedRecords = formatResult( + nonFormattedUpdatedObjectRecords.raw, objectMetadataItemWithFieldMaps, objectMetadataMaps, ); - if (updatedRecords.length === 0) { + this.apiEventEmitterService.emitUpdateEvents( + formattedExistingRecords, + formattedUpdatedRecords, + Object.keys(executionArgs.args.data), + authContext, + objectMetadataItemWithFieldMaps, + ); + + if (formattedUpdatedRecords.length === 0) { throw new GraphqlQueryRunnerException( 'Record not found', GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, ); } - const updatedRecord = updatedRecords[0] as T; + const updatedRecord = formattedUpdatedRecords[0]; const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); - if (relations) { + if (executionArgs.graphqlQuerySelectedFieldsResult.relations) { await processNestedRelationsHelper.processNestedRelations({ objectMetadataMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps, parentObjectRecords: [updatedRecord], - relations, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, - dataSource, + dataSource: executionArgs.dataSource, }); } const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); - return typeORMObjectRecordsParser.processRecord({ + return typeORMObjectRecordsParser.processRecord({ objectRecord: updatedRecord, objectName: objectMetadataItemWithFieldMaps.nameSingular, take: 1, @@ -115,8 +106,8 @@ export class GraphqlQueryUpdateOneResolverService }); } - async validate( - args: UpdateOneResolverArgs>, + async validate( + args: UpdateOneResolverArgs>, options: WorkspaceQueryRunnerOptions, ): Promise { assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps); 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 index 98f7adbd9..f02f9437b 100644 --- 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 @@ -26,7 +26,7 @@ export class ApiEventEmitterService { objectMetadata: objectMetadataItem, properties: { before: null, - after: this.removeGraphQLAndNestedProperties(record), + after: record, }, })), workspaceId: authContext.workspace.id, @@ -52,10 +52,8 @@ export class ApiEventEmitterService { objectMetadataNameSingular: objectMetadataItem.nameSingular, action: DatabaseEventAction.UPDATED, events: records.map((record) => { - const before = this.removeGraphQLAndNestedProperties( - mappedExistingRecords[record.id], - ); - const after = this.removeGraphQLAndNestedProperties(record); + const before = mappedExistingRecords[record.id]; + const after = record; const diff = objectRecordChangedValues( before, after, @@ -93,7 +91,7 @@ export class ApiEventEmitterService { recordId: record.id, objectMetadata: objectMetadataItem, properties: { - before: this.removeGraphQLAndNestedProperties(record), + before: record, after: null, }, }; @@ -102,6 +100,29 @@ export class ApiEventEmitterService { }); } + public emitRestoreEvents( + records: T[], + authContext: AuthContext, + objectMetadataItem: ObjectMetadataInterface, + ): void { + 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: authContext.workspace.id, + }); + } + public emitDestroyEvents( records: T[], authContext: AuthContext, @@ -116,7 +137,7 @@ export class ApiEventEmitterService { recordId: record.id, objectMetadata: objectMetadataItem, properties: { - before: this.removeGraphQLAndNestedProperties(record), + before: record, after: null, }, }; @@ -124,26 +145,4 @@ export class ApiEventEmitterService { workspaceId: authContext.workspace.id, }); } - - private removeGraphQLAndNestedProperties(record: T) { - if (!record) { - return {}; - } - - const sanitizedRecord = {}; - - for (const [key, value] of Object.entries(record)) { - if (value && typeof value === 'object' && value['edges']) { - continue; - } - - if (key === '__typename') { - continue; - } - - sanitizedRecord[key] = value; - } - - return sanitizedRecord; - } } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value.ts index 5a15e7369..c46d81447 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value.ts @@ -3,6 +3,7 @@ import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/inter export type QueryResultFieldValue = | IConnection + | IConnection[] | { records: ObjectRecord[] } | ObjectRecord | ObjectRecord[]; 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 84e16ce84..3a158d68e 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 @@ -8,7 +8,7 @@ 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 { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; @Injectable() @@ -18,7 +18,7 @@ export class CreateManyResolverFactory public static methodName = 'createMany' as const; constructor( - private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryCreateManyResolverService, ) {} create( @@ -36,7 +36,11 @@ export class CreateManyResolverFactory internalContext.objectMetadataItemWithFieldMaps, }; - return await this.graphqlQueryRunnerService.createMany(args, options); + return await this.graphqlQueryRunnerService.execute( + args, + options, + CreateManyResolverFactory.methodName, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error, context); } 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 650b0bda1..d6f2719c5 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 @@ -8,7 +8,7 @@ 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 { GraphqlQueryCreateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; @Injectable() @@ -18,7 +18,7 @@ export class CreateOneResolverFactory public static methodName = 'createOne' as const; constructor( - private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryCreateOneResolverService, ) {} create( @@ -36,7 +36,11 @@ export class CreateOneResolverFactory internalContext.objectMetadataItemWithFieldMaps, }; - return await this.graphqlQueryRunnerService.createOne(args, options); + return await this.graphqlQueryRunnerService.execute( + args, + options, + CreateOneResolverFactory.methodName, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts index 3be04fdd7..aad99e565 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts @@ -8,7 +8,7 @@ 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 { GraphqlQueryDeleteManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; @Injectable() @@ -18,7 +18,7 @@ export class DeleteManyResolverFactory public static methodName = 'deleteMany' as const; constructor( - private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryDeleteManyResolverService, ) {} create( @@ -36,7 +36,11 @@ export class DeleteManyResolverFactory internalContext.objectMetadataItemWithFieldMaps, }; - return await this.graphqlQueryRunnerService.deleteMany(args, options); + return await this.graphqlQueryRunnerService.execute( + args, + options, + DeleteManyResolverFactory.methodName, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts index 596a1d4db..d97e1946f 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts @@ -8,7 +8,7 @@ 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 { GraphqlQueryDeleteOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; @Injectable() @@ -18,7 +18,7 @@ export class DeleteOneResolverFactory public static methodName = 'deleteOne' as const; constructor( - private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryDeleteOneResolverService, ) {} create( @@ -36,7 +36,11 @@ export class DeleteOneResolverFactory internalContext.objectMetadataItemWithFieldMaps, }; - return await this.graphqlQueryRunnerService.deleteOne(args, options); + return await this.graphqlQueryRunnerService.execute( + args, + options, + DeleteOneResolverFactory.methodName, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } 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 3eacd530b..2199d050c 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,7 +8,7 @@ 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 { GraphqlQueryDestroyManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; @Injectable() @@ -18,7 +18,7 @@ export class DestroyManyResolverFactory public static methodName = 'destroyMany' as const; constructor( - private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryDestroyManyResolverService, ) {} create( @@ -36,7 +36,11 @@ export class DestroyManyResolverFactory internalContext.objectMetadataItemWithFieldMaps, }; - return await this.graphqlQueryRunnerService.destroyMany(args, options); + return await this.graphqlQueryRunnerService.execute( + args, + options, + DestroyManyResolverFactory.methodName, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts index 7dbf01a83..ca0e46dae 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts @@ -8,7 +8,7 @@ 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 { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; @Injectable() @@ -18,7 +18,7 @@ export class DestroyOneResolverFactory public static methodName = 'destroyOne' as const; constructor( - private readonly graphQLQueryRunnerService: GraphqlQueryRunnerService, + private readonly graphQLQueryRunnerService: GraphqlQueryDestroyOneResolverService, ) {} create( @@ -36,7 +36,11 @@ export class DestroyOneResolverFactory internalContext.objectMetadataItemWithFieldMaps, }; - return await this.graphQLQueryRunnerService.destroyOne(args, options); + return await this.graphQLQueryRunnerService.execute( + args, + options, + DestroyOneResolverFactory.methodName, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/factories.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/factories.ts index b728ef898..7c951ad5b 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/factories.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/factories.ts @@ -1,6 +1,7 @@ import { DestroyManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory'; import { DestroyOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory'; import { RestoreManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory'; +import { RestoreOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory'; import { SearchResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory'; import { UpdateManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory'; @@ -25,6 +26,7 @@ export const workspaceResolverBuilderFactories = [ DeleteManyResolverFactory, DestroyOneResolverFactory, DestroyManyResolverFactory, + RestoreOneResolverFactory, RestoreManyResolverFactory, SearchResolverFactory, ]; @@ -45,6 +47,7 @@ export const workspaceResolverBuilderMethodNames = { DeleteManyResolverFactory.methodName, DestroyOneResolverFactory.methodName, DestroyManyResolverFactory.methodName, + RestoreOneResolverFactory.methodName, RestoreManyResolverFactory.methodName, ], } as const; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts index 9fe6fc822..aaef6fe92 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts @@ -8,7 +8,7 @@ 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 { GraphqlQueryFindDuplicatesResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; @Injectable() @@ -18,7 +18,7 @@ export class FindDuplicatesResolverFactory public static methodName = 'findDuplicates' as const; constructor( - private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryFindDuplicatesResolverService, ) {} create( @@ -36,9 +36,10 @@ export class FindDuplicatesResolverFactory internalContext.objectMetadataItemWithFieldMaps, }; - return await this.graphqlQueryRunnerService.findDuplicates( + return await this.graphqlQueryRunnerService.execute( args, options, + FindDuplicatesResolverFactory.methodName, ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); 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 064927d21..5d06d9292 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 @@ -8,7 +8,7 @@ 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 { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; @Injectable() @@ -18,7 +18,7 @@ export class FindManyResolverFactory public static methodName = 'findMany' as const; constructor( - private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryFindManyResolverService, ) {} create( @@ -36,7 +36,11 @@ export class FindManyResolverFactory internalContext.objectMetadataItemWithFieldMaps, }; - return await this.graphqlQueryRunnerService.findMany(args, options); + return await this.graphqlQueryRunnerService.execute( + args, + options, + FindManyResolverFactory.methodName, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } 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 a6ce32c33..6785198dc 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 @@ -8,7 +8,7 @@ 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 { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; @Injectable() @@ -18,7 +18,7 @@ export class FindOneResolverFactory public static methodName = 'findOne' as const; constructor( - private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryFindOneResolverService, ) {} create( @@ -36,7 +36,11 @@ export class FindOneResolverFactory internalContext.objectMetadataItemWithFieldMaps, }; - return await this.graphqlQueryRunnerService.findOne(args, options); + return await this.graphqlQueryRunnerService.execute( + args, + options, + FindOneResolverFactory.methodName, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts index dbbe7eb31..2dbfd5ff0 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts @@ -8,7 +8,7 @@ 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 { GraphqlQueryRestoreManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; @Injectable() @@ -18,7 +18,7 @@ export class RestoreManyResolverFactory public static methodName = 'restoreMany' as const; constructor( - private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryRestoreManyResolverService, ) {} create( @@ -36,7 +36,11 @@ export class RestoreManyResolverFactory internalContext.objectMetadataItemWithFieldMaps, }; - return await this.graphqlQueryRunnerService.restoreMany(args, options); + return await this.graphqlQueryRunnerService.execute( + args, + options, + RestoreManyResolverFactory.methodName, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory.ts new file mode 100644 index 000000000..aa5c65123 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@nestjs/common'; + +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; +import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; +import { + Resolver, + RestoreOneResolverArgs, +} 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 { GraphqlQueryRestoreOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service'; +import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; + +@Injectable() +export class RestoreOneResolverFactory + implements WorkspaceResolverBuilderFactoryInterface +{ + public static methodName = 'restoreOne' as const; + + constructor( + private readonly graphqlQueryRunnerService: GraphqlQueryRestoreOneResolverService, + ) {} + + create( + context: WorkspaceSchemaBuilderContext, + ): Resolver { + const internalContext = context; + + return async (_source, args, context, info) => { + try { + const options: WorkspaceQueryRunnerOptions = { + authContext: internalContext.authContext, + info, + objectMetadataMaps: internalContext.objectMetadataMaps, + objectMetadataItemWithFieldMaps: + internalContext.objectMetadataItemWithFieldMaps, + }; + + return await this.graphqlQueryRunnerService.execute( + args, + options, + RestoreOneResolverFactory.methodName, + ); + } catch (error) { + workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); + } + }; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts index d2510862c..cb9c946be 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts @@ -8,7 +8,7 @@ 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 { GraphqlQuerySearchResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; @Injectable() @@ -18,7 +18,7 @@ export class SearchResolverFactory public static methodName = 'search' as const; constructor( - private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQuerySearchResolverService, ) {} create(context: WorkspaceSchemaBuilderContext): Resolver { @@ -34,7 +34,11 @@ export class SearchResolverFactory internalContext.objectMetadataItemWithFieldMaps, }; - return await this.graphqlQueryRunnerService.search(args, options); + return await this.graphqlQueryRunnerService.execute( + args, + options, + SearchResolverFactory.methodName, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts index dce0a58a2..05ab767a6 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts @@ -8,7 +8,7 @@ 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 { GraphqlQueryUpdateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; @Injectable() @@ -18,7 +18,7 @@ export class UpdateManyResolverFactory public static methodName = 'updateMany' as const; constructor( - private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryUpdateManyResolverService, ) {} create( @@ -36,7 +36,11 @@ export class UpdateManyResolverFactory internalContext.objectMetadataItemWithFieldMaps, }; - return await this.graphqlQueryRunnerService.updateMany(args, options); + return await this.graphqlQueryRunnerService.execute( + args, + options, + UpdateManyResolverFactory.methodName, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts index 258b51b4a..ebe752e96 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts @@ -8,7 +8,7 @@ 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 { GraphqlQueryUpdateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; @Injectable() @@ -18,7 +18,7 @@ export class UpdateOneResolverFactory public static methodName = 'updateOne' as const; constructor( - private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, + private readonly graphqlQueryRunnerService: GraphqlQueryUpdateOneResolverService, ) {} create( @@ -36,7 +36,11 @@ export class UpdateOneResolverFactory internalContext.objectMetadataItemWithFieldMaps, }; - return await this.graphqlQueryRunnerService.updateOne(args, options); + return await this.graphqlQueryRunnerService.execute( + args, + options, + UpdateOneResolverFactory.methodName, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts index ea40e9c8c..d216dfda6 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts @@ -37,7 +37,7 @@ export interface FindManyResolverArgs< orderBy?: OrderBy; } -export interface FindOneResolverArgs { +export interface FindOneResolverArgs { filter?: Filter; } @@ -93,6 +93,10 @@ export interface DeleteManyResolverArgs { filter: Filter; } +export interface RestoreOneResolverArgs { + id: string; +} + export interface RestoreManyResolverArgs { filter: Filter; } @@ -125,11 +129,12 @@ export type ResolverArgs = | CreateOneResolverArgs | DeleteManyResolverArgs | DeleteOneResolverArgs + | DestroyManyResolverArgs + | FindDuplicatesResolverArgs | FindManyResolverArgs | FindOneResolverArgs - | FindDuplicatesResolverArgs - | UpdateManyResolverArgs - | UpdateOneResolverArgs - | DestroyManyResolverArgs | RestoreManyResolverArgs - | SearchResolverArgs; + | RestoreOneResolverArgs + | SearchResolverArgs + | UpdateManyResolverArgs + | UpdateOneResolverArgs; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts index 9d5370546..badbe097a 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts @@ -6,6 +6,7 @@ import { DeleteManyResolverFactory } from 'src/engine/api/graphql/workspace-reso import { DestroyManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory'; import { DestroyOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory'; import { RestoreManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory'; +import { RestoreOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory'; import { SearchResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory'; import { UpdateManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; @@ -40,6 +41,7 @@ export class WorkspaceResolverFactory { private readonly destroyOneResolverFactory: DestroyOneResolverFactory, private readonly updateManyResolverFactory: UpdateManyResolverFactory, private readonly deleteManyResolverFactory: DeleteManyResolverFactory, + private readonly restoreOneResolverFactory: RestoreOneResolverFactory, private readonly restoreManyResolverFactory: RestoreManyResolverFactory, private readonly destroyManyResolverFactory: DestroyManyResolverFactory, private readonly searchResolverFactory: SearchResolverFactory, @@ -54,19 +56,20 @@ export class WorkspaceResolverFactory { WorkspaceResolverBuilderMethodNames, WorkspaceResolverBuilderFactoryInterface >([ - ['findMany', this.findManyResolverFactory], - ['findOne', this.findOneResolverFactory], - ['findDuplicates', this.findDuplicatesResolverFactory], ['createMany', this.createManyResolverFactory], ['createOne', this.createOneResolverFactory], - ['updateOne', this.updateOneResolverFactory], - ['deleteOne', this.deleteOneResolverFactory], - ['destroyOne', this.destroyOneResolverFactory], - ['updateMany', this.updateManyResolverFactory], ['deleteMany', this.deleteManyResolverFactory], - ['restoreMany', this.restoreManyResolverFactory], + ['deleteOne', this.deleteOneResolverFactory], ['destroyMany', this.destroyManyResolverFactory], + ['destroyOne', this.destroyOneResolverFactory], + ['findDuplicates', this.findDuplicatesResolverFactory], + ['findMany', this.findManyResolverFactory], + ['findOne', this.findOneResolverFactory], + ['restoreMany', this.restoreManyResolverFactory], + ['restoreOne', this.restoreOneResolverFactory], ['search', this.searchResolverFactory], + ['updateMany', this.updateManyResolverFactory], + ['updateOne', this.updateOneResolverFactory], ]); const resolvers: IResolvers = { Query: {}, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts index b50047e62..990d77f4a 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts @@ -130,6 +130,13 @@ export const getResolverArgs = ( isNullable: false, }, }; + case 'restoreOne': + return { + id: { + type: GraphQLID, + isNullable: false, + }, + }; case 'destroyMany': return { filter: { diff --git a/packages/twenty-server/src/engine/core-modules/event-emitter/types/object-record-restore.event.ts b/packages/twenty-server/src/engine/core-modules/event-emitter/types/object-record-restore.event.ts new file mode 100644 index 000000000..1e46993d1 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/event-emitter/types/object-record-restore.event.ts @@ -0,0 +1,9 @@ +import { ObjectRecordBaseEvent } from 'src/engine/core-modules/event-emitter/types/object-record.base.event'; + +export class ObjectRecordRestoreEvent< + T = object, +> extends ObjectRecordBaseEvent { + properties: { + before: T; + }; +} diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts index 2cba94d43..ceb2f427c 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts @@ -13,7 +13,7 @@ import { getCompositeFieldMetadataCollection } from 'src/engine/twenty-orm/utils import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; export function formatResult( - data: T, + data: any, ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, objectMetadataMaps: ObjectMetadataMaps, ): T { diff --git a/packages/twenty-server/src/engine/utils/get-resolver-name.util.ts b/packages/twenty-server/src/engine/utils/get-resolver-name.util.ts index f274475e7..c4b140ad2 100644 --- a/packages/twenty-server/src/engine/utils/get-resolver-name.util.ts +++ b/packages/twenty-server/src/engine/utils/get-resolver-name.util.ts @@ -15,24 +15,32 @@ export const getResolverName = ( return `${camelCase(objectMetadata.nameSingular)}`; case 'findDuplicates': return `${camelCase(objectMetadata.nameSingular)}Duplicates`; - case 'createMany': - return `create${pascalCase(objectMetadata.namePlural)}`; + case 'createOne': return `create${pascalCase(objectMetadata.nameSingular)}`; + case 'createMany': + return `create${pascalCase(objectMetadata.namePlural)}`; + case 'updateOne': return `update${pascalCase(objectMetadata.nameSingular)}`; - case 'deleteOne': - return `delete${pascalCase(objectMetadata.nameSingular)}`; - case 'destroyOne': - return `destroy${pascalCase(objectMetadata.nameSingular)}`; case 'updateMany': return `update${pascalCase(objectMetadata.namePlural)}`; - case 'restoreMany': - return `restore${pascalCase(objectMetadata.namePlural)}`; + + case 'deleteOne': + return `delete${pascalCase(objectMetadata.nameSingular)}`; case 'deleteMany': return `delete${pascalCase(objectMetadata.namePlural)}`; + + case 'destroyOne': + return `destroy${pascalCase(objectMetadata.nameSingular)}`; case 'destroyMany': return `destroy${pascalCase(objectMetadata.namePlural)}`; + + case 'restoreOne': + return `restore${pascalCase(objectMetadata.nameSingular)}`; + case 'restoreMany': + return `restore${pascalCase(objectMetadata.namePlural)}`; + case 'search': return `search${pascalCase(objectMetadata.namePlural)}`; default: 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 ee2e9be59..669aeccbd 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,11 +1,12 @@ import { Injectable } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; +import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event'; -import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.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 { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; +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 { CustomEventName } from 'src/engine/workspace-event-emitter/types/custom-event-name.type'; type ActionEventMap = { @@ -13,6 +14,7 @@ type ActionEventMap = { [DatabaseEventAction.UPDATED]: ObjectRecordUpdateEvent; [DatabaseEventAction.DELETED]: ObjectRecordDeleteEvent; [DatabaseEventAction.DESTROYED]: ObjectRecordDestroyEvent; + [DatabaseEventAction.RESTORED]: ObjectRecordRestoreEvent; }; @Injectable() diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud.workflow-action.ts index b95de52e1..3fc725b7b 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud.workflow-action.ts @@ -253,7 +253,7 @@ export class RecordCRUDWorkflowAction implements WorkflowAction { .take(workflowActionInput.limit ?? QUERY_MAX_RECORDS) .getMany(); - return formatResult( + return formatResult( nonFormattedObjectRecords, objectMetadataItemWithFieldsMaps, objectMetadataMaps,