mirror of
https://github.com/lingble/twenty.git
synced 2025-11-01 05:07:56 +00:00
Refactor graphql query runner and add mutation resolvers (#7418)
Fixes https://github.com/twentyhq/twenty/issues/6859 This PR adds all the remaining resolvers for - updateOne/updateMany - createOne/createMany - deleteOne/deleteMany - destroyOne - restoreMany Also - refactored the graphql-query-runner to be able to add other resolvers without too much boilerplate. - add missing events that were not sent anymore as well as webhooks - make resolver injectable so they can inject other services as well - use objectMetadataMap from cache instead of computing it multiple time - various fixes (mutation not correctly parsing JSON, relationHelper fetching data with empty ids set, ...) Next steps: - Wrapping query builder to handle DB events properly - Move webhook emitters to db event listener - Add pagination where it's missing (findDuplicates, nested relations, etc...)
This commit is contained in:
@@ -5,12 +5,12 @@ export class AddIndexType1725893697807 implements MigrationInterface {
|
|||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(
|
await queryRunner.query(
|
||||||
`CREATE TYPE metadata."indextype_enum" AS ENUM ('BTREE', 'GIN')`,
|
`CREATE TYPE "metadata"."indexMetadata_indextype_enum" AS ENUM('BTREE', 'GIN')`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await queryRunner.query(`
|
await queryRunner.query(`
|
||||||
ALTER TABLE metadata."indexMetadata"
|
ALTER TABLE metadata."indexMetadata"
|
||||||
ADD COLUMN "indexType" metadata."indextype_enum" NOT NULL DEFAULT 'BTREE';
|
ADD COLUMN "indexType" metadata."indexMetadata_indextype_enum" NOT NULL DEFAULT 'BTREE';
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,6 +19,8 @@ export class AddIndexType1725893697807 implements MigrationInterface {
|
|||||||
ALTER TABLE metadata."indexMetadata" DROP COLUMN "indexType"
|
ALTER TABLE metadata."indexMetadata" DROP COLUMN "indexType"
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await queryRunner.query(`DROP TYPE metadata."indextype_enum"`);
|
await queryRunner.query(
|
||||||
|
`DROP TYPE metadata."indexMetadata_indextype_enum"`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
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 { 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 '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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ export class GraphqlQueryFilterConditionParser {
|
|||||||
public parse(
|
public parse(
|
||||||
queryBuilder: SelectQueryBuilder<any>,
|
queryBuilder: SelectQueryBuilder<any>,
|
||||||
objectNameSingular: string,
|
objectNameSingular: string,
|
||||||
filter: RecordFilter,
|
filter: Partial<RecordFilter>,
|
||||||
): SelectQueryBuilder<any> {
|
): SelectQueryBuilder<any> {
|
||||||
if (!filter || Object.keys(filter).length === 0) {
|
if (!filter || Object.keys(filter).length === 0) {
|
||||||
return queryBuilder;
|
return queryBuilder;
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ export class GraphqlQueryFilterFieldParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { sql, params } = this.computeWhereConditionParts(
|
const { sql, params } = this.computeWhereConditionParts(
|
||||||
fieldMetadata,
|
|
||||||
operator,
|
operator,
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
key,
|
key,
|
||||||
@@ -73,7 +72,6 @@ export class GraphqlQueryFilterFieldParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private computeWhereConditionParts(
|
private computeWhereConditionParts(
|
||||||
fieldMetadata: FieldMetadataInterface,
|
|
||||||
operator: string,
|
operator: string,
|
||||||
objectNameSingular: string,
|
objectNameSingular: string,
|
||||||
key: string,
|
key: string,
|
||||||
@@ -185,7 +183,6 @@ export class GraphqlQueryFilterFieldParser {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { sql, params } = this.computeWhereConditionParts(
|
const { sql, params } = this.computeWhereConditionParts(
|
||||||
fieldMetadata,
|
|
||||||
operator,
|
operator,
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
fullFieldName,
|
fullFieldName,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
RecordFilter,
|
RecordFilter,
|
||||||
RecordOrderBy,
|
RecordOrderBy,
|
||||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
|
||||||
|
|
||||||
import { GraphqlQueryFilterConditionParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser';
|
import { GraphqlQueryFilterConditionParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser';
|
||||||
import { GraphqlQueryOrderFieldParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser';
|
import { GraphqlQueryOrderFieldParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser';
|
||||||
@@ -17,6 +16,7 @@ import { GraphqlQuerySelectedFieldsParser } from 'src/engine/api/graphql/graphql
|
|||||||
import {
|
import {
|
||||||
FieldMetadataMap,
|
FieldMetadataMap,
|
||||||
ObjectMetadataMap,
|
ObjectMetadataMap,
|
||||||
|
ObjectMetadataMapItem,
|
||||||
} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||||
|
|
||||||
export class GraphqlQueryParser {
|
export class GraphqlQueryParser {
|
||||||
@@ -39,10 +39,10 @@ export class GraphqlQueryParser {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyFilterToBuilder(
|
public applyFilterToBuilder(
|
||||||
queryBuilder: SelectQueryBuilder<any>,
|
queryBuilder: SelectQueryBuilder<any>,
|
||||||
objectNameSingular: string,
|
objectNameSingular: string,
|
||||||
recordFilter: RecordFilter,
|
recordFilter: Partial<RecordFilter>,
|
||||||
): SelectQueryBuilder<any> {
|
): SelectQueryBuilder<any> {
|
||||||
return this.filterConditionParser.parse(
|
return this.filterConditionParser.parse(
|
||||||
queryBuilder,
|
queryBuilder,
|
||||||
@@ -51,7 +51,7 @@ export class GraphqlQueryParser {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyDeletedAtToBuilder(
|
public applyDeletedAtToBuilder(
|
||||||
queryBuilder: SelectQueryBuilder<any>,
|
queryBuilder: SelectQueryBuilder<any>,
|
||||||
recordFilter: RecordFilter,
|
recordFilter: RecordFilter,
|
||||||
): SelectQueryBuilder<any> {
|
): SelectQueryBuilder<any> {
|
||||||
@@ -88,7 +88,7 @@ export class GraphqlQueryParser {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
applyOrderToBuilder(
|
public applyOrderToBuilder(
|
||||||
queryBuilder: SelectQueryBuilder<any>,
|
queryBuilder: SelectQueryBuilder<any>,
|
||||||
orderBy: RecordOrderBy,
|
orderBy: RecordOrderBy,
|
||||||
objectNameSingular: string,
|
objectNameSingular: string,
|
||||||
@@ -103,8 +103,8 @@ export class GraphqlQueryParser {
|
|||||||
return queryBuilder.orderBy(parsedOrderBys as OrderByCondition);
|
return queryBuilder.orderBy(parsedOrderBys as OrderByCondition);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseSelectedFields(
|
public parseSelectedFields(
|
||||||
parentObjectMetadata: ObjectMetadataInterface,
|
parentObjectMetadata: ObjectMetadataMapItem,
|
||||||
graphqlSelectedFields: Partial<Record<string, any>>,
|
graphqlSelectedFields: Partial<Record<string, any>>,
|
||||||
): { select: Record<string, any>; relations: Record<string, any> } {
|
): { select: Record<string, any>; relations: Record<string, any> } {
|
||||||
const parentFields =
|
const parentFields =
|
||||||
|
|||||||
@@ -1,16 +1,43 @@
|
|||||||
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 { 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 { 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';
|
||||||
|
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
||||||
import { WorkspaceQueryHookModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module';
|
import { WorkspaceQueryHookModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module';
|
||||||
import { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module';
|
import { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module';
|
||||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
|
|
||||||
|
const graphqlQueryResolvers = [
|
||||||
|
GraphqlQueryFindOneResolverService,
|
||||||
|
GraphqlQueryFindManyResolverService,
|
||||||
|
GraphqlQueryFindDuplicatesResolverService,
|
||||||
|
GraphqlQueryCreateManyResolverService,
|
||||||
|
GraphqlQueryDestroyOneResolverService,
|
||||||
|
GraphqlQueryUpdateOneResolverService,
|
||||||
|
GraphqlQueryUpdateManyResolverService,
|
||||||
|
GraphqlQuerySearchResolverService,
|
||||||
|
];
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
WorkspaceQueryHookModule,
|
WorkspaceQueryHookModule,
|
||||||
WorkspaceQueryRunnerModule,
|
WorkspaceQueryRunnerModule,
|
||||||
FeatureFlagModule,
|
FeatureFlagModule,
|
||||||
],
|
],
|
||||||
providers: [GraphqlQueryRunnerService],
|
providers: [
|
||||||
|
GraphqlQueryRunnerService,
|
||||||
|
GraphqlQueryResolverFactory,
|
||||||
|
ApiEventEmitterService,
|
||||||
|
...graphqlQueryResolvers,
|
||||||
|
],
|
||||||
exports: [GraphqlQueryRunnerService],
|
exports: [GraphqlQueryRunnerService],
|
||||||
})
|
})
|
||||||
export class GraphqlQueryRunnerModule {}
|
export class GraphqlQueryRunnerModule {}
|
||||||
|
|||||||
@@ -6,180 +6,90 @@ import {
|
|||||||
RecordOrderBy,
|
RecordOrderBy,
|
||||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||||
import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
|
import { 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 { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||||
import {
|
import {
|
||||||
CreateManyResolverArgs,
|
CreateManyResolverArgs,
|
||||||
CreateOneResolverArgs,
|
CreateOneResolverArgs,
|
||||||
|
DeleteManyResolverArgs,
|
||||||
|
DeleteOneResolverArgs,
|
||||||
DestroyOneResolverArgs,
|
DestroyOneResolverArgs,
|
||||||
|
FindDuplicatesResolverArgs,
|
||||||
FindManyResolverArgs,
|
FindManyResolverArgs,
|
||||||
FindOneResolverArgs,
|
FindOneResolverArgs,
|
||||||
|
ResolverArgs,
|
||||||
ResolverArgsType,
|
ResolverArgsType,
|
||||||
|
RestoreManyResolverArgs,
|
||||||
SearchResolverArgs,
|
SearchResolverArgs,
|
||||||
|
UpdateManyResolverArgs,
|
||||||
|
UpdateOneResolverArgs,
|
||||||
|
WorkspaceResolverBuilderMethodNames,
|
||||||
} 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 { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
|
||||||
|
|
||||||
import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service';
|
import { GraphqlQueryResolverFactory } from 'src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory';
|
||||||
import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service';
|
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.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 { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
|
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
|
||||||
import {
|
import {
|
||||||
CallWebhookJobsJob,
|
CallWebhookJobsJob,
|
||||||
CallWebhookJobsJobData,
|
CallWebhookJobsJobData,
|
||||||
CallWebhookJobsJobOperation,
|
CallWebhookJobsJobOperation,
|
||||||
} from 'src/engine/api/graphql/workspace-query-runner/jobs/call-webhook-jobs.job';
|
} from 'src/engine/api/graphql/workspace-query-runner/jobs/call-webhook-jobs.job';
|
||||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
|
||||||
import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service';
|
import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service';
|
||||||
import {
|
|
||||||
WorkspaceQueryRunnerException,
|
|
||||||
WorkspaceQueryRunnerExceptionCode,
|
|
||||||
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
|
|
||||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
|
||||||
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
|
|
||||||
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
|
|
||||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
|
||||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||||
import { LogExecutionTime } from 'src/engine/decorators/observability/log-execution-time.decorator';
|
import { LogExecutionTime } from 'src/engine/decorators/observability/log-execution-time.decorator';
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { capitalize } from 'src/utils/capitalize';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphqlQueryRunnerService {
|
export class GraphqlQueryRunnerService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
|
||||||
private readonly featureFlagService: FeatureFlagService,
|
|
||||||
private readonly workspaceQueryHookService: WorkspaceQueryHookService,
|
private readonly workspaceQueryHookService: WorkspaceQueryHookService,
|
||||||
private readonly queryRunnerArgsFactory: QueryRunnerArgsFactory,
|
private readonly queryRunnerArgsFactory: QueryRunnerArgsFactory,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
@InjectMessageQueue(MessageQueue.webhookQueue)
|
@InjectMessageQueue(MessageQueue.webhookQueue)
|
||||||
private readonly messageQueueService: MessageQueueService,
|
private readonly messageQueueService: MessageQueueService,
|
||||||
|
private readonly graphqlQueryResolverFactory: GraphqlQueryResolverFactory,
|
||||||
|
private readonly apiEventEmitterService: ApiEventEmitterService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/** QUERIES */
|
||||||
|
|
||||||
@LogExecutionTime()
|
@LogExecutionTime()
|
||||||
async findOne<
|
async findOne<ObjectRecord extends IRecord, Filter extends RecordFilter>(
|
||||||
ObjectRecord extends IRecord = IRecord,
|
|
||||||
Filter extends RecordFilter = RecordFilter,
|
|
||||||
>(
|
|
||||||
args: FindOneResolverArgs<Filter>,
|
args: FindOneResolverArgs<Filter>,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<ObjectRecord | undefined> {
|
): Promise<ObjectRecord> {
|
||||||
const graphqlQueryFindOneResolverService =
|
return this.executeQuery<FindOneResolverArgs<Filter>, ObjectRecord>(
|
||||||
new GraphqlQueryFindOneResolverService(this.twentyORMGlobalManager);
|
'findOne',
|
||||||
|
args,
|
||||||
const { authContext, objectMetadataItem } = options;
|
|
||||||
|
|
||||||
if (!args.filter || Object.keys(args.filter).length === 0) {
|
|
||||||
throw new WorkspaceQueryRunnerException(
|
|
||||||
'Missing filter argument',
|
|
||||||
WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const hookedArgs =
|
|
||||||
await this.workspaceQueryHookService.executePreQueryHooks(
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem.nameSingular,
|
|
||||||
'findOne',
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
|
|
||||||
const computedArgs = (await this.queryRunnerArgsFactory.create(
|
|
||||||
hookedArgs,
|
|
||||||
options,
|
options,
|
||||||
ResolverArgsType.FindOne,
|
);
|
||||||
)) as FindOneResolverArgs<Filter>;
|
|
||||||
|
|
||||||
return graphqlQueryFindOneResolverService.findOne(computedArgs, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@LogExecutionTime()
|
@LogExecutionTime()
|
||||||
async findMany<
|
async findMany<
|
||||||
ObjectRecord extends IRecord = IRecord,
|
ObjectRecord extends IRecord,
|
||||||
Filter extends RecordFilter = RecordFilter,
|
Filter extends RecordFilter,
|
||||||
OrderBy extends RecordOrderBy = RecordOrderBy,
|
OrderBy extends RecordOrderBy,
|
||||||
>(
|
>(
|
||||||
args: FindManyResolverArgs<Filter, OrderBy>,
|
args: FindManyResolverArgs<Filter, OrderBy>,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<IConnection<ObjectRecord>> {
|
): Promise<IConnection<ObjectRecord, IEdge<ObjectRecord>>> {
|
||||||
const graphqlQueryFindManyResolverService =
|
return this.executeQuery<
|
||||||
new GraphqlQueryFindManyResolverService(this.twentyORMGlobalManager);
|
FindManyResolverArgs<Filter, OrderBy>,
|
||||||
|
IConnection<ObjectRecord, IEdge<ObjectRecord>>
|
||||||
const { authContext, objectMetadataItem } = options;
|
>('findMany', args, options);
|
||||||
|
|
||||||
const hookedArgs =
|
|
||||||
await this.workspaceQueryHookService.executePreQueryHooks(
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem.nameSingular,
|
|
||||||
'findMany',
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
|
|
||||||
const computedArgs = (await this.queryRunnerArgsFactory.create(
|
|
||||||
hookedArgs,
|
|
||||||
options,
|
|
||||||
ResolverArgsType.FindMany,
|
|
||||||
)) as FindManyResolverArgs<Filter, OrderBy>;
|
|
||||||
|
|
||||||
return graphqlQueryFindManyResolverService.findMany(computedArgs, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@LogExecutionTime()
|
@LogExecutionTime()
|
||||||
async createOne<ObjectRecord extends IRecord = IRecord>(
|
async findDuplicates<ObjectRecord extends IRecord>(
|
||||||
args: CreateOneResolverArgs<Partial<ObjectRecord>>,
|
args: FindDuplicatesResolverArgs<Partial<ObjectRecord>>,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<ObjectRecord | undefined> {
|
): Promise<IConnection<ObjectRecord>[]> {
|
||||||
const graphqlQueryCreateManyResolverService =
|
return this.executeQuery<
|
||||||
new GraphqlQueryCreateManyResolverService(this.twentyORMGlobalManager);
|
FindDuplicatesResolverArgs<Partial<ObjectRecord>>,
|
||||||
|
IConnection<ObjectRecord>[]
|
||||||
const { authContext, objectMetadataItem } = options;
|
>('findDuplicates', args, options);
|
||||||
|
|
||||||
assertMutationNotOnRemoteObject(objectMetadataItem);
|
|
||||||
|
|
||||||
if (args.data.id) {
|
|
||||||
assertIsValidUuid(args.data.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const createManyArgs = {
|
|
||||||
data: [args.data],
|
|
||||||
upsert: args.upsert,
|
|
||||||
} as CreateManyResolverArgs<ObjectRecord>;
|
|
||||||
|
|
||||||
const hookedArgs =
|
|
||||||
await this.workspaceQueryHookService.executePreQueryHooks(
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem.nameSingular,
|
|
||||||
'createMany',
|
|
||||||
createManyArgs,
|
|
||||||
);
|
|
||||||
|
|
||||||
const computedArgs = (await this.queryRunnerArgsFactory.create(
|
|
||||||
hookedArgs,
|
|
||||||
options,
|
|
||||||
ResolverArgsType.CreateMany,
|
|
||||||
)) as CreateManyResolverArgs<ObjectRecord>;
|
|
||||||
|
|
||||||
const results = (await graphqlQueryCreateManyResolverService.createMany(
|
|
||||||
computedArgs,
|
|
||||||
options,
|
|
||||||
)) as ObjectRecord[];
|
|
||||||
|
|
||||||
await this.triggerWebhooks<ObjectRecord>(
|
|
||||||
results,
|
|
||||||
CallWebhookJobsJobOperation.create,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.emitCreateEvents<ObjectRecord>(
|
|
||||||
results,
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem,
|
|
||||||
);
|
|
||||||
|
|
||||||
return results?.[0] as ObjectRecord;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@LogExecutionTime()
|
@LogExecutionTime()
|
||||||
@@ -187,104 +97,286 @@ export class GraphqlQueryRunnerService {
|
|||||||
args: SearchResolverArgs,
|
args: SearchResolverArgs,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<IConnection<ObjectRecord>> {
|
): Promise<IConnection<ObjectRecord>> {
|
||||||
const graphqlQuerySearchResolverService =
|
return this.executeQuery<SearchResolverArgs, IConnection<ObjectRecord>>(
|
||||||
new GraphqlQuerySearchResolverService(
|
'search',
|
||||||
this.twentyORMGlobalManager,
|
args,
|
||||||
this.featureFlagService,
|
options,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return graphqlQuerySearchResolverService.search(args, options);
|
/** MUTATIONS */
|
||||||
|
|
||||||
|
@LogExecutionTime()
|
||||||
|
async createOne<ObjectRecord extends IRecord>(
|
||||||
|
args: CreateOneResolverArgs<Partial<ObjectRecord>>,
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<ObjectRecord> {
|
||||||
|
const results = await this.executeQuery<
|
||||||
|
CreateManyResolverArgs<Partial<ObjectRecord>>,
|
||||||
|
ObjectRecord[]
|
||||||
|
>('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.objectMetadataItem,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@LogExecutionTime()
|
@LogExecutionTime()
|
||||||
async createMany<ObjectRecord extends IRecord = IRecord>(
|
async createMany<ObjectRecord extends IRecord>(
|
||||||
args: CreateManyResolverArgs<Partial<ObjectRecord>>,
|
args: CreateManyResolverArgs<Partial<ObjectRecord>>,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<ObjectRecord[] | undefined> {
|
): Promise<ObjectRecord[]> {
|
||||||
const graphqlQueryCreateManyResolverService =
|
const results = await this.executeQuery<
|
||||||
new GraphqlQueryCreateManyResolverService(this.twentyORMGlobalManager);
|
CreateManyResolverArgs<Partial<ObjectRecord>>,
|
||||||
|
ObjectRecord[]
|
||||||
|
>('createMany', args, options);
|
||||||
|
|
||||||
|
if (results) {
|
||||||
|
this.apiEventEmitterService.emitCreateEvents(
|
||||||
|
results,
|
||||||
|
options.authContext,
|
||||||
|
options.objectMetadataItem,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogExecutionTime()
|
||||||
|
public async updateOne<ObjectRecord extends IRecord>(
|
||||||
|
args: UpdateOneResolverArgs<Partial<ObjectRecord>>,
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<ObjectRecord> {
|
||||||
|
const existingRecord = await this.executeQuery<
|
||||||
|
FindOneResolverArgs,
|
||||||
|
ObjectRecord
|
||||||
|
>(
|
||||||
|
'findOne',
|
||||||
|
{
|
||||||
|
filter: { id: { eq: args.id } },
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await this.executeQuery<
|
||||||
|
UpdateOneResolverArgs<Partial<ObjectRecord>>,
|
||||||
|
ObjectRecord
|
||||||
|
>('updateOne', args, options);
|
||||||
|
|
||||||
|
this.apiEventEmitterService.emitUpdateEvents(
|
||||||
|
[existingRecord],
|
||||||
|
[result],
|
||||||
|
Object.keys(args.data),
|
||||||
|
options.authContext,
|
||||||
|
options.objectMetadataItem,
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogExecutionTime()
|
||||||
|
public async updateMany<ObjectRecord extends IRecord>(
|
||||||
|
args: UpdateManyResolverArgs<Partial<ObjectRecord>>,
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<ObjectRecord[]> {
|
||||||
|
const existingRecords = await this.executeQuery<
|
||||||
|
FindManyResolverArgs,
|
||||||
|
IConnection<ObjectRecord, IEdge<ObjectRecord>>
|
||||||
|
>(
|
||||||
|
'findMany',
|
||||||
|
{
|
||||||
|
filter: args.filter,
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await this.executeQuery<
|
||||||
|
UpdateManyResolverArgs<Partial<ObjectRecord>>,
|
||||||
|
ObjectRecord[]
|
||||||
|
>('updateMany', args, options);
|
||||||
|
|
||||||
|
this.apiEventEmitterService.emitUpdateEvents(
|
||||||
|
existingRecords.edges.map((edge) => edge.node),
|
||||||
|
result,
|
||||||
|
Object.keys(args.data),
|
||||||
|
options.authContext,
|
||||||
|
options.objectMetadataItem,
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogExecutionTime()
|
||||||
|
public async deleteOne<ObjectRecord extends IRecord & { deletedAt?: Date }>(
|
||||||
|
args: DeleteOneResolverArgs,
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<ObjectRecord> {
|
||||||
|
const result = await this.executeQuery<
|
||||||
|
UpdateOneResolverArgs<Partial<ObjectRecord>>,
|
||||||
|
ObjectRecord
|
||||||
|
>(
|
||||||
|
'deleteOne',
|
||||||
|
{
|
||||||
|
id: args.id,
|
||||||
|
data: { deletedAt: new Date() } as Partial<ObjectRecord>,
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.apiEventEmitterService.emitDeletedEvents(
|
||||||
|
[result],
|
||||||
|
options.authContext,
|
||||||
|
options.objectMetadataItem,
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogExecutionTime()
|
||||||
|
public async deleteMany<ObjectRecord extends IRecord & { deletedAt?: Date }>(
|
||||||
|
args: DeleteManyResolverArgs,
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<ObjectRecord[]> {
|
||||||
|
const result = await this.executeQuery<
|
||||||
|
UpdateManyResolverArgs<Partial<ObjectRecord>>,
|
||||||
|
ObjectRecord[]
|
||||||
|
>(
|
||||||
|
'deleteMany',
|
||||||
|
{
|
||||||
|
filter: args.filter,
|
||||||
|
|
||||||
|
data: { deletedAt: new Date() } as Partial<ObjectRecord>,
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.apiEventEmitterService.emitDeletedEvents(
|
||||||
|
result,
|
||||||
|
options.authContext,
|
||||||
|
options.objectMetadataItem,
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogExecutionTime()
|
||||||
|
async destroyOne<ObjectRecord extends IRecord>(
|
||||||
|
args: DestroyOneResolverArgs,
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<ObjectRecord> {
|
||||||
|
const result = await this.executeQuery<
|
||||||
|
DestroyOneResolverArgs,
|
||||||
|
ObjectRecord
|
||||||
|
>('destroyOne', args, options);
|
||||||
|
|
||||||
|
this.apiEventEmitterService.emitDestroyEvents(
|
||||||
|
[result],
|
||||||
|
options.authContext,
|
||||||
|
options.objectMetadataItem,
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogExecutionTime()
|
||||||
|
public async restoreMany<ObjectRecord extends IRecord>(
|
||||||
|
args: RestoreManyResolverArgs,
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<ObjectRecord> {
|
||||||
|
const result = await this.executeQuery<
|
||||||
|
UpdateManyResolverArgs<Partial<ObjectRecord>>,
|
||||||
|
ObjectRecord
|
||||||
|
>(
|
||||||
|
'restoreMany',
|
||||||
|
{
|
||||||
|
filter: args.filter,
|
||||||
|
data: { deletedAt: null } as Partial<ObjectRecord>,
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async executeQuery<Input extends ResolverArgs, Response>(
|
||||||
|
operationName: WorkspaceResolverBuilderMethodNames,
|
||||||
|
args: Input,
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<Response> {
|
||||||
const { authContext, objectMetadataItem } = options;
|
const { authContext, objectMetadataItem } = options;
|
||||||
|
|
||||||
assertMutationNotOnRemoteObject(objectMetadataItem);
|
const resolver =
|
||||||
|
this.graphqlQueryResolverFactory.getResolver(operationName);
|
||||||
|
|
||||||
args.data.forEach((record) => {
|
await resolver.validate(args, options);
|
||||||
if (record?.id) {
|
|
||||||
assertIsValidUuid(record.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const hookedArgs =
|
const hookedArgs =
|
||||||
await this.workspaceQueryHookService.executePreQueryHooks(
|
await this.workspaceQueryHookService.executePreQueryHooks(
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataItem.nameSingular,
|
||||||
'createMany',
|
operationName,
|
||||||
args,
|
args,
|
||||||
);
|
);
|
||||||
|
|
||||||
const computedArgs = (await this.queryRunnerArgsFactory.create(
|
const computedArgs = await this.queryRunnerArgsFactory.create(
|
||||||
hookedArgs,
|
hookedArgs,
|
||||||
options,
|
options,
|
||||||
ResolverArgsType.CreateMany,
|
ResolverArgsType[capitalize(operationName)],
|
||||||
)) as CreateManyResolverArgs<ObjectRecord>;
|
);
|
||||||
|
|
||||||
const results = (await graphqlQueryCreateManyResolverService.createMany(
|
const results = await resolver.resolve(computedArgs as Input, options);
|
||||||
computedArgs,
|
|
||||||
options,
|
|
||||||
)) as ObjectRecord[];
|
|
||||||
|
|
||||||
await this.workspaceQueryHookService.executePostQueryHooks(
|
await this.workspaceQueryHookService.executePostQueryHooks(
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataItem.nameSingular,
|
||||||
'createMany',
|
operationName,
|
||||||
results,
|
Array.isArray(results) ? results : [results],
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.triggerWebhooks<ObjectRecord>(
|
const jobOperation = this.operationNameToJobOperation(operationName);
|
||||||
results,
|
|
||||||
CallWebhookJobsJobOperation.create,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.emitCreateEvents<ObjectRecord>(
|
if (jobOperation) {
|
||||||
results,
|
await this.triggerWebhooks(results, jobOperation, options);
|
||||||
authContext,
|
}
|
||||||
objectMetadataItem,
|
|
||||||
);
|
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private emitCreateEvents<BaseRecord extends IRecord = IRecord>(
|
private operationNameToJobOperation(
|
||||||
records: BaseRecord[],
|
operationName: WorkspaceResolverBuilderMethodNames,
|
||||||
authContext: AuthContext,
|
): CallWebhookJobsJobOperation | undefined {
|
||||||
objectMetadataItem: ObjectMetadataInterface,
|
switch (operationName) {
|
||||||
) {
|
case 'createOne':
|
||||||
this.workspaceEventEmitter.emit(
|
case 'createMany':
|
||||||
`${objectMetadataItem.nameSingular}.created`,
|
return CallWebhookJobsJobOperation.create;
|
||||||
records.map(
|
case 'updateOne':
|
||||||
(record) =>
|
case 'updateMany':
|
||||||
({
|
case 'restoreMany':
|
||||||
userId: authContext.user?.id,
|
return CallWebhookJobsJobOperation.update;
|
||||||
recordId: record.id,
|
case 'deleteOne':
|
||||||
objectMetadata: objectMetadataItem,
|
case 'deleteMany':
|
||||||
properties: {
|
return CallWebhookJobsJobOperation.delete;
|
||||||
after: record,
|
case 'destroyOne':
|
||||||
},
|
return CallWebhookJobsJobOperation.destroy;
|
||||||
}) satisfies ObjectRecordCreateEvent<any>,
|
default:
|
||||||
),
|
return undefined;
|
||||||
authContext.workspace.id,
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async triggerWebhooks<Record>(
|
private async triggerWebhooks<T>(
|
||||||
jobsData: Record[] | undefined,
|
jobsData: T[] | undefined,
|
||||||
operation: CallWebhookJobsJobOperation,
|
operation: CallWebhookJobsJobOperation,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
) {
|
): Promise<void> {
|
||||||
if (!Array.isArray(jobsData)) {
|
if (!jobsData || !Array.isArray(jobsData)) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
jobsData.forEach((jobData) => {
|
jobsData.forEach((jobData) => {
|
||||||
this.messageQueueService.add<CallWebhookJobsJobData>(
|
this.messageQueueService.add<CallWebhookJobsJobData>(
|
||||||
CallWebhookJobsJob.name,
|
CallWebhookJobsJob.name,
|
||||||
@@ -298,99 +390,4 @@ export class GraphqlQueryRunnerService {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@LogExecutionTime()
|
|
||||||
async destroyOne<ObjectRecord extends IRecord = IRecord>(
|
|
||||||
args: DestroyOneResolverArgs,
|
|
||||||
options: WorkspaceQueryRunnerOptions,
|
|
||||||
): Promise<ObjectRecord | undefined> {
|
|
||||||
const graphqlQueryDestroyOneResolverService =
|
|
||||||
new GraphqlQueryDestroyOneResolverService(this.twentyORMGlobalManager);
|
|
||||||
|
|
||||||
const { authContext, objectMetadataItem } = options;
|
|
||||||
|
|
||||||
assertMutationNotOnRemoteObject(objectMetadataItem);
|
|
||||||
assertIsValidUuid(args.id);
|
|
||||||
|
|
||||||
const hookedArgs =
|
|
||||||
await this.workspaceQueryHookService.executePreQueryHooks(
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem.nameSingular,
|
|
||||||
'destroyOne',
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
|
|
||||||
const computedArgs = (await this.queryRunnerArgsFactory.create(
|
|
||||||
hookedArgs,
|
|
||||||
options,
|
|
||||||
ResolverArgsType.DestroyOne,
|
|
||||||
)) as DestroyOneResolverArgs;
|
|
||||||
|
|
||||||
const result = (await graphqlQueryDestroyOneResolverService.destroyOne(
|
|
||||||
computedArgs,
|
|
||||||
options,
|
|
||||||
)) as ObjectRecord;
|
|
||||||
|
|
||||||
await this.workspaceQueryHookService.executePostQueryHooks(
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem.nameSingular,
|
|
||||||
'destroyOne',
|
|
||||||
[result],
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.triggerWebhooks<IRecord>(
|
|
||||||
[result],
|
|
||||||
CallWebhookJobsJobOperation.destroy,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.emitDestroyEvents<IRecord>([result], authContext, objectMetadataItem);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private emitDestroyEvents<BaseRecord extends IRecord = IRecord>(
|
|
||||||
records: BaseRecord[],
|
|
||||||
authContext: AuthContext,
|
|
||||||
objectMetadataItem: ObjectMetadataInterface,
|
|
||||||
) {
|
|
||||||
this.workspaceEventEmitter.emit(
|
|
||||||
`${objectMetadataItem.nameSingular}.destroyed`,
|
|
||||||
records.map((record) => {
|
|
||||||
return {
|
|
||||||
userId: authContext.user?.id,
|
|
||||||
recordId: record.id,
|
|
||||||
objectMetadata: objectMetadataItem,
|
|
||||||
properties: {
|
|
||||||
before: this.removeNestedProperties(record),
|
|
||||||
},
|
|
||||||
} satisfies ObjectRecordDeleteEvent<any>;
|
|
||||||
}),
|
|
||||||
authContext.workspace.id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private removeNestedProperties<Record extends IRecord = IRecord>(
|
|
||||||
record: Record,
|
|
||||||
) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspac
|
|||||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||||
import { isPlainObject } from 'src/utils/is-plain-object';
|
import { isPlainObject } from 'src/utils/is-plain-object';
|
||||||
|
|
||||||
export class ObjectRecordsToGraphqlConnectionMapper {
|
export class ObjectRecordsToGraphqlConnectionHelper {
|
||||||
private objectMetadataMap: ObjectMetadataMap;
|
private objectMetadataMap: ObjectMetadataMap;
|
||||||
|
|
||||||
constructor(objectMetadataMap: ObjectMetadataMap) {
|
constructor(objectMetadataMap: ObjectMetadataMap) {
|
||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
FindOptionsRelations,
|
FindOptionsRelations,
|
||||||
In,
|
In,
|
||||||
ObjectLiteral,
|
ObjectLiteral,
|
||||||
|
Repository,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||||
@@ -16,14 +17,70 @@ import {
|
|||||||
ObjectMetadataMap,
|
ObjectMetadataMap,
|
||||||
ObjectMetadataMapItem,
|
ObjectMetadataMapItem,
|
||||||
} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
|
||||||
import { deduceRelationDirection } from 'src/engine/utils/deduce-relation-direction.util';
|
import { deduceRelationDirection } from 'src/engine/utils/deduce-relation-direction.util';
|
||||||
|
|
||||||
export class ProcessNestedRelationsHelper {
|
export class ProcessNestedRelationsHelper {
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager;
|
constructor() {}
|
||||||
|
|
||||||
constructor(twentyORMGlobalManager: TwentyORMGlobalManager) {
|
public async processNestedRelations<ObjectRecord extends IRecord = IRecord>(
|
||||||
this.twentyORMGlobalManager = twentyORMGlobalManager;
|
objectMetadataMap: ObjectMetadataMap,
|
||||||
|
parentObjectMetadataItem: ObjectMetadataMapItem,
|
||||||
|
parentObjectRecords: ObjectRecord[],
|
||||||
|
relations: Record<string, FindOptionsRelations<ObjectLiteral>>,
|
||||||
|
limit: number,
|
||||||
|
authContext: any,
|
||||||
|
dataSource: DataSource,
|
||||||
|
): Promise<void> {
|
||||||
|
const processRelationTasks = Object.entries(relations).map(
|
||||||
|
([relationName, nestedRelations]) =>
|
||||||
|
this.processRelation(
|
||||||
|
objectMetadataMap,
|
||||||
|
parentObjectMetadataItem,
|
||||||
|
parentObjectRecords,
|
||||||
|
relationName,
|
||||||
|
nestedRelations,
|
||||||
|
limit,
|
||||||
|
authContext,
|
||||||
|
dataSource,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(processRelationTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async processRelation<ObjectRecord extends IRecord = IRecord>(
|
||||||
|
objectMetadataMap: ObjectMetadataMap,
|
||||||
|
parentObjectMetadataItem: ObjectMetadataMapItem,
|
||||||
|
parentObjectRecords: ObjectRecord[],
|
||||||
|
relationName: string,
|
||||||
|
nestedRelations: any,
|
||||||
|
limit: number,
|
||||||
|
authContext: any,
|
||||||
|
dataSource: DataSource,
|
||||||
|
): Promise<void> {
|
||||||
|
const relationFieldMetadata = parentObjectMetadataItem.fields[relationName];
|
||||||
|
const relationMetadata = getRelationMetadata(relationFieldMetadata);
|
||||||
|
const relationDirection = deduceRelationDirection(
|
||||||
|
relationFieldMetadata,
|
||||||
|
relationMetadata,
|
||||||
|
);
|
||||||
|
|
||||||
|
const processor =
|
||||||
|
relationDirection === 'to'
|
||||||
|
? this.processToRelation
|
||||||
|
: this.processFromRelation;
|
||||||
|
|
||||||
|
await processor.call(
|
||||||
|
this,
|
||||||
|
objectMetadataMap,
|
||||||
|
parentObjectMetadataItem,
|
||||||
|
parentObjectRecords,
|
||||||
|
relationName,
|
||||||
|
nestedRelations,
|
||||||
|
limit,
|
||||||
|
authContext,
|
||||||
|
dataSource,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async processFromRelation<ObjectRecord extends IRecord = IRecord>(
|
private async processFromRelation<ObjectRecord extends IRecord = IRecord>(
|
||||||
@@ -35,49 +92,36 @@ export class ProcessNestedRelationsHelper {
|
|||||||
limit: number,
|
limit: number,
|
||||||
authContext: any,
|
authContext: any,
|
||||||
dataSource: DataSource,
|
dataSource: DataSource,
|
||||||
) {
|
): Promise<void> {
|
||||||
const relationFieldMetadata = parentObjectMetadataItem.fields[relationName];
|
const { inverseRelationName, referenceObjectMetadata } =
|
||||||
const relationMetadata = getRelationMetadata(relationFieldMetadata);
|
this.getRelationMetadata(
|
||||||
|
objectMetadataMap,
|
||||||
const inverseRelationName =
|
parentObjectMetadataItem,
|
||||||
objectMetadataMap[relationMetadata.toObjectMetadataId]?.fields[
|
relationName,
|
||||||
relationMetadata.toFieldMetadataId
|
|
||||||
]?.name;
|
|
||||||
|
|
||||||
const referenceObjectMetadata = getRelationObjectMetadata(
|
|
||||||
relationFieldMetadata,
|
|
||||||
objectMetadataMap,
|
|
||||||
);
|
|
||||||
|
|
||||||
const referenceObjectMetadataName = referenceObjectMetadata.nameSingular;
|
|
||||||
|
|
||||||
const relationRepository = await dataSource.getRepository(
|
|
||||||
referenceObjectMetadataName,
|
|
||||||
);
|
|
||||||
|
|
||||||
const relationIds = parentObjectRecords.map((item) => item.id);
|
|
||||||
|
|
||||||
const uniqueRelationIds = [...new Set(relationIds)];
|
|
||||||
|
|
||||||
const relationFindOptions: FindManyOptions = {
|
|
||||||
where: {
|
|
||||||
[`${inverseRelationName}Id`]: In(uniqueRelationIds),
|
|
||||||
},
|
|
||||||
take: limit * parentObjectRecords.length,
|
|
||||||
};
|
|
||||||
|
|
||||||
const relationResults = await relationRepository.find(relationFindOptions);
|
|
||||||
|
|
||||||
parentObjectRecords.forEach((item) => {
|
|
||||||
(item as any)[relationName] = relationResults.filter(
|
|
||||||
(rel) => rel[`${inverseRelationName}Id`] === item.id,
|
|
||||||
);
|
);
|
||||||
});
|
const relationRepository = dataSource.getRepository(
|
||||||
|
referenceObjectMetadata.nameSingular,
|
||||||
|
);
|
||||||
|
|
||||||
|
const relationIds = this.getUniqueIds(parentObjectRecords, 'id');
|
||||||
|
const relationResults = await this.findRelations(
|
||||||
|
relationRepository,
|
||||||
|
inverseRelationName,
|
||||||
|
relationIds,
|
||||||
|
limit * parentObjectRecords.length,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.assignRelationResults(
|
||||||
|
parentObjectRecords,
|
||||||
|
relationResults,
|
||||||
|
relationName,
|
||||||
|
`${inverseRelationName}Id`,
|
||||||
|
);
|
||||||
|
|
||||||
if (Object.keys(nestedRelations).length > 0) {
|
if (Object.keys(nestedRelations).length > 0) {
|
||||||
await this.processNestedRelations(
|
await this.processNestedRelations(
|
||||||
objectMetadataMap,
|
objectMetadataMap,
|
||||||
objectMetadataMap[referenceObjectMetadataName],
|
objectMetadataMap[referenceObjectMetadata.nameSingular],
|
||||||
relationResults as ObjectRecord[],
|
relationResults as ObjectRecord[],
|
||||||
nestedRelations as Record<string, FindOptionsRelations<ObjectLiteral>>,
|
nestedRelations as Record<string, FindOptionsRelations<ObjectLiteral>>,
|
||||||
limit,
|
limit,
|
||||||
@@ -96,48 +140,37 @@ export class ProcessNestedRelationsHelper {
|
|||||||
limit: number,
|
limit: number,
|
||||||
authContext: any,
|
authContext: any,
|
||||||
dataSource: DataSource,
|
dataSource: DataSource,
|
||||||
) {
|
): Promise<void> {
|
||||||
const relationFieldMetadata = parentObjectMetadataItem.fields[relationName];
|
const { referenceObjectMetadata } = this.getRelationMetadata(
|
||||||
|
|
||||||
const referenceObjectMetadata = getRelationObjectMetadata(
|
|
||||||
relationFieldMetadata,
|
|
||||||
objectMetadataMap,
|
objectMetadataMap,
|
||||||
|
parentObjectMetadataItem,
|
||||||
|
relationName,
|
||||||
);
|
);
|
||||||
|
|
||||||
const referenceObjectMetadataName = referenceObjectMetadata.nameSingular;
|
|
||||||
|
|
||||||
const relationRepository = dataSource.getRepository(
|
const relationRepository = dataSource.getRepository(
|
||||||
referenceObjectMetadataName,
|
referenceObjectMetadata.nameSingular,
|
||||||
);
|
);
|
||||||
|
|
||||||
const relationIds = parentObjectRecords.map(
|
const relationIds = this.getUniqueIds(
|
||||||
(item) => item[`${relationName}Id`],
|
parentObjectRecords,
|
||||||
|
`${relationName}Id`,
|
||||||
|
);
|
||||||
|
const relationResults = await this.findRelations(
|
||||||
|
relationRepository,
|
||||||
|
'id',
|
||||||
|
relationIds,
|
||||||
|
limit,
|
||||||
);
|
);
|
||||||
|
|
||||||
const uniqueRelationIds = [...new Set(relationIds)];
|
this.assignToRelationResults(
|
||||||
|
parentObjectRecords,
|
||||||
const relationFindOptions: FindManyOptions = {
|
relationResults,
|
||||||
where: {
|
relationName,
|
||||||
id: In(uniqueRelationIds),
|
);
|
||||||
},
|
|
||||||
take: limit,
|
|
||||||
};
|
|
||||||
|
|
||||||
const relationResults = await relationRepository.find(relationFindOptions);
|
|
||||||
|
|
||||||
parentObjectRecords.forEach((item) => {
|
|
||||||
if (relationResults.length === 0) {
|
|
||||||
(item as any)[`${relationName}Id`] = null;
|
|
||||||
}
|
|
||||||
(item as any)[relationName] = relationResults.filter(
|
|
||||||
(rel) => rel.id === item[`${relationName}Id`],
|
|
||||||
)[0];
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Object.keys(nestedRelations).length > 0) {
|
if (Object.keys(nestedRelations).length > 0) {
|
||||||
await this.processNestedRelations(
|
await this.processNestedRelations(
|
||||||
objectMetadataMap,
|
objectMetadataMap,
|
||||||
objectMetadataMap[referenceObjectMetadataName],
|
objectMetadataMap[referenceObjectMetadata.nameSingular],
|
||||||
relationResults as ObjectRecord[],
|
relationResults as ObjectRecord[],
|
||||||
nestedRelations as Record<string, FindOptionsRelations<ObjectLiteral>>,
|
nestedRelations as Record<string, FindOptionsRelations<ObjectLiteral>>,
|
||||||
limit,
|
limit,
|
||||||
@@ -147,48 +180,71 @@ export class ProcessNestedRelationsHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async processNestedRelations<ObjectRecord extends IRecord = IRecord>(
|
private getRelationMetadata(
|
||||||
objectMetadataMap: ObjectMetadataMap,
|
objectMetadataMap: ObjectMetadataMap,
|
||||||
parentObjectMetadataItem: ObjectMetadataMapItem,
|
parentObjectMetadataItem: ObjectMetadataMapItem,
|
||||||
parentObjectRecords: ObjectRecord[],
|
relationName: string,
|
||||||
relations: Record<string, FindOptionsRelations<ObjectLiteral>>,
|
|
||||||
limit: number,
|
|
||||||
authContext: any,
|
|
||||||
dataSource: DataSource,
|
|
||||||
) {
|
) {
|
||||||
for (const [relationName, nestedRelations] of Object.entries(relations)) {
|
const relationFieldMetadata = parentObjectMetadataItem.fields[relationName];
|
||||||
const relationFieldMetadata =
|
const relationMetadata = getRelationMetadata(relationFieldMetadata);
|
||||||
parentObjectMetadataItem.fields[relationName];
|
const referenceObjectMetadata = getRelationObjectMetadata(
|
||||||
const relationMetadata = getRelationMetadata(relationFieldMetadata);
|
relationFieldMetadata,
|
||||||
|
objectMetadataMap,
|
||||||
|
);
|
||||||
|
const inverseRelationName =
|
||||||
|
objectMetadataMap[relationMetadata.toObjectMetadataId]?.fields[
|
||||||
|
relationMetadata.toFieldMetadataId
|
||||||
|
]?.name;
|
||||||
|
|
||||||
const relationDirection = deduceRelationDirection(
|
return { inverseRelationName, referenceObjectMetadata };
|
||||||
relationFieldMetadata,
|
}
|
||||||
relationMetadata,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (relationDirection === 'to') {
|
private getUniqueIds(records: IRecord[], idField: string): any[] {
|
||||||
await this.processToRelation(
|
return [...new Set(records.map((item) => item[idField]))];
|
||||||
objectMetadataMap,
|
}
|
||||||
parentObjectMetadataItem,
|
|
||||||
parentObjectRecords,
|
private async findRelations(
|
||||||
relationName,
|
repository: Repository<any>,
|
||||||
nestedRelations,
|
field: string,
|
||||||
limit,
|
ids: any[],
|
||||||
authContext,
|
limit: number,
|
||||||
dataSource,
|
): Promise<any[]> {
|
||||||
);
|
if (ids.length === 0) {
|
||||||
} else {
|
return [];
|
||||||
await this.processFromRelation(
|
|
||||||
objectMetadataMap,
|
|
||||||
parentObjectMetadataItem,
|
|
||||||
parentObjectRecords,
|
|
||||||
relationName,
|
|
||||||
nestedRelations,
|
|
||||||
limit,
|
|
||||||
authContext,
|
|
||||||
dataSource,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const findOptions: FindManyOptions = {
|
||||||
|
where: { [field]: In(ids) },
|
||||||
|
take: limit,
|
||||||
|
};
|
||||||
|
|
||||||
|
return repository.find(findOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private assignRelationResults(
|
||||||
|
parentRecords: IRecord[],
|
||||||
|
relationResults: any[],
|
||||||
|
relationName: string,
|
||||||
|
joinField: string,
|
||||||
|
): void {
|
||||||
|
parentRecords.forEach((item) => {
|
||||||
|
(item as any)[relationName] = relationResults.filter(
|
||||||
|
(rel) => rel[joinField] === item.id,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private assignToRelationResults(
|
||||||
|
parentRecords: IRecord[],
|
||||||
|
relationResults: any[],
|
||||||
|
relationName: string,
|
||||||
|
): void {
|
||||||
|
parentRecords.forEach((item) => {
|
||||||
|
if (relationResults.length === 0) {
|
||||||
|
(item as any)[`${relationName}Id`] = null;
|
||||||
|
}
|
||||||
|
(item as any)[relationName] =
|
||||||
|
relationResults.find((rel) => rel.id === item[`${relationName}Id`]) ??
|
||||||
|
null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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>;
|
||||||
|
}
|
||||||
@@ -1,51 +1,53 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import graphqlFields from 'graphql-fields';
|
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 { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
import { 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 { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||||
import { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper';
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
import { getObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util';
|
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
||||||
import { generateObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.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 { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
|
||||||
export class GraphqlQueryCreateManyResolverService {
|
@Injectable()
|
||||||
private twentyORMGlobalManager: TwentyORMGlobalManager;
|
export class GraphqlQueryCreateManyResolverService
|
||||||
|
implements ResolverService<CreateManyResolverArgs, IRecord[]>
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
constructor(twentyORMGlobalManager: TwentyORMGlobalManager) {
|
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||||
this.twentyORMGlobalManager = twentyORMGlobalManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createMany<ObjectRecord extends IRecord = IRecord>(
|
|
||||||
args: CreateManyResolverArgs<Partial<ObjectRecord>>,
|
args: CreateManyResolverArgs<Partial<ObjectRecord>>,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<ObjectRecord[] | undefined> {
|
): Promise<ObjectRecord[]> {
|
||||||
const { authContext, objectMetadataItem, objectMetadataCollection, info } =
|
const { authContext, info, objectMetadataMap, objectMetadataMapItem } =
|
||||||
options;
|
options;
|
||||||
const repository =
|
const dataSource =
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||||
authContext.workspace.id,
|
authContext.workspace.id,
|
||||||
objectMetadataItem.nameSingular,
|
|
||||||
);
|
);
|
||||||
|
const repository = dataSource.getRepository(
|
||||||
|
objectMetadataMapItem.nameSingular,
|
||||||
|
);
|
||||||
|
|
||||||
const objectMetadataMap = generateObjectMetadataMap(
|
|
||||||
objectMetadataCollection,
|
|
||||||
);
|
|
||||||
const objectMetadata = getObjectMetadataOrThrow(
|
|
||||||
objectMetadataMap,
|
|
||||||
objectMetadataItem.nameSingular,
|
|
||||||
);
|
|
||||||
const graphqlQueryParser = new GraphqlQueryParser(
|
const graphqlQueryParser = new GraphqlQueryParser(
|
||||||
objectMetadata.fields,
|
objectMetadataMapItem.fields,
|
||||||
objectMetadataMap,
|
objectMetadataMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedFields = graphqlFields(info);
|
const selectedFields = graphqlFields(info);
|
||||||
|
|
||||||
const { select, relations } = graphqlQueryParser.parseSelectedFields(
|
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||||
objectMetadataItem,
|
objectMetadataMapItem,
|
||||||
selectedFields,
|
selectedFields,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -56,24 +58,59 @@ export class GraphqlQueryCreateManyResolverService {
|
|||||||
skipUpdateIfNoValuesChanged: true,
|
skipUpdateIfNoValuesChanged: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const upsertedRecords = await repository.find({
|
const queryBuilder = repository.createQueryBuilder(
|
||||||
where: {
|
objectMetadataMapItem.nameSingular,
|
||||||
|
);
|
||||||
|
|
||||||
|
const nonFormattedUpsertedRecords = (await queryBuilder
|
||||||
|
.where({
|
||||||
id: In(objectRecords.generatedMaps.map((record) => record.id)),
|
id: In(objectRecords.generatedMaps.map((record) => record.id)),
|
||||||
},
|
})
|
||||||
select,
|
.take(QUERY_MAX_RECORDS)
|
||||||
relations,
|
.getMany()) as ObjectRecord[];
|
||||||
});
|
|
||||||
|
const upsertedRecords = formatResult(
|
||||||
|
nonFormattedUpsertedRecords,
|
||||||
|
objectMetadataMapItem,
|
||||||
|
objectMetadataMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
||||||
|
|
||||||
|
if (relations) {
|
||||||
|
await processNestedRelationsHelper.processNestedRelations(
|
||||||
|
objectMetadataMap,
|
||||||
|
objectMetadataMapItem,
|
||||||
|
upsertedRecords,
|
||||||
|
relations,
|
||||||
|
QUERY_MAX_RECORDS,
|
||||||
|
authContext,
|
||||||
|
dataSource,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const typeORMObjectRecordsParser =
|
const typeORMObjectRecordsParser =
|
||||||
new ObjectRecordsToGraphqlConnectionMapper(objectMetadataMap);
|
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||||
|
|
||||||
return upsertedRecords.map((record: ObjectRecord) =>
|
return upsertedRecords.map((record: ObjectRecord) =>
|
||||||
typeORMObjectRecordsParser.processRecord(
|
typeORMObjectRecordsParser.processRecord(
|
||||||
record,
|
record,
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataMapItem.nameSingular,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async validate<ObjectRecord extends IRecord>(
|
||||||
|
args: CreateManyResolverArgs<Partial<ObjectRecord>>,
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<void> {
|
||||||
|
assertMutationNotOnRemoteObject(options.objectMetadataItem);
|
||||||
|
args.data.forEach((record) => {
|
||||||
|
if (record?.id) {
|
||||||
|
assertIsValidUuid(record.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,68 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||||
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
import { 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';
|
||||||
|
|
||||||
|
import {
|
||||||
|
GraphqlQueryRunnerException,
|
||||||
|
GraphqlQueryRunnerExceptionCode,
|
||||||
|
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
|
||||||
export class GraphqlQueryDestroyOneResolverService {
|
@Injectable()
|
||||||
private twentyORMGlobalManager: TwentyORMGlobalManager;
|
export class GraphqlQueryDestroyOneResolverService
|
||||||
|
implements ResolverService<DestroyOneResolverArgs, IRecord>
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
constructor(twentyORMGlobalManager: TwentyORMGlobalManager) {
|
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||||
this.twentyORMGlobalManager = twentyORMGlobalManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
async destroyOne<ObjectRecord extends IRecord = IRecord>(
|
|
||||||
args: DestroyOneResolverArgs,
|
args: DestroyOneResolverArgs,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<ObjectRecord> {
|
): Promise<ObjectRecord> {
|
||||||
const { authContext, objectMetadataItem } = options;
|
const { authContext, objectMetadataMapItem, objectMetadataMap } = options;
|
||||||
const repository =
|
const repository =
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
authContext.workspace.id,
|
authContext.workspace.id,
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataMapItem.nameSingular,
|
||||||
);
|
);
|
||||||
|
|
||||||
const record = await repository.findOne({
|
const nonFormattedRecordBeforeDeletion = await repository.findOne({
|
||||||
where: { id: args.id },
|
where: { id: args.id },
|
||||||
|
withDeleted: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!nonFormattedRecordBeforeDeletion) {
|
||||||
|
throw new GraphqlQueryRunnerException(
|
||||||
|
'Record not found',
|
||||||
|
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordBeforeDeletion = formatResult(
|
||||||
|
[nonFormattedRecordBeforeDeletion],
|
||||||
|
objectMetadataMapItem,
|
||||||
|
objectMetadataMap,
|
||||||
|
)[0];
|
||||||
|
|
||||||
await repository.delete(args.id);
|
await repository.delete(args.id);
|
||||||
|
|
||||||
return record as ObjectRecord;
|
return recordBeforeDeletion as ObjectRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate(
|
||||||
|
args: DestroyOneResolverArgs,
|
||||||
|
_options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<void> {
|
||||||
|
if (!args.id) {
|
||||||
|
throw new GraphqlQueryRunnerException(
|
||||||
|
'Missing id',
|
||||||
|
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import isEmpty from 'lodash.isempty';
|
||||||
|
import { In } from 'typeorm';
|
||||||
|
|
||||||
|
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||||
|
import {
|
||||||
|
Record as IRecord,
|
||||||
|
OrderByDirection,
|
||||||
|
RecordFilter,
|
||||||
|
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||||
|
import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
|
||||||
|
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||||
|
import { FindDuplicatesResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
|
import {
|
||||||
|
GraphqlQueryRunnerException,
|
||||||
|
GraphqlQueryRunnerExceptionCode,
|
||||||
|
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||||
|
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||||
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
|
import { settings } from 'src/engine/constants/settings';
|
||||||
|
import { DUPLICATE_CRITERIA_COLLECTION } from 'src/engine/core-modules/duplicate/constants/duplicate-criteria.constants';
|
||||||
|
import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||||
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||||
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GraphqlQueryFindDuplicatesResolverService
|
||||||
|
implements
|
||||||
|
ResolverService<FindDuplicatesResolverArgs, IConnection<IRecord>[]>
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||||
|
args: FindDuplicatesResolverArgs<Partial<ObjectRecord>>,
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<IConnection<ObjectRecord>[]> {
|
||||||
|
const { authContext, objectMetadataMapItem, objectMetadataMap } = options;
|
||||||
|
|
||||||
|
const dataSource =
|
||||||
|
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||||
|
authContext.workspace.id,
|
||||||
|
);
|
||||||
|
const repository = dataSource.getRepository(
|
||||||
|
objectMetadataMapItem.nameSingular,
|
||||||
|
);
|
||||||
|
const existingRecordsQueryBuilder = repository.createQueryBuilder(
|
||||||
|
objectMetadataMapItem.nameSingular,
|
||||||
|
);
|
||||||
|
const duplicateRecordsQueryBuilder = repository.createQueryBuilder(
|
||||||
|
objectMetadataMapItem.nameSingular,
|
||||||
|
);
|
||||||
|
|
||||||
|
const graphqlQueryParser = new GraphqlQueryParser(
|
||||||
|
objectMetadataMap[objectMetadataMapItem.nameSingular].fields,
|
||||||
|
objectMetadataMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
const typeORMObjectRecordsParser =
|
||||||
|
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||||
|
|
||||||
|
let objectRecords: Partial<ObjectRecord>[] = [];
|
||||||
|
|
||||||
|
if (args.ids) {
|
||||||
|
const nonFormattedObjectRecords = (await existingRecordsQueryBuilder
|
||||||
|
.where({ id: In(args.ids) })
|
||||||
|
.getMany()) as ObjectRecord[];
|
||||||
|
|
||||||
|
objectRecords = formatResult(
|
||||||
|
nonFormattedObjectRecords,
|
||||||
|
objectMetadataMapItem,
|
||||||
|
objectMetadataMap,
|
||||||
|
);
|
||||||
|
} else if (args.data && !isEmpty(args.data)) {
|
||||||
|
objectRecords = formatData(args.data, objectMetadataMapItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
const duplicateConnections: IConnection<ObjectRecord>[] = await Promise.all(
|
||||||
|
objectRecords.map(async (record) => {
|
||||||
|
const duplicateConditions = this.buildDuplicateConditions(
|
||||||
|
objectMetadataMapItem,
|
||||||
|
[record],
|
||||||
|
record.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isEmpty(duplicateConditions)) {
|
||||||
|
return typeORMObjectRecordsParser.createConnection(
|
||||||
|
[],
|
||||||
|
objectMetadataMapItem.nameSingular,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
[{ id: OrderByDirection.AscNullsFirst }],
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
||||||
|
duplicateRecordsQueryBuilder,
|
||||||
|
objectMetadataMapItem.nameSingular,
|
||||||
|
duplicateConditions,
|
||||||
|
);
|
||||||
|
|
||||||
|
const nonFormattedDuplicates =
|
||||||
|
(await withFilterQueryBuilder.getMany()) as ObjectRecord[];
|
||||||
|
|
||||||
|
const duplicates = formatResult(
|
||||||
|
nonFormattedDuplicates,
|
||||||
|
objectMetadataMapItem,
|
||||||
|
objectMetadataMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
return typeORMObjectRecordsParser.createConnection(
|
||||||
|
duplicates,
|
||||||
|
objectMetadataMapItem.nameSingular,
|
||||||
|
duplicates.length,
|
||||||
|
duplicates.length,
|
||||||
|
[{ id: OrderByDirection.AscNullsFirst }],
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return duplicateConnections;
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildDuplicateConditions(
|
||||||
|
objectMetadataMapItem: ObjectMetadataMapItem,
|
||||||
|
records?: Partial<IRecord>[] | undefined,
|
||||||
|
filteringByExistingRecordId?: string,
|
||||||
|
): Partial<RecordFilter> {
|
||||||
|
if (!records || records.length === 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const criteriaCollection = this.getApplicableDuplicateCriteriaCollection(
|
||||||
|
objectMetadataMapItem,
|
||||||
|
);
|
||||||
|
|
||||||
|
const conditions = records.flatMap((record) => {
|
||||||
|
const criteriaWithMatchingArgs = criteriaCollection.filter((criteria) =>
|
||||||
|
criteria.columnNames.every((columnName) => {
|
||||||
|
const value = record[columnName] as string | undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
value && value.length >= settings.minLengthOfStringForDuplicateCheck
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return criteriaWithMatchingArgs.map((criteria) => {
|
||||||
|
const condition = {};
|
||||||
|
|
||||||
|
criteria.columnNames.forEach((columnName) => {
|
||||||
|
condition[columnName] = { eq: record[columnName] };
|
||||||
|
});
|
||||||
|
|
||||||
|
return condition;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const filter: Partial<RecordFilter> = {};
|
||||||
|
|
||||||
|
if (conditions && !isEmpty(conditions)) {
|
||||||
|
filter.or = conditions;
|
||||||
|
|
||||||
|
if (filteringByExistingRecordId) {
|
||||||
|
filter.id = { neq: filteringByExistingRecordId };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getApplicableDuplicateCriteriaCollection(
|
||||||
|
objectMetadataMapItem: ObjectMetadataMapItem,
|
||||||
|
) {
|
||||||
|
return DUPLICATE_CRITERIA_COLLECTION.filter(
|
||||||
|
(duplicateCriteria) =>
|
||||||
|
duplicateCriteria.objectName === objectMetadataMapItem.nameSingular,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate(
|
||||||
|
args: FindDuplicatesResolverArgs,
|
||||||
|
_options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<void> {
|
||||||
|
if (!args.data && !args.ids) {
|
||||||
|
throw new GraphqlQueryRunnerException(
|
||||||
|
'You have to provide either "data" or "ids" argument',
|
||||||
|
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.data && args.ids) {
|
||||||
|
throw new GraphqlQueryRunnerException(
|
||||||
|
'You cannot provide both "data" and "ids" arguments',
|
||||||
|
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args.ids && isEmpty(args.data)) {
|
||||||
|
throw new GraphqlQueryRunnerException(
|
||||||
|
'The "data" condition can not be empty when "ids" input not provided',
|
||||||
|
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { isDefined } from 'class-validator';
|
import { isDefined } from 'class-validator';
|
||||||
import graphqlFields from 'graphql-fields';
|
import graphqlFields from 'graphql-fields';
|
||||||
|
|
||||||
|
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||||
import {
|
import {
|
||||||
Record as IRecord,
|
Record as IRecord,
|
||||||
OrderByDirection,
|
OrderByDirection,
|
||||||
@@ -17,26 +20,25 @@ import {
|
|||||||
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 { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||||
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
||||||
import { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper';
|
|
||||||
import { computeCursorArgFilter } from 'src/engine/api/graphql/graphql-query-runner/utils/compute-cursor-arg-filter';
|
import { computeCursorArgFilter } from 'src/engine/api/graphql/graphql-query-runner/utils/compute-cursor-arg-filter';
|
||||||
import { decodeCursor } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
|
|
||||||
import { getObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util';
|
|
||||||
import {
|
import {
|
||||||
ObjectMetadataMapItem,
|
getCursor,
|
||||||
generateObjectMetadataMap,
|
getPaginationInfo,
|
||||||
} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
} from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
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';
|
||||||
|
|
||||||
export class GraphqlQueryFindManyResolverService {
|
@Injectable()
|
||||||
private twentyORMGlobalManager: TwentyORMGlobalManager;
|
export class GraphqlQueryFindManyResolverService
|
||||||
|
implements ResolverService<FindManyResolverArgs, IConnection<IRecord>>
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
constructor(twentyORMGlobalManager: TwentyORMGlobalManager) {
|
async resolve<
|
||||||
this.twentyORMGlobalManager = twentyORMGlobalManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
async findMany<
|
|
||||||
ObjectRecord extends IRecord = IRecord,
|
ObjectRecord extends IRecord = IRecord,
|
||||||
Filter extends RecordFilter = RecordFilter,
|
Filter extends RecordFilter = RecordFilter,
|
||||||
OrderBy extends RecordOrderBy = RecordOrderBy,
|
OrderBy extends RecordOrderBy = RecordOrderBy,
|
||||||
@@ -44,51 +46,41 @@ export class GraphqlQueryFindManyResolverService {
|
|||||||
args: FindManyResolverArgs<Filter, OrderBy>,
|
args: FindManyResolverArgs<Filter, OrderBy>,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<IConnection<ObjectRecord>> {
|
): Promise<IConnection<ObjectRecord>> {
|
||||||
const { authContext, objectMetadataItem, info, objectMetadataCollection } =
|
const { authContext, objectMetadataMapItem, info, objectMetadataMap } =
|
||||||
options;
|
options;
|
||||||
|
|
||||||
this.validateArgsOrThrow(args);
|
|
||||||
|
|
||||||
const dataSource =
|
const dataSource =
|
||||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||||
authContext.workspace.id,
|
authContext.workspace.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
const repository = dataSource.getRepository(
|
const repository = dataSource.getRepository(
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataMapItem.nameSingular,
|
||||||
);
|
);
|
||||||
|
|
||||||
const queryBuilder = repository.createQueryBuilder(
|
const queryBuilder = repository.createQueryBuilder(
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataMapItem.nameSingular,
|
||||||
);
|
);
|
||||||
|
|
||||||
const countQueryBuilder = repository.createQueryBuilder(
|
const countQueryBuilder = repository.createQueryBuilder(
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataMapItem.nameSingular,
|
||||||
);
|
);
|
||||||
|
|
||||||
const objectMetadataMap = generateObjectMetadataMap(
|
|
||||||
objectMetadataCollection,
|
|
||||||
);
|
|
||||||
|
|
||||||
const objectMetadata = getObjectMetadataOrThrow(
|
|
||||||
objectMetadataMap,
|
|
||||||
objectMetadataItem.nameSingular,
|
|
||||||
);
|
|
||||||
const graphqlQueryParser = new GraphqlQueryParser(
|
const graphqlQueryParser = new GraphqlQueryParser(
|
||||||
objectMetadata.fields,
|
objectMetadataMapItem.fields,
|
||||||
objectMetadataMap,
|
objectMetadataMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const withFilterCountQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
const withFilterCountQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
||||||
countQueryBuilder,
|
countQueryBuilder,
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataMapItem.nameSingular,
|
||||||
args.filter ?? ({} as Filter),
|
args.filter ?? ({} as Filter),
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedFields = graphqlFields(info);
|
const selectedFields = graphqlFields(info);
|
||||||
|
|
||||||
const { relations } = graphqlQueryParser.parseSelectedFields(
|
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||||
objectMetadataItem,
|
objectMetadataMapItem,
|
||||||
selectedFields,
|
selectedFields,
|
||||||
);
|
);
|
||||||
const isForwardPagination = !isDefined(args.before);
|
const isForwardPagination = !isDefined(args.before);
|
||||||
@@ -105,7 +97,7 @@ export class GraphqlQueryFindManyResolverService {
|
|||||||
? await withDeletedCountQueryBuilder.getCount()
|
? await withDeletedCountQueryBuilder.getCount()
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
const cursor = this.getCursor(args);
|
const cursor = getCursor(args);
|
||||||
|
|
||||||
let appliedFilters = args.filter ?? ({} as Filter);
|
let appliedFilters = args.filter ?? ({} as Filter);
|
||||||
|
|
||||||
@@ -118,7 +110,7 @@ export class GraphqlQueryFindManyResolverService {
|
|||||||
const cursorArgFilter = computeCursorArgFilter(
|
const cursorArgFilter = computeCursorArgFilter(
|
||||||
cursor,
|
cursor,
|
||||||
orderByWithIdCondition,
|
orderByWithIdCondition,
|
||||||
objectMetadata.fields,
|
objectMetadataMapItem.fields,
|
||||||
isForwardPagination,
|
isForwardPagination,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -131,14 +123,14 @@ export class GraphqlQueryFindManyResolverService {
|
|||||||
|
|
||||||
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
||||||
queryBuilder,
|
queryBuilder,
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataMapItem.nameSingular,
|
||||||
appliedFilters,
|
appliedFilters,
|
||||||
);
|
);
|
||||||
|
|
||||||
const withOrderByQueryBuilder = graphqlQueryParser.applyOrderToBuilder(
|
const withOrderByQueryBuilder = graphqlQueryParser.applyOrderToBuilder(
|
||||||
withFilterQueryBuilder,
|
withFilterQueryBuilder,
|
||||||
orderByWithIdCondition,
|
orderByWithIdCondition,
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataMapItem.nameSingular,
|
||||||
isForwardPagination,
|
isForwardPagination,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -153,11 +145,11 @@ export class GraphqlQueryFindManyResolverService {
|
|||||||
|
|
||||||
const objectRecords = formatResult(
|
const objectRecords = formatResult(
|
||||||
nonFormattedObjectRecords,
|
nonFormattedObjectRecords,
|
||||||
objectMetadata,
|
objectMetadataMapItem,
|
||||||
objectMetadataMap,
|
objectMetadataMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { hasNextPage, hasPreviousPage } = this.getPaginationInfo(
|
const { hasNextPage, hasPreviousPage } = getPaginationInfo(
|
||||||
objectRecords,
|
objectRecords,
|
||||||
limit,
|
limit,
|
||||||
isForwardPagination,
|
isForwardPagination,
|
||||||
@@ -167,14 +159,12 @@ export class GraphqlQueryFindManyResolverService {
|
|||||||
objectRecords.pop();
|
objectRecords.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
const processNestedRelationsHelper = new ProcessNestedRelationsHelper(
|
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
||||||
this.twentyORMGlobalManager,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (relations) {
|
if (relations) {
|
||||||
await processNestedRelationsHelper.processNestedRelations(
|
await processNestedRelationsHelper.processNestedRelations(
|
||||||
objectMetadataMap,
|
objectMetadataMap,
|
||||||
objectMetadata,
|
objectMetadataMapItem,
|
||||||
objectRecords,
|
objectRecords,
|
||||||
relations,
|
relations,
|
||||||
limit,
|
limit,
|
||||||
@@ -184,20 +174,25 @@ export class GraphqlQueryFindManyResolverService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const typeORMObjectRecordsParser =
|
const typeORMObjectRecordsParser =
|
||||||
new ObjectRecordsToGraphqlConnectionMapper(objectMetadataMap);
|
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||||
|
|
||||||
return typeORMObjectRecordsParser.createConnection(
|
const result = typeORMObjectRecordsParser.createConnection(
|
||||||
objectRecords,
|
objectRecords,
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataMapItem.nameSingular,
|
||||||
limit,
|
limit,
|
||||||
totalCount,
|
totalCount,
|
||||||
orderByWithIdCondition,
|
orderByWithIdCondition,
|
||||||
hasNextPage,
|
hasNextPage,
|
||||||
hasPreviousPage,
|
hasPreviousPage,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private validateArgsOrThrow(args: FindManyResolverArgs<any, any>) {
|
async validate<Filter extends RecordFilter>(
|
||||||
|
args: FindManyResolverArgs<Filter>,
|
||||||
|
_options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<void> {
|
||||||
if (args.first && args.last) {
|
if (args.first && args.last) {
|
||||||
throw new GraphqlQueryRunnerException(
|
throw new GraphqlQueryRunnerException(
|
||||||
'Cannot provide both first and last',
|
'Cannot provide both first and last',
|
||||||
@@ -235,49 +230,4 @@ export class GraphqlQueryFindManyResolverService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCursor(
|
|
||||||
args: FindManyResolverArgs<any, any>,
|
|
||||||
): Record<string, any> | undefined {
|
|
||||||
if (args.after) return decodeCursor(args.after);
|
|
||||||
if (args.before) return decodeCursor(args.before);
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private addOrderByColumnsToSelect(
|
|
||||||
order: Record<string, any>,
|
|
||||||
select: Record<string, boolean>,
|
|
||||||
) {
|
|
||||||
for (const column of Object.keys(order || {})) {
|
|
||||||
if (!select[column]) {
|
|
||||||
select[column] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private addForeingKeyColumnsToSelect(
|
|
||||||
relations: Record<string, any>,
|
|
||||||
select: Record<string, boolean>,
|
|
||||||
objectMetadata: ObjectMetadataMapItem,
|
|
||||||
) {
|
|
||||||
for (const column of Object.keys(relations || {})) {
|
|
||||||
if (!select[`${column}Id`] && objectMetadata.fields[`${column}Id`]) {
|
|
||||||
select[`${column}Id`] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getPaginationInfo(
|
|
||||||
objectRecords: any[],
|
|
||||||
limit: number,
|
|
||||||
isForwardPagination: boolean,
|
|
||||||
) {
|
|
||||||
const hasMoreRecords = objectRecords.length > limit;
|
|
||||||
|
|
||||||
return {
|
|
||||||
hasNextPage: isForwardPagination && hasMoreRecords,
|
|
||||||
hasPreviousPage: !isForwardPagination && hasMoreRecords,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import graphqlFields from 'graphql-fields';
|
import graphqlFields from 'graphql-fields';
|
||||||
|
|
||||||
|
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||||
import {
|
import {
|
||||||
Record as IRecord,
|
Record as IRecord,
|
||||||
RecordFilter,
|
RecordFilter,
|
||||||
@@ -13,28 +16,31 @@ import {
|
|||||||
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 { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||||
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
||||||
import { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper';
|
import {
|
||||||
import { getObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util';
|
WorkspaceQueryRunnerException,
|
||||||
import { generateObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
WorkspaceQueryRunnerExceptionCode,
|
||||||
|
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { 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';
|
||||||
|
|
||||||
export class GraphqlQueryFindOneResolverService {
|
@Injectable()
|
||||||
private twentyORMGlobalManager: TwentyORMGlobalManager;
|
export class GraphqlQueryFindOneResolverService
|
||||||
|
implements ResolverService<FindOneResolverArgs, IRecord>
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
constructor(twentyORMGlobalManager: TwentyORMGlobalManager) {
|
async resolve<
|
||||||
this.twentyORMGlobalManager = twentyORMGlobalManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
async findOne<
|
|
||||||
ObjectRecord extends IRecord = IRecord,
|
ObjectRecord extends IRecord = IRecord,
|
||||||
Filter extends RecordFilter = RecordFilter,
|
Filter extends RecordFilter = RecordFilter,
|
||||||
>(
|
>(
|
||||||
args: FindOneResolverArgs<Filter>,
|
args: FindOneResolverArgs<Filter>,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<ObjectRecord | undefined> {
|
): Promise<ObjectRecord> {
|
||||||
const { authContext, objectMetadataItem, info, objectMetadataCollection } =
|
const { authContext, objectMetadataMapItem, info, objectMetadataMap } =
|
||||||
options;
|
options;
|
||||||
|
|
||||||
const dataSource =
|
const dataSource =
|
||||||
@@ -43,37 +49,28 @@ export class GraphqlQueryFindOneResolverService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const repository = dataSource.getRepository(
|
const repository = dataSource.getRepository(
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataMapItem.nameSingular,
|
||||||
);
|
);
|
||||||
|
|
||||||
const queryBuilder = repository.createQueryBuilder(
|
const queryBuilder = repository.createQueryBuilder(
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataMapItem.nameSingular,
|
||||||
);
|
|
||||||
|
|
||||||
const objectMetadataMap = generateObjectMetadataMap(
|
|
||||||
objectMetadataCollection,
|
|
||||||
);
|
|
||||||
|
|
||||||
const objectMetadata = getObjectMetadataOrThrow(
|
|
||||||
objectMetadataMap,
|
|
||||||
objectMetadataItem.nameSingular,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const graphqlQueryParser = new GraphqlQueryParser(
|
const graphqlQueryParser = new GraphqlQueryParser(
|
||||||
objectMetadata.fields,
|
objectMetadataMapItem.fields,
|
||||||
objectMetadataMap,
|
objectMetadataMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedFields = graphqlFields(info);
|
const selectedFields = graphqlFields(info);
|
||||||
|
|
||||||
const { relations } = graphqlQueryParser.parseSelectedFields(
|
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||||
objectMetadataItem,
|
objectMetadataMapItem,
|
||||||
selectedFields,
|
selectedFields,
|
||||||
);
|
);
|
||||||
|
|
||||||
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
||||||
queryBuilder,
|
queryBuilder,
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataMapItem.nameSingular,
|
||||||
args.filter ?? ({} as Filter),
|
args.filter ?? ({} as Filter),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -86,12 +83,10 @@ export class GraphqlQueryFindOneResolverService {
|
|||||||
|
|
||||||
const objectRecord = formatResult(
|
const objectRecord = formatResult(
|
||||||
nonFormattedObjectRecord,
|
nonFormattedObjectRecord,
|
||||||
objectMetadata,
|
objectMetadataMapItem,
|
||||||
objectMetadataMap,
|
objectMetadataMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const limit = QUERY_MAX_RECORDS;
|
|
||||||
|
|
||||||
if (!objectRecord) {
|
if (!objectRecord) {
|
||||||
throw new GraphqlQueryRunnerException(
|
throw new GraphqlQueryRunnerException(
|
||||||
'Record not found',
|
'Record not found',
|
||||||
@@ -99,32 +94,42 @@ export class GraphqlQueryFindOneResolverService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const processNestedRelationsHelper = new ProcessNestedRelationsHelper(
|
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
||||||
this.twentyORMGlobalManager,
|
|
||||||
);
|
|
||||||
|
|
||||||
const objectRecords = [objectRecord];
|
const objectRecords = [objectRecord];
|
||||||
|
|
||||||
if (relations) {
|
if (relations) {
|
||||||
await processNestedRelationsHelper.processNestedRelations(
|
await processNestedRelationsHelper.processNestedRelations(
|
||||||
objectMetadataMap,
|
objectMetadataMap,
|
||||||
objectMetadata,
|
objectMetadataMapItem,
|
||||||
objectRecords,
|
objectRecords,
|
||||||
relations,
|
relations,
|
||||||
limit,
|
QUERY_MAX_RECORDS,
|
||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const typeORMObjectRecordsParser =
|
const typeORMObjectRecordsParser =
|
||||||
new ObjectRecordsToGraphqlConnectionMapper(objectMetadataMap);
|
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||||
|
|
||||||
return typeORMObjectRecordsParser.processRecord(
|
return typeORMObjectRecordsParser.processRecord(
|
||||||
objectRecords[0],
|
objectRecords[0],
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataMapItem.nameSingular,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
) as ObjectRecord;
|
) as ObjectRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async validate<Filter extends RecordFilter>(
|
||||||
|
args: FindOneResolverArgs<Filter>,
|
||||||
|
_options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<void> {
|
||||||
|
if (!args.filter || Object.keys(args.filter).length === 0) {
|
||||||
|
throw new WorkspaceQueryRunnerException(
|
||||||
|
'Missing filter argument',
|
||||||
|
WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||||
import {
|
import {
|
||||||
Record as IRecord,
|
Record as IRecord,
|
||||||
OrderByDirection,
|
OrderByDirection,
|
||||||
@@ -11,47 +14,25 @@ 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 { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper';
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
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 { 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 { generateObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
|
||||||
export class GraphqlQuerySearchResolverService {
|
@Injectable()
|
||||||
private twentyORMGlobalManager: TwentyORMGlobalManager;
|
export class GraphqlQuerySearchResolverService
|
||||||
private featureFlagService: FeatureFlagService;
|
implements ResolverService<SearchResolverArgs, IConnection<IRecord>>
|
||||||
|
{
|
||||||
constructor(
|
constructor(
|
||||||
twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
featureFlagService: FeatureFlagService,
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
) {
|
) {}
|
||||||
this.twentyORMGlobalManager = twentyORMGlobalManager;
|
|
||||||
this.featureFlagService = featureFlagService;
|
|
||||||
}
|
|
||||||
|
|
||||||
async search<ObjectRecord extends IRecord = IRecord>(
|
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||||
args: SearchResolverArgs,
|
args: SearchResolverArgs,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<IConnection<ObjectRecord>> {
|
): Promise<IConnection<ObjectRecord>> {
|
||||||
const { authContext, objectMetadataItem, objectMetadataCollection } =
|
const { authContext, objectMetadataItem, objectMetadataMap } = options;
|
||||||
options;
|
|
||||||
|
|
||||||
const featureFlagsForWorkspace =
|
|
||||||
await this.featureFlagService.getWorkspaceFeatureFlags(
|
|
||||||
authContext.workspace.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
const isQueryRunnerTwentyORMEnabled =
|
|
||||||
featureFlagsForWorkspace.IS_QUERY_RUNNER_TWENTY_ORM_ENABLED;
|
|
||||||
|
|
||||||
const isSearchEnabled = featureFlagsForWorkspace.IS_SEARCH_ENABLED;
|
|
||||||
|
|
||||||
if (!isQueryRunnerTwentyORMEnabled || !isSearchEnabled) {
|
|
||||||
throw new GraphqlQueryRunnerException(
|
|
||||||
'This endpoint is not available yet, please use findMany instead.',
|
|
||||||
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const repository =
|
const repository =
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
@@ -59,21 +40,8 @@ export class GraphqlQuerySearchResolverService {
|
|||||||
objectMetadataItem.nameSingular,
|
objectMetadataItem.nameSingular,
|
||||||
);
|
);
|
||||||
|
|
||||||
const objectMetadataMap = generateObjectMetadataMap(
|
|
||||||
objectMetadataCollection,
|
|
||||||
);
|
|
||||||
|
|
||||||
const objectMetadata = objectMetadataMap[objectMetadataItem.nameSingular];
|
|
||||||
|
|
||||||
if (!objectMetadata) {
|
|
||||||
throw new GraphqlQueryRunnerException(
|
|
||||||
`Object metadata not found for ${objectMetadataItem.nameSingular}`,
|
|
||||||
GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const typeORMObjectRecordsParser =
|
const typeORMObjectRecordsParser =
|
||||||
new ObjectRecordsToGraphqlConnectionMapper(objectMetadataMap);
|
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||||
|
|
||||||
if (!args.searchInput) {
|
if (!args.searchInput) {
|
||||||
return typeORMObjectRecordsParser.createConnection(
|
return typeORMObjectRecordsParser.createConnection(
|
||||||
@@ -100,7 +68,7 @@ export class GraphqlQuerySearchResolverService {
|
|||||||
'DESC',
|
'DESC',
|
||||||
)
|
)
|
||||||
.setParameter('searchTerms', searchTerms)
|
.setParameter('searchTerms', searchTerms)
|
||||||
.limit(limit)
|
.take(limit)
|
||||||
.getMany()) as ObjectRecord[];
|
.getMany()) as ObjectRecord[];
|
||||||
|
|
||||||
const objectRecords = await repository.formatResult(resultsWithTsVector);
|
const objectRecords = await repository.formatResult(resultsWithTsVector);
|
||||||
@@ -129,4 +97,26 @@ export class GraphqlQuerySearchResolverService {
|
|||||||
|
|
||||||
return formattedWords.join(' | ');
|
return formattedWords.join(' | ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async validate(
|
||||||
|
_args: SearchResolverArgs,
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<void> {
|
||||||
|
const featureFlagsForWorkspace =
|
||||||
|
await this.featureFlagService.getWorkspaceFeatureFlags(
|
||||||
|
options.authContext.workspace.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isQueryRunnerTwentyORMEnabled =
|
||||||
|
featureFlagsForWorkspace.IS_QUERY_RUNNER_TWENTY_ORM_ENABLED;
|
||||||
|
|
||||||
|
const isSearchEnabled = featureFlagsForWorkspace.IS_SEARCH_ENABLED;
|
||||||
|
|
||||||
|
if (!isQueryRunnerTwentyORMEnabled || !isSearchEnabled) {
|
||||||
|
throw new GraphqlQueryRunnerException(
|
||||||
|
'This endpoint is not available yet, please use findMany instead.',
|
||||||
|
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import graphqlFields from 'graphql-fields';
|
||||||
|
|
||||||
|
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||||
|
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||||
|
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||||
|
import { UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
|
import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant';
|
||||||
|
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||||
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
|
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
||||||
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||||
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||||
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GraphqlQueryUpdateManyResolverService
|
||||||
|
implements ResolverService<UpdateManyResolverArgs, IRecord[]>
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||||
|
args: UpdateManyResolverArgs<Partial<ObjectRecord>>,
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<ObjectRecord[]> {
|
||||||
|
const { authContext, objectMetadataMapItem, objectMetadataMap, info } =
|
||||||
|
options;
|
||||||
|
|
||||||
|
const dataSource =
|
||||||
|
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||||
|
authContext.workspace.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const repository = dataSource.getRepository(
|
||||||
|
objectMetadataMapItem.nameSingular,
|
||||||
|
);
|
||||||
|
|
||||||
|
const graphqlQueryParser = new GraphqlQueryParser(
|
||||||
|
objectMetadataMapItem.fields,
|
||||||
|
objectMetadataMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedFields = graphqlFields(info);
|
||||||
|
|
||||||
|
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||||
|
objectMetadataMapItem,
|
||||||
|
selectedFields,
|
||||||
|
);
|
||||||
|
|
||||||
|
const queryBuilder = repository.createQueryBuilder(
|
||||||
|
objectMetadataMapItem.nameSingular,
|
||||||
|
);
|
||||||
|
|
||||||
|
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
||||||
|
queryBuilder,
|
||||||
|
objectMetadataMapItem.nameSingular,
|
||||||
|
args.filter,
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = formatData(args.data, objectMetadataMapItem);
|
||||||
|
|
||||||
|
const result = await withFilterQueryBuilder
|
||||||
|
.update()
|
||||||
|
.set(data)
|
||||||
|
.returning('*')
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
const nonFormattedUpdatedObjectRecords = result.raw;
|
||||||
|
|
||||||
|
const updatedRecords = formatResult(
|
||||||
|
nonFormattedUpdatedObjectRecords,
|
||||||
|
objectMetadataMapItem,
|
||||||
|
objectMetadataMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
||||||
|
|
||||||
|
if (relations) {
|
||||||
|
await processNestedRelationsHelper.processNestedRelations(
|
||||||
|
objectMetadataMap,
|
||||||
|
objectMetadataMapItem,
|
||||||
|
updatedRecords,
|
||||||
|
relations,
|
||||||
|
QUERY_MAX_RECORDS,
|
||||||
|
authContext,
|
||||||
|
dataSource,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeORMObjectRecordsParser =
|
||||||
|
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||||
|
|
||||||
|
return updatedRecords.map((record: ObjectRecord) =>
|
||||||
|
typeORMObjectRecordsParser.processRecord(
|
||||||
|
record,
|
||||||
|
objectMetadataMapItem.nameSingular,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate<ObjectRecord extends IRecord = IRecord>(
|
||||||
|
args: UpdateManyResolverArgs<Partial<ObjectRecord>>,
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<void> {
|
||||||
|
assertMutationNotOnRemoteObject(options.objectMetadataMapItem);
|
||||||
|
args.filter?.id?.in?.forEach((id: string) => assertIsValidUuid(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import graphqlFields from 'graphql-fields';
|
||||||
|
|
||||||
|
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||||
|
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||||
|
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||||
|
import { UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
|
import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant';
|
||||||
|
import {
|
||||||
|
GraphqlQueryRunnerException,
|
||||||
|
GraphqlQueryRunnerExceptionCode,
|
||||||
|
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||||
|
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||||
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
|
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
||||||
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||||
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||||
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GraphqlQueryUpdateOneResolverService
|
||||||
|
implements ResolverService<UpdateOneResolverArgs, IRecord>
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||||
|
args: UpdateOneResolverArgs<Partial<ObjectRecord>>,
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<ObjectRecord> {
|
||||||
|
const { authContext, objectMetadataMapItem, objectMetadataMap, info } =
|
||||||
|
options;
|
||||||
|
|
||||||
|
const dataSource =
|
||||||
|
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||||
|
authContext.workspace.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const repository = dataSource.getRepository(
|
||||||
|
objectMetadataMapItem.nameSingular,
|
||||||
|
);
|
||||||
|
|
||||||
|
const graphqlQueryParser = new GraphqlQueryParser(
|
||||||
|
objectMetadataMapItem.fields,
|
||||||
|
objectMetadataMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedFields = graphqlFields(info);
|
||||||
|
|
||||||
|
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||||
|
objectMetadataMapItem,
|
||||||
|
selectedFields,
|
||||||
|
);
|
||||||
|
|
||||||
|
const queryBuilder = repository.createQueryBuilder(
|
||||||
|
objectMetadataMapItem.nameSingular,
|
||||||
|
);
|
||||||
|
|
||||||
|
const withFilterQueryBuilder = queryBuilder.where({ id: args.id });
|
||||||
|
|
||||||
|
const data = formatData(args.data, objectMetadataMapItem);
|
||||||
|
|
||||||
|
const result = await withFilterQueryBuilder
|
||||||
|
.update()
|
||||||
|
.set(data)
|
||||||
|
.returning('*')
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
const nonFormattedUpdatedObjectRecords = result.raw;
|
||||||
|
|
||||||
|
const updatedRecords = formatResult(
|
||||||
|
nonFormattedUpdatedObjectRecords,
|
||||||
|
objectMetadataMapItem,
|
||||||
|
objectMetadataMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (updatedRecords.length === 0) {
|
||||||
|
throw new GraphqlQueryRunnerException(
|
||||||
|
'Record not found',
|
||||||
|
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedRecord = updatedRecords[0] as ObjectRecord;
|
||||||
|
|
||||||
|
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
||||||
|
|
||||||
|
if (relations) {
|
||||||
|
await processNestedRelationsHelper.processNestedRelations(
|
||||||
|
objectMetadataMap,
|
||||||
|
objectMetadataMapItem,
|
||||||
|
[updatedRecord],
|
||||||
|
relations,
|
||||||
|
QUERY_MAX_RECORDS,
|
||||||
|
authContext,
|
||||||
|
dataSource,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeORMObjectRecordsParser =
|
||||||
|
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||||
|
|
||||||
|
return typeORMObjectRecordsParser.processRecord<ObjectRecord>(
|
||||||
|
updatedRecord,
|
||||||
|
objectMetadataMapItem.nameSingular,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate<ObjectRecord extends IRecord = IRecord>(
|
||||||
|
args: UpdateOneResolverArgs<Partial<ObjectRecord>>,
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<void> {
|
||||||
|
assertMutationNotOnRemoteObject(options.objectMetadataMapItem);
|
||||||
|
assertIsValidUuid(args.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||||
|
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||||
|
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ApiEventEmitterService {
|
||||||
|
constructor(private readonly workspaceEventEmitter: WorkspaceEventEmitter) {}
|
||||||
|
|
||||||
|
public emitCreateEvents<T extends IRecord>(
|
||||||
|
records: T[],
|
||||||
|
authContext: AuthContext,
|
||||||
|
objectMetadataItem: ObjectMetadataInterface,
|
||||||
|
): void {
|
||||||
|
this.workspaceEventEmitter.emit(
|
||||||
|
`${objectMetadataItem.nameSingular}.created`,
|
||||||
|
records.map((record) => ({
|
||||||
|
userId: authContext.user?.id,
|
||||||
|
recordId: record.id,
|
||||||
|
objectMetadata: objectMetadataItem,
|
||||||
|
properties: {
|
||||||
|
before: null,
|
||||||
|
after: this.removeGraphQLAndNestedProperties(record),
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
authContext.workspace.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public emitUpdateEvents<T extends IRecord>(
|
||||||
|
existingRecords: T[],
|
||||||
|
records: T[],
|
||||||
|
updatedFields: string[],
|
||||||
|
authContext: AuthContext,
|
||||||
|
objectMetadataItem: ObjectMetadataInterface,
|
||||||
|
): void {
|
||||||
|
const mappedExistingRecords = existingRecords.reduce(
|
||||||
|
(acc, { id, ...record }) => ({
|
||||||
|
...acc,
|
||||||
|
[id]: record,
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.workspaceEventEmitter.emit(
|
||||||
|
`${objectMetadataItem.nameSingular}.updated`,
|
||||||
|
records.map((record) => {
|
||||||
|
return {
|
||||||
|
userId: authContext.user?.id,
|
||||||
|
recordId: record.id,
|
||||||
|
objectMetadata: objectMetadataItem,
|
||||||
|
properties: {
|
||||||
|
before: mappedExistingRecords[record.id]
|
||||||
|
? this.removeGraphQLAndNestedProperties(
|
||||||
|
mappedExistingRecords[record.id],
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
after: this.removeGraphQLAndNestedProperties(record),
|
||||||
|
updatedFields,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
authContext.workspace.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public emitDeletedEvents<T extends IRecord>(
|
||||||
|
records: T[],
|
||||||
|
authContext: AuthContext,
|
||||||
|
objectMetadataItem: ObjectMetadataInterface,
|
||||||
|
): void {
|
||||||
|
this.workspaceEventEmitter.emit(
|
||||||
|
`${objectMetadataItem.nameSingular}.deleted`,
|
||||||
|
records.map((record) => {
|
||||||
|
return {
|
||||||
|
userId: authContext.user?.id,
|
||||||
|
recordId: record.id,
|
||||||
|
objectMetadata: objectMetadataItem,
|
||||||
|
properties: {
|
||||||
|
before: this.removeGraphQLAndNestedProperties(record),
|
||||||
|
after: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
authContext.workspace.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public emitDestroyEvents<T extends IRecord>(
|
||||||
|
records: T[],
|
||||||
|
authContext: AuthContext,
|
||||||
|
objectMetadataItem: ObjectMetadataInterface,
|
||||||
|
): void {
|
||||||
|
this.workspaceEventEmitter.emit(
|
||||||
|
`${objectMetadataItem.nameSingular}.destroyed`,
|
||||||
|
records.map((record) => {
|
||||||
|
return {
|
||||||
|
userId: authContext.user?.id,
|
||||||
|
recordId: record.id,
|
||||||
|
objectMetadata: objectMetadataItem,
|
||||||
|
properties: {
|
||||||
|
before: this.removeGraphQLAndNestedProperties(record),
|
||||||
|
after: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
authContext.workspace.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeGraphQLAndNestedProperties<ObjectRecord extends IRecord>(
|
||||||
|
record: ObjectRecord,
|
||||||
|
) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
Record as IRecord,
|
Record as IRecord,
|
||||||
RecordOrderBy,
|
RecordOrderBy,
|
||||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||||
|
import { FindManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GraphqlQueryRunnerException,
|
GraphqlQueryRunnerException,
|
||||||
@@ -44,3 +45,25 @@ export const encodeCursor = <ObjectRecord extends IRecord = IRecord>(
|
|||||||
|
|
||||||
return Buffer.from(JSON.stringify(cursorData)).toString('base64');
|
return Buffer.from(JSON.stringify(cursorData)).toString('base64');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getCursor = (
|
||||||
|
args: FindManyResolverArgs<any, any>,
|
||||||
|
): Record<string, any> | undefined => {
|
||||||
|
if (args.after) return decodeCursor(args.after);
|
||||||
|
if (args.before) return decodeCursor(args.before);
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPaginationInfo = (
|
||||||
|
objectRecords: any[],
|
||||||
|
limit: number,
|
||||||
|
isForwardPagination: boolean,
|
||||||
|
) => {
|
||||||
|
const hasMoreRecords = objectRecords.length > limit;
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasNextPage: isForwardPagination && hasMoreRecords,
|
||||||
|
hasPreviousPage: !isForwardPagination && hasMoreRecords,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metada
|
|||||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||||
|
|
||||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import {
|
||||||
|
ObjectMetadataMap,
|
||||||
|
ObjectMetadataMapItem,
|
||||||
|
} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||||
|
|
||||||
export interface WorkspaceQueryRunnerOptions {
|
export interface WorkspaceQueryRunnerOptions {
|
||||||
authContext: AuthContext;
|
authContext: AuthContext;
|
||||||
@@ -11,4 +15,6 @@ export interface WorkspaceQueryRunnerOptions {
|
|||||||
objectMetadataItem: ObjectMetadataInterface;
|
objectMetadataItem: ObjectMetadataInterface;
|
||||||
fieldMetadataCollection: FieldMetadataInterface[];
|
fieldMetadataCollection: FieldMetadataInterface[];
|
||||||
objectMetadataCollection: ObjectMetadataInterface[];
|
objectMetadataCollection: ObjectMetadataInterface[];
|
||||||
|
objectMetadataMap: ObjectMetadataMap;
|
||||||
|
objectMetadataMapItem: ObjectMetadataMapItem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
FindManyResolverArgs,
|
FindManyResolverArgs,
|
||||||
FindOneResolverArgs,
|
FindOneResolverArgs,
|
||||||
RestoreManyResolverArgs,
|
RestoreManyResolverArgs,
|
||||||
|
SearchResolverArgs,
|
||||||
UpdateManyResolverArgs,
|
UpdateManyResolverArgs,
|
||||||
UpdateOneResolverArgs,
|
UpdateOneResolverArgs,
|
||||||
} 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';
|
||||||
@@ -42,4 +43,6 @@ export type WorkspacePreQueryHookPayload<T> = T extends 'createMany'
|
|||||||
? DestroyManyResolverArgs
|
? DestroyManyResolverArgs
|
||||||
: T extends 'destroyOne'
|
: T extends 'destroyOne'
|
||||||
? DestroyOneResolverArgs
|
? DestroyOneResolverArgs
|
||||||
: never;
|
: T extends 'search'
|
||||||
|
? SearchResolverArgs
|
||||||
|
: never;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
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 { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
|
||||||
import {
|
import {
|
||||||
CreateManyResolverArgs,
|
CreateManyResolverArgs,
|
||||||
@@ -32,12 +33,14 @@ export class CreateManyResolverFactory
|
|||||||
|
|
||||||
return async (_source, args, _context, info) => {
|
return async (_source, args, _context, info) => {
|
||||||
try {
|
try {
|
||||||
const options = {
|
const options: WorkspaceQueryRunnerOptions = {
|
||||||
authContext: internalContext.authContext,
|
authContext: internalContext.authContext,
|
||||||
objectMetadataItem: internalContext.objectMetadataItem,
|
objectMetadataItem: internalContext.objectMetadataItem,
|
||||||
info,
|
info,
|
||||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||||
objectMetadataCollection: internalContext.objectMetadataCollection,
|
objectMetadataCollection: internalContext.objectMetadataCollection,
|
||||||
|
objectMetadataMap: internalContext.objectMetadataMap,
|
||||||
|
objectMetadataMapItem: internalContext.objectMetadataMapItem,
|
||||||
};
|
};
|
||||||
|
|
||||||
const isQueryRunnerTwentyORMEnabled =
|
const isQueryRunnerTwentyORMEnabled =
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
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 { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
|
||||||
import {
|
import {
|
||||||
CreateOneResolverArgs,
|
CreateOneResolverArgs,
|
||||||
@@ -32,12 +33,14 @@ export class CreateOneResolverFactory
|
|||||||
|
|
||||||
return async (_source, args, _context, info) => {
|
return async (_source, args, _context, info) => {
|
||||||
try {
|
try {
|
||||||
const options = {
|
const options: WorkspaceQueryRunnerOptions = {
|
||||||
authContext: internalContext.authContext,
|
authContext: internalContext.authContext,
|
||||||
objectMetadataItem: internalContext.objectMetadataItem,
|
objectMetadataItem: internalContext.objectMetadataItem,
|
||||||
info,
|
info,
|
||||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||||
objectMetadataCollection: internalContext.objectMetadataCollection,
|
objectMetadataCollection: internalContext.objectMetadataCollection,
|
||||||
|
objectMetadataMap: internalContext.objectMetadataMap,
|
||||||
|
objectMetadataMapItem: internalContext.objectMetadataMapItem,
|
||||||
};
|
};
|
||||||
|
|
||||||
const isQueryRunnerTwentyORMEnabled =
|
const isQueryRunnerTwentyORMEnabled =
|
||||||
@@ -50,13 +53,7 @@ export class CreateOneResolverFactory
|
|||||||
return await this.graphqlQueryRunnerService.createOne(args, options);
|
return await this.graphqlQueryRunnerService.createOne(args, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.workspaceQueryRunnerService.createOne(args, {
|
return await this.workspaceQueryRunnerService.createOne(args, options);
|
||||||
authContext: internalContext.authContext,
|
|
||||||
objectMetadataItem: internalContext.objectMetadataItem,
|
|
||||||
info,
|
|
||||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
|
||||||
objectMetadataCollection: internalContext.objectMetadataCollection,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
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 { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
|
||||||
import {
|
import {
|
||||||
DeleteManyResolverArgs,
|
DeleteManyResolverArgs,
|
||||||
@@ -7,8 +8,11 @@ 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 { 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';
|
||||||
import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service';
|
import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service';
|
||||||
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteManyResolverFactory
|
export class DeleteManyResolverFactory
|
||||||
@@ -18,6 +22,8 @@ export class DeleteManyResolverFactory
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService,
|
private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService,
|
||||||
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
|
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
create(
|
create(
|
||||||
@@ -27,13 +33,27 @@ export class DeleteManyResolverFactory
|
|||||||
|
|
||||||
return async (_source, args, context, info) => {
|
return async (_source, args, context, info) => {
|
||||||
try {
|
try {
|
||||||
return await this.workspaceQueryRunnerService.deleteMany(args, {
|
const options: WorkspaceQueryRunnerOptions = {
|
||||||
authContext: internalContext.authContext,
|
authContext: internalContext.authContext,
|
||||||
objectMetadataItem: internalContext.objectMetadataItem,
|
objectMetadataItem: internalContext.objectMetadataItem,
|
||||||
info,
|
info,
|
||||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||||
objectMetadataCollection: internalContext.objectMetadataCollection,
|
objectMetadataCollection: internalContext.objectMetadataCollection,
|
||||||
});
|
objectMetadataMap: internalContext.objectMetadataMap,
|
||||||
|
objectMetadataMapItem: internalContext.objectMetadataMapItem,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isQueryRunnerTwentyORMEnabled =
|
||||||
|
await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsQueryRunnerTwentyORMEnabled,
|
||||||
|
internalContext.authContext.workspace.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isQueryRunnerTwentyORMEnabled) {
|
||||||
|
return await this.graphqlQueryRunnerService.deleteMany(args, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.workspaceQueryRunnerService.deleteMany(args, options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
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 { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
|
||||||
import {
|
import {
|
||||||
DeleteOneResolverArgs,
|
DeleteOneResolverArgs,
|
||||||
@@ -7,8 +8,11 @@ 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 { 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';
|
||||||
import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service';
|
import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service';
|
||||||
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteOneResolverFactory
|
export class DeleteOneResolverFactory
|
||||||
@@ -18,6 +22,8 @@ export class DeleteOneResolverFactory
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService,
|
private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService,
|
||||||
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
|
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
create(
|
create(
|
||||||
@@ -27,13 +33,27 @@ export class DeleteOneResolverFactory
|
|||||||
|
|
||||||
return async (_source, args, context, info) => {
|
return async (_source, args, context, info) => {
|
||||||
try {
|
try {
|
||||||
return await this.workspaceQueryRunnerService.deleteOne(args, {
|
const options: WorkspaceQueryRunnerOptions = {
|
||||||
authContext: internalContext.authContext,
|
authContext: internalContext.authContext,
|
||||||
objectMetadataItem: internalContext.objectMetadataItem,
|
objectMetadataItem: internalContext.objectMetadataItem,
|
||||||
info,
|
info,
|
||||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||||
objectMetadataCollection: internalContext.objectMetadataCollection,
|
objectMetadataCollection: internalContext.objectMetadataCollection,
|
||||||
});
|
objectMetadataMap: internalContext.objectMetadataMap,
|
||||||
|
objectMetadataMapItem: internalContext.objectMetadataMapItem,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isQueryRunnerTwentyORMEnabled =
|
||||||
|
await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsQueryRunnerTwentyORMEnabled,
|
||||||
|
internalContext.authContext.workspace.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isQueryRunnerTwentyORMEnabled) {
|
||||||
|
return await this.graphqlQueryRunnerService.deleteOne(args, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.workspaceQueryRunnerService.deleteOne(args, options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
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 { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
|
||||||
import {
|
import {
|
||||||
DestroyManyResolverArgs,
|
DestroyManyResolverArgs,
|
||||||
@@ -27,13 +28,20 @@ export class DestroyManyResolverFactory
|
|||||||
|
|
||||||
return async (_source, args, context, info) => {
|
return async (_source, args, context, info) => {
|
||||||
try {
|
try {
|
||||||
return await this.workspaceQueryRunnerService.destroyMany(args, {
|
const options: WorkspaceQueryRunnerOptions = {
|
||||||
authContext: internalContext.authContext,
|
authContext: internalContext.authContext,
|
||||||
objectMetadataItem: internalContext.objectMetadataItem,
|
objectMetadataItem: internalContext.objectMetadataItem,
|
||||||
info,
|
info,
|
||||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||||
objectMetadataCollection: internalContext.objectMetadataCollection,
|
objectMetadataCollection: internalContext.objectMetadataCollection,
|
||||||
});
|
objectMetadataMap: internalContext.objectMetadataMap,
|
||||||
|
objectMetadataMapItem: internalContext.objectMetadataMapItem,
|
||||||
|
};
|
||||||
|
|
||||||
|
return await this.workspaceQueryRunnerService.destroyMany(
|
||||||
|
args,
|
||||||
|
options,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
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 { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
|
||||||
import {
|
import {
|
||||||
DestroyOneResolverArgs,
|
DestroyOneResolverArgs,
|
||||||
@@ -27,13 +28,17 @@ export class DestroyOneResolverFactory
|
|||||||
|
|
||||||
return async (_source, args, context, info) => {
|
return async (_source, args, context, info) => {
|
||||||
try {
|
try {
|
||||||
return await this.graphQLQueryRunnerService.destroyOne(args, {
|
const options: WorkspaceQueryRunnerOptions = {
|
||||||
authContext: internalContext.authContext,
|
authContext: internalContext.authContext,
|
||||||
objectMetadataItem: internalContext.objectMetadataItem,
|
objectMetadataItem: internalContext.objectMetadataItem,
|
||||||
info,
|
info,
|
||||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||||
objectMetadataCollection: internalContext.objectMetadataCollection,
|
objectMetadataCollection: internalContext.objectMetadataCollection,
|
||||||
});
|
objectMetadataMap: internalContext.objectMetadataMap,
|
||||||
|
objectMetadataMapItem: internalContext.objectMetadataMapItem,
|
||||||
|
};
|
||||||
|
|
||||||
|
return await this.graphQLQueryRunnerService.destroyOne(args, options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
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 { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
|
||||||
import {
|
import {
|
||||||
FindDuplicatesResolverArgs,
|
FindDuplicatesResolverArgs,
|
||||||
@@ -7,8 +8,11 @@ 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 { 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';
|
||||||
import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service';
|
import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service';
|
||||||
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FindDuplicatesResolverFactory
|
export class FindDuplicatesResolverFactory
|
||||||
@@ -18,6 +22,8 @@ export class FindDuplicatesResolverFactory
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService,
|
private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService,
|
||||||
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
|
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
create(
|
create(
|
||||||
@@ -27,13 +33,33 @@ export class FindDuplicatesResolverFactory
|
|||||||
|
|
||||||
return async (_source, args, context, info) => {
|
return async (_source, args, context, info) => {
|
||||||
try {
|
try {
|
||||||
return await this.workspaceQueryRunnerService.findDuplicates(args, {
|
const options: WorkspaceQueryRunnerOptions = {
|
||||||
authContext: internalContext.authContext,
|
authContext: internalContext.authContext,
|
||||||
objectMetadataItem: internalContext.objectMetadataItem,
|
objectMetadataItem: internalContext.objectMetadataItem,
|
||||||
info,
|
info,
|
||||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||||
objectMetadataCollection: internalContext.objectMetadataCollection,
|
objectMetadataCollection: internalContext.objectMetadataCollection,
|
||||||
});
|
objectMetadataMap: internalContext.objectMetadataMap,
|
||||||
|
objectMetadataMapItem: internalContext.objectMetadataMapItem,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isQueryRunnerTwentyORMEnabled =
|
||||||
|
await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsQueryRunnerTwentyORMEnabled,
|
||||||
|
internalContext.authContext.workspace.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isQueryRunnerTwentyORMEnabled) {
|
||||||
|
return await this.graphqlQueryRunnerService.findDuplicates(
|
||||||
|
args,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.workspaceQueryRunnerService.findDuplicates(
|
||||||
|
args,
|
||||||
|
options,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
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 { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
|
||||||
import {
|
import {
|
||||||
FindManyResolverArgs,
|
FindManyResolverArgs,
|
||||||
@@ -27,12 +28,14 @@ export class FindManyResolverFactory
|
|||||||
|
|
||||||
return async (_source, args, _context, info) => {
|
return async (_source, args, _context, info) => {
|
||||||
try {
|
try {
|
||||||
const options = {
|
const options: WorkspaceQueryRunnerOptions = {
|
||||||
authContext: internalContext.authContext,
|
authContext: internalContext.authContext,
|
||||||
objectMetadataItem: internalContext.objectMetadataItem,
|
objectMetadataItem: internalContext.objectMetadataItem,
|
||||||
info,
|
info,
|
||||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||||
objectMetadataCollection: internalContext.objectMetadataCollection,
|
objectMetadataCollection: internalContext.objectMetadataCollection,
|
||||||
|
objectMetadataMap: internalContext.objectMetadataMap,
|
||||||
|
objectMetadataMapItem: internalContext.objectMetadataMapItem,
|
||||||
};
|
};
|
||||||
|
|
||||||
return await this.graphqlQueryRunnerService.findMany(args, options);
|
return await this.graphqlQueryRunnerService.findMany(args, options);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
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 { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
|
||||||
import {
|
import {
|
||||||
FindOneResolverArgs,
|
FindOneResolverArgs,
|
||||||
@@ -27,12 +28,14 @@ export class FindOneResolverFactory
|
|||||||
|
|
||||||
return async (_source, args, _context, info) => {
|
return async (_source, args, _context, info) => {
|
||||||
try {
|
try {
|
||||||
const options = {
|
const options: WorkspaceQueryRunnerOptions = {
|
||||||
authContext: internalContext.authContext,
|
authContext: internalContext.authContext,
|
||||||
objectMetadataItem: internalContext.objectMetadataItem,
|
objectMetadataItem: internalContext.objectMetadataItem,
|
||||||
info,
|
info,
|
||||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||||
objectMetadataCollection: internalContext.objectMetadataCollection,
|
objectMetadataCollection: internalContext.objectMetadataCollection,
|
||||||
|
objectMetadataMap: internalContext.objectMetadataMap,
|
||||||
|
objectMetadataMapItem: internalContext.objectMetadataMapItem,
|
||||||
};
|
};
|
||||||
|
|
||||||
return await this.graphqlQueryRunnerService.findOne(args, options);
|
return await this.graphqlQueryRunnerService.findOne(args, options);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
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 { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
|
||||||
import {
|
import {
|
||||||
Resolver,
|
Resolver,
|
||||||
@@ -7,8 +8,11 @@ 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 { 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';
|
||||||
import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service';
|
import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service';
|
||||||
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RestoreManyResolverFactory
|
export class RestoreManyResolverFactory
|
||||||
@@ -18,6 +22,8 @@ export class RestoreManyResolverFactory
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService,
|
private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService,
|
||||||
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
|
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
create(
|
create(
|
||||||
@@ -27,13 +33,33 @@ export class RestoreManyResolverFactory
|
|||||||
|
|
||||||
return async (_source, args, context, info) => {
|
return async (_source, args, context, info) => {
|
||||||
try {
|
try {
|
||||||
return await this.workspaceQueryRunnerService.restoreMany(args, {
|
const options: WorkspaceQueryRunnerOptions = {
|
||||||
authContext: internalContext.authContext,
|
authContext: internalContext.authContext,
|
||||||
objectMetadataItem: internalContext.objectMetadataItem,
|
objectMetadataItem: internalContext.objectMetadataItem,
|
||||||
info,
|
info,
|
||||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||||
objectMetadataCollection: internalContext.objectMetadataCollection,
|
objectMetadataCollection: internalContext.objectMetadataCollection,
|
||||||
});
|
objectMetadataMap: internalContext.objectMetadataMap,
|
||||||
|
objectMetadataMapItem: internalContext.objectMetadataMapItem,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isQueryRunnerTwentyORMEnabled =
|
||||||
|
await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsQueryRunnerTwentyORMEnabled,
|
||||||
|
internalContext.authContext.workspace.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isQueryRunnerTwentyORMEnabled) {
|
||||||
|
return await this.graphqlQueryRunnerService.restoreMany(
|
||||||
|
args,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.workspaceQueryRunnerService.restoreMany(
|
||||||
|
args,
|
||||||
|
options,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
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 { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
|
||||||
import {
|
import {
|
||||||
Resolver,
|
Resolver,
|
||||||
@@ -25,13 +26,17 @@ export class SearchResolverFactory
|
|||||||
|
|
||||||
return async (_source, args, _context, info) => {
|
return async (_source, args, _context, info) => {
|
||||||
try {
|
try {
|
||||||
return await this.graphqlQueryRunnerService.search(args, {
|
const options: WorkspaceQueryRunnerOptions = {
|
||||||
authContext: internalContext.authContext,
|
authContext: internalContext.authContext,
|
||||||
objectMetadataItem: internalContext.objectMetadataItem,
|
objectMetadataItem: internalContext.objectMetadataItem,
|
||||||
info,
|
info,
|
||||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||||
objectMetadataCollection: internalContext.objectMetadataCollection,
|
objectMetadataCollection: internalContext.objectMetadataCollection,
|
||||||
});
|
objectMetadataMap: internalContext.objectMetadataMap,
|
||||||
|
objectMetadataMapItem: internalContext.objectMetadataMapItem,
|
||||||
|
};
|
||||||
|
|
||||||
|
return await this.graphqlQueryRunnerService.search(args, options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
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 { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
|
||||||
import {
|
import {
|
||||||
Resolver,
|
Resolver,
|
||||||
@@ -7,8 +8,11 @@ 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 { 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';
|
||||||
import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service';
|
import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service';
|
||||||
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UpdateManyResolverFactory
|
export class UpdateManyResolverFactory
|
||||||
@@ -18,6 +22,8 @@ export class UpdateManyResolverFactory
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService,
|
private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService,
|
||||||
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
|
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
create(
|
create(
|
||||||
@@ -27,13 +33,27 @@ export class UpdateManyResolverFactory
|
|||||||
|
|
||||||
return async (_source, args, context, info) => {
|
return async (_source, args, context, info) => {
|
||||||
try {
|
try {
|
||||||
return await this.workspaceQueryRunnerService.updateMany(args, {
|
const options: WorkspaceQueryRunnerOptions = {
|
||||||
authContext: internalContext.authContext,
|
authContext: internalContext.authContext,
|
||||||
objectMetadataItem: internalContext.objectMetadataItem,
|
objectMetadataItem: internalContext.objectMetadataItem,
|
||||||
info,
|
info,
|
||||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||||
objectMetadataCollection: internalContext.objectMetadataCollection,
|
objectMetadataCollection: internalContext.objectMetadataCollection,
|
||||||
});
|
objectMetadataMap: internalContext.objectMetadataMap,
|
||||||
|
objectMetadataMapItem: internalContext.objectMetadataMapItem,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isQueryRunnerTwentyORMEnabled =
|
||||||
|
await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsQueryRunnerTwentyORMEnabled,
|
||||||
|
internalContext.authContext.workspace.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isQueryRunnerTwentyORMEnabled) {
|
||||||
|
return await this.graphqlQueryRunnerService.updateMany(args, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.workspaceQueryRunnerService.updateMany(args, options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
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 { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
|
||||||
import {
|
import {
|
||||||
Resolver,
|
Resolver,
|
||||||
@@ -7,8 +8,11 @@ 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 { 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';
|
||||||
import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service';
|
import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service';
|
||||||
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UpdateOneResolverFactory
|
export class UpdateOneResolverFactory
|
||||||
@@ -18,6 +22,8 @@ export class UpdateOneResolverFactory
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService,
|
private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService,
|
||||||
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
|
private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
create(
|
create(
|
||||||
@@ -27,13 +33,27 @@ export class UpdateOneResolverFactory
|
|||||||
|
|
||||||
return async (_source, args, context, info) => {
|
return async (_source, args, context, info) => {
|
||||||
try {
|
try {
|
||||||
return await this.workspaceQueryRunnerService.updateOne(args, {
|
const options: WorkspaceQueryRunnerOptions = {
|
||||||
authContext: internalContext.authContext,
|
authContext: internalContext.authContext,
|
||||||
objectMetadataItem: internalContext.objectMetadataItem,
|
objectMetadataItem: internalContext.objectMetadataItem,
|
||||||
info,
|
info,
|
||||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||||
objectMetadataCollection: internalContext.objectMetadataCollection,
|
objectMetadataCollection: internalContext.objectMetadataCollection,
|
||||||
});
|
objectMetadataMap: internalContext.objectMetadataMap,
|
||||||
|
objectMetadataMapItem: internalContext.objectMetadataMapItem,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isQueryRunnerTwentyORMEnabled =
|
||||||
|
await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsQueryRunnerTwentyORMEnabled,
|
||||||
|
internalContext.authContext.workspace.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isQueryRunnerTwentyORMEnabled) {
|
||||||
|
return await this.graphqlQueryRunnerService.updateOne(args, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.workspaceQueryRunnerService.updateOne(args, options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { RestoreManyResolverFactory } from 'src/engine/api/graphql/workspace-res
|
|||||||
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';
|
||||||
|
import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||||
import { getResolverName } from 'src/engine/utils/get-resolver-name.util';
|
import { getResolverName } from 'src/engine/utils/get-resolver-name.util';
|
||||||
|
|
||||||
import { CreateManyResolverFactory } from './factories/create-many-resolver.factory';
|
import { CreateManyResolverFactory } from './factories/create-many-resolver.factory';
|
||||||
@@ -49,6 +50,7 @@ export class WorkspaceResolverFactory {
|
|||||||
async create(
|
async create(
|
||||||
authContext: AuthContext,
|
authContext: AuthContext,
|
||||||
objectMetadataCollection: ObjectMetadataInterface[],
|
objectMetadataCollection: ObjectMetadataInterface[],
|
||||||
|
objectMetadataMap: ObjectMetadataMap,
|
||||||
workspaceResolverBuilderMethods: WorkspaceResolverBuilderMethods,
|
workspaceResolverBuilderMethods: WorkspaceResolverBuilderMethods,
|
||||||
): Promise<IResolvers> {
|
): Promise<IResolvers> {
|
||||||
const factories = new Map<
|
const factories = new Map<
|
||||||
@@ -94,7 +96,9 @@ export class WorkspaceResolverFactory {
|
|||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadata,
|
objectMetadataItem: objectMetadata,
|
||||||
fieldMetadataCollection: objectMetadata.fields,
|
fieldMetadataCollection: objectMetadata.fields,
|
||||||
objectMetadataCollection: objectMetadataCollection,
|
objectMetadataCollection,
|
||||||
|
objectMetadataMap,
|
||||||
|
objectMetadataMapItem: objectMetadataMap[objectMetadata.nameSingular],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +121,9 @@ export class WorkspaceResolverFactory {
|
|||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadata,
|
objectMetadataItem: objectMetadata,
|
||||||
fieldMetadataCollection: objectMetadata.fields,
|
fieldMetadataCollection: objectMetadata.fields,
|
||||||
objectMetadataCollection: objectMetadataCollection,
|
objectMetadataCollection,
|
||||||
|
objectMetadataMap,
|
||||||
|
objectMetadataMapItem: objectMetadataMap[objectMetadata.nameSingular],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,16 @@ import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metada
|
|||||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||||
|
|
||||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import {
|
||||||
|
ObjectMetadataMap,
|
||||||
|
ObjectMetadataMapItem,
|
||||||
|
} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||||
|
|
||||||
export interface WorkspaceSchemaBuilderContext {
|
export interface WorkspaceSchemaBuilderContext {
|
||||||
authContext: AuthContext;
|
authContext: AuthContext;
|
||||||
objectMetadataItem: ObjectMetadataInterface;
|
|
||||||
fieldMetadataCollection: FieldMetadataInterface[];
|
fieldMetadataCollection: FieldMetadataInterface[];
|
||||||
objectMetadataCollection: ObjectMetadataInterface[];
|
objectMetadataCollection: ObjectMetadataInterface[];
|
||||||
|
objectMetadataItem: ObjectMetadataInterface;
|
||||||
|
objectMetadataMap: ObjectMetadataMap;
|
||||||
|
objectMetadataMapItem: ObjectMetadataMapItem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ export class WorkspaceSchemaFactory {
|
|||||||
const autoGeneratedResolvers = await this.workspaceResolverFactory.create(
|
const autoGeneratedResolvers = await this.workspaceResolverFactory.create(
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataCollection,
|
objectMetadataCollection,
|
||||||
|
objectMetadataMap,
|
||||||
workspaceResolverBuilderMethodNames,
|
workspaceResolverBuilderMethodNames,
|
||||||
);
|
);
|
||||||
const scalarsResolvers =
|
const scalarsResolvers =
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
|
||||||
import {
|
import {
|
||||||
Record as IRecord,
|
Record as IRecord,
|
||||||
Record,
|
Record,
|
||||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||||
|
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||||
|
|
||||||
import { settings } from 'src/engine/constants/settings';
|
import { settings } from 'src/engine/constants/settings';
|
||||||
|
import { DUPLICATE_CRITERIA_COLLECTION } from 'src/engine/core-modules/duplicate/constants/duplicate-criteria.constants';
|
||||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||||
import { DUPLICATE_CRITERIA_COLLECTION } from 'src/engine/core-modules/duplicate/constants/duplicate-criteria.constants';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DuplicateService {
|
export class DuplicateService {
|
||||||
@@ -94,80 +94,4 @@ export class DuplicateService {
|
|||||||
duplicateCriteria.objectName === objectMetadataItem.nameSingular,
|
duplicateCriteria.objectName === objectMetadataItem.nameSingular,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: Remove this code by September 1st, 2024 if it isn't used
|
|
||||||
* It was build to be used by the upsertMany function, but it was not used.
|
|
||||||
* It's a re-implementation of the methods to findDuplicates, but done
|
|
||||||
* at the SQL layer instead of doing it at the GraphQL layer
|
|
||||||
*
|
|
||||||
async findDuplicate(
|
|
||||||
data: Partial<Record>,
|
|
||||||
objectMetadata: ObjectMetadataInterface,
|
|
||||||
workspaceId: string,
|
|
||||||
) {
|
|
||||||
const dataSourceSchema =
|
|
||||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
|
||||||
|
|
||||||
const { duplicateWhereClause, duplicateWhereParameters } =
|
|
||||||
this.buildDuplicateConditionForUpsert(objectMetadata, data);
|
|
||||||
|
|
||||||
const results = await this.workspaceDataSourceService.executeRawQuery(
|
|
||||||
`
|
|
||||||
SELECT
|
|
||||||
*
|
|
||||||
FROM
|
|
||||||
${dataSourceSchema}."${computeObjectTargetTable(
|
|
||||||
objectMetadata,
|
|
||||||
)}" p
|
|
||||||
WHERE
|
|
||||||
${duplicateWhereClause}
|
|
||||||
`,
|
|
||||||
duplicateWhereParameters,
|
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
|
|
||||||
return results.length > 0 ? results[0] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildDuplicateConditionForUpsert(
|
|
||||||
objectMetadata: ObjectMetadataInterface,
|
|
||||||
data: Partial<Record>,
|
|
||||||
) {
|
|
||||||
const criteriaCollection = this.getApplicableDuplicateCriteriaCollection(
|
|
||||||
objectMetadata,
|
|
||||||
).filter(
|
|
||||||
(duplicateCriteria) => duplicateCriteria.useAsUniqueKeyForUpsert === true,
|
|
||||||
);
|
|
||||||
|
|
||||||
const whereClauses: string[] = [];
|
|
||||||
const whereParameters: any[] = [];
|
|
||||||
let parameterIndex = 1;
|
|
||||||
|
|
||||||
criteriaCollection.forEach((c) => {
|
|
||||||
const clauseParts: string[] = [];
|
|
||||||
|
|
||||||
c.columnNames.forEach((column) => {
|
|
||||||
const dataKey = Object.keys(data).find(
|
|
||||||
(key) => key.toLowerCase() === column.toLowerCase(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (dataKey) {
|
|
||||||
clauseParts.push(`p."${column}" = $${parameterIndex}`);
|
|
||||||
whereParameters.push(data[dataKey]);
|
|
||||||
parameterIndex++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (clauseParts.length > 0) {
|
|
||||||
whereClauses.push(`(${clauseParts.join(' AND ')})`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const duplicateWhereClause = whereClauses.join(' OR ');
|
|
||||||
const duplicateWhereParameters = whereParameters;
|
|
||||||
|
|
||||||
return { duplicateWhereClause, duplicateWhereParameters };
|
|
||||||
}
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ export class IndexMetadataEntity {
|
|||||||
@Column({
|
@Column({
|
||||||
type: 'enum',
|
type: 'enum',
|
||||||
enum: IndexType,
|
enum: IndexType,
|
||||||
nullable: true,
|
|
||||||
default: IndexType.BTREE,
|
default: IndexType.BTREE,
|
||||||
|
nullable: false,
|
||||||
})
|
})
|
||||||
indexType?: IndexType;
|
indexType?: IndexType;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { isPlainObject } from '@nestjs/common/utils/shared.utils';
|
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||||
|
|
||||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||||
import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||||
import { getCompositeFieldMetadataCollection } from 'src/engine/twenty-orm/utils/get-composite-field-metadata-collection';
|
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||||
|
import { capitalize } from 'src/utils/capitalize';
|
||||||
|
|
||||||
export function formatData<T>(
|
export function formatData<T>(
|
||||||
data: T,
|
data: T,
|
||||||
@@ -17,49 +19,70 @@ export function formatData<T>(
|
|||||||
return data.map((item) => formatData(item, objectMetadata)) as T;
|
return data.map((item) => formatData(item, objectMetadata)) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
const compositeFieldMetadataCollection =
|
const newData: Record<string, any> = {};
|
||||||
getCompositeFieldMetadataCollection(objectMetadata);
|
|
||||||
|
|
||||||
const compositeFieldMetadataMap = new Map(
|
|
||||||
compositeFieldMetadataCollection.map((fieldMetadata) => [
|
|
||||||
fieldMetadata.name,
|
|
||||||
fieldMetadata,
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
const newData: object = {};
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(data)) {
|
for (const [key, value] of Object.entries(data)) {
|
||||||
const fieldMetadata = compositeFieldMetadataMap.get(key);
|
const fieldMetadata = objectMetadata.fields[key];
|
||||||
|
|
||||||
if (!fieldMetadata) {
|
if (!fieldMetadata) {
|
||||||
if (isPlainObject(value)) {
|
throw new Error(
|
||||||
newData[key] = formatData(value, objectMetadata);
|
`Field metadata for field "${key}" is missing in object metadata`,
|
||||||
} else {
|
|
||||||
newData[key] = value;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const compositeType = compositeTypeDefinitions.get(fieldMetadata.type);
|
|
||||||
|
|
||||||
if (!compositeType) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const compositeProperty of compositeType.properties) {
|
|
||||||
const compositeKey = computeCompositeColumnName(
|
|
||||||
fieldMetadata.name,
|
|
||||||
compositeProperty,
|
|
||||||
);
|
);
|
||||||
const value = data?.[key]?.[compositeProperty.name];
|
}
|
||||||
|
|
||||||
if (value === undefined || value === null) {
|
if (isCompositeFieldMetadataType(fieldMetadata.type)) {
|
||||||
continue;
|
const formattedCompositeField = formatCompositeField(
|
||||||
}
|
value,
|
||||||
|
fieldMetadata,
|
||||||
|
);
|
||||||
|
|
||||||
newData[compositeKey] = data[key][compositeProperty.name];
|
Object.assign(newData, formattedCompositeField);
|
||||||
|
} else {
|
||||||
|
newData[key] = formatFieldMetadataValue(value, fieldMetadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newData as T;
|
return newData as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatCompositeField(
|
||||||
|
value: any,
|
||||||
|
fieldMetadata: FieldMetadataInterface,
|
||||||
|
): Record<string, any> {
|
||||||
|
const compositeType = compositeTypeDefinitions.get(
|
||||||
|
fieldMetadata.type as CompositeFieldMetadataType,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!compositeType) {
|
||||||
|
throw new Error(
|
||||||
|
`Composite type definition not found for type: ${fieldMetadata.type}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedCompositeField: Record<string, any> = {};
|
||||||
|
|
||||||
|
for (const property of compositeType.properties) {
|
||||||
|
const subFieldKey = property.name;
|
||||||
|
const fullFieldName = `${fieldMetadata.name}${capitalize(subFieldKey)}`;
|
||||||
|
|
||||||
|
if (value && value[subFieldKey] !== undefined) {
|
||||||
|
formattedCompositeField[fullFieldName] = formatFieldMetadataValue(
|
||||||
|
value[subFieldKey],
|
||||||
|
property as unknown as FieldMetadataInterface,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedCompositeField;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatFieldMetadataValue(
|
||||||
|
value: any,
|
||||||
|
fieldMetadata: FieldMetadataInterface,
|
||||||
|
) {
|
||||||
|
if (fieldMetadata.type === FieldMetadataType.RAW_JSON) {
|
||||||
|
return JSON.parse(value as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { isPlainObject } from '@nestjs/common/utils/shared.utils';
|
import { isPlainObject } from '@nestjs/common/utils/shared.utils';
|
||||||
|
|
||||||
|
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||||
|
|
||||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||||
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||||
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||||
import {
|
import {
|
||||||
@@ -81,9 +84,15 @@ export function formatResult<T>(
|
|||||||
if (!compositePropertyArgs && !relationMetadata) {
|
if (!compositePropertyArgs && !relationMetadata) {
|
||||||
if (isPlainObject(value)) {
|
if (isPlainObject(value)) {
|
||||||
newData[key] = formatResult(value, objectMetadata, objectMetadataMap);
|
newData[key] = formatResult(value, objectMetadata, objectMetadataMap);
|
||||||
|
} else if (objectMetadata.fields[key]) {
|
||||||
|
newData[key] = formatFieldMetadataValue(
|
||||||
|
value,
|
||||||
|
objectMetadata.fields[key],
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
newData[key] = value;
|
newData[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,3 +138,18 @@ export function formatResult<T>(
|
|||||||
|
|
||||||
return newData as T;
|
return newData as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatFieldMetadataValue(
|
||||||
|
value: any,
|
||||||
|
fieldMetadata: FieldMetadataInterface,
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
typeof value === 'string' &&
|
||||||
|
(fieldMetadata.type === FieldMetadataType.MULTI_SELECT ||
|
||||||
|
fieldMetadata.type === FieldMetadataType.ARRAY)
|
||||||
|
) {
|
||||||
|
return value.replace(/{|}/g, '').split(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user