[Flexible-schema] Refactor gql query runner to emit api event before processing to gql types (#8596)

Fixes https://github.com/twentyhq/twenty/issues/8300

## Context
API events were created too late and were already formatted as Gql
responses (including nesting with edges/node/type + formatting that
should not exist in an event payload). This PR moves the emit logic to
the resolver where we actually do the DB query
Note: Also added RESTORED events
This commit is contained in:
Weiko
2024-11-21 18:11:28 +01:00
committed by GitHub
parent 8f5515cab3
commit 1c04b2b0b7
46 changed files with 1251 additions and 1049 deletions

View File

@ -3,4 +3,5 @@ export enum DatabaseEventAction {
UPDATED = 'updated', UPDATED = 'updated',
DELETED = 'deleted', DELETED = 'deleted',
DESTROYED = 'destroyed', DESTROYED = 'destroyed',
RESTORED = 'restored',
} }

View File

@ -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<ResolverArgs, any> {
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}`);
}
}
}

View File

@ -1,13 +1,16 @@
import { Module } from '@nestjs/common'; 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { GraphqlQueryUpdateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service';
import { GraphqlQueryUpdateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service'; import { 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 = [ const graphqlQueryResolvers = [
GraphqlQueryCreateManyResolverService, GraphqlQueryCreateManyResolverService,
GraphqlQueryCreateOneResolverService,
GraphqlQueryDeleteManyResolverService,
GraphqlQueryDeleteOneResolverService,
GraphqlQueryDestroyManyResolverService, GraphqlQueryDestroyManyResolverService,
GraphqlQueryDestroyOneResolverService, GraphqlQueryDestroyOneResolverService,
GraphqlQueryFindDuplicatesResolverService, GraphqlQueryFindDuplicatesResolverService,
GraphqlQueryFindManyResolverService, GraphqlQueryFindManyResolverService,
GraphqlQueryFindOneResolverService, GraphqlQueryFindOneResolverService,
GraphqlQueryRestoreManyResolverService,
GraphqlQueryRestoreOneResolverService,
GraphqlQuerySearchResolverService, GraphqlQuerySearchResolverService,
GraphqlQueryUpdateManyResolverService, GraphqlQueryUpdateManyResolverService,
GraphqlQueryUpdateOneResolverService, GraphqlQueryUpdateOneResolverService,
@ -34,12 +42,7 @@ const graphqlQueryResolvers = [
WorkspaceQueryRunnerModule, WorkspaceQueryRunnerModule,
FeatureFlagModule, FeatureFlagModule,
], ],
providers: [ providers: [ApiEventEmitterService, ...graphqlQueryResolvers],
GraphqlQueryRunnerService, exports: [...graphqlQueryResolvers],
GraphqlQueryResolverFactory,
ApiEventEmitterService,
...graphqlQueryResolvers,
],
exports: [GraphqlQueryRunnerService],
}) })
export class GraphqlQueryRunnerModule {} export class GraphqlQueryRunnerModule {}

View File

@ -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<T extends ObjectRecord, Filter extends ObjectRecordFilter>(
args: FindOneResolverArgs<Filter>,
options: WorkspaceQueryRunnerOptions,
): Promise<T> {
return this.executeQuery<FindOneResolverArgs<Filter>, T>(
'findOne',
args,
options,
);
}
@LogExecutionTime()
async findMany<
T extends ObjectRecord,
Filter extends ObjectRecordFilter,
OrderBy extends ObjectRecordOrderBy,
>(
args: FindManyResolverArgs<Filter, OrderBy>,
options: WorkspaceQueryRunnerOptions,
): Promise<IConnection<T, IEdge<T>>> {
return this.executeQuery<
FindManyResolverArgs<Filter, OrderBy>,
IConnection<T, IEdge<T>>
>('findMany', args, options);
}
@LogExecutionTime()
async findDuplicates<T extends ObjectRecord>(
args: FindDuplicatesResolverArgs<Partial<T>>,
options: WorkspaceQueryRunnerOptions,
): Promise<IConnection<T>[]> {
return this.executeQuery<
FindDuplicatesResolverArgs<Partial<T>>,
IConnection<T>[]
>('findDuplicates', args, options);
}
@LogExecutionTime()
async search<T extends ObjectRecord = ObjectRecord>(
args: SearchResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<IConnection<T>> {
return this.executeQuery<SearchResolverArgs, IConnection<T>>(
'search',
args,
options,
);
}
/** MUTATIONS */
@LogExecutionTime()
async createOne<T extends ObjectRecord>(
args: CreateOneResolverArgs<Partial<T>>,
options: WorkspaceQueryRunnerOptions,
): Promise<T> {
const results = await this.executeQuery<
CreateManyResolverArgs<Partial<T>>,
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<T extends ObjectRecord>(
args: CreateManyResolverArgs<Partial<T>>,
options: WorkspaceQueryRunnerOptions,
): Promise<T[]> {
const results = await this.executeQuery<
CreateManyResolverArgs<Partial<T>>,
T[]
>('createMany', args, options);
if (results) {
this.apiEventEmitterService.emitCreateEvents(
results,
options.authContext,
options.objectMetadataItemWithFieldMaps,
);
}
return results;
}
@LogExecutionTime()
public async updateOne<T extends ObjectRecord>(
args: UpdateOneResolverArgs<Partial<T>>,
options: WorkspaceQueryRunnerOptions,
): Promise<T> {
const existingRecord = await this.executeQuery<FindOneResolverArgs, T>(
'findOne',
{
filter: { id: { eq: args.id } },
},
options,
);
const result = await this.executeQuery<
UpdateOneResolverArgs<Partial<T>>,
T
>('updateOne', args, options);
this.apiEventEmitterService.emitUpdateEvents(
[existingRecord],
[result],
Object.keys(args.data),
options.authContext,
options.objectMetadataItemWithFieldMaps,
);
return result;
}
@LogExecutionTime()
public async updateMany<T extends ObjectRecord>(
args: UpdateManyResolverArgs<Partial<T>>,
options: WorkspaceQueryRunnerOptions,
): Promise<T[]> {
const existingRecords = await this.executeQuery<
FindManyResolverArgs,
IConnection<T, IEdge<T>>
>(
'findMany',
{
filter: args.filter,
},
options,
);
const result = await this.executeQuery<
UpdateManyResolverArgs<Partial<T>>,
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<T extends ObjectRecord & { deletedAt?: Date }>(
args: DeleteOneResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<T> {
const result = await this.executeQuery<
UpdateOneResolverArgs<Partial<T>>,
T
>(
'deleteOne',
{
id: args.id,
data: { deletedAt: new Date() } as Partial<T>,
},
options,
);
this.apiEventEmitterService.emitDeletedEvents(
[result],
options.authContext,
options.objectMetadataItemWithFieldMaps,
);
return result;
}
@LogExecutionTime()
public async deleteMany<T extends ObjectRecord & { deletedAt?: Date }>(
args: DeleteManyResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<T[]> {
const result = await this.executeQuery<
UpdateManyResolverArgs<Partial<T>>,
T[]
>(
'deleteMany',
{
filter: args.filter,
data: { deletedAt: new Date() } as Partial<T>,
},
options,
);
this.apiEventEmitterService.emitDeletedEvents(
result,
options.authContext,
options.objectMetadataItemWithFieldMaps,
);
return result;
}
@LogExecutionTime()
async destroyOne<T extends ObjectRecord>(
args: DestroyOneResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<T> {
const result = await this.executeQuery<DestroyOneResolverArgs, T>(
'destroyOne',
args,
options,
);
this.apiEventEmitterService.emitDestroyEvents(
[result],
options.authContext,
options.objectMetadataItemWithFieldMaps,
);
return result;
}
@LogExecutionTime()
async destroyMany<T extends ObjectRecord>(
args: DestroyManyResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<T[]> {
const result = await this.executeQuery<DestroyManyResolverArgs, T[]>(
'destroyMany',
args,
options,
);
this.apiEventEmitterService.emitDestroyEvents(
result,
options.authContext,
options.objectMetadataItemWithFieldMaps,
);
return result;
}
@LogExecutionTime()
public async restoreMany<T extends ObjectRecord>(
args: RestoreManyResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<T> {
return await this.executeQuery<UpdateManyResolverArgs<Partial<T>>, T>(
'restoreMany',
{
filter: args.filter,
data: { deletedAt: null } as Partial<T>,
},
options,
);
}
private async executeQuery<Input extends ResolverArgs, Response>(
operationName: WorkspaceResolverBuilderMethodNames,
args: Input,
options: WorkspaceQueryRunnerOptions,
): Promise<Response> {
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;
}
}

View File

@ -403,7 +403,7 @@ export class ProcessNestedRelationsHelper {
.take(limit) .take(limit)
.getMany(); .getMany();
const relationResults = formatResult( const relationResults = formatResult<ObjectRecord[]>(
result, result,
referenceObjectMetadata, referenceObjectMetadata,
objectMetadataMaps, objectMetadataMaps,

View File

@ -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<Input extends ResolverArgs> = {
args: Input;
options: WorkspaceQueryRunnerOptions;
dataSource: DataSource;
repository: WorkspaceRepository<ObjectLiteral>;
graphqlQueryParser: GraphqlQueryParser;
graphqlQuerySelectedFieldsResult: GraphqlQuerySelectedFieldsResult;
};
@Injectable()
export abstract class GraphqlQueryBaseResolverService<
Input extends ResolverArgs,
Response extends
| ObjectRecord
| ObjectRecord[]
| IConnection<ObjectRecord, IEdge<ObjectRecord>>
| IConnection<ObjectRecord, IEdge<ObjectRecord>>[],
> {
@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<Response> {
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<Input>,
): Promise<Response>;
protected abstract validate(
args: Input,
options: WorkspaceQueryRunnerOptions,
): Promise<void>;
}

View File

@ -1,12 +0,0 @@
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
export interface ResolverService<ResolverArgs, T> {
resolve: (
args: ResolverArgs,
options: WorkspaceQueryRunnerOptions,
) => Promise<T>;
validate: (
args: ResolverArgs,
options: WorkspaceQueryRunnerOptions,
) => Promise<void>;
}

View File

@ -1,103 +1,81 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { In, InsertResult } from 'typeorm'; 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@Injectable() @Injectable()
export class GraphqlQueryCreateManyResolverService export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResolverService<
implements ResolverService<CreateManyResolverArgs, ObjectRecord[]> CreateManyResolverArgs,
{ ObjectRecord[]
constructor( > {
private readonly twentyORMGlobalManager: TwentyORMGlobalManager, async resolve(
) {} executionArgs: GraphqlQueryResolverExecutionArgs<CreateManyResolverArgs>,
): Promise<ObjectRecord[]> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
async resolve<T extends ObjectRecord = ObjectRecord>( const objectRecords: InsertResult = !executionArgs.args.upsert
args: CreateManyResolverArgs<Partial<ObjectRecord>>, ? await executionArgs.repository.insert(executionArgs.args.data)
options: WorkspaceQueryRunnerOptions, : await executionArgs.repository.upsert(executionArgs.args.data, {
): Promise<T[]> {
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, {
conflictPaths: ['id'], conflictPaths: ['id'],
skipUpdateIfNoValuesChanged: true, skipUpdateIfNoValuesChanged: true,
}); });
const queryBuilder = repository.createQueryBuilder( const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular, objectMetadataItemWithFieldMaps.nameSingular,
); );
const nonFormattedUpsertedRecords = (await queryBuilder const nonFormattedUpsertedRecords = await queryBuilder
.where({ .where({
id: In(objectRecords.generatedMaps.map((record) => record.id)), id: In(objectRecords.generatedMaps.map((record) => record.id)),
}) })
.take(QUERY_MAX_RECORDS) .take(QUERY_MAX_RECORDS)
.getMany()) as ObjectRecord[]; .getMany();
const upsertedRecords = formatResult( const upsertedRecords = formatResult<ObjectRecord[]>(
nonFormattedUpsertedRecords, nonFormattedUpsertedRecords,
objectMetadataItemWithFieldMaps, objectMetadataItemWithFieldMaps,
objectMetadataMaps, objectMetadataMaps,
); );
this.apiEventEmitterService.emitCreateEvents(
upsertedRecords,
authContext,
objectMetadataItemWithFieldMaps,
);
const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
if (relations) { if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await processNestedRelationsHelper.processNestedRelations({ await processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps, objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: upsertedRecords, parentObjectRecords: upsertedRecords,
relations, relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS, limit: QUERY_MAX_RECORDS,
authContext, authContext,
dataSource, dataSource: executionArgs.dataSource,
}); });
} }
const typeORMObjectRecordsParser = const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return upsertedRecords.map((record: T) => return upsertedRecords.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({ typeORMObjectRecordsParser.processRecord({
objectRecord: record, objectRecord: record,
objectName: objectMetadataItemWithFieldMaps.nameSingular, objectName: objectMetadataItemWithFieldMaps.nameSingular,

View File

@ -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<CreateOneResolverArgs>,
): Promise<ObjectRecord> {
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<ObjectRecord[]>(
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<Partial<ObjectRecord>>,
options: WorkspaceQueryRunnerOptions,
): Promise<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
if (args.data?.id) {
assertIsValidUuid(args.data.id);
}
}
}

View File

@ -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<DeleteManyResolverArgs>,
): Promise<ObjectRecord[]> {
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<ObjectRecord[]>(
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<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
if (!args.filter) {
throw new Error('Filter is required');
}
args.filter.id?.in?.forEach((id: string) => assertIsValidUuid(id));
}
}

View File

@ -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<DeleteOneResolverArgs>,
): Promise<ObjectRecord> {
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<ObjectRecord[]>(
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<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
assertIsValidUuid(args.id);
}
}

View File

@ -1,98 +1,80 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields'; import {
GraphqlQueryBaseResolverService,
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; 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 { 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 { 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 { 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 { 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 { 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 { 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 { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
@Injectable() @Injectable()
export class GraphqlQueryDestroyManyResolverService export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseResolverService<
implements ResolverService<DestroyManyResolverArgs, ObjectRecord[]> DestroyManyResolverArgs,
{ ObjectRecord[]
constructor( > {
private readonly twentyORMGlobalManager: TwentyORMGlobalManager, async resolve(
) {} executionArgs: GraphqlQueryResolverExecutionArgs<DestroyManyResolverArgs>,
): Promise<ObjectRecord[]> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
async resolve<T extends ObjectRecord = ObjectRecord>( const queryBuilder = executionArgs.repository.createQueryBuilder(
args: DestroyManyResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<T[]> {
const {
authContext,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
info,
} = options;
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
authContext.workspace.id,
);
const repository = dataSource.getRepository(
objectMetadataItemWithFieldMaps.nameSingular, objectMetadataItemWithFieldMaps.nameSingular,
); );
const graphqlQueryParser = new GraphqlQueryParser( const tableName = computeTableName(
objectMetadataItemWithFieldMaps.fieldsByName,
objectMetadataMaps,
);
const selectedFields = graphqlFields(info);
const { relations } = graphqlQueryParser.parseSelectedFields(
objectMetadataItemWithFieldMaps,
selectedFields,
);
const queryBuilder = repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular, objectMetadataItemWithFieldMaps.nameSingular,
objectMetadataItemWithFieldMaps.isCustom,
); );
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( executionArgs.graphqlQueryParser.applyFilterToBuilder(
queryBuilder, queryBuilder,
objectMetadataItemWithFieldMaps.nameSingular, tableName,
args.filter, executionArgs.args.filter,
); );
const nonFormattedDeletedObjectRecords = await withFilterQueryBuilder const nonFormattedDeletedObjectRecords = await queryBuilder
.delete() .delete()
.returning('*') .returning('*')
.execute(); .execute();
const deletedRecords = formatResult( const deletedRecords = formatResult<ObjectRecord[]>(
nonFormattedDeletedObjectRecords.raw, nonFormattedDeletedObjectRecords.raw,
objectMetadataItemWithFieldMaps, objectMetadataItemWithFieldMaps,
objectMetadataMaps, objectMetadataMaps,
); );
this.apiEventEmitterService.emitDestroyEvents(
deletedRecords,
authContext,
objectMetadataItemWithFieldMaps,
);
const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
if (relations) { if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await processNestedRelationsHelper.processNestedRelations({ await processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps, objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: deletedRecords, parentObjectRecords: deletedRecords,
relations, relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS, limit: QUERY_MAX_RECORDS,
authContext, authContext,
dataSource, dataSource: executionArgs.dataSource,
}); });
} }
const typeORMObjectRecordsParser = const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return deletedRecords.map((record: T) => return deletedRecords.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({ typeORMObjectRecordsParser.processRecord({
objectRecord: record, objectRecord: record,
objectName: objectMetadataItemWithFieldMaps.nameSingular, objectName: objectMetadataItemWithFieldMaps.nameSingular,

View File

@ -1,8 +1,9 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields'; import {
GraphqlQueryBaseResolverService,
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; 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 { 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 { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { DestroyOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { DestroyOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@ -12,59 +13,34 @@ import {
GraphqlQueryRunnerException, GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode, GraphqlQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; } 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 { 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 { 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 { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
@Injectable() @Injectable()
export class GraphqlQueryDestroyOneResolverService export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResolverService<
implements ResolverService<DestroyOneResolverArgs, ObjectRecord> DestroyOneResolverArgs,
{ ObjectRecord
constructor( > {
private readonly twentyORMGlobalManager: TwentyORMGlobalManager, async resolve(
) {} executionArgs: GraphqlQueryResolverExecutionArgs<DestroyOneResolverArgs>,
): Promise<ObjectRecord> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
async resolve<T extends ObjectRecord = ObjectRecord>( const queryBuilder = executionArgs.repository.createQueryBuilder(
args: DestroyOneResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<T> {
const {
authContext,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
info,
} = options;
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
authContext.workspace.id,
);
const repository = dataSource.getRepository(
objectMetadataItemWithFieldMaps.nameSingular, objectMetadataItemWithFieldMaps.nameSingular,
); );
const graphqlQueryParser = new GraphqlQueryParser( const tableName = computeTableName(
objectMetadataItemWithFieldMaps.fieldsByName,
objectMetadataMaps,
);
const selectedFields = graphqlFields(info);
const { relations } = graphqlQueryParser.parseSelectedFields(
objectMetadataItemWithFieldMaps,
selectedFields,
);
const queryBuilder = repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular, objectMetadataItemWithFieldMaps.nameSingular,
objectMetadataItemWithFieldMaps.isCustom,
); );
const nonFormattedDeletedObjectRecords = await queryBuilder const nonFormattedDeletedObjectRecords = await queryBuilder
.where(`"${objectMetadataItemWithFieldMaps.nameSingular}".id = :id`, { .where(`"${tableName}".id = :id`, {
id: args.id, id: executionArgs.args.id,
}) })
.take(1) .take(1)
.delete() .delete()
@ -78,23 +54,29 @@ export class GraphqlQueryDestroyOneResolverService
); );
} }
const recordBeforeDeletion = formatResult( const deletedRecords = formatResult<ObjectRecord[]>(
nonFormattedDeletedObjectRecords.raw, nonFormattedDeletedObjectRecords.raw,
objectMetadataItemWithFieldMaps, objectMetadataItemWithFieldMaps,
objectMetadataMaps, objectMetadataMaps,
)[0]; );
this.apiEventEmitterService.emitDestroyEvents(
deletedRecords,
authContext,
objectMetadataItemWithFieldMaps,
);
const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
if (relations) { if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await processNestedRelationsHelper.processNestedRelations({ await processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps, objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: [recordBeforeDeletion], parentObjectRecords: deletedRecords,
relations, relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS, limit: QUERY_MAX_RECORDS,
authContext, authContext,
dataSource, dataSource: executionArgs.dataSource,
}); });
} }
@ -102,7 +84,7 @@ export class GraphqlQueryDestroyOneResolverService
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return typeORMObjectRecordsParser.processRecord({ return typeORMObjectRecordsParser.processRecord({
objectRecord: recordBeforeDeletion, objectRecord: deletedRecords[0],
objectName: objectMetadataItemWithFieldMaps.nameSingular, objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1, take: 1,
totalCount: 1, totalCount: 1,

View File

@ -3,7 +3,10 @@ import { Injectable } from '@nestjs/common';
import isEmpty from 'lodash.isempty'; import isEmpty from 'lodash.isempty';
import { In } from 'typeorm'; 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 { import {
ObjectRecord, ObjectRecord,
ObjectRecordFilter, 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 { 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 { 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 { 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 { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
@Injectable() @Injectable()
export class GraphqlQueryFindDuplicatesResolverService export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseResolverService<
implements FindDuplicatesResolverArgs,
ResolverService<FindDuplicatesResolverArgs, IConnection<ObjectRecord>[]> IConnection<ObjectRecord>[]
{ > {
constructor( async resolve(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager, executionArgs: GraphqlQueryResolverExecutionArgs<FindDuplicatesResolverArgs>,
) {} ): Promise<IConnection<ObjectRecord>[]> {
const { objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
async resolve<T extends ObjectRecord = ObjectRecord>( const existingRecordsQueryBuilder =
args: FindDuplicatesResolverArgs<Partial<T>>, executionArgs.repository.createQueryBuilder(
options: WorkspaceQueryRunnerOptions, objectMetadataItemWithFieldMaps.nameSingular,
): Promise<IConnection<T>[]> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
options;
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
authContext.workspace.id,
); );
const repository = dataSource.getRepository(
objectMetadataItemWithFieldMaps.nameSingular,
);
const existingRecordsQueryBuilder = repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
const duplicateRecordsQueryBuilder = repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
const objectMetadataItemWithFieldsMaps = const objectMetadataItemWithFieldsMaps =
getObjectMetadataMapItemByNameSingular( getObjectMetadataMapItemByNameSingular(
@ -78,23 +67,26 @@ export class GraphqlQueryFindDuplicatesResolverService
const typeORMObjectRecordsParser = const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
let objectRecords: Partial<T>[] = []; let objectRecords: Partial<ObjectRecord>[] = [];
if (args.ids) { if (executionArgs.args.ids) {
const nonFormattedObjectRecords = (await existingRecordsQueryBuilder const nonFormattedObjectRecords = (await existingRecordsQueryBuilder
.where({ id: In(args.ids) }) .where({ id: In(executionArgs.args.ids) })
.getMany()) as T[]; .getMany()) as ObjectRecord[];
objectRecords = formatResult( objectRecords = formatResult(
nonFormattedObjectRecords, nonFormattedObjectRecords,
objectMetadataItemWithFieldMaps, objectMetadataItemWithFieldMaps,
objectMetadataMaps, objectMetadataMaps,
); );
} else if (args.data && !isEmpty(args.data)) { } else if (executionArgs.args.data && !isEmpty(executionArgs.args.data)) {
objectRecords = formatData(args.data, objectMetadataItemWithFieldMaps); objectRecords = formatData(
executionArgs.args.data,
objectMetadataItemWithFieldMaps,
);
} }
const duplicateConnections: IConnection<T>[] = await Promise.all( const duplicateConnections: IConnection<ObjectRecord>[] = await Promise.all(
objectRecords.map(async (record) => { objectRecords.map(async (record) => {
const duplicateConditions = this.buildDuplicateConditions( const duplicateConditions = this.buildDuplicateConditions(
objectMetadataItemWithFieldMaps, objectMetadataItemWithFieldMaps,
@ -114,16 +106,26 @@ export class GraphqlQueryFindDuplicatesResolverService
}); });
} }
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( const duplicateRecordsQueryBuilder =
duplicateRecordsQueryBuilder, executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
const tableName = computeTableName(
objectMetadataItemWithFieldMaps.nameSingular, objectMetadataItemWithFieldMaps.nameSingular,
objectMetadataItemWithFieldMaps.isCustom,
);
graphqlQueryParser.applyFilterToBuilder(
duplicateRecordsQueryBuilder,
tableName,
duplicateConditions, duplicateConditions,
); );
const nonFormattedDuplicates = const nonFormattedDuplicates =
(await withFilterQueryBuilder.getMany()) as T[]; (await duplicateRecordsQueryBuilder.getMany()) as ObjectRecord[];
const duplicates = formatResult( const duplicates = formatResult<ObjectRecord[]>(
nonFormattedDuplicates, nonFormattedDuplicates,
objectMetadataItemWithFieldMaps, objectMetadataItemWithFieldMaps,
objectMetadataMaps, objectMetadataMaps,

View File

@ -1,8 +1,9 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields'; import {
GraphqlQueryBaseResolverService,
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { import {
ObjectRecord, ObjectRecord,
ObjectRecordFilter, ObjectRecordFilter,
@ -18,8 +19,6 @@ import {
GraphqlQueryRunnerException, GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode, GraphqlQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; } 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 { 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 { 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'; 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'; } 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 { 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 { 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 { 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'; import { isDefined } from 'src/utils/is-defined';
@Injectable() @Injectable()
export class GraphqlQueryFindManyResolverService export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolverService<
implements ResolverService<FindManyResolverArgs, IConnection<ObjectRecord>> FindManyResolverArgs,
{ IConnection<ObjectRecord>
constructor( > {
private readonly twentyORMGlobalManager: TwentyORMGlobalManager, constructor(private readonly featureFlagService: FeatureFlagService) {
private readonly featureFlagService: FeatureFlagService, super();
) {} }
async resolve< async resolve(
T extends ObjectRecord = ObjectRecord, executionArgs: GraphqlQueryResolverExecutionArgs<FindManyResolverArgs>,
Filter extends ObjectRecordFilter = ObjectRecordFilter, ): Promise<IConnection<ObjectRecord>> {
OrderBy extends ObjectRecordOrderBy = ObjectRecordOrderBy, const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
>( executionArgs.options;
args: FindManyResolverArgs<Filter, OrderBy>,
options: WorkspaceQueryRunnerOptions,
): Promise<IConnection<T>> {
const {
authContext,
objectMetadataItemWithFieldMaps,
info,
objectMetadataMaps,
} = options;
const dataSource = const queryBuilder = executionArgs.repository.createQueryBuilder(
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
authContext.workspace.id,
);
const repository = dataSource.getRepository(
objectMetadataItemWithFieldMaps.nameSingular, objectMetadataItemWithFieldMaps.nameSingular,
); );
const queryBuilder = repository.createQueryBuilder( const aggregateQueryBuilder = queryBuilder.clone();
let appliedFilters =
executionArgs.args.filter ?? ({} as ObjectRecordFilter);
const tableName = computeTableName(
objectMetadataItemWithFieldMaps.nameSingular, objectMetadataItemWithFieldMaps.nameSingular,
objectMetadataItemWithFieldMaps.isCustom,
); );
const aggregateQueryBuilder = repository.createQueryBuilder( executionArgs.graphqlQueryParser.applyFilterToBuilder(
objectMetadataItemWithFieldMaps.nameSingular, aggregateQueryBuilder,
tableName,
appliedFilters,
); );
const graphqlQueryParser = new GraphqlQueryParser( executionArgs.graphqlQueryParser.applyDeletedAtToBuilder(
objectMetadataItemWithFieldMaps.fieldsByName, aggregateQueryBuilder,
objectMetadataMaps, 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 = [ const orderByWithIdCondition = [
...(args.orderBy ?? []), ...(executionArgs.args.orderBy ?? []),
{ id: OrderByDirection.AscNullsFirst }, { id: OrderByDirection.AscNullsFirst },
] as OrderBy; ] as ObjectRecordOrderBy;
const isForwardPagination = !isDefined(executionArgs.args.before);
const cursor = getCursor(executionArgs.args);
if (cursor) { if (cursor) {
const cursorArgFilter = computeCursorArgFilter( const cursorArgFilter = computeCursorArgFilter(
@ -119,29 +90,29 @@ export class GraphqlQueryFindManyResolverService
isForwardPagination, 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, queryBuilder,
objectMetadataItemWithFieldMaps.nameSingular, tableName,
appliedFilters, appliedFilters,
); );
const withOrderByQueryBuilder = graphqlQueryParser.applyOrderToBuilder( executionArgs.graphqlQueryParser.applyOrderToBuilder(
withFilterQueryBuilder, queryBuilder,
orderByWithIdCondition, orderByWithIdCondition,
objectMetadataItemWithFieldMaps.nameSingular, tableName,
isForwardPagination, isForwardPagination,
); );
const withDeletedQueryBuilder = graphqlQueryParser.applyDeletedAtToBuilder( executionArgs.graphqlQueryParser.applyDeletedAtToBuilder(
withOrderByQueryBuilder, queryBuilder,
args.filter ?? ({} as Filter), appliedFilters,
); );
const isAggregationsEnabled = const isAggregationsEnabled =
@ -151,25 +122,28 @@ export class GraphqlQueryFindManyResolverService
); );
if (!isAggregationsEnabled) { if (!isAggregationsEnabled) {
graphqlQuerySelectedFieldsResult.aggregate = { executionArgs.graphqlQuerySelectedFieldsResult.aggregate = {
totalCount: graphqlQuerySelectedFieldsResult.aggregate.totalCount, totalCount:
executionArgs.graphqlQuerySelectedFieldsResult.aggregate.totalCount,
}; };
} }
const processAggregateHelper = new ProcessAggregateHelper(); const processAggregateHelper = new ProcessAggregateHelper();
processAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder({ processAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder({
selectedAggregatedFields: graphqlQuerySelectedFieldsResult.aggregate, selectedAggregatedFields:
queryBuilder: withDeletedAggregateQueryBuilder, 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) .take(limit + 1)
.getMany(); .getMany();
const objectRecords = formatResult( const objectRecords = formatResult<ObjectRecord[]>(
nonFormattedObjectRecords, nonFormattedObjectRecords,
objectMetadataItemWithFieldMaps, objectMetadataItemWithFieldMaps,
objectMetadataMaps, objectMetadataMaps,
@ -186,21 +160,21 @@ export class GraphqlQueryFindManyResolverService
} }
const parentObjectRecordsAggregatedValues = const parentObjectRecordsAggregatedValues =
await withDeletedAggregateQueryBuilder.getRawOne(); await aggregateQueryBuilder.getRawOne();
const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
if (graphqlQuerySelectedFieldsResult.relations) { if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await processNestedRelationsHelper.processNestedRelations({ await processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps, objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: objectRecords, parentObjectRecords: objectRecords,
parentObjectRecordsAggregatedValues, parentObjectRecordsAggregatedValues,
relations: graphqlQuerySelectedFieldsResult.relations, relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
aggregate: graphqlQuerySelectedFieldsResult.aggregate, aggregate: executionArgs.graphqlQuerySelectedFieldsResult.aggregate,
limit, limit,
authContext, authContext,
dataSource, dataSource: executionArgs.dataSource,
}); });
} }
@ -210,7 +184,8 @@ export class GraphqlQueryFindManyResolverService
return typeORMObjectRecordsParser.createConnection({ return typeORMObjectRecordsParser.createConnection({
objectRecords, objectRecords,
objectRecordsAggregatedValues: parentObjectRecordsAggregatedValues, objectRecordsAggregatedValues: parentObjectRecordsAggregatedValues,
selectedAggregatedFields: graphqlQuerySelectedFieldsResult.aggregate, selectedAggregatedFields:
executionArgs.graphqlQuerySelectedFieldsResult.aggregate,
objectName: objectMetadataItemWithFieldMaps.nameSingular, objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: limit, take: limit,
totalCount: parentObjectRecordsAggregatedValues?.totalCount, totalCount: parentObjectRecordsAggregatedValues?.totalCount,

View File

@ -1,8 +1,9 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields'; import {
GraphqlQueryBaseResolverService,
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { import {
ObjectRecord, ObjectRecord,
ObjectRecordFilter, ObjectRecordFilter,
@ -15,77 +16,49 @@ import {
GraphqlQueryRunnerException, GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode, GraphqlQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; } 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 { 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 { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
import { import {
WorkspaceQueryRunnerException, WorkspaceQueryRunnerException,
WorkspaceQueryRunnerExceptionCode, WorkspaceQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception'; } 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 { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
@Injectable() @Injectable()
export class GraphqlQueryFindOneResolverService export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolverService<
implements ResolverService<FindOneResolverArgs, ObjectRecord> FindOneResolverArgs,
{ ObjectRecord
constructor( > {
private readonly twentyORMGlobalManager: TwentyORMGlobalManager, async resolve(
) {} executionArgs: GraphqlQueryResolverExecutionArgs<FindOneResolverArgs>,
): Promise<ObjectRecord> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
async resolve< const queryBuilder = executionArgs.repository.createQueryBuilder(
T extends ObjectRecord = ObjectRecord,
Filter extends ObjectRecordFilter = ObjectRecordFilter,
>(
args: FindOneResolverArgs<Filter>,
options: WorkspaceQueryRunnerOptions,
): Promise<T> {
const {
authContext,
objectMetadataItemWithFieldMaps,
info,
objectMetadataMaps,
} = options;
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
authContext.workspace.id,
);
const repository = dataSource.getRepository(
objectMetadataItemWithFieldMaps.nameSingular, objectMetadataItemWithFieldMaps.nameSingular,
); );
const queryBuilder = repository.createQueryBuilder( const tableName = computeTableName(
objectMetadataItemWithFieldMaps.nameSingular, objectMetadataItemWithFieldMaps.nameSingular,
objectMetadataItemWithFieldMaps.isCustom,
); );
const graphqlQueryParser = new GraphqlQueryParser( executionArgs.graphqlQueryParser.applyFilterToBuilder(
objectMetadataItemWithFieldMaps.fieldsByName,
objectMetadataMaps,
);
const selectedFields = graphqlFields(info);
const { relations } = graphqlQueryParser.parseSelectedFields(
objectMetadataItemWithFieldMaps,
selectedFields,
);
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
queryBuilder, queryBuilder,
objectMetadataItemWithFieldMaps.nameSingular, tableName,
args.filter ?? ({} as Filter), executionArgs.args.filter ?? ({} as ObjectRecordFilter),
); );
const withDeletedQueryBuilder = graphqlQueryParser.applyDeletedAtToBuilder( executionArgs.graphqlQueryParser.applyDeletedAtToBuilder(
withFilterQueryBuilder, queryBuilder,
args.filter ?? ({} as Filter), executionArgs.args.filter ?? ({} as ObjectRecordFilter),
); );
const nonFormattedObjectRecord = await withDeletedQueryBuilder.getOne(); const nonFormattedObjectRecord = await queryBuilder.getOne();
const objectRecord = formatResult( const objectRecord = formatResult<ObjectRecord>(
nonFormattedObjectRecord, nonFormattedObjectRecord,
objectMetadataItemWithFieldMaps, objectMetadataItemWithFieldMaps,
objectMetadataMaps, objectMetadataMaps,
@ -102,15 +75,15 @@ export class GraphqlQueryFindOneResolverService
const objectRecords = [objectRecord]; const objectRecords = [objectRecord];
if (relations) { if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await processNestedRelationsHelper.processNestedRelations({ await processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps, objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: objectRecords, parentObjectRecords: objectRecords,
relations, relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS, limit: QUERY_MAX_RECORDS,
authContext, authContext,
dataSource, dataSource: executionArgs.dataSource,
}); });
} }
@ -122,11 +95,11 @@ export class GraphqlQueryFindOneResolverService
objectName: objectMetadataItemWithFieldMaps.nameSingular, objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1, take: 1,
totalCount: 1, totalCount: 1,
}) as T; }) as ObjectRecord;
} }
async validate<Filter extends ObjectRecordFilter>( async validate(
args: FindOneResolverArgs<Filter>, args: FindOneResolverArgs<ObjectRecordFilter>,
_options: WorkspaceQueryRunnerOptions, _options: WorkspaceQueryRunnerOptions,
): Promise<void> { ): Promise<void> {
if (!args.filter || Object.keys(args.filter).length === 0) { if (!args.filter || Object.keys(args.filter).length === 0) {

View File

@ -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<RestoreManyResolverArgs>,
): Promise<ObjectRecord[]> {
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<ObjectRecord[]>(
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<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
if (!args.filter) {
throw new Error('Filter is required');
}
args.filter.id?.in?.forEach((id: string) => assertIsValidUuid(id));
}
}

View File

@ -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<RestoreOneResolverArgs>,
): Promise<ObjectRecord> {
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<ObjectRecord[]>(
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<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
assertIsValidUuid(args.id);
}
}

View File

@ -1,9 +1,11 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { Brackets } from 'typeorm'; 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 { import {
ObjectRecord, ObjectRecord,
ObjectRecordFilter, 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 { 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 { 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 { 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 { 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 { 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'; import { isDefined } from 'src/utils/is-defined';
@Injectable() @Injectable()
export class GraphqlQuerySearchResolverService export class GraphqlQuerySearchResolverService extends GraphqlQueryBaseResolverService<
implements ResolverService<SearchResolverArgs, IConnection<ObjectRecord>> SearchResolverArgs,
{ IConnection<ObjectRecord>
constructor( > {
private readonly twentyORMGlobalManager: TwentyORMGlobalManager, async resolve(
) {} executionArgs: GraphqlQueryResolverExecutionArgs<SearchResolverArgs>,
): Promise<IConnection<ObjectRecord>> {
async resolve< const { authContext, objectMetadataMaps, objectMetadataItemWithFieldMaps } =
T extends ObjectRecord = ObjectRecord, executionArgs.options;
Filter extends ObjectRecordFilter = ObjectRecordFilter,
>(
args: SearchResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<IConnection<T>> {
const {
authContext,
objectMetadataMaps,
objectMetadataItemWithFieldMaps,
info,
} = options;
const repository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
authContext.workspace.id,
objectMetadataItemWithFieldMaps.nameSingular,
);
const typeORMObjectRecordsParser = const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
if (!isDefined(args.searchInput)) { if (!isDefined(executionArgs.args.searchInput)) {
return typeORMObjectRecordsParser.createConnection({ return typeORMObjectRecordsParser.createConnection({
objectRecords: [], objectRecords: [],
objectName: objectMetadataItemWithFieldMaps.nameSingular, objectName: objectMetadataItemWithFieldMaps.nameSingular,
@ -64,26 +48,36 @@ export class GraphqlQuerySearchResolverService
hasPreviousPage: false, 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, objectMetadataItemWithFieldMaps.nameSingular,
); );
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataItemWithFieldMaps.fieldsByName, const tableName = computeTableName(
objectMetadataMaps, objectMetadataItemWithFieldMaps.nameSingular,
objectMetadataItemWithFieldMaps.isCustom,
); );
const queryBuilderWithFilter = graphqlQueryParser.applyFilterToBuilder( executionArgs.graphqlQueryParser.applyFilterToBuilder(
queryBuilder, queryBuilder,
objectMetadataItemWithFieldMaps.nameSingular, tableName,
args.filter ?? ({} as Filter), executionArgs.args.filter ?? ({} as ObjectRecordFilter),
); );
const resultsWithTsVector = (await queryBuilderWithFilter const countQueryBuilder = queryBuilder.clone();
const resultsWithTsVector = (await queryBuilder
.andWhere( .andWhere(
new Brackets((qb) => { new Brackets((qb) => {
qb.where( qb.where(
@ -110,40 +104,33 @@ export class GraphqlQuerySearchResolverService
.setParameter('searchTerms', searchTerms) .setParameter('searchTerms', searchTerms)
.setParameter('searchTermsOr', searchTermsOr) .setParameter('searchTermsOr', searchTermsOr)
.take(limit) .take(limit)
.getMany()) as T[]; .getMany()) as ObjectRecord[];
const objectRecords = await repository.formatResult(resultsWithTsVector); const objectRecords = formatResult<ObjectRecord[]>(
resultsWithTsVector,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);
const selectedFields = graphqlFields(info); const totalCount = isDefined(
executionArgs.graphqlQuerySelectedFieldsResult.aggregate.totalCount,
const graphqlQuerySelectedFieldsResult: GraphqlQuerySelectedFieldsResult = )
graphqlQueryParser.parseSelectedFields( ? await countQueryBuilder.getCount()
objectMetadataItemWithFieldMaps,
selectedFields,
);
const totalCount = isDefined(selectedFields.totalCount)
? await queryBuilderWithFilter.getCount()
: 0; : 0;
const order = undefined; const order = undefined;
const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
const dataSource = if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
authContext.workspace.id,
);
if (graphqlQuerySelectedFieldsResult.relations) {
await processNestedRelationsHelper.processNestedRelations({ await processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps, objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: objectRecords, parentObjectRecords: objectRecords,
relations: graphqlQuerySelectedFieldsResult.relations, relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
aggregate: graphqlQuerySelectedFieldsResult.aggregate, aggregate: executionArgs.graphqlQuerySelectedFieldsResult.aggregate,
limit, limit,
authContext, authContext,
dataSource, dataSource: executionArgs.dataSource,
}); });
} }

View File

@ -1,64 +1,34 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields'; import {
GraphqlQueryBaseResolverService,
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
import { computeTableName } from 'src/engine/utils/compute-table-name.util'; import { computeTableName } from 'src/engine/utils/compute-table-name.util';
@Injectable() @Injectable()
export class GraphqlQueryUpdateManyResolverService export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResolverService<
implements ResolverService<UpdateManyResolverArgs, ObjectRecord[]> UpdateManyResolverArgs,
{ ObjectRecord[]
constructor( > {
private readonly twentyORMGlobalManager: TwentyORMGlobalManager, async resolve(
) {} executionArgs: GraphqlQueryResolverExecutionArgs<UpdateManyResolverArgs>,
): Promise<ObjectRecord[]> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
async resolve<T extends ObjectRecord = ObjectRecord>( const queryBuilder = executionArgs.repository.createQueryBuilder(
args: UpdateManyResolverArgs<Partial<T>>,
options: WorkspaceQueryRunnerOptions,
): Promise<T[]> {
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(
objectMetadataItemWithFieldMaps.nameSingular, objectMetadataItemWithFieldMaps.nameSingular,
); );
@ -67,43 +37,64 @@ export class GraphqlQueryUpdateManyResolverService
objectMetadataItemWithFieldMaps.isCustom, objectMetadataItemWithFieldMaps.isCustom,
); );
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( executionArgs.graphqlQueryParser.applyFilterToBuilder(
queryBuilder, queryBuilder,
tableName, 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<ObjectRecord[]>(
existingRecords,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);
const data = formatData(
executionArgs.args.data,
objectMetadataItemWithFieldMaps,
);
const nonFormattedUpdatedObjectRecords = await queryBuilder
.update(data) .update(data)
.returning('*') .returning('*')
.execute(); .execute();
const updatedRecords = formatResult( const formattedUpdatedRecords = formatResult<ObjectRecord[]>(
nonFormattedUpdatedObjectRecords.raw, nonFormattedUpdatedObjectRecords.raw,
objectMetadataItemWithFieldMaps, objectMetadataItemWithFieldMaps,
objectMetadataMaps, objectMetadataMaps,
); );
this.apiEventEmitterService.emitUpdateEvents(
formattedExistingRecords,
formattedUpdatedRecords,
Object.keys(executionArgs.args.data),
authContext,
objectMetadataItemWithFieldMaps,
);
const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
if (relations) { if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await processNestedRelationsHelper.processNestedRelations({ await processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps, objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: updatedRecords, parentObjectRecords: formattedUpdatedRecords,
relations, relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS, limit: QUERY_MAX_RECORDS,
authContext, authContext,
dataSource, dataSource: executionArgs.dataSource,
}); });
} }
const typeORMObjectRecordsParser = const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return updatedRecords.map((record: T) => return formattedUpdatedRecords.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({ typeORMObjectRecordsParser.processRecord({
objectRecord: record, objectRecord: record,
objectName: objectMetadataItemWithFieldMaps.nameSingular, objectName: objectMetadataItemWithFieldMaps.nameSingular,
@ -113,8 +104,8 @@ export class GraphqlQueryUpdateManyResolverService
); );
} }
async validate<T extends ObjectRecord = ObjectRecord>( async validate(
args: UpdateManyResolverArgs<Partial<T>>, args: UpdateManyResolverArgs<Partial<ObjectRecord>>,
options: WorkspaceQueryRunnerOptions, options: WorkspaceQueryRunnerOptions,
): Promise<void> { ): Promise<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps); assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);

View File

@ -1,8 +1,9 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields'; import {
GraphqlQueryBaseResolverService,
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; 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 { 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 { 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'; import { UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@ -12,102 +13,92 @@ import {
GraphqlQueryRunnerException, GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode, GraphqlQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; } 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 { 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 { 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 { 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 { 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 { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@Injectable() @Injectable()
export class GraphqlQueryUpdateOneResolverService export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolverService<
implements ResolverService<UpdateOneResolverArgs, ObjectRecord> UpdateOneResolverArgs,
{ ObjectRecord
constructor( > {
private readonly twentyORMGlobalManager: TwentyORMGlobalManager, async resolve(
) {} executionArgs: GraphqlQueryResolverExecutionArgs<UpdateOneResolverArgs>,
): Promise<ObjectRecord> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
async resolve<T extends ObjectRecord = ObjectRecord>( const queryBuilder = executionArgs.repository.createQueryBuilder(
args: UpdateOneResolverArgs<Partial<T>>,
options: WorkspaceQueryRunnerOptions,
): Promise<T> {
const {
authContext,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
info,
} = options;
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
authContext.workspace.id,
);
const repository = dataSource.getRepository(
objectMetadataItemWithFieldMaps.nameSingular, objectMetadataItemWithFieldMaps.nameSingular,
); );
const graphqlQueryParser = new GraphqlQueryParser( const data = formatData(
objectMetadataItemWithFieldMaps.fieldsByName, executionArgs.args.data,
objectMetadataItemWithFieldMaps,
);
const existingRecordBuilder = queryBuilder.clone();
const existingRecords = (await existingRecordBuilder
.where({ id: executionArgs.args.id })
.getMany()) as ObjectRecord[];
const formattedExistingRecords = formatResult<ObjectRecord[]>(
existingRecords,
objectMetadataItemWithFieldMaps,
objectMetadataMaps, objectMetadataMaps,
); );
const selectedFields = graphqlFields(info); const nonFormattedUpdatedObjectRecords = await queryBuilder
const { relations } = graphqlQueryParser.parseSelectedFields(
objectMetadataItemWithFieldMaps,
selectedFields,
);
const queryBuilder = repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
const data = formatData(args.data, objectMetadataItemWithFieldMaps);
const result = await queryBuilder
.update(data) .update(data)
.where({ id: args.id }) .where({ id: executionArgs.args.id })
.returning('*') .returning('*')
.execute(); .execute();
const nonFormattedUpdatedObjectRecords = result.raw; const formattedUpdatedRecords = formatResult<ObjectRecord[]>(
nonFormattedUpdatedObjectRecords.raw,
const updatedRecords = formatResult(
nonFormattedUpdatedObjectRecords,
objectMetadataItemWithFieldMaps, objectMetadataItemWithFieldMaps,
objectMetadataMaps, objectMetadataMaps,
); );
if (updatedRecords.length === 0) { this.apiEventEmitterService.emitUpdateEvents(
formattedExistingRecords,
formattedUpdatedRecords,
Object.keys(executionArgs.args.data),
authContext,
objectMetadataItemWithFieldMaps,
);
if (formattedUpdatedRecords.length === 0) {
throw new GraphqlQueryRunnerException( throw new GraphqlQueryRunnerException(
'Record not found', 'Record not found',
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
); );
} }
const updatedRecord = updatedRecords[0] as T; const updatedRecord = formattedUpdatedRecords[0];
const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
if (relations) { if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await processNestedRelationsHelper.processNestedRelations({ await processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps, objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps, parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: [updatedRecord], parentObjectRecords: [updatedRecord],
relations, relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS, limit: QUERY_MAX_RECORDS,
authContext, authContext,
dataSource, dataSource: executionArgs.dataSource,
}); });
} }
const typeORMObjectRecordsParser = const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return typeORMObjectRecordsParser.processRecord<T>({ return typeORMObjectRecordsParser.processRecord({
objectRecord: updatedRecord, objectRecord: updatedRecord,
objectName: objectMetadataItemWithFieldMaps.nameSingular, objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1, take: 1,
@ -115,8 +106,8 @@ export class GraphqlQueryUpdateOneResolverService
}); });
} }
async validate<T extends ObjectRecord = ObjectRecord>( async validate(
args: UpdateOneResolverArgs<Partial<T>>, args: UpdateOneResolverArgs<Partial<ObjectRecord>>,
options: WorkspaceQueryRunnerOptions, options: WorkspaceQueryRunnerOptions,
): Promise<void> { ): Promise<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps); assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);

View File

@ -26,7 +26,7 @@ export class ApiEventEmitterService {
objectMetadata: objectMetadataItem, objectMetadata: objectMetadataItem,
properties: { properties: {
before: null, before: null,
after: this.removeGraphQLAndNestedProperties(record), after: record,
}, },
})), })),
workspaceId: authContext.workspace.id, workspaceId: authContext.workspace.id,
@ -52,10 +52,8 @@ export class ApiEventEmitterService {
objectMetadataNameSingular: objectMetadataItem.nameSingular, objectMetadataNameSingular: objectMetadataItem.nameSingular,
action: DatabaseEventAction.UPDATED, action: DatabaseEventAction.UPDATED,
events: records.map((record) => { events: records.map((record) => {
const before = this.removeGraphQLAndNestedProperties( const before = mappedExistingRecords[record.id];
mappedExistingRecords[record.id], const after = record;
);
const after = this.removeGraphQLAndNestedProperties(record);
const diff = objectRecordChangedValues( const diff = objectRecordChangedValues(
before, before,
after, after,
@ -93,7 +91,7 @@ export class ApiEventEmitterService {
recordId: record.id, recordId: record.id,
objectMetadata: objectMetadataItem, objectMetadata: objectMetadataItem,
properties: { properties: {
before: this.removeGraphQLAndNestedProperties(record), before: record,
after: null, after: null,
}, },
}; };
@ -102,6 +100,29 @@ export class ApiEventEmitterService {
}); });
} }
public emitRestoreEvents<T extends ObjectRecord>(
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<T extends ObjectRecord>( public emitDestroyEvents<T extends ObjectRecord>(
records: T[], records: T[],
authContext: AuthContext, authContext: AuthContext,
@ -116,7 +137,7 @@ export class ApiEventEmitterService {
recordId: record.id, recordId: record.id,
objectMetadata: objectMetadataItem, objectMetadata: objectMetadataItem,
properties: { properties: {
before: this.removeGraphQLAndNestedProperties(record), before: record,
after: null, after: null,
}, },
}; };
@ -124,26 +145,4 @@ export class ApiEventEmitterService {
workspaceId: authContext.workspace.id, workspaceId: authContext.workspace.id,
}); });
} }
private removeGraphQLAndNestedProperties<T extends ObjectRecord>(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;
}
} }

View File

@ -3,6 +3,7 @@ import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/inter
export type QueryResultFieldValue = export type QueryResultFieldValue =
| IConnection<ObjectRecord> | IConnection<ObjectRecord>
| IConnection<ObjectRecord>[]
| { records: ObjectRecord[] } | { records: ObjectRecord[] }
| ObjectRecord | ObjectRecord
| ObjectRecord[]; | ObjectRecord[];

View File

@ -8,7 +8,7 @@ import {
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } 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 { 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'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
@ -18,7 +18,7 @@ export class CreateManyResolverFactory
public static methodName = 'createMany' as const; public static methodName = 'createMany' as const;
constructor( constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, private readonly graphqlQueryRunnerService: GraphqlQueryCreateManyResolverService,
) {} ) {}
create( create(
@ -36,7 +36,11 @@ export class CreateManyResolverFactory
internalContext.objectMetadataItemWithFieldMaps, internalContext.objectMetadataItemWithFieldMaps,
}; };
return await this.graphqlQueryRunnerService.createMany(args, options); return await this.graphqlQueryRunnerService.execute(
args,
options,
CreateManyResolverFactory.methodName,
);
} catch (error) { } catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, context); workspaceQueryRunnerGraphqlApiExceptionHandler(error, context);
} }

View File

@ -8,7 +8,7 @@ import {
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } 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 { 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'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
@ -18,7 +18,7 @@ export class CreateOneResolverFactory
public static methodName = 'createOne' as const; public static methodName = 'createOne' as const;
constructor( constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, private readonly graphqlQueryRunnerService: GraphqlQueryCreateOneResolverService,
) {} ) {}
create( create(
@ -36,7 +36,11 @@ export class CreateOneResolverFactory
internalContext.objectMetadataItemWithFieldMaps, internalContext.objectMetadataItemWithFieldMaps,
}; };
return await this.graphqlQueryRunnerService.createOne(args, options); return await this.graphqlQueryRunnerService.execute(
args,
options,
CreateOneResolverFactory.methodName,
);
} catch (error) { } catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
} }

View File

@ -8,7 +8,7 @@ import {
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } 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 { 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'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
@ -18,7 +18,7 @@ export class DeleteManyResolverFactory
public static methodName = 'deleteMany' as const; public static methodName = 'deleteMany' as const;
constructor( constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, private readonly graphqlQueryRunnerService: GraphqlQueryDeleteManyResolverService,
) {} ) {}
create( create(
@ -36,7 +36,11 @@ export class DeleteManyResolverFactory
internalContext.objectMetadataItemWithFieldMaps, internalContext.objectMetadataItemWithFieldMaps,
}; };
return await this.graphqlQueryRunnerService.deleteMany(args, options); return await this.graphqlQueryRunnerService.execute(
args,
options,
DeleteManyResolverFactory.methodName,
);
} catch (error) { } catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
} }

View File

@ -8,7 +8,7 @@ import {
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } 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 { 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'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
@ -18,7 +18,7 @@ export class DeleteOneResolverFactory
public static methodName = 'deleteOne' as const; public static methodName = 'deleteOne' as const;
constructor( constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, private readonly graphqlQueryRunnerService: GraphqlQueryDeleteOneResolverService,
) {} ) {}
create( create(
@ -36,7 +36,11 @@ export class DeleteOneResolverFactory
internalContext.objectMetadataItemWithFieldMaps, internalContext.objectMetadataItemWithFieldMaps,
}; };
return await this.graphqlQueryRunnerService.deleteOne(args, options); return await this.graphqlQueryRunnerService.execute(
args,
options,
DeleteOneResolverFactory.methodName,
);
} catch (error) { } catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
} }

View File

@ -8,7 +8,7 @@ import {
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } 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 { 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'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
@ -18,7 +18,7 @@ export class DestroyManyResolverFactory
public static methodName = 'destroyMany' as const; public static methodName = 'destroyMany' as const;
constructor( constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, private readonly graphqlQueryRunnerService: GraphqlQueryDestroyManyResolverService,
) {} ) {}
create( create(
@ -36,7 +36,11 @@ export class DestroyManyResolverFactory
internalContext.objectMetadataItemWithFieldMaps, internalContext.objectMetadataItemWithFieldMaps,
}; };
return await this.graphqlQueryRunnerService.destroyMany(args, options); return await this.graphqlQueryRunnerService.execute(
args,
options,
DestroyManyResolverFactory.methodName,
);
} catch (error) { } catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
} }

View File

@ -8,7 +8,7 @@ import {
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } 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 { 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'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
@ -18,7 +18,7 @@ export class DestroyOneResolverFactory
public static methodName = 'destroyOne' as const; public static methodName = 'destroyOne' as const;
constructor( constructor(
private readonly graphQLQueryRunnerService: GraphqlQueryRunnerService, private readonly graphQLQueryRunnerService: GraphqlQueryDestroyOneResolverService,
) {} ) {}
create( create(
@ -36,7 +36,11 @@ export class DestroyOneResolverFactory
internalContext.objectMetadataItemWithFieldMaps, internalContext.objectMetadataItemWithFieldMaps,
}; };
return await this.graphQLQueryRunnerService.destroyOne(args, options); return await this.graphQLQueryRunnerService.execute(
args,
options,
DestroyOneResolverFactory.methodName,
);
} catch (error) { } catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
} }

View File

@ -1,6 +1,7 @@
import { DestroyManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory'; 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 { 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 { 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 { 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 { UpdateManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory';
@ -25,6 +26,7 @@ export const workspaceResolverBuilderFactories = [
DeleteManyResolverFactory, DeleteManyResolverFactory,
DestroyOneResolverFactory, DestroyOneResolverFactory,
DestroyManyResolverFactory, DestroyManyResolverFactory,
RestoreOneResolverFactory,
RestoreManyResolverFactory, RestoreManyResolverFactory,
SearchResolverFactory, SearchResolverFactory,
]; ];
@ -45,6 +47,7 @@ export const workspaceResolverBuilderMethodNames = {
DeleteManyResolverFactory.methodName, DeleteManyResolverFactory.methodName,
DestroyOneResolverFactory.methodName, DestroyOneResolverFactory.methodName,
DestroyManyResolverFactory.methodName, DestroyManyResolverFactory.methodName,
RestoreOneResolverFactory.methodName,
RestoreManyResolverFactory.methodName, RestoreManyResolverFactory.methodName,
], ],
} as const; } as const;

View File

@ -8,7 +8,7 @@ import {
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } 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 { 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'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
@ -18,7 +18,7 @@ export class FindDuplicatesResolverFactory
public static methodName = 'findDuplicates' as const; public static methodName = 'findDuplicates' as const;
constructor( constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, private readonly graphqlQueryRunnerService: GraphqlQueryFindDuplicatesResolverService,
) {} ) {}
create( create(
@ -36,9 +36,10 @@ export class FindDuplicatesResolverFactory
internalContext.objectMetadataItemWithFieldMaps, internalContext.objectMetadataItemWithFieldMaps,
}; };
return await this.graphqlQueryRunnerService.findDuplicates( return await this.graphqlQueryRunnerService.execute(
args, args,
options, options,
FindDuplicatesResolverFactory.methodName,
); );
} catch (error) { } catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);

View File

@ -8,7 +8,7 @@ import {
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } 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 { 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'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
@ -18,7 +18,7 @@ export class FindManyResolverFactory
public static methodName = 'findMany' as const; public static methodName = 'findMany' as const;
constructor( constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, private readonly graphqlQueryRunnerService: GraphqlQueryFindManyResolverService,
) {} ) {}
create( create(
@ -36,7 +36,11 @@ export class FindManyResolverFactory
internalContext.objectMetadataItemWithFieldMaps, internalContext.objectMetadataItemWithFieldMaps,
}; };
return await this.graphqlQueryRunnerService.findMany(args, options); return await this.graphqlQueryRunnerService.execute(
args,
options,
FindManyResolverFactory.methodName,
);
} catch (error) { } catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
} }

View File

@ -8,7 +8,7 @@ import {
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } 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 { 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'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
@ -18,7 +18,7 @@ export class FindOneResolverFactory
public static methodName = 'findOne' as const; public static methodName = 'findOne' as const;
constructor( constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, private readonly graphqlQueryRunnerService: GraphqlQueryFindOneResolverService,
) {} ) {}
create( create(
@ -36,7 +36,11 @@ export class FindOneResolverFactory
internalContext.objectMetadataItemWithFieldMaps, internalContext.objectMetadataItemWithFieldMaps,
}; };
return await this.graphqlQueryRunnerService.findOne(args, options); return await this.graphqlQueryRunnerService.execute(
args,
options,
FindOneResolverFactory.methodName,
);
} catch (error) { } catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
} }

View File

@ -8,7 +8,7 @@ import {
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } 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 { 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'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
@ -18,7 +18,7 @@ export class RestoreManyResolverFactory
public static methodName = 'restoreMany' as const; public static methodName = 'restoreMany' as const;
constructor( constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, private readonly graphqlQueryRunnerService: GraphqlQueryRestoreManyResolverService,
) {} ) {}
create( create(
@ -36,7 +36,11 @@ export class RestoreManyResolverFactory
internalContext.objectMetadataItemWithFieldMaps, internalContext.objectMetadataItemWithFieldMaps,
}; };
return await this.graphqlQueryRunnerService.restoreMany(args, options); return await this.graphqlQueryRunnerService.execute(
args,
options,
RestoreManyResolverFactory.methodName,
);
} catch (error) { } catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
} }

View File

@ -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<RestoreOneResolverArgs> {
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);
}
};
}
}

View File

@ -8,7 +8,7 @@ import {
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } 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 { 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'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
@ -18,7 +18,7 @@ export class SearchResolverFactory
public static methodName = 'search' as const; public static methodName = 'search' as const;
constructor( constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, private readonly graphqlQueryRunnerService: GraphqlQuerySearchResolverService,
) {} ) {}
create(context: WorkspaceSchemaBuilderContext): Resolver<SearchResolverArgs> { create(context: WorkspaceSchemaBuilderContext): Resolver<SearchResolverArgs> {
@ -34,7 +34,11 @@ export class SearchResolverFactory
internalContext.objectMetadataItemWithFieldMaps, internalContext.objectMetadataItemWithFieldMaps,
}; };
return await this.graphqlQueryRunnerService.search(args, options); return await this.graphqlQueryRunnerService.execute(
args,
options,
SearchResolverFactory.methodName,
);
} catch (error) { } catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
} }

View File

@ -8,7 +8,7 @@ import {
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } 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 { 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'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
@ -18,7 +18,7 @@ export class UpdateManyResolverFactory
public static methodName = 'updateMany' as const; public static methodName = 'updateMany' as const;
constructor( constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, private readonly graphqlQueryRunnerService: GraphqlQueryUpdateManyResolverService,
) {} ) {}
create( create(
@ -36,7 +36,11 @@ export class UpdateManyResolverFactory
internalContext.objectMetadataItemWithFieldMaps, internalContext.objectMetadataItemWithFieldMaps,
}; };
return await this.graphqlQueryRunnerService.updateMany(args, options); return await this.graphqlQueryRunnerService.execute(
args,
options,
UpdateManyResolverFactory.methodName,
);
} catch (error) { } catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
} }

View File

@ -8,7 +8,7 @@ import {
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } 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 { 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'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
@Injectable() @Injectable()
@ -18,7 +18,7 @@ export class UpdateOneResolverFactory
public static methodName = 'updateOne' as const; public static methodName = 'updateOne' as const;
constructor( constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, private readonly graphqlQueryRunnerService: GraphqlQueryUpdateOneResolverService,
) {} ) {}
create( create(
@ -36,7 +36,11 @@ export class UpdateOneResolverFactory
internalContext.objectMetadataItemWithFieldMaps, internalContext.objectMetadataItemWithFieldMaps,
}; };
return await this.graphqlQueryRunnerService.updateOne(args, options); return await this.graphqlQueryRunnerService.execute(
args,
options,
UpdateOneResolverFactory.methodName,
);
} catch (error) { } catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext); workspaceQueryRunnerGraphqlApiExceptionHandler(error, internalContext);
} }

View File

@ -37,7 +37,7 @@ export interface FindManyResolverArgs<
orderBy?: OrderBy; orderBy?: OrderBy;
} }
export interface FindOneResolverArgs<Filter = any> { export interface FindOneResolverArgs<Filter = ObjectRecordFilter> {
filter?: Filter; filter?: Filter;
} }
@ -93,6 +93,10 @@ export interface DeleteManyResolverArgs<Filter = any> {
filter: Filter; filter: Filter;
} }
export interface RestoreOneResolverArgs {
id: string;
}
export interface RestoreManyResolverArgs<Filter = any> { export interface RestoreManyResolverArgs<Filter = any> {
filter: Filter; filter: Filter;
} }
@ -125,11 +129,12 @@ export type ResolverArgs =
| CreateOneResolverArgs | CreateOneResolverArgs
| DeleteManyResolverArgs | DeleteManyResolverArgs
| DeleteOneResolverArgs | DeleteOneResolverArgs
| DestroyManyResolverArgs
| FindDuplicatesResolverArgs
| FindManyResolverArgs | FindManyResolverArgs
| FindOneResolverArgs | FindOneResolverArgs
| FindDuplicatesResolverArgs
| UpdateManyResolverArgs
| UpdateOneResolverArgs
| DestroyManyResolverArgs
| RestoreManyResolverArgs | RestoreManyResolverArgs
| SearchResolverArgs; | RestoreOneResolverArgs
| SearchResolverArgs
| UpdateManyResolverArgs
| UpdateOneResolverArgs;

View File

@ -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 { 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 { 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 { 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 { 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 { 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'; 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 destroyOneResolverFactory: DestroyOneResolverFactory,
private readonly updateManyResolverFactory: UpdateManyResolverFactory, private readonly updateManyResolverFactory: UpdateManyResolverFactory,
private readonly deleteManyResolverFactory: DeleteManyResolverFactory, private readonly deleteManyResolverFactory: DeleteManyResolverFactory,
private readonly restoreOneResolverFactory: RestoreOneResolverFactory,
private readonly restoreManyResolverFactory: RestoreManyResolverFactory, private readonly restoreManyResolverFactory: RestoreManyResolverFactory,
private readonly destroyManyResolverFactory: DestroyManyResolverFactory, private readonly destroyManyResolverFactory: DestroyManyResolverFactory,
private readonly searchResolverFactory: SearchResolverFactory, private readonly searchResolverFactory: SearchResolverFactory,
@ -54,19 +56,20 @@ export class WorkspaceResolverFactory {
WorkspaceResolverBuilderMethodNames, WorkspaceResolverBuilderMethodNames,
WorkspaceResolverBuilderFactoryInterface WorkspaceResolverBuilderFactoryInterface
>([ >([
['findMany', this.findManyResolverFactory],
['findOne', this.findOneResolverFactory],
['findDuplicates', this.findDuplicatesResolverFactory],
['createMany', this.createManyResolverFactory], ['createMany', this.createManyResolverFactory],
['createOne', this.createOneResolverFactory], ['createOne', this.createOneResolverFactory],
['updateOne', this.updateOneResolverFactory],
['deleteOne', this.deleteOneResolverFactory],
['destroyOne', this.destroyOneResolverFactory],
['updateMany', this.updateManyResolverFactory],
['deleteMany', this.deleteManyResolverFactory], ['deleteMany', this.deleteManyResolverFactory],
['restoreMany', this.restoreManyResolverFactory], ['deleteOne', this.deleteOneResolverFactory],
['destroyMany', this.destroyManyResolverFactory], ['destroyMany', this.destroyManyResolverFactory],
['destroyOne', this.destroyOneResolverFactory],
['findDuplicates', this.findDuplicatesResolverFactory],
['findMany', this.findManyResolverFactory],
['findOne', this.findOneResolverFactory],
['restoreMany', this.restoreManyResolverFactory],
['restoreOne', this.restoreOneResolverFactory],
['search', this.searchResolverFactory], ['search', this.searchResolverFactory],
['updateMany', this.updateManyResolverFactory],
['updateOne', this.updateOneResolverFactory],
]); ]);
const resolvers: IResolvers = { const resolvers: IResolvers = {
Query: {}, Query: {},

View File

@ -130,6 +130,13 @@ export const getResolverArgs = (
isNullable: false, isNullable: false,
}, },
}; };
case 'restoreOne':
return {
id: {
type: GraphQLID,
isNullable: false,
},
};
case 'destroyMany': case 'destroyMany':
return { return {
filter: { filter: {

View File

@ -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<T> {
properties: {
before: T;
};
}

View File

@ -13,7 +13,7 @@ import { getCompositeFieldMetadataCollection } from 'src/engine/twenty-orm/utils
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
export function formatResult<T>( export function formatResult<T>(
data: T, data: any,
ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
objectMetadataMaps: ObjectMetadataMaps, objectMetadataMaps: ObjectMetadataMaps,
): T { ): T {

View File

@ -15,24 +15,32 @@ export const getResolverName = (
return `${camelCase(objectMetadata.nameSingular)}`; return `${camelCase(objectMetadata.nameSingular)}`;
case 'findDuplicates': case 'findDuplicates':
return `${camelCase(objectMetadata.nameSingular)}Duplicates`; return `${camelCase(objectMetadata.nameSingular)}Duplicates`;
case 'createMany':
return `create${pascalCase(objectMetadata.namePlural)}`;
case 'createOne': case 'createOne':
return `create${pascalCase(objectMetadata.nameSingular)}`; return `create${pascalCase(objectMetadata.nameSingular)}`;
case 'createMany':
return `create${pascalCase(objectMetadata.namePlural)}`;
case 'updateOne': case 'updateOne':
return `update${pascalCase(objectMetadata.nameSingular)}`; return `update${pascalCase(objectMetadata.nameSingular)}`;
case 'deleteOne':
return `delete${pascalCase(objectMetadata.nameSingular)}`;
case 'destroyOne':
return `destroy${pascalCase(objectMetadata.nameSingular)}`;
case 'updateMany': case 'updateMany':
return `update${pascalCase(objectMetadata.namePlural)}`; return `update${pascalCase(objectMetadata.namePlural)}`;
case 'restoreMany':
return `restore${pascalCase(objectMetadata.namePlural)}`; case 'deleteOne':
return `delete${pascalCase(objectMetadata.nameSingular)}`;
case 'deleteMany': case 'deleteMany':
return `delete${pascalCase(objectMetadata.namePlural)}`; return `delete${pascalCase(objectMetadata.namePlural)}`;
case 'destroyOne':
return `destroy${pascalCase(objectMetadata.nameSingular)}`;
case 'destroyMany': case 'destroyMany':
return `destroy${pascalCase(objectMetadata.namePlural)}`; return `destroy${pascalCase(objectMetadata.namePlural)}`;
case 'restoreOne':
return `restore${pascalCase(objectMetadata.nameSingular)}`;
case 'restoreMany':
return `restore${pascalCase(objectMetadata.namePlural)}`;
case 'search': case 'search':
return `search${pascalCase(objectMetadata.namePlural)}`; return `search${pascalCase(objectMetadata.namePlural)}`;
default: default:

View File

@ -1,11 +1,12 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter'; 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 { 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 { 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 { 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'; import { CustomEventName } from 'src/engine/workspace-event-emitter/types/custom-event-name.type';
type ActionEventMap<T> = { type ActionEventMap<T> = {
@ -13,6 +14,7 @@ type ActionEventMap<T> = {
[DatabaseEventAction.UPDATED]: ObjectRecordUpdateEvent<T>; [DatabaseEventAction.UPDATED]: ObjectRecordUpdateEvent<T>;
[DatabaseEventAction.DELETED]: ObjectRecordDeleteEvent<T>; [DatabaseEventAction.DELETED]: ObjectRecordDeleteEvent<T>;
[DatabaseEventAction.DESTROYED]: ObjectRecordDestroyEvent<T>; [DatabaseEventAction.DESTROYED]: ObjectRecordDestroyEvent<T>;
[DatabaseEventAction.RESTORED]: ObjectRecordRestoreEvent<T>;
}; };
@Injectable() @Injectable()

View File

@ -253,7 +253,7 @@ export class RecordCRUDWorkflowAction implements WorkflowAction {
.take(workflowActionInput.limit ?? QUERY_MAX_RECORDS) .take(workflowActionInput.limit ?? QUERY_MAX_RECORDS)
.getMany(); .getMany();
return formatResult( return formatResult<T[]>(
nonFormattedObjectRecords, nonFormattedObjectRecords,
objectMetadataItemWithFieldsMaps, objectMetadataItemWithFieldsMaps,
objectMetadataMaps, objectMetadataMaps,