Common - Remove feature flag (#15371)

To be done in an other PR, move graphql-query-runner handlers in
common-api-query-runner folder (+ renaming + typing improvments)


Fixes : 
- totalCount on findMany (Rest)
- getAllSelectedFields did not handle composite field (Rest)
- issue with endCursor (Rest)
- args processing for updateOne and createOne (Rest + Gql)
- fix findDuplicates (Gql)
This commit is contained in:
Etienne
2025-10-29 11:01:52 +01:00
committed by GitHub
parent 5ae7674b9d
commit 0849483d2e
97 changed files with 655 additions and 6683 deletions
@@ -4,20 +4,6 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper';
import { ProcessNestedRelationsV2Helper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper';
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service';
import { GraphqlQueryCreateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service';
import { GraphqlQueryDeleteManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service';
import { GraphqlQueryDeleteOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service';
import { GraphqlQueryDestroyManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service';
import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service';
import { GraphqlQueryFindDuplicatesResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service';
import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service';
import { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service';
import { GraphqlQueryMergeManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-merge-many-resolver.service';
import { GraphqlQueryRestoreManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service';
import { GraphqlQueryRestoreOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service';
import { 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 { 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 { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
@@ -28,23 +14,6 @@ import { ViewFilterGroupModule } from 'src/engine/metadata-modules/view-filter-g
import { ViewFilterModule } from 'src/engine/metadata-modules/view-filter/view-filter.module';
import { ViewModule } from 'src/engine/metadata-modules/view/view.module';
const graphqlQueryResolvers = [
GraphqlQueryCreateManyResolverService,
GraphqlQueryCreateOneResolverService,
GraphqlQueryDeleteManyResolverService,
GraphqlQueryDeleteOneResolverService,
GraphqlQueryDestroyManyResolverService,
GraphqlQueryDestroyOneResolverService,
GraphqlQueryFindDuplicatesResolverService,
GraphqlQueryFindManyResolverService,
GraphqlQueryFindOneResolverService,
GraphqlQueryMergeManyResolverService,
GraphqlQueryRestoreManyResolverService,
GraphqlQueryRestoreOneResolverService,
GraphqlQueryUpdateManyResolverService,
GraphqlQueryUpdateOneResolverService,
];
@Module({
imports: [
WorkspaceQueryHookModule,
@@ -61,8 +30,6 @@ const graphqlQueryResolvers = [
ProcessNestedRelationsHelper,
ProcessNestedRelationsV2Helper,
ProcessAggregateHelper,
...graphqlQueryResolvers,
],
exports: [...graphqlQueryResolvers],
})
export class GraphqlQueryRunnerModule {}
@@ -1,248 +0,0 @@
import { Inject, Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { type ObjectRecord } from 'twenty-shared/types';
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
import { type ObjectLiteral } from 'typeorm';
import { type IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
import { type IEdge } from 'src/engine/api/graphql/workspace-query-runner/interfaces/edge.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import {
type ResolverArgs,
type WorkspaceResolverBuilderMethodNames,
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { OBJECTS_WITH_SETTINGS_PERMISSIONS_REQUIREMENTS } from 'src/engine/api/graphql/graphql-query-runner/constants/objects-with-settings-permissions-requirements';
import { type GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory';
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service';
import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/api-key-role.service';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
import { type PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
import {
PermissionsException,
PermissionsExceptionCode,
PermissionsExceptionMessage,
} from 'src/engine/metadata-modules/permissions/permissions.exception';
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
import { type WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
export type GraphqlQueryResolverExecutionArgs<Input extends ResolverArgs> = {
args: Input;
options: WorkspaceQueryRunnerOptions;
workspaceDataSource: WorkspaceDataSource;
repository: WorkspaceRepository<ObjectLiteral>;
graphqlQueryParser: GraphqlQueryParser;
graphqlQuerySelectedFieldsResult: GraphqlQuerySelectedFieldsResult;
isExecutedByApiKey: boolean;
rolePermissionConfig?: RolePermissionConfig;
};
@Injectable()
export abstract class GraphqlQueryBaseResolverService<
Input extends ResolverArgs,
Response extends
| ObjectRecord
| ObjectRecord[]
| IConnection<ObjectRecord, IEdge<ObjectRecord>>
| IConnection<ObjectRecord, IEdge<ObjectRecord>>[],
> {
@Inject()
protected readonly workspaceQueryHookService: WorkspaceQueryHookService;
@Inject()
protected readonly queryRunnerArgsFactory: QueryRunnerArgsFactory;
@Inject()
protected readonly queryResultGettersFactory: QueryResultGettersFactory;
@Inject()
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager;
@Inject()
protected readonly processNestedRelationsHelper: ProcessNestedRelationsHelper;
@Inject()
protected readonly permissionsService: PermissionsService;
@Inject()
protected readonly userRoleService: UserRoleService;
@Inject()
protected readonly apiKeyRoleService: ApiKeyRoleService;
public async execute(
args: Input,
options: WorkspaceQueryRunnerOptions,
operationName: WorkspaceResolverBuilderMethodNames,
): Promise<Response | undefined> {
try {
const { authContext, objectMetadataItemWithFieldMaps } = options;
const workspace = authContext.workspace;
assertIsDefinedOrThrow(workspace);
await this.validate(args, options);
const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId: workspace.id,
});
const featureFlagsMap = workspaceDataSource.featureFlagMap;
if (objectMetadataItemWithFieldMaps.isSystem === true) {
await this.validateSettingsPermissionsOnObjectOrThrow(options);
}
const hookedArgs =
await this.workspaceQueryHookService.executePreQueryHooks(
authContext,
objectMetadataItemWithFieldMaps.nameSingular,
operationName,
args,
);
const computedArgs = (await this.queryRunnerArgsFactory.create(
hookedArgs,
options,
operationName,
)) as Input;
let roleId: string | undefined;
if (isDefined(authContext.apiKey)) {
roleId = await this.apiKeyRoleService.getRoleIdForApiKey(
authContext.apiKey.id,
workspace.id,
);
}
if (isDefined(authContext.userWorkspaceId)) {
roleId = await this.userRoleService.getRoleIdForUserWorkspace({
userWorkspaceId: authContext.userWorkspaceId,
workspaceId: workspace.id,
});
if (!roleId) {
throw new PermissionsException(
PermissionsExceptionMessage.NO_ROLE_FOUND_FOR_USER_WORKSPACE,
PermissionsExceptionCode.NO_ROLE_FOUND_FOR_USER_WORKSPACE,
);
}
}
if (
!isDefined(authContext.apiKey) &&
!isDefined(authContext.userWorkspaceId)
) {
throw new PermissionsException(
PermissionsExceptionMessage.NO_AUTHENTICATION_CONTEXT,
PermissionsExceptionCode.NO_AUTHENTICATION_CONTEXT,
);
}
const rolePermissionConfig: RolePermissionConfig | undefined = roleId
? { unionOf: [roleId] }
: undefined;
const repository = workspaceDataSource.getRepository(
objectMetadataItemWithFieldMaps.nameSingular,
rolePermissionConfig,
authContext,
);
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataItemWithFieldMaps,
options.objectMetadataMaps,
);
const selectedFields = graphqlFields(options.info);
const graphqlQuerySelectedFieldsResult =
graphqlQueryParser.parseSelectedFields(selectedFields);
const graphqlQueryResolverExecutionArgs = {
args: computedArgs,
options,
workspaceDataSource,
repository,
graphqlQueryParser,
graphqlQuerySelectedFieldsResult,
isExecutedByApiKey: isDefined(authContext.apiKey),
rolePermissionConfig,
};
const results = await this.resolve(
graphqlQueryResolverExecutionArgs,
featureFlagsMap,
);
const resultWithGetters = await this.queryResultGettersFactory.create(
results,
objectMetadataItemWithFieldMaps,
workspace.id,
options.objectMetadataMaps,
);
await this.workspaceQueryHookService.executePostQueryHooks(
authContext,
objectMetadataItemWithFieldMaps.nameSingular,
operationName,
resultWithGetters,
);
return resultWithGetters;
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
}
private async validateSettingsPermissionsOnObjectOrThrow(
options: WorkspaceQueryRunnerOptions,
) {
const { authContext, objectMetadataItemWithFieldMaps } = options;
const workspace = authContext.workspace;
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
if (
Object.keys(OBJECTS_WITH_SETTINGS_PERMISSIONS_REQUIREMENTS).includes(
objectMetadataItemWithFieldMaps.nameSingular,
)
) {
const permissionRequired: PermissionFlagType =
// @ts-expect-error legacy noImplicitAny
OBJECTS_WITH_SETTINGS_PERMISSIONS_REQUIREMENTS[
objectMetadataItemWithFieldMaps.nameSingular
];
const userHasPermission =
await this.permissionsService.userHasWorkspaceSettingPermission({
userWorkspaceId: authContext.userWorkspaceId,
setting: permissionRequired,
workspaceId: workspace.id,
apiKeyId: authContext.apiKey?.id,
});
if (!userHasPermission) {
throw new PermissionsException(
PermissionsExceptionMessage.PERMISSION_DENIED,
PermissionsExceptionCode.PERMISSION_DENIED,
);
}
}
}
protected abstract resolve(
executionArgs: GraphqlQueryResolverExecutionArgs<Input>,
featureFlagsMap: Record<FeatureFlagKey, boolean>,
): Promise<Response>;
protected abstract validate(
args: Input,
options: WorkspaceQueryRunnerOptions,
): Promise<void>;
}
@@ -1,542 +0,0 @@
import { Injectable } from '@nestjs/common';
import { msg } from '@lingui/core/macro';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import { capitalize, isDefined } from 'twenty-shared/utils';
import {
In,
type FindOperator,
type InsertResult,
type ObjectLiteral,
} from 'typeorm';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type CreateManyResolverArgs } 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 { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
import { getAllSelectableColumnNames } from 'src/engine/api/utils/get-all-selectable-column-names.utils';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
type PartialObjectRecordWithId = Partial<ObjectRecord> & { id: string };
@Injectable()
export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResolverService<
CreateManyResolverArgs,
ObjectRecord[]
> {
async resolve(
executionArgs: GraphqlQueryResolverExecutionArgs<CreateManyResolverArgs>,
): Promise<ObjectRecord[]> {
if (executionArgs.args.data.length > QUERY_MAX_RECORDS) {
throw new GraphqlQueryRunnerException(
`Maximum number of records to upsert is ${QUERY_MAX_RECORDS}.`,
GraphqlQueryRunnerExceptionCode.UPSERT_MAX_RECORDS_EXCEEDED,
{
userFriendlyMessage: msg`Maximum number of records to upsert is ${QUERY_MAX_RECORDS}.`,
},
);
}
const { objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
const objectRecords = await this.insertOrUpsertRecords(executionArgs);
const upsertedRecords = await this.fetchUpsertedRecords(
executionArgs,
objectRecords,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);
await this.processNestedRelationsIfNeeded({
executionArgs,
records: upsertedRecords,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
return this.formatRecordsForResponse(
upsertedRecords,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);
}
private async insertOrUpsertRecords(
executionArgs: GraphqlQueryResolverExecutionArgs<CreateManyResolverArgs>,
): Promise<InsertResult> {
if (!executionArgs.args.upsert) {
const { objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
const selectedColumns = buildColumnsToReturn({
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
return await executionArgs.repository.insert(
executionArgs.args.data,
undefined,
selectedColumns,
);
}
return this.performUpsertOperation(executionArgs);
}
private async performUpsertOperation(
executionArgs: GraphqlQueryResolverExecutionArgs<CreateManyResolverArgs>,
): Promise<InsertResult> {
const { objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
const conflictingFields = this.getConflictingFields(
objectMetadataItemWithFieldMaps,
);
const existingRecords = await this.findExistingRecords(
executionArgs,
conflictingFields,
);
const { recordsToUpdate, recordsToInsert } = this.categorizeRecords(
executionArgs.args.data,
conflictingFields,
existingRecords,
);
const result: InsertResult = {
identifiers: [],
generatedMaps: [],
raw: [],
};
const columnsToReturn = buildColumnsToReturn({
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
if (recordsToUpdate.length > 0) {
await this.processRecordsToUpdate({
partialRecordsToUpdate: recordsToUpdate,
repository: executionArgs.repository,
objectMetadataItemWithFieldMaps,
result,
columnsToReturn,
});
}
await this.processRecordsToInsert({
recordsToInsert,
repository: executionArgs.repository,
result,
columnsToReturn,
});
return result;
}
//TODO : Improve conflicting fields logic - unicity can be define on combination of fields - should be based on unique index not on field metadata
//TODO : https://github.com/twentyhq/core-team-issues/issues/1115
private getConflictingFields(
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
): {
baseField: string;
fullPath: string;
column: string;
}[] {
return Object.values(objectMetadataItemWithFieldMaps.fieldsById)
.filter((field) => field.isUnique || field.name === 'id')
.flatMap((field) => {
const compositeType = compositeTypeDefinitions.get(field.type);
if (!compositeType) {
return [
{
baseField: field.name,
fullPath: field.name,
column: field.name,
},
];
}
const property = compositeType.properties.find(
(prop) => prop.isIncludedInUniqueConstraint,
);
return property
? [
{
baseField: field.name,
fullPath: `${field.name}.${property.name}`,
column: `${field.name}${capitalize(property.name)}`,
},
]
: [];
});
}
private async findExistingRecords(
executionArgs: GraphqlQueryResolverExecutionArgs<CreateManyResolverArgs>,
conflictingFields: {
baseField: string;
fullPath: string;
column: string;
}[],
): Promise<PartialObjectRecordWithId[]> {
const { objectMetadataItemWithFieldMaps } = executionArgs.options;
const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
const whereConditions = this.buildWhereConditions(
executionArgs.args.data,
conflictingFields,
);
whereConditions.forEach((condition) => {
queryBuilder.orWhere(condition);
});
const restrictedFields =
executionArgs.repository.objectRecordsPermissions?.[
objectMetadataItemWithFieldMaps.id
]?.restrictedFields;
const selectOptions = getAllSelectableColumnNames({
restrictedFields: restrictedFields ?? {},
objectMetadata: {
objectMetadataMapItem: objectMetadataItemWithFieldMaps,
},
});
return (await queryBuilder
.withDeleted()
.setFindOptions({
select: selectOptions,
})
.getMany()) as PartialObjectRecordWithId[];
}
private getValueFromPath(
record: Partial<ObjectRecord>,
path: string,
): unknown {
const pathParts = path.split('.');
if (pathParts.length === 1) {
return record[path];
}
const [parentField, childField] = pathParts;
return record[parentField]?.[childField];
}
private buildWhereConditions(
records: Partial<ObjectRecord>[],
conflictingFields: {
baseField: string;
fullPath: string;
column: string;
}[],
): Record<string, FindOperator<string>>[] {
const whereConditions = [];
for (const field of conflictingFields) {
const fieldValues = records
.map((record) => this.getValueFromPath(record, field.fullPath))
.filter(Boolean);
//TODO : Adapt to composite constraint - https://github.com/twentyhq/core-team-issues/issues/1115
if (fieldValues.length > 0) {
whereConditions.push({ [field.column]: In(fieldValues) });
}
}
return whereConditions;
}
private categorizeRecords(
records: Partial<ObjectRecord>[],
conflictingFields: {
baseField: string;
fullPath: string;
column: string;
}[],
existingRecords: PartialObjectRecordWithId[],
): {
recordsToUpdate: PartialObjectRecordWithId[];
recordsToInsert: Partial<ObjectRecord>[];
} {
const recordsToUpdate: PartialObjectRecordWithId[] = [];
const recordsToInsert: Partial<ObjectRecord>[] = [];
for (const record of records) {
const matchingRecordId = this.getMatchingRecordId(
record,
conflictingFields,
existingRecords,
);
if (isDefined(matchingRecordId)) {
recordsToUpdate.push({ ...record, id: matchingRecordId });
} else {
recordsToInsert.push(record);
}
}
return { recordsToUpdate, recordsToInsert };
}
private getMatchingRecordId(
record: Partial<ObjectRecord>,
conflictingFields: {
baseField: string;
fullPath: string;
column: string;
}[],
existingRecords: PartialObjectRecordWithId[],
): string | undefined {
const matchingRecordIds = conflictingFields.reduce<string[]>(
(acc, field) => {
const requestFieldValue = this.getValueFromPath(record, field.fullPath);
const matchingRecord = existingRecords.find((existingRecord) => {
const existingFieldValue = this.getValueFromPath(
existingRecord,
field.fullPath,
);
return (
isDefined(existingFieldValue) &&
existingFieldValue === requestFieldValue
);
});
if (isDefined(matchingRecord)) {
acc.push(matchingRecord.id);
}
return acc;
},
[],
);
if ([...new Set(matchingRecordIds)].length > 1) {
const conflictingFieldsValues = conflictingFields
.map((field) => {
const value = this.getValueFromPath(record, field.fullPath);
return isDefined(value) ? `${field.fullPath}: ${value}` : undefined;
})
.filter(isDefined)
.join(', ');
throw new GraphqlQueryRunnerException(
`Multiple records found with the same unique field values for ${conflictingFieldsValues}. Cannot determine which record to update.`,
GraphqlQueryRunnerExceptionCode.UPSERT_MULTIPLE_MATCHING_RECORDS_CONFLICT,
{
userFriendlyMessage: msg`Multiple records found with the same unique field values for ${conflictingFieldsValues}. Cannot determine which record to update.`,
},
);
}
return matchingRecordIds[0];
}
private async processRecordsToUpdate({
partialRecordsToUpdate,
repository,
objectMetadataItemWithFieldMaps,
result,
columnsToReturn,
}: {
partialRecordsToUpdate: PartialObjectRecordWithId[];
repository: WorkspaceRepository<ObjectLiteral>;
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
result: InsertResult;
columnsToReturn: string[];
}): Promise<void> {
const partialRecordsToUpdateWithoutCreatedByUpdate =
partialRecordsToUpdate.map((record) =>
this.getRecordWithoutCreatedBy(record, objectMetadataItemWithFieldMaps),
);
const savedRecords = await repository.updateMany(
partialRecordsToUpdateWithoutCreatedByUpdate.map((record) => ({
criteria: record.id,
partialEntity: { ...record, deletedAt: null },
})),
undefined,
columnsToReturn,
);
result.identifiers.push(
...savedRecords.generatedMaps.map((record) => ({ id: record.id })),
);
result.generatedMaps.push(
...savedRecords.generatedMaps.map((record) => ({ id: record.id })),
);
}
private async processRecordsToInsert({
recordsToInsert,
repository,
result,
columnsToReturn,
}: {
recordsToInsert: Partial<ObjectRecord>[];
repository: WorkspaceRepository<ObjectLiteral>;
result: InsertResult;
columnsToReturn: string[];
}): Promise<void> {
if (recordsToInsert.length > 0) {
const insertResult = await repository.insert(
recordsToInsert,
undefined,
columnsToReturn,
);
result.identifiers.push(...insertResult.identifiers);
result.generatedMaps.push(...insertResult.generatedMaps);
result.raw.push(...insertResult.raw);
}
}
private async fetchUpsertedRecords(
executionArgs: GraphqlQueryResolverExecutionArgs<CreateManyResolverArgs>,
objectRecords: InsertResult,
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
objectMetadataMaps: ObjectMetadataMaps,
): Promise<ObjectRecord[]> {
const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
const columnsToSelect = buildColumnsToSelect({
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
const upsertedRecords = await queryBuilder
.setFindOptions({
select: columnsToSelect,
})
.where({
id: In(objectRecords.generatedMaps.map((record) => record.id)),
})
.withDeleted()
.take(QUERY_MAX_RECORDS)
.getMany();
return upsertedRecords as ObjectRecord[];
}
private async processNestedRelationsIfNeeded({
executionArgs,
records,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
}: {
executionArgs: GraphqlQueryResolverExecutionArgs<CreateManyResolverArgs>;
records: ObjectRecord[];
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
objectMetadataMaps: ObjectMetadataMaps;
}): Promise<void> {
if (!executionArgs.graphqlQuerySelectedFieldsResult.relations) {
return;
}
await this.processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: records,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS,
authContext: executionArgs.options.authContext,
workspaceDataSource: executionArgs.workspaceDataSource,
rolePermissionConfig: executionArgs.rolePermissionConfig,
selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select,
});
}
private formatRecordsForResponse(
upsertedRecords: ObjectRecord[],
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
objectMetadataMaps: ObjectMetadataMaps,
): ObjectRecord[] {
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return upsertedRecords.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),
);
}
private getRecordWithoutCreatedBy(
record: PartialObjectRecordWithId,
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
): Omit<PartialObjectRecordWithId, 'createdBy'> {
let recordWithoutCreatedByUpdate = record;
const createdByFieldMetadataId =
objectMetadataItemWithFieldMaps.fieldIdByName['createdBy'];
const createdByFieldMetadata =
objectMetadataItemWithFieldMaps.fieldsById[createdByFieldMetadataId];
if (!isDefined(createdByFieldMetadata)) {
throw new GraphqlQueryRunnerException(
`Missing createdBy field metadata for object ${objectMetadataItemWithFieldMaps.nameSingular}`,
GraphqlQueryRunnerExceptionCode.MISSING_SYSTEM_FIELD,
);
}
if ('createdBy' in record && createdByFieldMetadata.isCustom === false) {
const { createdBy: _createdBy, ...recordWithoutCreatedBy } = record;
recordWithoutCreatedByUpdate = recordWithoutCreatedBy;
}
return recordWithoutCreatedByUpdate;
}
async validate<T extends ObjectRecord>(
args: CreateManyResolverArgs<Partial<T>>,
options: WorkspaceQueryRunnerOptions,
): Promise<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
args.data.forEach((record) => {
if (record?.id) {
assertIsValidUuid(record.id);
}
});
}
}
@@ -1,51 +0,0 @@
import { Injectable } from '@nestjs/common';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type CreateOneResolverArgs } 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 { 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';
@Injectable()
export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolverService<
CreateOneResolverArgs,
ObjectRecord
> {
constructor(
private readonly createManyResolverService: GraphqlQueryCreateManyResolverService,
) {
super();
}
async resolve(
executionArgs: GraphqlQueryResolverExecutionArgs<CreateOneResolverArgs>,
): Promise<ObjectRecord> {
const result = await this.createManyResolverService.resolve({
...executionArgs,
args: {
...executionArgs.args,
data: [executionArgs.args.data],
},
});
return result[0];
}
async validate(
args: CreateOneResolverArgs<Partial<ObjectRecord>>,
options: WorkspaceQueryRunnerOptions,
): Promise<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
if (args.data?.id) {
assertIsValidUuid(args.data.id);
}
}
}
@@ -1,90 +0,0 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type DeleteManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
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';
@Injectable()
export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResolverService<
DeleteManyResolverArgs,
ObjectRecord[]
> {
async resolve(
executionArgs: GraphqlQueryResolverExecutionArgs<DeleteManyResolverArgs>,
): Promise<ObjectRecord[]> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
executionArgs.graphqlQueryParser.applyFilterToBuilder(
queryBuilder,
objectMetadataItemWithFieldMaps.nameSingular,
executionArgs.args.filter,
);
const columnsToReturn = buildColumnsToReturn({
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
const deletedObjectRecords = await queryBuilder
.softDelete()
.returning(columnsToReturn)
.execute();
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await this.processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords:
deletedObjectRecords.generatedMaps as ObjectRecord[],
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS,
authContext,
workspaceDataSource: executionArgs.workspaceDataSource,
rolePermissionConfig: executionArgs.rolePermissionConfig,
selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select,
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return deletedObjectRecords.generatedMaps.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),
);
}
async validate(
args: DeleteManyResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
if (!args.filter) {
throw new Error('Filter is required');
}
args.filter.id?.in?.forEach((id: string) => assertIsValidUuid(id));
}
}
@@ -1,93 +0,0 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type DeleteOneResolverArgs } 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 { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
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';
@Injectable()
export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolverService<
DeleteOneResolverArgs,
ObjectRecord
> {
async resolve(
executionArgs: GraphqlQueryResolverExecutionArgs<DeleteOneResolverArgs>,
): Promise<ObjectRecord> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
const columnsToReturn = buildColumnsToReturn({
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
const deletedObjectRecords = await queryBuilder
.softDelete()
.where({ id: executionArgs.args.id })
.returning(columnsToReturn)
.execute();
if (deletedObjectRecords.generatedMaps.length === 0) {
throw new GraphqlQueryRunnerException(
'Record not found',
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
);
}
const deletedRecord = deletedObjectRecords.generatedMaps[0] as ObjectRecord;
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await this.processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: [deletedRecord],
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS,
authContext,
workspaceDataSource: executionArgs.workspaceDataSource,
rolePermissionConfig: executionArgs.rolePermissionConfig,
selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select,
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
const result = typeORMObjectRecordsParser.processRecord({
objectRecord: deletedRecord,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
return result;
}
async validate(
args: DeleteOneResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
assertIsValidUuid(args.id);
}
}
@@ -1,85 +0,0 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type DestroyManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
@Injectable()
export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseResolverService<
DestroyManyResolverArgs,
ObjectRecord[]
> {
async resolve(
executionArgs: GraphqlQueryResolverExecutionArgs<DestroyManyResolverArgs>,
): Promise<ObjectRecord[]> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
executionArgs.graphqlQueryParser.applyFilterToBuilder(
queryBuilder,
objectMetadataItemWithFieldMaps.nameSingular,
executionArgs.args.filter,
);
const columnsToReturn = buildColumnsToReturn({
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
const deletedObjectRecords = await queryBuilder
.delete()
.returning(columnsToReturn)
.execute();
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await this.processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords:
deletedObjectRecords.generatedMaps as ObjectRecord[],
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS,
authContext,
workspaceDataSource: executionArgs.workspaceDataSource,
rolePermissionConfig: executionArgs.rolePermissionConfig,
selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select,
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return deletedObjectRecords.generatedMaps.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),
);
}
async validate(
args: DestroyManyResolverArgs,
_options: WorkspaceQueryRunnerOptions,
): Promise<void> {
if (!args.filter) {
throw new Error('Filter is required');
}
}
}
@@ -1,92 +0,0 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type 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 { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
@Injectable()
export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResolverService<
DestroyOneResolverArgs,
ObjectRecord
> {
async resolve(
executionArgs: GraphqlQueryResolverExecutionArgs<DestroyOneResolverArgs>,
): Promise<ObjectRecord> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
const columnsToReturn = buildColumnsToReturn({
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
const deletedObjectRecords = await queryBuilder
.delete()
.where({ id: executionArgs.args.id })
.returning(columnsToReturn)
.execute();
if (!deletedObjectRecords.affected) {
throw new GraphqlQueryRunnerException(
'Record not found',
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
);
}
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await this.processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords:
deletedObjectRecords.generatedMaps as ObjectRecord[],
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS,
authContext,
workspaceDataSource: executionArgs.workspaceDataSource,
rolePermissionConfig: executionArgs.rolePermissionConfig,
selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select,
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return typeORMObjectRecordsParser.processRecord({
objectRecord: deletedObjectRecords.generatedMaps[0] as ObjectRecord,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
}
async validate(
args: DestroyOneResolverArgs,
_options: WorkspaceQueryRunnerOptions,
): Promise<void> {
if (!args.id) {
throw new GraphqlQueryRunnerException(
'Missing id',
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
);
}
}
}
@@ -1,161 +0,0 @@
import { Injectable } from '@nestjs/common';
import isEmpty from 'lodash.isempty';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { OrderByDirection, type ObjectRecord } from 'twenty-shared/types';
import { In } from 'typeorm';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type 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 { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
import { buildDuplicateConditions } from 'src/engine/api/utils/build-duplicate-conditions.utils';
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
@Injectable()
export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseResolverService<
FindDuplicatesResolverArgs,
IConnection<ObjectRecord>[]
> {
async resolve(
executionArgs: GraphqlQueryResolverExecutionArgs<FindDuplicatesResolverArgs>,
): Promise<IConnection<ObjectRecord>[]> {
const { objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
const existingRecordsQueryBuilder =
executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
const objectMetadataItemWithFieldsMaps =
getObjectMetadataMapItemByNameSingular(
objectMetadataMaps,
objectMetadataItemWithFieldMaps.nameSingular,
);
if (!objectMetadataItemWithFieldsMaps) {
throw new GraphqlQueryRunnerException(
`Object ${objectMetadataItemWithFieldMaps.nameSingular} not found`,
GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND,
);
}
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
let objectRecords: Partial<ObjectRecord>[] = [];
const columnsToSelect = buildColumnsToSelect({
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
if (executionArgs.args.ids) {
objectRecords = (await existingRecordsQueryBuilder
.where({ id: In(executionArgs.args.ids) })
.setFindOptions({
select: columnsToSelect,
})
.getMany()) as ObjectRecord[];
} else if (executionArgs.args.data && !isEmpty(executionArgs.args.data)) {
objectRecords = executionArgs.args.data;
}
const duplicateConnections: IConnection<ObjectRecord>[] = await Promise.all(
objectRecords.map(async (record) => {
const duplicateConditions = buildDuplicateConditions(
objectMetadataItemWithFieldMaps,
[record],
record.id,
);
if (isEmpty(duplicateConditions)) {
return typeORMObjectRecordsParser.createConnection({
objectRecords: [],
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 0,
totalCount: 0,
order: [{ id: OrderByDirection.AscNullsFirst }],
hasNextPage: false,
hasPreviousPage: false,
});
}
const duplicateRecordsQueryBuilder =
executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
graphqlQueryParser.applyFilterToBuilder(
duplicateRecordsQueryBuilder,
objectMetadataItemWithFieldMaps.nameSingular,
duplicateConditions,
);
const duplicates = (await duplicateRecordsQueryBuilder
.setFindOptions({
select: columnsToSelect,
})
.take(QUERY_MAX_RECORDS)
.getMany()) as ObjectRecord[];
return typeORMObjectRecordsParser.createConnection({
objectRecords: duplicates,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: duplicates.length,
totalCount: duplicates.length,
order: [{ id: OrderByDirection.AscNullsFirst }],
hasNextPage: false,
hasPreviousPage: false,
});
}),
);
return duplicateConnections;
}
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,229 +0,0 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { ObjectRecord, OrderByDirection } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import {
type ObjectRecordFilter,
type ObjectRecordOrderBy,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type FindManyResolverArgs } 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 { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper';
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
import {
getCursor,
getPaginationInfo,
} from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
import { computeCursorArgFilter } from 'src/engine/api/utils/compute-cursor-arg-filter.utils';
@Injectable()
export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolverService<
FindManyResolverArgs,
IConnection<ObjectRecord>
> {
constructor() {
super();
}
async resolve(
executionArgs: GraphqlQueryResolverExecutionArgs<FindManyResolverArgs>,
): Promise<IConnection<ObjectRecord>> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
const objectMetadataNameSingular =
objectMetadataItemWithFieldMaps.nameSingular;
const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataNameSingular,
);
const aggregateQueryBuilder = queryBuilder.clone();
let appliedFilters =
executionArgs.args.filter ?? ({} as ObjectRecordFilter);
executionArgs.graphqlQueryParser.applyFilterToBuilder(
aggregateQueryBuilder,
objectMetadataNameSingular,
appliedFilters,
);
executionArgs.graphqlQueryParser.applyDeletedAtToBuilder(
aggregateQueryBuilder,
appliedFilters,
);
const orderByWithIdCondition = [
...(executionArgs.args.orderBy ?? []),
{ id: OrderByDirection.AscNullsFirst },
] as ObjectRecordOrderBy;
const isForwardPagination = !isDefined(executionArgs.args.before);
const cursor = getCursor(executionArgs.args);
if (cursor) {
const cursorArgFilter = computeCursorArgFilter(
cursor,
orderByWithIdCondition,
objectMetadataItemWithFieldMaps,
isForwardPagination,
);
appliedFilters = (executionArgs.args.filter
? {
and: [executionArgs.args.filter, { or: cursorArgFilter }],
}
: { or: cursorArgFilter }) as unknown as ObjectRecordFilter;
}
executionArgs.graphqlQueryParser.applyFilterToBuilder(
queryBuilder,
objectMetadataNameSingular,
appliedFilters,
);
executionArgs.graphqlQueryParser.applyOrderToBuilder(
queryBuilder,
orderByWithIdCondition,
objectMetadataNameSingular,
isForwardPagination,
);
executionArgs.graphqlQueryParser.applyDeletedAtToBuilder(
queryBuilder,
appliedFilters,
);
ProcessAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder({
selectedAggregatedFields:
executionArgs.graphqlQuerySelectedFieldsResult.aggregate,
queryBuilder: aggregateQueryBuilder,
objectMetadataNameSingular,
});
const limit =
executionArgs.args.first ?? executionArgs.args.last ?? QUERY_MAX_RECORDS;
const columnsToSelect = buildColumnsToSelect({
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
if (isDefined(executionArgs.args.offset)) {
queryBuilder.skip(executionArgs.args.offset);
}
const objectRecords = (await queryBuilder
.setFindOptions({
select: columnsToSelect,
})
.take(limit + 1)
.getMany()) as ObjectRecord[];
const { hasNextPage, hasPreviousPage } = getPaginationInfo(
objectRecords,
limit,
isForwardPagination,
);
if (objectRecords.length > limit) {
objectRecords.pop();
}
const parentObjectRecordsAggregatedValues =
await aggregateQueryBuilder.getRawOne();
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await this.processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: objectRecords,
parentObjectRecordsAggregatedValues,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
aggregate: executionArgs.graphqlQuerySelectedFieldsResult.aggregate,
limit: QUERY_MAX_RECORDS,
authContext,
workspaceDataSource: executionArgs.workspaceDataSource,
rolePermissionConfig: executionArgs.rolePermissionConfig,
selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select,
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return typeORMObjectRecordsParser.createConnection({
objectRecords: isForwardPagination
? objectRecords
: objectRecords.reverse(),
objectRecordsAggregatedValues: parentObjectRecordsAggregatedValues,
selectedAggregatedFields:
executionArgs.graphqlQuerySelectedFieldsResult.aggregate,
objectName: objectMetadataNameSingular,
take: limit,
totalCount: parentObjectRecordsAggregatedValues?.totalCount,
order: orderByWithIdCondition,
hasNextPage,
hasPreviousPage,
});
}
async validate<Filter extends ObjectRecordFilter>(
args: FindManyResolverArgs<Filter>,
_options: WorkspaceQueryRunnerOptions,
): Promise<void> {
if (args.first && args.last) {
throw new GraphqlQueryRunnerException(
'Cannot provide both first and last',
GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT,
);
}
if (args.before && args.after) {
throw new GraphqlQueryRunnerException(
'Cannot provide both before and after',
GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT,
);
}
if (args.before && args.first) {
throw new GraphqlQueryRunnerException(
'Cannot provide both before and first',
GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT,
);
}
if (args.after && args.last) {
throw new GraphqlQueryRunnerException(
'Cannot provide both after and last',
GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT,
);
}
if (args.first !== undefined && args.first < 0) {
throw new GraphqlQueryRunnerException(
'First argument must be non-negative',
GraphqlQueryRunnerExceptionCode.INVALID_ARGS_FIRST,
);
}
if (args.last !== undefined && args.last < 0) {
throw new GraphqlQueryRunnerException(
'Last argument must be non-negative',
GraphqlQueryRunnerExceptionCode.INVALID_ARGS_LAST,
);
}
}
}
@@ -1,110 +0,0 @@
//TODO : Refacto-common - To delete
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type FindOneResolverArgs } 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 { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
import {
WorkspaceQueryRunnerException,
WorkspaceQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
@Injectable()
export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolverService<
FindOneResolverArgs,
ObjectRecord
> {
async resolve(
executionArgs: GraphqlQueryResolverExecutionArgs<FindOneResolverArgs>,
): Promise<ObjectRecord> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
executionArgs.graphqlQueryParser.applyFilterToBuilder(
queryBuilder,
objectMetadataItemWithFieldMaps.nameSingular,
executionArgs.args.filter ?? ({} as ObjectRecordFilter),
);
executionArgs.graphqlQueryParser.applyDeletedAtToBuilder(
queryBuilder,
executionArgs.args.filter ?? ({} as ObjectRecordFilter),
);
const columnsToSelect = buildColumnsToSelect({
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
const objectRecord = await queryBuilder
.setFindOptions({
select: columnsToSelect,
})
.getOne();
if (!objectRecord) {
throw new GraphqlQueryRunnerException(
'Record not found',
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
);
}
const objectRecords = [objectRecord] as ObjectRecord[];
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await this.processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: objectRecords,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS,
authContext,
workspaceDataSource: executionArgs.workspaceDataSource,
rolePermissionConfig: executionArgs.rolePermissionConfig,
selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select,
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return typeORMObjectRecordsParser.processRecord({
objectRecord: objectRecords[0],
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}) as ObjectRecord;
}
async validate(
args: FindOneResolverArgs<ObjectRecordFilter>,
_options: WorkspaceQueryRunnerOptions,
): Promise<void> {
if (!args.filter || Object.keys(args.filter).length === 0) {
throw new WorkspaceQueryRunnerException(
'Missing filter argument',
WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
);
}
}
}
@@ -1,438 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import {
MUTATION_MAX_MERGE_RECORDS,
QUERY_MAX_RECORDS,
} from 'twenty-shared/constants';
import { FieldMetadataType, type ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { In } from 'typeorm';
import { v4 as uuidv4 } from 'uuid';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type MergeManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { type FieldMetadataRelationSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
import { hasRecordFieldValue } from 'src/engine/api/graphql/graphql-query-runner/utils/has-record-field-value.util';
import { mergeFieldValues } from 'src/engine/api/graphql/graphql-query-runner/utils/merge-field-values.util';
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
@Injectable()
export class GraphqlQueryMergeManyResolverService extends GraphqlQueryBaseResolverService<
MergeManyResolverArgs,
ObjectRecord
> {
private readonly logger = new Logger(
GraphqlQueryMergeManyResolverService.name,
);
async resolve(
executionArgs: GraphqlQueryResolverExecutionArgs<MergeManyResolverArgs>,
): Promise<ObjectRecord> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
const { ids, conflictPriorityIndex, dryRun } = executionArgs.args;
const recordsToMerge = await this.fetchRecordsToMerge(executionArgs, ids);
const priorityRecord = this.validateAndGetPriorityRecord(
recordsToMerge,
ids,
conflictPriorityIndex,
);
const mergedData = this.performDeepMerge(
recordsToMerge,
priorityRecord.id,
objectMetadataItemWithFieldMaps,
);
if (dryRun) {
return this.createDryRunResponse(
priorityRecord,
mergedData,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);
}
const idsToDelete = ids.filter((id) => id !== priorityRecord.id);
await this.migrateRelatedRecords(
executionArgs,
idsToDelete,
priorityRecord.id,
);
const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
await queryBuilder
.softDelete()
.whereInIds(idsToDelete)
.returning('*')
.execute();
const updatedRecord = await this.updatePriorityRecord(
executionArgs,
priorityRecord.id,
mergedData,
);
if (executionArgs.rolePermissionConfig) {
await this.processNestedRelations({
executionArgs,
updatedRecords: [updatedRecord],
authContext,
});
}
return this.formatResponse(
updatedRecord,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);
}
private async fetchRecordsToMerge(
executionArgs: GraphqlQueryResolverExecutionArgs<MergeManyResolverArgs>,
ids: string[],
): Promise<ObjectRecord[]> {
const recordsToMerge = await executionArgs.repository.find({
where: { id: In(ids) },
});
if (recordsToMerge.length !== ids.length) {
throw new GraphqlQueryRunnerException(
'One or more records not found',
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
);
}
return recordsToMerge as ObjectRecord[];
}
private validateAndGetPriorityRecord(
recordsToMerge: ObjectRecord[],
ids: string[],
conflictPriorityIndex: number,
): ObjectRecord {
const priorityRecordId = ids[conflictPriorityIndex];
const priorityRecord = recordsToMerge.find(
(record) => record.id === priorityRecordId,
);
if (!priorityRecord) {
throw new GraphqlQueryRunnerException(
'Priority record not found',
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
);
}
return priorityRecord;
}
private performDeepMerge(
recordsToMerge: ObjectRecord[],
priorityRecordId: string,
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
): Partial<ObjectRecord> {
const mergedResult: Partial<ObjectRecord> = {};
const allFieldNames = new Set<string>();
recordsToMerge.forEach((record) => {
Object.keys(record).forEach((fieldName) => {
if (
!this.shouldExcludeFieldFromMerge(
fieldName,
objectMetadataItemWithFieldMaps,
)
) {
allFieldNames.add(fieldName);
}
});
});
allFieldNames.forEach((fieldName) => {
const recordsWithValues: { value: unknown; recordId: string }[] = [];
recordsToMerge.forEach((record) => {
const fieldValue = record[fieldName];
if (hasRecordFieldValue(fieldValue)) {
recordsWithValues.push({ value: fieldValue, recordId: record.id });
}
});
if (recordsWithValues.length === 0) {
return;
} else if (recordsWithValues.length === 1) {
mergedResult[fieldName] = recordsWithValues[0].value;
} else {
const fieldMetadata = Object.values(
objectMetadataItemWithFieldMaps.fieldsById,
).find((field) => field?.name === fieldName);
if (!fieldMetadata) {
return;
}
mergedResult[fieldName] = mergeFieldValues(
fieldMetadata.type,
recordsWithValues,
priorityRecordId,
);
}
});
return mergedResult;
}
private shouldExcludeFieldFromMerge(
fieldName: string,
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
): boolean {
const fieldMetadata = Object.values(
objectMetadataItemWithFieldMaps.fieldsById,
).find((field) => field?.name === fieldName);
return fieldMetadata?.isSystem ?? false;
}
private createDryRunResponse(
priorityRecord: ObjectRecord,
mergedData: Partial<ObjectRecord>,
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
objectMetadataMaps: ObjectMetadataMaps,
): ObjectRecord {
const dryRunRecord = {
...priorityRecord,
...mergedData,
id: uuidv4(),
deletedAt: new Date().toISOString(),
} as ObjectRecord;
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return typeORMObjectRecordsParser.processRecord({
objectRecord: dryRunRecord,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
}
private async updatePriorityRecord(
executionArgs: GraphqlQueryResolverExecutionArgs<MergeManyResolverArgs>,
priorityRecordId: string,
mergedData: Partial<ObjectRecord>,
): Promise<ObjectRecord> {
const { objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
const columnsToReturn = buildColumnsToReturn({
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
const updatedObjectRecords = await queryBuilder
.update()
.set(mergedData)
.where({ id: priorityRecordId })
.returning(columnsToReturn)
.execute();
if (!updatedObjectRecords.generatedMaps.length) {
throw new GraphqlQueryRunnerException(
'Failed to update record',
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
);
}
const updatedRecord = updatedObjectRecords.generatedMaps[0] as ObjectRecord;
return updatedRecord;
}
private async migrateRelatedRecords(
executionArgs: GraphqlQueryResolverExecutionArgs<MergeManyResolverArgs>,
fromIds: string[],
toId: string,
): Promise<void> {
const { objectMetadataMaps, objectMetadataItemWithFieldMaps } =
executionArgs.options;
const relationFieldsPointingToCurrentObject = Object.values(
objectMetadataMaps.byId,
)
.filter(isDefined)
.flatMap((metadata) => {
const relationFields = Object.values(metadata.fieldsById)
.filter(isDefined)
.filter((field) =>
isFieldMetadataEntityOfType(field, FieldMetadataType.RELATION),
)
.filter(
(field) =>
field.relationTargetObjectMetadataId ===
objectMetadataItemWithFieldMaps.id && field.isActive,
);
return relationFields
.filter((field) => {
const relationSettings =
field.settings as FieldMetadataRelationSettings;
return (
relationSettings?.relationType === RelationType.MANY_TO_ONE &&
relationSettings?.joinColumnName
);
})
.map((field) => {
const relationSettings =
field.settings as FieldMetadataRelationSettings;
return {
objectMetadata: metadata,
fieldName: field.name,
fieldId: field.id,
joinColumnName: relationSettings.joinColumnName,
};
});
});
for (const relationField of relationFieldsPointingToCurrentObject) {
if (!relationField.joinColumnName) {
continue;
}
try {
const repository = executionArgs.workspaceDataSource.getRepository(
relationField.objectMetadata.nameSingular,
executionArgs.rolePermissionConfig,
);
const whereCondition = { [relationField.joinColumnName]: In(fromIds) };
const existingRecords = await repository.find({
where: whereCondition,
});
if (existingRecords.length > 0) {
await repository.update(whereCondition, {
[relationField.joinColumnName]: toId,
});
}
} catch (error) {
this.logger.warn(
`Failed to migrate relation field "${relationField.fieldName}" (${relationField.joinColumnName}) in object "${relationField.objectMetadata.nameSingular}":`,
error.message,
);
}
}
}
private async processNestedRelations({
executionArgs,
updatedRecords,
authContext,
}: {
executionArgs: GraphqlQueryResolverExecutionArgs<MergeManyResolverArgs>;
updatedRecords: ObjectRecord[];
authContext: AuthContext;
}): Promise<void> {
const { objectMetadataMaps, objectMetadataItemWithFieldMaps } =
executionArgs.options;
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await this.processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: updatedRecords,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS,
authContext,
workspaceDataSource: executionArgs.workspaceDataSource,
rolePermissionConfig: executionArgs.rolePermissionConfig,
selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select,
});
}
}
private formatResponse(
updatedRecord: ObjectRecord,
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
objectMetadataMaps: ObjectMetadataMaps,
): ObjectRecord {
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return typeORMObjectRecordsParser.processRecord({
objectRecord: updatedRecord,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
}
async validate(
args: MergeManyResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
if (!isDefined(options.objectMetadataItemWithFieldMaps.duplicateCriteria)) {
throw new GraphqlQueryRunnerException(
`Merge is only available for objects with duplicate criteria. Object '${options.objectMetadataItemWithFieldMaps.nameSingular}' does not have duplicate criteria defined.`,
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
);
}
const { ids, conflictPriorityIndex } = args;
if (!ids || ids.length < 2) {
throw new GraphqlQueryRunnerException(
'At least 2 record IDs are required for merge',
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
);
}
if (ids.length > MUTATION_MAX_MERGE_RECORDS) {
throw new GraphqlQueryRunnerException(
`Maximum ${MUTATION_MAX_MERGE_RECORDS} records can be merged at once`,
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
);
}
if (conflictPriorityIndex < 0 || conflictPriorityIndex >= ids.length) {
throw new GraphqlQueryRunnerException(
`Invalid conflict priority '${conflictPriorityIndex}'. Valid options for ${ids.length} records: 0-${ids.length - 1}`,
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
);
}
}
}
@@ -1,90 +0,0 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type RestoreManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
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';
@Injectable()
export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseResolverService<
RestoreManyResolverArgs,
ObjectRecord[]
> {
async resolve(
executionArgs: GraphqlQueryResolverExecutionArgs<RestoreManyResolverArgs>,
): Promise<ObjectRecord[]> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
executionArgs.graphqlQueryParser.applyFilterToBuilder(
queryBuilder,
objectMetadataItemWithFieldMaps.nameSingular,
executionArgs.args.filter,
);
const columnsToReturn = buildColumnsToReturn({
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
const restoredObjectRecords = await queryBuilder
.restore()
.returning(columnsToReturn)
.execute();
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await this.processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords:
restoredObjectRecords.generatedMaps as ObjectRecord[],
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS,
authContext,
workspaceDataSource: executionArgs.workspaceDataSource,
rolePermissionConfig: executionArgs.rolePermissionConfig,
selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select,
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return restoredObjectRecords.generatedMaps.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),
);
}
async validate(
args: RestoreManyResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
if (!args.filter) {
throw new Error('Filter is required');
}
args.filter.id?.in?.forEach((id: string) => assertIsValidUuid(id));
}
}
@@ -1,92 +0,0 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type RestoreOneResolverArgs } 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 { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
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';
@Injectable()
export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResolverService<
RestoreOneResolverArgs,
ObjectRecord
> {
async resolve(
executionArgs: GraphqlQueryResolverExecutionArgs<RestoreOneResolverArgs>,
): Promise<ObjectRecord> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
const columnsToReturn = buildColumnsToReturn({
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
const restoredObjectRecords = await queryBuilder
.restore()
.where({ id: executionArgs.args.id })
.returning(columnsToReturn)
.execute();
if (restoredObjectRecords.generatedMaps.length === 0) {
throw new GraphqlQueryRunnerException(
'Record not found',
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
);
}
const restoredRecord = restoredObjectRecords
.generatedMaps[0] as ObjectRecord;
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await this.processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: [restoredRecord],
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS,
authContext,
workspaceDataSource: executionArgs.workspaceDataSource,
rolePermissionConfig: executionArgs.rolePermissionConfig,
selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select,
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return typeORMObjectRecordsParser.processRecord({
objectRecord: restoredRecord,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
}
async validate(
args: RestoreOneResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
assertIsValidUuid(args.id);
}
}
@@ -1,91 +0,0 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
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';
@Injectable()
export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResolverService<
UpdateManyResolverArgs,
ObjectRecord[]
> {
async resolve(
executionArgs: GraphqlQueryResolverExecutionArgs<UpdateManyResolverArgs>,
): Promise<ObjectRecord[]> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
executionArgs.graphqlQueryParser.applyFilterToBuilder(
queryBuilder,
objectMetadataItemWithFieldMaps.nameSingular,
executionArgs.args.filter,
);
const columnsToReturn = buildColumnsToReturn({
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
const updatedObjectRecords = await queryBuilder
.update()
.set(executionArgs.args.data)
.returning(columnsToReturn)
.execute();
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await this.processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords:
updatedObjectRecords.generatedMaps as ObjectRecord[],
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS,
authContext,
workspaceDataSource: executionArgs.workspaceDataSource,
rolePermissionConfig: executionArgs.rolePermissionConfig,
selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select,
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return updatedObjectRecords.generatedMaps.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),
);
}
async validate(
args: UpdateManyResolverArgs<Partial<ObjectRecord>>,
options: WorkspaceQueryRunnerOptions,
): Promise<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
if (!args.filter) {
throw new Error('Filter is required');
}
args.filter.id?.in?.forEach((id: string) => assertIsValidUuid(id));
}
}
@@ -1,92 +0,0 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type UpdateOneResolverArgs } 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 { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
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';
@Injectable()
export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolverService<
UpdateOneResolverArgs,
ObjectRecord
> {
async resolve(
executionArgs: GraphqlQueryResolverExecutionArgs<UpdateOneResolverArgs>,
): Promise<ObjectRecord> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
executionArgs.options;
const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
const columnsToReturn = buildColumnsToReturn({
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
const updatedObjectRecords = await queryBuilder
.update()
.set(executionArgs.args.data)
.where({ id: executionArgs.args.id })
.returning(columnsToReturn)
.execute();
const updatedRecord = updatedObjectRecords.generatedMaps[0] as ObjectRecord;
if (!updatedRecord) {
throw new GraphqlQueryRunnerException(
'Record not found',
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
);
}
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await this.processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: [updatedRecord],
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
limit: QUERY_MAX_RECORDS,
authContext,
workspaceDataSource: executionArgs.workspaceDataSource,
rolePermissionConfig: executionArgs.rolePermissionConfig,
selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select,
});
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
return typeORMObjectRecordsParser.processRecord({
objectRecord: updatedRecord,
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
}
async validate(
args: UpdateOneResolverArgs<Partial<ObjectRecord>>,
options: WorkspaceQueryRunnerOptions,
): Promise<void> {
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
assertIsValidUuid(args.id);
}
}
@@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { ObjectRecord } from 'twenty-shared/types';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
import {
type CreateManyResolverArgs,
@@ -13,11 +12,8 @@ import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/works
import { CommonCreateManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-create-many-query-runner/common-create-many-query-runner.service';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@Injectable()
export class CreateManyResolverFactory
@@ -26,9 +22,7 @@ export class CreateManyResolverFactory
public static methodName = RESOLVER_METHOD_NAMES.CREATE_MANY;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryCreateManyResolverService,
private readonly commonCreateManyQueryRunnerService: CommonCreateManyQueryRunnerService,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
create(
@@ -37,54 +31,31 @@ export class CreateManyResolverFactory
const internalContext = context;
return async (_source, args, _context, info) => {
const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId: internalContext.authContext.workspace?.id as string,
});
const selectedFields = graphqlFields(info);
const featureFlagsMap = workspaceDataSource.featureFlagMap;
try {
const records = await this.commonCreateManyQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
);
if (featureFlagsMap[FeatureFlagKey.IS_COMMON_API_ENABLED]) {
const selectedFields = graphqlFields(info);
try {
const records = await this.commonCreateManyQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
return records.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),
);
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
return records.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),
);
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
const options: WorkspaceQueryRunnerOptions = {
authContext: internalContext.authContext,
info,
objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataItemWithFieldMaps:
internalContext.objectMetadataItemWithFieldMaps,
};
return await this.graphqlQueryRunnerService.execute(
args,
options,
CreateManyResolverFactory.methodName,
);
};
}
}
@@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
import {
type CreateOneResolverArgs,
@@ -12,11 +11,8 @@ import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/works
import { CommonCreateOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-create-one-query-runner.service';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { GraphqlQueryCreateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
@Injectable()
export class CreateOneResolverFactory
@@ -25,8 +21,6 @@ export class CreateOneResolverFactory
public static methodName = RESOLVER_METHOD_NAMES.CREATE_ONE;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryCreateOneResolverService,
private readonly featureFlagService: FeatureFlagService,
private readonly commonCreateOneQueryRunnerService: CommonCreateOneQueryRunnerService,
) {}
@@ -36,50 +30,29 @@ export class CreateOneResolverFactory
const internalContext = context;
return async (_source, args, _context, info) => {
const isCommonApiEnabled = await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_COMMON_API_ENABLED,
internalContext.authContext.workspace?.id as string,
);
const selectedFields = graphqlFields(info);
if (isCommonApiEnabled) {
const selectedFields = graphqlFields(info);
try {
const record = await this.commonCreateOneQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
);
try {
const record = await this.commonCreateOneQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
return typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
return typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
const options: WorkspaceQueryRunnerOptions = {
authContext: internalContext.authContext,
info,
objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataItemWithFieldMaps:
internalContext.objectMetadataItemWithFieldMaps,
};
return await this.graphqlQueryRunnerService.execute(
args,
options,
CreateOneResolverFactory.methodName,
);
};
}
}
@@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { ObjectRecord } from 'twenty-shared/types';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
import {
type DeleteManyResolverArgs,
@@ -13,11 +12,8 @@ import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/works
import { CommonDeleteManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-delete-many-query-runner.service';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { GraphqlQueryDeleteManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@Injectable()
export class DeleteManyResolverFactory
@@ -26,9 +22,7 @@ export class DeleteManyResolverFactory
public static methodName = RESOLVER_METHOD_NAMES.DELETE_MANY;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryDeleteManyResolverService,
private readonly commonDeleteManyQueryRunnerService: CommonDeleteManyQueryRunnerService,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
create(
@@ -37,54 +31,31 @@ export class DeleteManyResolverFactory
const internalContext = context;
return async (_source, args, _context, info) => {
const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId: internalContext.authContext.workspace?.id as string,
});
const selectedFields = graphqlFields(info);
const featureFlagsMap = workspaceDataSource.featureFlagMap;
try {
const records = await this.commonDeleteManyQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
);
if (featureFlagsMap[FeatureFlagKey.IS_COMMON_API_ENABLED]) {
const selectedFields = graphqlFields(info);
try {
const records = await this.commonDeleteManyQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
return records.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),
);
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
return records.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),
);
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
const options: WorkspaceQueryRunnerOptions = {
authContext: internalContext.authContext,
info,
objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataItemWithFieldMaps:
internalContext.objectMetadataItemWithFieldMaps,
};
return await this.graphqlQueryRunnerService.execute(
args,
options,
DeleteManyResolverFactory.methodName,
);
};
}
}
@@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
import {
type DeleteOneResolverArgs,
@@ -12,11 +11,8 @@ import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/works
import { CommonDeleteOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-delete-one-query-runner.service';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { GraphqlQueryDeleteOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@Injectable()
export class DeleteOneResolverFactory
@@ -25,9 +21,7 @@ export class DeleteOneResolverFactory
public static methodName = RESOLVER_METHOD_NAMES.DELETE_ONE;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryDeleteOneResolverService,
private readonly commonDeleteOneQueryRunnerService: CommonDeleteOneQueryRunnerService,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
create(
@@ -36,52 +30,29 @@ export class DeleteOneResolverFactory
const internalContext = context;
return async (_source, args, _context, info) => {
const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId: internalContext.authContext.workspace?.id as string,
});
const selectedFields = graphqlFields(info);
const featureFlagsMap = workspaceDataSource.featureFlagMap;
try {
const record = await this.commonDeleteOneQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
);
if (featureFlagsMap[FeatureFlagKey.IS_COMMON_API_ENABLED]) {
const selectedFields = graphqlFields(info);
try {
const record = await this.commonDeleteOneQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
return typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
return typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
const options: WorkspaceQueryRunnerOptions = {
authContext: internalContext.authContext,
info,
objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataItemWithFieldMaps:
internalContext.objectMetadataItemWithFieldMaps,
};
return await this.graphqlQueryRunnerService.execute(
args,
options,
DeleteOneResolverFactory.methodName,
);
};
}
}
@@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { ObjectRecord } from 'twenty-shared/types';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
import {
type DestroyManyResolverArgs,
@@ -13,11 +12,8 @@ import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/works
import { CommonDestroyManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-destroy-many-query-runner.service';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { GraphqlQueryDestroyManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@Injectable()
export class DestroyManyResolverFactory
@@ -26,9 +22,7 @@ export class DestroyManyResolverFactory
public static methodName = RESOLVER_METHOD_NAMES.DESTROY_MANY;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryDestroyManyResolverService,
private readonly commonDestroyManyQueryRunnerService: CommonDestroyManyQueryRunnerService,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
create(
@@ -37,55 +31,31 @@ export class DestroyManyResolverFactory
const internalContext = context;
return async (_source, args, _context, info) => {
const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId: internalContext.authContext.workspace?.id as string,
});
const selectedFields = graphqlFields(info);
const featureFlagsMap = workspaceDataSource.featureFlagMap;
try {
const records = await this.commonDestroyManyQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
);
if (featureFlagsMap[FeatureFlagKey.IS_COMMON_API_ENABLED]) {
const selectedFields = graphqlFields(info);
try {
const records =
await this.commonDestroyManyQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
);
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
return records.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
return records.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),
);
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
const options: WorkspaceQueryRunnerOptions = {
authContext: internalContext.authContext,
info,
objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataItemWithFieldMaps:
internalContext.objectMetadataItemWithFieldMaps,
};
return await this.graphqlQueryRunnerService.execute(
args,
options,
DestroyManyResolverFactory.methodName,
);
};
}
}
@@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
import {
type DestroyOneResolverArgs,
@@ -12,11 +11,8 @@ import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/works
import { CommonDestroyOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-destroy-one-query-runner.service';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@Injectable()
export class DestroyOneResolverFactory
@@ -25,9 +21,7 @@ export class DestroyOneResolverFactory
public static methodName = RESOLVER_METHOD_NAMES.DESTROY_ONE;
constructor(
private readonly graphQLQueryRunnerService: GraphqlQueryDestroyOneResolverService,
private readonly commonDestroyOneQueryRunnerService: CommonDestroyOneQueryRunnerService,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
create(
@@ -36,52 +30,29 @@ export class DestroyOneResolverFactory
const internalContext = context;
return async (_source, args, _context, info) => {
const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId: internalContext.authContext.workspace?.id as string,
});
const selectedFields = graphqlFields(info);
const featureFlagsMap = workspaceDataSource.featureFlagMap;
try {
const record = await this.commonDestroyOneQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
);
if (featureFlagsMap[FeatureFlagKey.IS_COMMON_API_ENABLED]) {
const selectedFields = graphqlFields(info);
try {
const record = await this.commonDestroyOneQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
return typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
return typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
const options: WorkspaceQueryRunnerOptions = {
authContext: internalContext.authContext,
info,
objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataItemWithFieldMaps:
internalContext.objectMetadataItemWithFieldMaps,
};
return await this.graphQLQueryRunnerService.execute(
args,
options,
DestroyOneResolverFactory.methodName,
);
};
}
}
@@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { OrderByDirection } from 'twenty-shared/types';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
import {
type FindDuplicatesResolverArgs,
@@ -13,11 +12,8 @@ import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/works
import { CommonFindDuplicatesQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-find-duplicates-query-runner.service';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { GraphqlQueryFindDuplicatesResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@Injectable()
export class FindDuplicatesResolverFactory
@@ -26,9 +22,7 @@ export class FindDuplicatesResolverFactory
public static methodName = RESOLVER_METHOD_NAMES.FIND_DUPLICATES;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryFindDuplicatesResolverService,
private readonly commonFindDuplicatesQueryRunnerService: CommonFindDuplicatesQueryRunnerService,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
create(
@@ -37,58 +31,35 @@ export class FindDuplicatesResolverFactory
const internalContext = context;
return async (_source, args, _context, info) => {
const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId: internalContext.authContext.workspace?.id as string,
});
const selectedFields = graphqlFields(info);
const featureFlagsMap = workspaceDataSource.featureFlagMap;
if (featureFlagsMap[FeatureFlagKey.IS_COMMON_API_ENABLED]) {
const selectedFields = graphqlFields(info);
try {
const paginatedDuplicates =
await this.commonFindDuplicatesQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
);
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
return paginatedDuplicates.map((duplicate) =>
typeORMObjectRecordsParser.createConnection({
objectRecords: duplicate.records,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: duplicate.records.length,
totalCount: duplicate.totalCount,
order: [{ id: OrderByDirection.AscNullsFirst }],
hasNextPage: duplicate.hasNextPage,
hasPreviousPage: duplicate.hasPreviousPage,
}),
try {
const paginatedDuplicates =
await this.commonFindDuplicatesQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
);
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
return paginatedDuplicates.map((duplicate) =>
typeORMObjectRecordsParser.createConnection({
objectRecords: duplicate.records,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: duplicate.records.length,
totalCount: duplicate.totalCount,
order: [{ id: OrderByDirection.AscNullsFirst }],
hasNextPage: duplicate.hasNextPage,
hasPreviousPage: duplicate.hasPreviousPage,
}),
);
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
const options: WorkspaceQueryRunnerOptions = {
authContext: internalContext.authContext,
info,
objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataItemWithFieldMaps:
internalContext.objectMetadataItemWithFieldMaps,
};
return await this.graphqlQueryRunnerService.execute(
args,
options,
FindDuplicatesResolverFactory.methodName,
);
};
}
}
@@ -12,11 +12,8 @@ import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-
import { CommonFindManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-find-many-query-runner.service';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
@Injectable()
export class FindManyResolverFactory
@@ -26,8 +23,6 @@ export class FindManyResolverFactory
constructor(
private readonly commonFindManyQueryRunnerService: CommonFindManyQueryRunnerService,
private readonly graphqlQueryRunnerService: GraphqlQueryFindManyResolverService,
private readonly featureFlagService: FeatureFlagService,
) {}
create(
@@ -36,59 +31,40 @@ export class FindManyResolverFactory
const internalContext = context;
return async (_source, args, _context, info) => {
const isCommonApiEnabled = await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_COMMON_API_ENABLED,
internalContext.authContext.workspace?.id as string,
);
const selectedFields = graphqlFields(info);
if (isCommonApiEnabled) {
const selectedFields = graphqlFields(info);
try {
const {
records,
aggregatedValues,
totalCount,
pageInfo,
selectedFieldsResult,
} = await this.commonFindManyQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
);
try {
const {
records,
aggregatedValues,
totalCount,
pageInfo,
selectedFieldsResult,
} = await this.commonFindManyQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
return typeORMObjectRecordsParser.createConnection({
objectRecords: records,
objectRecordsAggregatedValues: aggregatedValues,
selectedAggregatedFields: selectedFieldsResult.aggregate,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: args.first ?? args.last ?? QUERY_MAX_RECORDS,
totalCount,
order: args.orderBy,
hasNextPage: pageInfo.hasNextPage,
hasPreviousPage: pageInfo.hasPreviousPage,
});
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
return typeORMObjectRecordsParser.createConnection({
objectRecords: records,
objectRecordsAggregatedValues: aggregatedValues,
selectedAggregatedFields: selectedFieldsResult.aggregate,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: args.first ?? args.last ?? QUERY_MAX_RECORDS,
totalCount,
order: args.orderBy,
hasNextPage: pageInfo.hasNextPage,
hasPreviousPage: pageInfo.hasPreviousPage,
});
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
return await this.graphqlQueryRunnerService.execute(
args,
{
authContext: internalContext.authContext,
info,
objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataItemWithFieldMaps:
internalContext.objectMetadataItemWithFieldMaps,
},
FindManyResolverFactory.methodName,
);
};
}
}
@@ -11,11 +11,8 @@ import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-
import { CommonFindOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-find-one-query-runner.service';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
@Injectable()
export class FindOneResolverFactory
@@ -25,8 +22,6 @@ export class FindOneResolverFactory
constructor(
private readonly commonFindOneQueryRunnerService: CommonFindOneQueryRunnerService,
private readonly featureFlagService: FeatureFlagService,
private readonly graphqlQueryRunnerService: GraphqlQueryFindOneResolverService,
) {}
create(
@@ -35,48 +30,29 @@ export class FindOneResolverFactory
const internalContext = context;
return async (_source, args, _context, info) => {
const isCommonApiEnabled = await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_COMMON_API_ENABLED,
internalContext.authContext.workspace?.id as string,
);
try {
const selectedFields = graphqlFields(info);
if (isCommonApiEnabled) {
try {
const selectedFields = graphqlFields(info);
const record = await this.commonFindOneQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
);
const record = await this.commonFindOneQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
return typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
return typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
return await this.graphqlQueryRunnerService.execute(
args,
{
authContext: internalContext.authContext,
info,
objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataItemWithFieldMaps:
internalContext.objectMetadataItemWithFieldMaps,
},
FindOneResolverFactory.methodName,
);
};
}
}
@@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
import {
type MergeManyResolverArgs,
@@ -12,11 +11,8 @@ import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-
import { CommonMergeManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-merge-many-query-runner.service';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { GraphqlQueryMergeManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-merge-many-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
@Injectable()
export class MergeManyResolverFactory
@@ -25,9 +21,7 @@ export class MergeManyResolverFactory
public static methodName = RESOLVER_METHOD_NAMES.MERGE_MANY;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryMergeManyResolverService,
private readonly commonMergeManyQueryRunnerService: CommonMergeManyQueryRunnerService,
private readonly featureFlagService: FeatureFlagService,
) {}
create(
@@ -36,50 +30,29 @@ export class MergeManyResolverFactory
const internalContext = context;
return async (_source, args, _context, info) => {
const isCommonApiEnabled = await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_COMMON_API_ENABLED,
internalContext.authContext.workspace?.id as string,
);
const selectedFields = graphqlFields(info);
if (isCommonApiEnabled) {
const selectedFields = graphqlFields(info);
try {
const record = await this.commonMergeManyQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
);
try {
const record = await this.commonMergeManyQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
return typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
return typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
const options: WorkspaceQueryRunnerOptions = {
authContext: internalContext.authContext,
info,
objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataItemWithFieldMaps:
internalContext.objectMetadataItemWithFieldMaps,
};
return await this.graphqlQueryRunnerService.execute(
args,
options,
MergeManyResolverFactory.methodName,
);
};
}
}
@@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { ObjectRecord } from 'twenty-shared/types';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
import {
type Resolver,
@@ -13,11 +12,8 @@ import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/works
import { CommonRestoreManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-restore-many-query-runner.service';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { GraphqlQueryRestoreManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@Injectable()
export class RestoreManyResolverFactory
@@ -26,8 +22,6 @@ export class RestoreManyResolverFactory
public static methodName = RESOLVER_METHOD_NAMES.RESTORE_MANY;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryRestoreManyResolverService,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly commonRestoreManyQueryRunnerService: CommonRestoreManyQueryRunnerService,
) {}
@@ -37,54 +31,31 @@ export class RestoreManyResolverFactory
const internalContext = context;
return async (_source, args, _context, info) => {
const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId: internalContext.authContext.workspace?.id as string,
});
const selectedFields = graphqlFields(info);
const featureFlagsMap = workspaceDataSource.featureFlagMap;
try {
const records = await this.commonRestoreManyQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
);
if (featureFlagsMap[FeatureFlagKey.IS_COMMON_API_ENABLED]) {
const selectedFields = graphqlFields(info);
try {
const records =
await this.commonRestoreManyQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
);
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
return records.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
}
const options: WorkspaceQueryRunnerOptions = {
authContext: internalContext.authContext,
info,
objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataItemWithFieldMaps:
internalContext.objectMetadataItemWithFieldMaps,
};
return await this.graphqlQueryRunnerService.execute(
args,
options,
RestoreManyResolverFactory.methodName,
);
return records.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),
);
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
};
}
}
@@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
import {
type Resolver,
@@ -12,11 +11,8 @@ import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/works
import { CommonRestoreOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-restore-one-query-runner.service';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { GraphqlQueryRestoreOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@Injectable()
export class RestoreOneResolverFactory
@@ -25,8 +21,6 @@ export class RestoreOneResolverFactory
public static methodName = RESOLVER_METHOD_NAMES.RESTORE_ONE;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryRestoreOneResolverService,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly commonRestoreOneQueryRunnerService: CommonRestoreOneQueryRunnerService,
) {}
@@ -36,52 +30,29 @@ export class RestoreOneResolverFactory
const internalContext = context;
return async (_source, args, _context, info) => {
const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId: internalContext.authContext.workspace?.id as string,
});
const selectedFields = graphqlFields(info);
const featureFlagsMap = workspaceDataSource.featureFlagMap;
try {
const record = await this.commonRestoreOneQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
);
if (featureFlagsMap[FeatureFlagKey.IS_COMMON_API_ENABLED]) {
const selectedFields = graphqlFields(info);
try {
const record = await this.commonRestoreOneQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
return typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
return typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
const options: WorkspaceQueryRunnerOptions = {
authContext: internalContext.authContext,
info,
objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataItemWithFieldMaps:
internalContext.objectMetadataItemWithFieldMaps,
};
return await this.graphqlQueryRunnerService.execute(
args,
options,
RestoreOneResolverFactory.methodName,
);
};
}
}
@@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { ObjectRecord } from 'twenty-shared/types';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
import {
type Resolver,
@@ -13,11 +12,8 @@ import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/works
import { CommonUpdateManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-update-many-query-runner.service';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { GraphqlQueryUpdateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@Injectable()
export class UpdateManyResolverFactory
@@ -26,9 +22,7 @@ export class UpdateManyResolverFactory
public static methodName = RESOLVER_METHOD_NAMES.UPDATE_MANY;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryUpdateManyResolverService,
private readonly commonUpdateManyQueryRunnerService: CommonUpdateManyQueryRunnerService,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
create(
@@ -37,54 +31,31 @@ export class UpdateManyResolverFactory
const internalContext = context;
return async (_source, args, _context, info) => {
const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId: internalContext.authContext.workspace?.id as string,
});
const selectedFields = graphqlFields(info);
const featureFlagsMap = workspaceDataSource.featureFlagMap;
try {
const records = await this.commonUpdateManyQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
);
if (featureFlagsMap[FeatureFlagKey.IS_COMMON_API_ENABLED]) {
const selectedFields = graphqlFields(info);
try {
const records = await this.commonUpdateManyQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
return records.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),
);
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
return records.map((record: ObjectRecord) =>
typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
}),
);
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
const options: WorkspaceQueryRunnerOptions = {
authContext: internalContext.authContext,
info,
objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataItemWithFieldMaps:
internalContext.objectMetadataItemWithFieldMaps,
};
return await this.graphqlQueryRunnerService.execute(
args,
options,
UpdateManyResolverFactory.methodName,
);
};
}
}
@@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
import {
type Resolver,
@@ -12,11 +11,8 @@ import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/works
import { CommonUpdateOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-update-one-query-runner.service';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { GraphqlQueryUpdateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@Injectable()
export class UpdateOneResolverFactory
@@ -25,9 +21,7 @@ export class UpdateOneResolverFactory
public static methodName = RESOLVER_METHOD_NAMES.UPDATE_ONE;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryUpdateOneResolverService,
private readonly commonUpdateOneQueryRunnerService: CommonUpdateOneQueryRunnerService,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
create(
@@ -36,52 +30,29 @@ export class UpdateOneResolverFactory
const internalContext = context;
return async (_source, args, _context, info) => {
const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId: internalContext.authContext.workspace?.id as string,
});
const selectedFields = graphqlFields(info);
const featureFlagsMap = workspaceDataSource.featureFlagMap;
try {
const record = await this.commonUpdateOneQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
);
if (featureFlagsMap[FeatureFlagKey.IS_COMMON_API_ENABLED]) {
const selectedFields = graphqlFields(info);
try {
const record = await this.commonUpdateOneQueryRunnerService.execute(
{ ...args, selectedFields },
internalContext,
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
internalContext.objectMetadataMaps,
);
return typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
return typeORMObjectRecordsParser.processRecord({
objectRecord: record,
objectName:
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
take: 1,
totalCount: 1,
});
} catch (error) {
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
}
const options: WorkspaceQueryRunnerOptions = {
authContext: internalContext.authContext,
info,
objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataItemWithFieldMaps:
internalContext.objectMetadataItemWithFieldMaps,
};
return await this.graphqlQueryRunnerService.execute(
args,
options,
UpdateOneResolverFactory.methodName,
);
};
}
}