Common - Update one/many, restore one/many, findDuplicates & mergeMany (#15279)
closes https://github.com/twentyhq/core-team-issues/issues/1739
This commit is contained in:
+4
-3
@@ -72,10 +72,11 @@ export abstract class CommonBaseQueryRunnerService<
|
||||
@Inject()
|
||||
protected readonly commonResultGettersService: CommonResultGettersService;
|
||||
|
||||
protected abstract readonly operationName: CommonQueryNames;
|
||||
|
||||
public async execute(
|
||||
args: CommonInput<Args>,
|
||||
queryRunnerContext: CommonBaseQueryRunnerContext,
|
||||
operationName: CommonQueryNames,
|
||||
): Promise<Output> {
|
||||
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
||||
queryRunnerContext;
|
||||
@@ -104,7 +105,7 @@ export abstract class CommonBaseQueryRunnerService<
|
||||
const processedArgs = await this.processArgs(
|
||||
args,
|
||||
queryRunnerContext,
|
||||
operationName,
|
||||
this.operationName,
|
||||
commonQueryParser,
|
||||
);
|
||||
|
||||
@@ -121,7 +122,7 @@ export abstract class CommonBaseQueryRunnerService<
|
||||
|
||||
return this.enrichResultsWithGettersAndHooks({
|
||||
results,
|
||||
operationName,
|
||||
operationName: this.operationName,
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
|
||||
+3
-1
@@ -21,6 +21,7 @@ import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/co
|
||||
import {
|
||||
CommonExtendedInput,
|
||||
CommonInput,
|
||||
CommonQueryNames,
|
||||
CreateManyQueryArgs,
|
||||
} from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { CommonSelectedFieldsResult } from 'src/engine/api/common/types/common-selected-fields-result.type';
|
||||
@@ -34,13 +35,14 @@ import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/typ
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
|
||||
import { RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
|
||||
|
||||
@Injectable()
|
||||
export class CommonCreateManyQueryRunnerService extends CommonBaseQueryRunnerService<
|
||||
CreateManyQueryArgs,
|
||||
ObjectRecord[]
|
||||
> {
|
||||
protected readonly operationName = CommonQueryNames.CREATE_MANY;
|
||||
async run(
|
||||
args: CommonExtendedInput<CreateManyQueryArgs>,
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext,
|
||||
|
||||
+3
@@ -11,6 +11,7 @@ import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/co
|
||||
import {
|
||||
CommonExtendedInput,
|
||||
CommonInput,
|
||||
CommonQueryNames,
|
||||
CreateManyQueryArgs,
|
||||
CreateOneQueryArgs,
|
||||
} from 'src/engine/api/common/types/common-query-args.type';
|
||||
@@ -29,6 +30,8 @@ export class CommonCreateOneQueryRunnerService extends CommonBaseQueryRunnerServ
|
||||
super();
|
||||
}
|
||||
|
||||
protected readonly operationName = CommonQueryNames.CREATE_ONE;
|
||||
|
||||
async run(
|
||||
args: CommonExtendedInput<CreateManyQueryArgs>,
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext,
|
||||
|
||||
+3
@@ -17,6 +17,7 @@ import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/co
|
||||
import {
|
||||
CommonExtendedInput,
|
||||
CommonInput,
|
||||
CommonQueryNames,
|
||||
DeleteManyQueryArgs,
|
||||
} from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
||||
@@ -30,6 +31,8 @@ export class CommonDeleteManyQueryRunnerService extends CommonBaseQueryRunnerSer
|
||||
DeleteManyQueryArgs,
|
||||
ObjectRecord[]
|
||||
> {
|
||||
protected readonly operationName = CommonQueryNames.DELETE_MANY;
|
||||
|
||||
async run(
|
||||
args: CommonExtendedInput<DeleteManyQueryArgs>,
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext,
|
||||
|
||||
+3
@@ -16,6 +16,7 @@ import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/co
|
||||
import {
|
||||
CommonExtendedInput,
|
||||
CommonInput,
|
||||
CommonQueryNames,
|
||||
DeleteOneQueryArgs,
|
||||
} from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||
@@ -33,6 +34,8 @@ export class CommonDeleteOneQueryRunnerService extends CommonBaseQueryRunnerServ
|
||||
super();
|
||||
}
|
||||
|
||||
protected readonly operationName = CommonQueryNames.DELETE_ONE;
|
||||
|
||||
async run(
|
||||
args: CommonExtendedInput<DeleteOneQueryArgs>,
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext,
|
||||
|
||||
+3
@@ -17,6 +17,7 @@ import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/co
|
||||
import {
|
||||
CommonExtendedInput,
|
||||
CommonInput,
|
||||
CommonQueryNames,
|
||||
DestroyManyQueryArgs,
|
||||
} from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
||||
@@ -27,6 +28,8 @@ export class CommonDestroyManyQueryRunnerService extends CommonBaseQueryRunnerSe
|
||||
DestroyManyQueryArgs,
|
||||
ObjectRecord[]
|
||||
> {
|
||||
protected readonly operationName = CommonQueryNames.DESTROY_MANY;
|
||||
|
||||
async run(
|
||||
args: CommonExtendedInput<DestroyManyQueryArgs>,
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext,
|
||||
|
||||
+3
@@ -16,6 +16,7 @@ import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/co
|
||||
import {
|
||||
CommonExtendedInput,
|
||||
CommonInput,
|
||||
CommonQueryNames,
|
||||
DestroyOneQueryArgs,
|
||||
} from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
@@ -31,6 +32,8 @@ export class CommonDestroyOneQueryRunnerService extends CommonBaseQueryRunnerSer
|
||||
super();
|
||||
}
|
||||
|
||||
protected readonly operationName = CommonQueryNames.DESTROY_ONE;
|
||||
|
||||
async run(
|
||||
args: CommonExtendedInput<DestroyOneQueryArgs>,
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext,
|
||||
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
|
||||
import { ObjectRecord, OrderByDirection } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { In } from 'typeorm';
|
||||
|
||||
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
|
||||
|
||||
import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
|
||||
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
|
||||
import { CommonFindDuplicatesOutputItem } from 'src/engine/api/common/types/common-find-duplicates-output-item.type';
|
||||
import {
|
||||
CommonExtendedInput,
|
||||
CommonInput,
|
||||
CommonQueryNames,
|
||||
FindDuplicatesQueryArgs,
|
||||
} from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { getPageInfo } from 'src/engine/api/common/utils/get-page-info.util';
|
||||
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 { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
|
||||
@Injectable()
|
||||
export class CommonFindDuplicatesQueryRunnerService extends CommonBaseQueryRunnerService<
|
||||
FindDuplicatesQueryArgs,
|
||||
CommonFindDuplicatesOutputItem[]
|
||||
> {
|
||||
protected readonly operationName = CommonQueryNames.FIND_DUPLICATES;
|
||||
|
||||
async run(
|
||||
args: CommonExtendedInput<FindDuplicatesQueryArgs>,
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext,
|
||||
): Promise<CommonFindDuplicatesOutputItem[]> {
|
||||
const {
|
||||
repository,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
commonQueryParser,
|
||||
} = queryRunnerContext;
|
||||
|
||||
const existingRecordsQueryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
let objectRecords: Partial<ObjectRecord>[] = [];
|
||||
|
||||
const columnsToSelect = buildColumnsToSelect({
|
||||
select: args.selectedFieldsResult.select,
|
||||
relations: args.selectedFieldsResult.relations,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
});
|
||||
|
||||
if (isDefined(args.ids)) {
|
||||
objectRecords = (await existingRecordsQueryBuilder
|
||||
.where({ id: In(args.ids) })
|
||||
.setFindOptions({
|
||||
select: columnsToSelect,
|
||||
})
|
||||
.getMany()) as ObjectRecord[];
|
||||
} else if (args.data && !isEmpty(args.data)) {
|
||||
objectRecords = args.data;
|
||||
}
|
||||
|
||||
const findDuplicatesOutput: CommonFindDuplicatesOutputItem[] =
|
||||
await Promise.all(
|
||||
objectRecords.map(async (record) => {
|
||||
const duplicateConditions = buildDuplicateConditions(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
[record],
|
||||
record.id,
|
||||
);
|
||||
|
||||
if (isEmpty(duplicateConditions)) {
|
||||
return {
|
||||
records: [],
|
||||
totalCount: 0,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
endCursor: null,
|
||||
};
|
||||
}
|
||||
|
||||
const duplicateRecordsQueryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
commonQueryParser.applyFilterToBuilder(
|
||||
duplicateRecordsQueryBuilder,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
duplicateConditions,
|
||||
);
|
||||
|
||||
const duplicates = (await duplicateRecordsQueryBuilder
|
||||
.setFindOptions({
|
||||
select: columnsToSelect,
|
||||
})
|
||||
.take(QUERY_MAX_RECORDS)
|
||||
.getMany()) as ObjectRecord[];
|
||||
|
||||
const aggregateQueryBuilder = duplicateRecordsQueryBuilder.clone();
|
||||
const totalCount = await aggregateQueryBuilder.getCount();
|
||||
|
||||
const { startCursor, endCursor } = getPageInfo(
|
||||
duplicates,
|
||||
[{ id: OrderByDirection.AscNullsFirst }],
|
||||
QUERY_MAX_RECORDS,
|
||||
true,
|
||||
);
|
||||
|
||||
return {
|
||||
records: duplicates,
|
||||
totalCount,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor,
|
||||
endCursor,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return findDuplicatesOutput;
|
||||
}
|
||||
|
||||
async computeArgs(
|
||||
args: CommonInput<FindDuplicatesQueryArgs>,
|
||||
queryRunnerContext: CommonBaseQueryRunnerContext,
|
||||
): Promise<CommonInput<FindDuplicatesQueryArgs>> {
|
||||
const { authContext, objectMetadataItemWithFieldMaps } = queryRunnerContext;
|
||||
|
||||
return {
|
||||
...args,
|
||||
ids: await Promise.all(
|
||||
args.ids?.map((id) =>
|
||||
this.queryRunnerArgsFactory.overrideValueByFieldMetadata(
|
||||
'id',
|
||||
id,
|
||||
objectMetadataItemWithFieldMaps.fieldsById,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
),
|
||||
) ?? [],
|
||||
),
|
||||
data: await this.queryRunnerArgsFactory.overrideDataByFieldMetadata({
|
||||
partialRecordInputs: args.data,
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
shouldBackfillPositionIfUndefined: false,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
async processQueryResult(
|
||||
queryResult: CommonFindDuplicatesOutputItem[],
|
||||
objectMetadataItemId: string,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
authContext: WorkspaceAuthContext,
|
||||
): Promise<CommonFindDuplicatesOutputItem[]> {
|
||||
const processedResults = await Promise.all(
|
||||
queryResult.map(async (result) => {
|
||||
return {
|
||||
...result,
|
||||
records: await this.commonResultGettersService.processRecordArray(
|
||||
result.records,
|
||||
objectMetadataItemId,
|
||||
objectMetadataMaps,
|
||||
authContext.workspace.id,
|
||||
),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return processedResults;
|
||||
}
|
||||
|
||||
async validate(
|
||||
args: CommonInput<FindDuplicatesQueryArgs>,
|
||||
_queryRunnerContext: CommonBaseQueryRunnerContext,
|
||||
): Promise<void> {
|
||||
if (!args.data && !args.ids) {
|
||||
throw new CommonQueryRunnerException(
|
||||
'You have to provide either "data" or "ids" argument',
|
||||
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
if (args.data && args.ids) {
|
||||
throw new CommonQueryRunnerException(
|
||||
'You cannot provide both "data" and "ids" arguments',
|
||||
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
if (!args.ids && isEmpty(args.data)) {
|
||||
throw new CommonQueryRunnerException(
|
||||
'The "data" condition can not be empty when "ids" input not provided',
|
||||
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -22,6 +22,7 @@ import { CommonFindManyOutput } from 'src/engine/api/common/types/common-find-ma
|
||||
import {
|
||||
CommonExtendedInput,
|
||||
CommonInput,
|
||||
CommonQueryNames,
|
||||
FindManyQueryArgs,
|
||||
} from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { getPageInfo } from 'src/engine/api/common/utils/get-page-info.util';
|
||||
@@ -36,6 +37,8 @@ export class CommonFindManyQueryRunnerService extends CommonBaseQueryRunnerServi
|
||||
FindManyQueryArgs,
|
||||
CommonFindManyOutput
|
||||
> {
|
||||
protected readonly operationName = CommonQueryNames.FIND_MANY;
|
||||
|
||||
async run(
|
||||
args: CommonExtendedInput<FindManyQueryArgs>,
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext,
|
||||
|
||||
+3
@@ -18,6 +18,7 @@ import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/co
|
||||
import {
|
||||
CommonExtendedInput,
|
||||
CommonInput,
|
||||
CommonQueryNames,
|
||||
FindOneQueryArgs,
|
||||
} from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
|
||||
@@ -28,6 +29,8 @@ export class CommonFindOneQueryRunnerService extends CommonBaseQueryRunnerServic
|
||||
FindOneQueryArgs,
|
||||
ObjectRecord
|
||||
> {
|
||||
protected readonly operationName = CommonQueryNames.FIND_ONE;
|
||||
|
||||
async run(
|
||||
args: CommonExtendedInput<FindOneQueryArgs>,
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext,
|
||||
|
||||
+3
@@ -25,6 +25,7 @@ import { CommonGroupByOutputItem } from 'src/engine/api/common/types/common-grou
|
||||
import {
|
||||
CommonExtendedInput,
|
||||
CommonInput,
|
||||
CommonQueryNames,
|
||||
GroupByQueryArgs,
|
||||
} from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { formatResultWithGroupByDimensionValues } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/format-result-with-group-by-dimension-values.util';
|
||||
@@ -54,6 +55,8 @@ export class CommonGroupByQueryRunnerService extends CommonBaseQueryRunnerServic
|
||||
super();
|
||||
}
|
||||
|
||||
protected readonly operationName = CommonQueryNames.GROUP_BY;
|
||||
|
||||
async run(
|
||||
args: CommonExtendedInput<GroupByQueryArgs>,
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext,
|
||||
|
||||
+471
@@ -0,0 +1,471 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
MUTATION_MAX_MERGE_RECORDS,
|
||||
QUERY_MAX_RECORDS,
|
||||
} from 'twenty-shared/constants';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
ObjectRecord,
|
||||
RelationType,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FindOptionsRelations, In, ObjectLiteral } from 'typeorm';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
|
||||
import { FieldMetadataRelationSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||
|
||||
import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
|
||||
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
|
||||
import {
|
||||
CommonExtendedInput,
|
||||
CommonInput,
|
||||
CommonQueryNames,
|
||||
MergeManyQueryArgs,
|
||||
} from 'src/engine/api/common/types/common-query-args.type';
|
||||
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 { 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 { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { 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 CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerService<
|
||||
MergeManyQueryArgs,
|
||||
ObjectRecord
|
||||
> {
|
||||
protected readonly operationName = CommonQueryNames.MERGE_MANY;
|
||||
|
||||
private readonly logger = new Logger(CommonMergeManyQueryRunnerService.name);
|
||||
async run(
|
||||
args: CommonExtendedInput<MergeManyQueryArgs>,
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext,
|
||||
): Promise<ObjectRecord> {
|
||||
const { objectMetadataMaps, objectMetadataItemWithFieldMaps } =
|
||||
queryRunnerContext;
|
||||
|
||||
const recordsToMerge = await this.fetchRecordsToMerge(
|
||||
queryRunnerContext,
|
||||
args,
|
||||
);
|
||||
|
||||
const priorityRecord = this.validateAndGetPriorityRecord(
|
||||
recordsToMerge,
|
||||
args.ids,
|
||||
args.conflictPriorityIndex,
|
||||
);
|
||||
|
||||
const mergedData = this.performDeepMerge(
|
||||
recordsToMerge,
|
||||
priorityRecord.id,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
);
|
||||
|
||||
if (args.dryRun) {
|
||||
return this.createDryRunResponse(
|
||||
priorityRecord,
|
||||
mergedData,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
}
|
||||
|
||||
const idsToDelete = args.ids.filter((id) => id !== priorityRecord.id);
|
||||
|
||||
await this.migrateRelatedRecords(
|
||||
queryRunnerContext,
|
||||
idsToDelete,
|
||||
priorityRecord.id,
|
||||
);
|
||||
|
||||
const queryBuilder = queryRunnerContext.repository.createQueryBuilder(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const columnsToReturn = buildColumnsToReturn({
|
||||
select: args.selectedFieldsResult.select,
|
||||
relations: args.selectedFieldsResult.relations,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
});
|
||||
|
||||
await queryBuilder
|
||||
.softDelete()
|
||||
.whereInIds(idsToDelete)
|
||||
.returning(columnsToReturn)
|
||||
.execute();
|
||||
|
||||
const updatedRecord = await this.updatePriorityRecord(
|
||||
args,
|
||||
queryRunnerContext,
|
||||
priorityRecord.id,
|
||||
mergedData,
|
||||
);
|
||||
|
||||
await this.processNestedRelations({
|
||||
args,
|
||||
queryRunnerContext,
|
||||
updatedRecords: [updatedRecord],
|
||||
});
|
||||
|
||||
return updatedRecord;
|
||||
}
|
||||
|
||||
private async fetchRecordsToMerge(
|
||||
context: CommonExtendedQueryRunnerContext,
|
||||
args: CommonExtendedInput<MergeManyQueryArgs>,
|
||||
): Promise<ObjectRecord[]> {
|
||||
const columnsToSelect = buildColumnsToSelect({
|
||||
select: args.selectedFieldsResult.select,
|
||||
relations: args.selectedFieldsResult.relations,
|
||||
objectMetadataItemWithFieldMaps: context.objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps: context.objectMetadataMaps,
|
||||
});
|
||||
|
||||
const recordsToMerge = await context.repository.find({
|
||||
where: { id: In(args.ids) },
|
||||
select: columnsToSelect,
|
||||
});
|
||||
|
||||
if (recordsToMerge.length !== args.ids.length) {
|
||||
throw new CommonQueryRunnerException(
|
||||
'One or more records not found',
|
||||
CommonQueryRunnerExceptionCode.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(
|
||||
args: CommonExtendedInput<MergeManyQueryArgs>,
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext,
|
||||
priorityRecordId: string,
|
||||
mergedData: Partial<ObjectRecord>,
|
||||
): Promise<ObjectRecord> {
|
||||
const { objectMetadataItemWithFieldMaps, objectMetadataMaps, repository } =
|
||||
queryRunnerContext;
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const columnsToReturn = buildColumnsToReturn({
|
||||
select: args.selectedFieldsResult.select,
|
||||
relations: args.selectedFieldsResult.relations,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
});
|
||||
|
||||
const updatedObjectRecords = await queryBuilder
|
||||
.update()
|
||||
.set(mergedData)
|
||||
.where({ id: priorityRecordId })
|
||||
.returning(columnsToReturn)
|
||||
.execute();
|
||||
|
||||
if (!updatedObjectRecords.generatedMaps.length) {
|
||||
throw new CommonQueryRunnerException(
|
||||
'Failed to update record',
|
||||
CommonQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const updatedRecord = updatedObjectRecords.generatedMaps[0] as ObjectRecord;
|
||||
|
||||
return updatedRecord;
|
||||
}
|
||||
|
||||
private async migrateRelatedRecords(
|
||||
context: CommonExtendedQueryRunnerContext,
|
||||
fromIds: string[],
|
||||
toId: string,
|
||||
): Promise<void> {
|
||||
const { objectMetadataMaps, objectMetadataItemWithFieldMaps } = context;
|
||||
|
||||
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 = context.workspaceDataSource.getRepository(
|
||||
relationField.objectMetadata.nameSingular,
|
||||
context.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({
|
||||
args,
|
||||
queryRunnerContext,
|
||||
updatedRecords,
|
||||
}: {
|
||||
args: CommonExtendedInput<MergeManyQueryArgs>;
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext;
|
||||
updatedRecords: ObjectRecord[];
|
||||
}): Promise<void> {
|
||||
const {
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
authContext,
|
||||
workspaceDataSource,
|
||||
rolePermissionConfig,
|
||||
} = queryRunnerContext;
|
||||
|
||||
if (args.selectedFieldsResult.relations) {
|
||||
await this.processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: updatedRecords,
|
||||
relations: args.selectedFieldsResult.relations as Record<
|
||||
string,
|
||||
FindOptionsRelations<ObjectLiteral>
|
||||
>,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
workspaceDataSource,
|
||||
rolePermissionConfig,
|
||||
selectedFields: args.selectedFieldsResult.select,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async computeArgs(
|
||||
args: CommonInput<MergeManyQueryArgs>,
|
||||
_queryRunnerContext: CommonBaseQueryRunnerContext,
|
||||
): Promise<CommonInput<MergeManyQueryArgs>> {
|
||||
return args;
|
||||
}
|
||||
|
||||
async processQueryResult(
|
||||
queryResult: ObjectRecord,
|
||||
_objectMetadataItemId: string,
|
||||
_objectMetadataMaps: ObjectMetadataMaps,
|
||||
_authContext: WorkspaceAuthContext,
|
||||
): Promise<ObjectRecord> {
|
||||
return queryResult;
|
||||
}
|
||||
|
||||
async validate(
|
||||
args: CommonInput<MergeManyQueryArgs>,
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext,
|
||||
): Promise<void> {
|
||||
const { objectMetadataItemWithFieldMaps } = queryRunnerContext;
|
||||
|
||||
assertMutationNotOnRemoteObject(objectMetadataItemWithFieldMaps);
|
||||
|
||||
if (!isDefined(objectMetadataItemWithFieldMaps.duplicateCriteria)) {
|
||||
throw new CommonQueryRunnerException(
|
||||
`Merge is only available for objects with duplicate criteria. Object '${objectMetadataItemWithFieldMaps.nameSingular}' does not have duplicate criteria defined.`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
const { ids, conflictPriorityIndex } = args;
|
||||
|
||||
if (!ids || ids.length < 2) {
|
||||
throw new CommonQueryRunnerException(
|
||||
'At least 2 record IDs are required for merge',
|
||||
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
if (ids.length > MUTATION_MAX_MERGE_RECORDS) {
|
||||
throw new CommonQueryRunnerException(
|
||||
`Maximum ${MUTATION_MAX_MERGE_RECORDS} records can be merged at once`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
if (conflictPriorityIndex < 0 || conflictPriorityIndex >= ids.length) {
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid conflict priority '${conflictPriorityIndex}'. Valid options for ${ids.length} records: 0-${ids.length - 1}`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
-4
@@ -4,18 +4,30 @@ import { CommonDeleteManyQueryRunnerService } from 'src/engine/api/common/common
|
||||
import { CommonDeleteOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-delete-one-query-runner.service';
|
||||
import { CommonDestroyManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-destroy-many-query-runner.service';
|
||||
import { CommonDestroyOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-destroy-one-query-runner.service';
|
||||
import { CommonFindDuplicatesQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-find-duplicates-query-runner.service';
|
||||
import { CommonFindManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-find-many-query-runner.service';
|
||||
import { CommonFindOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-find-one-query-runner.service';
|
||||
import { CommonGroupByQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-group-by-query-runner.service';
|
||||
import { CommonMergeManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-merge-many-query-runner.service';
|
||||
import { CommonRestoreManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-restore-many-query-runner.service';
|
||||
import { CommonRestoreOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-restore-one-query-runner.service';
|
||||
import { CommonUpdateManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-update-many-query-runner.service';
|
||||
import { CommonUpdateOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-update-one-query-runner.service';
|
||||
|
||||
export const CommonQueryRunners = [
|
||||
CommonFindOneQueryRunnerService,
|
||||
CommonFindManyQueryRunnerService,
|
||||
CommonCreateOneQueryRunnerService,
|
||||
CommonCreateManyQueryRunnerService,
|
||||
CommonFindManyQueryRunnerService,
|
||||
CommonFindOneQueryRunnerService,
|
||||
CommonGroupByQueryRunnerService,
|
||||
CommonDestroyOneQueryRunnerService,
|
||||
CommonUpdateOneQueryRunnerService,
|
||||
CommonUpdateManyQueryRunnerService,
|
||||
CommonDestroyManyQueryRunnerService,
|
||||
CommonDeleteOneQueryRunnerService,
|
||||
CommonDestroyOneQueryRunnerService,
|
||||
CommonDeleteManyQueryRunnerService,
|
||||
CommonDeleteOneQueryRunnerService,
|
||||
CommonFindDuplicatesQueryRunnerService,
|
||||
CommonRestoreManyQueryRunnerService,
|
||||
CommonRestoreOneQueryRunnerService,
|
||||
CommonMergeManyQueryRunnerService,
|
||||
];
|
||||
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
|
||||
import { ObjectRecord } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FindOptionsRelations, ObjectLiteral } from 'typeorm';
|
||||
|
||||
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
|
||||
|
||||
import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
|
||||
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
|
||||
import {
|
||||
CommonExtendedInput,
|
||||
CommonInput,
|
||||
CommonQueryNames,
|
||||
RestoreManyQueryArgs,
|
||||
} from 'src/engine/api/common/types/common-query-args.type';
|
||||
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';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||
|
||||
@Injectable()
|
||||
export class CommonRestoreManyQueryRunnerService extends CommonBaseQueryRunnerService<
|
||||
RestoreManyQueryArgs,
|
||||
ObjectRecord[]
|
||||
> {
|
||||
protected readonly operationName = CommonQueryNames.RESTORE_MANY;
|
||||
|
||||
async run(
|
||||
args: CommonExtendedInput<RestoreManyQueryArgs>,
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext,
|
||||
): Promise<ObjectRecord[]> {
|
||||
const {
|
||||
repository,
|
||||
authContext,
|
||||
rolePermissionConfig,
|
||||
workspaceDataSource,
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
commonQueryParser,
|
||||
} = queryRunnerContext;
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const tableName = computeTableName(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.isCustom,
|
||||
);
|
||||
|
||||
commonQueryParser.applyFilterToBuilder(
|
||||
queryBuilder,
|
||||
tableName,
|
||||
args.filter,
|
||||
);
|
||||
|
||||
const columnsToReturn = buildColumnsToReturn({
|
||||
select: args.selectedFieldsResult.select,
|
||||
relations: args.selectedFieldsResult.relations,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
});
|
||||
|
||||
const restoredObjectRecords = await queryBuilder
|
||||
.restore()
|
||||
.returning(columnsToReturn)
|
||||
.execute();
|
||||
|
||||
const restoredRecords =
|
||||
restoredObjectRecords.generatedMaps as ObjectRecord[];
|
||||
|
||||
if (isDefined(args.selectedFieldsResult.relations)) {
|
||||
await this.processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: restoredRecords,
|
||||
//TODO : Refacto-common - Typing to fix when switching processNestedRelationsHelper to Common
|
||||
relations: args.selectedFieldsResult.relations as Record<
|
||||
string,
|
||||
FindOptionsRelations<ObjectLiteral>
|
||||
>,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
workspaceDataSource,
|
||||
rolePermissionConfig,
|
||||
selectedFields: args.selectedFieldsResult.select,
|
||||
});
|
||||
}
|
||||
|
||||
return restoredRecords;
|
||||
}
|
||||
|
||||
async computeArgs(
|
||||
args: CommonInput<RestoreManyQueryArgs>,
|
||||
queryRunnerContext: CommonBaseQueryRunnerContext,
|
||||
): Promise<CommonInput<RestoreManyQueryArgs>> {
|
||||
const { objectMetadataItemWithFieldMaps } = queryRunnerContext;
|
||||
|
||||
return {
|
||||
...args,
|
||||
filter: this.queryRunnerArgsFactory.overrideFilterByFieldMetadata(
|
||||
args.filter,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
async processQueryResult(
|
||||
queryResult: ObjectRecord[],
|
||||
objectMetadataItemId: string,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
authContext: WorkspaceAuthContext,
|
||||
): Promise<ObjectRecord[]> {
|
||||
return this.commonResultGettersService.processRecordArray(
|
||||
queryResult,
|
||||
objectMetadataItemId,
|
||||
objectMetadataMaps,
|
||||
authContext.workspace.id,
|
||||
);
|
||||
}
|
||||
|
||||
async validate(
|
||||
args: RestoreManyQueryArgs,
|
||||
queryRunnerContext: CommonBaseQueryRunnerContext,
|
||||
) {
|
||||
const { objectMetadataItemWithFieldMaps } = queryRunnerContext;
|
||||
|
||||
assertMutationNotOnRemoteObject(objectMetadataItemWithFieldMaps);
|
||||
if (!isDefined(args.filter)) {
|
||||
throw new CommonQueryRunnerException(
|
||||
'Filter is required',
|
||||
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
args.filter.id?.in?.forEach((id: string) => assertIsValidUuid(id));
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { type ObjectRecord } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
|
||||
|
||||
import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service';
|
||||
import { CommonRestoreManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-restore-many-query-runner.service';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
|
||||
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
|
||||
import {
|
||||
CommonExtendedInput,
|
||||
CommonInput,
|
||||
CommonQueryNames,
|
||||
RestoreOneQueryArgs,
|
||||
} from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
|
||||
@Injectable()
|
||||
export class CommonRestoreOneQueryRunnerService extends CommonBaseQueryRunnerService<
|
||||
RestoreOneQueryArgs,
|
||||
ObjectRecord
|
||||
> {
|
||||
constructor(
|
||||
private readonly commonRestoreManyQueryRunnerService: CommonRestoreManyQueryRunnerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected readonly operationName = CommonQueryNames.RESTORE_ONE;
|
||||
|
||||
async run(
|
||||
args: CommonExtendedInput<RestoreOneQueryArgs>,
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext,
|
||||
): Promise<ObjectRecord> {
|
||||
const result = await this.commonRestoreManyQueryRunnerService.run(
|
||||
{
|
||||
...args,
|
||||
filter: { id: { eq: args.id } },
|
||||
},
|
||||
queryRunnerContext,
|
||||
);
|
||||
|
||||
if (!isDefined(result) || result.length === 0) {
|
||||
throw new CommonQueryRunnerException(
|
||||
'Record not found',
|
||||
CommonQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return result[0];
|
||||
}
|
||||
|
||||
async computeArgs(
|
||||
args: CommonInput<RestoreOneQueryArgs>,
|
||||
_queryRunnerContext: CommonBaseQueryRunnerContext,
|
||||
): Promise<CommonInput<RestoreOneQueryArgs>> {
|
||||
return args;
|
||||
}
|
||||
|
||||
async processQueryResult(
|
||||
queryResult: ObjectRecord,
|
||||
_objectMetadataItemId: string,
|
||||
_objectMetadataMaps: ObjectMetadataMaps,
|
||||
_authContext: WorkspaceAuthContext,
|
||||
): Promise<ObjectRecord> {
|
||||
return queryResult;
|
||||
}
|
||||
|
||||
async validate(
|
||||
args: CommonInput<RestoreOneQueryArgs>,
|
||||
queryRunnerContext: CommonBaseQueryRunnerContext,
|
||||
): Promise<void> {
|
||||
const { objectMetadataItemWithFieldMaps } = queryRunnerContext;
|
||||
|
||||
assertMutationNotOnRemoteObject(objectMetadataItemWithFieldMaps);
|
||||
assertIsValidUuid(args.id);
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'class-validator';
|
||||
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
|
||||
import { ObjectRecord } from 'twenty-shared/types';
|
||||
import { FindOptionsRelations, ObjectLiteral } from 'typeorm';
|
||||
|
||||
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
|
||||
|
||||
import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
|
||||
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
|
||||
import {
|
||||
CommonExtendedInput,
|
||||
CommonInput,
|
||||
CommonQueryNames,
|
||||
UpdateManyQueryArgs,
|
||||
} from 'src/engine/api/common/types/common-query-args.type';
|
||||
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';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||
|
||||
@Injectable()
|
||||
export class CommonUpdateManyQueryRunnerService extends CommonBaseQueryRunnerService<
|
||||
UpdateManyQueryArgs,
|
||||
ObjectRecord[]
|
||||
> {
|
||||
protected readonly operationName = CommonQueryNames.UPDATE_MANY;
|
||||
|
||||
async run(
|
||||
args: CommonExtendedInput<UpdateManyQueryArgs>,
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext,
|
||||
): Promise<ObjectRecord[]> {
|
||||
const {
|
||||
repository,
|
||||
authContext,
|
||||
rolePermissionConfig,
|
||||
workspaceDataSource,
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
commonQueryParser,
|
||||
} = queryRunnerContext;
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const tableName = computeTableName(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.isCustom,
|
||||
);
|
||||
|
||||
commonQueryParser.applyFilterToBuilder(
|
||||
queryBuilder,
|
||||
tableName,
|
||||
args.filter,
|
||||
);
|
||||
|
||||
const columnsToReturn = buildColumnsToReturn({
|
||||
select: args.selectedFieldsResult.select,
|
||||
relations: args.selectedFieldsResult.relations,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
});
|
||||
|
||||
const updatedObjectRecords = await queryBuilder
|
||||
.update()
|
||||
.set(args.data)
|
||||
.returning(columnsToReturn)
|
||||
.execute();
|
||||
|
||||
const updatedRecords = updatedObjectRecords.generatedMaps as ObjectRecord[];
|
||||
|
||||
if (isDefined(args.selectedFieldsResult.relations)) {
|
||||
await this.processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: updatedRecords,
|
||||
//TODO : Refacto-common - Typing to fix when switching processNestedRelationsHelper to Common
|
||||
relations: args.selectedFieldsResult.relations as Record<
|
||||
string,
|
||||
FindOptionsRelations<ObjectLiteral>
|
||||
>,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
workspaceDataSource,
|
||||
rolePermissionConfig,
|
||||
selectedFields: args.selectedFieldsResult.select,
|
||||
});
|
||||
}
|
||||
|
||||
return updatedRecords;
|
||||
}
|
||||
|
||||
async computeArgs(
|
||||
args: CommonInput<UpdateManyQueryArgs>,
|
||||
queryRunnerContext: CommonBaseQueryRunnerContext,
|
||||
): Promise<CommonInput<UpdateManyQueryArgs>> {
|
||||
const { authContext, objectMetadataItemWithFieldMaps } = queryRunnerContext;
|
||||
|
||||
return {
|
||||
...args,
|
||||
filter:
|
||||
this.queryRunnerArgsFactory.overrideFilterByFieldMetadata(
|
||||
args.filter,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
) || {},
|
||||
data: (
|
||||
await this.queryRunnerArgsFactory.overrideDataByFieldMetadata({
|
||||
partialRecordInputs: [args.data],
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
shouldBackfillPositionIfUndefined: false,
|
||||
})
|
||||
)[0],
|
||||
};
|
||||
}
|
||||
|
||||
async validate(
|
||||
args: CommonInput<UpdateManyQueryArgs>,
|
||||
queryRunnerContext: CommonBaseQueryRunnerContext,
|
||||
): Promise<void> {
|
||||
const { objectMetadataItemWithFieldMaps } = queryRunnerContext;
|
||||
|
||||
assertMutationNotOnRemoteObject(objectMetadataItemWithFieldMaps);
|
||||
if (!args.filter) {
|
||||
throw new CommonQueryRunnerException(
|
||||
'Filter is required',
|
||||
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
args.filter.id?.in?.forEach((id: string) => assertIsValidUuid(id));
|
||||
}
|
||||
|
||||
async processQueryResult(
|
||||
queryResult: ObjectRecord[],
|
||||
objectMetadataItemId: string,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
authContext: WorkspaceAuthContext,
|
||||
): Promise<ObjectRecord[]> {
|
||||
return await this.commonResultGettersService.processRecordArray(
|
||||
queryResult,
|
||||
objectMetadataItemId,
|
||||
objectMetadataMaps,
|
||||
authContext.workspace.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { type ObjectRecord } from 'twenty-shared/types';
|
||||
|
||||
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
|
||||
|
||||
import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service';
|
||||
import { CommonUpdateManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-update-many-query-runner.service';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
|
||||
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
|
||||
import {
|
||||
CommonExtendedInput,
|
||||
CommonInput,
|
||||
CommonQueryNames,
|
||||
UpdateOneQueryArgs,
|
||||
} from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
|
||||
@Injectable()
|
||||
export class CommonUpdateOneQueryRunnerService extends CommonBaseQueryRunnerService<
|
||||
UpdateOneQueryArgs,
|
||||
ObjectRecord
|
||||
> {
|
||||
constructor(
|
||||
private readonly commonUpdateManyQueryRunnerService: CommonUpdateManyQueryRunnerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
protected readonly operationName = CommonQueryNames.UPDATE_ONE;
|
||||
|
||||
async run(
|
||||
args: CommonExtendedInput<UpdateOneQueryArgs>,
|
||||
queryRunnerContext: CommonExtendedQueryRunnerContext,
|
||||
): Promise<ObjectRecord> {
|
||||
const result = await this.commonUpdateManyQueryRunnerService.run(
|
||||
{
|
||||
...args,
|
||||
filter: { id: { eq: args.id } },
|
||||
},
|
||||
queryRunnerContext,
|
||||
);
|
||||
|
||||
if (!result || result.length === 0) {
|
||||
throw new CommonQueryRunnerException(
|
||||
'Record not found',
|
||||
CommonQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return result[0];
|
||||
}
|
||||
|
||||
async computeArgs(
|
||||
args: CommonInput<UpdateOneQueryArgs>,
|
||||
_queryRunnerContext: CommonBaseQueryRunnerContext,
|
||||
): Promise<CommonInput<UpdateOneQueryArgs>> {
|
||||
return args;
|
||||
}
|
||||
|
||||
async processQueryResult(
|
||||
queryResult: ObjectRecord,
|
||||
_objectMetadataItemId: string,
|
||||
_objectMetadataMaps: ObjectMetadataMaps,
|
||||
_authContext: WorkspaceAuthContext,
|
||||
): Promise<ObjectRecord> {
|
||||
return queryResult;
|
||||
}
|
||||
|
||||
async validate(
|
||||
args: CommonInput<UpdateOneQueryArgs>,
|
||||
queryRunnerContext: CommonBaseQueryRunnerContext,
|
||||
): Promise<void> {
|
||||
const { objectMetadataItemWithFieldMaps } = queryRunnerContext;
|
||||
|
||||
assertMutationNotOnRemoteObject(objectMetadataItemWithFieldMaps);
|
||||
assertIsValidUuid(args.id);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { type ObjectRecord } from 'twenty-shared/types';
|
||||
|
||||
export type CommonFindDuplicatesOutputItem = {
|
||||
records: ObjectRecord[];
|
||||
totalCount: number;
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
};
|
||||
@@ -24,6 +24,12 @@ export enum CommonQueryNames {
|
||||
DESTROY_ONE = 'destroyOne',
|
||||
DELETE_MANY = 'deleteMany',
|
||||
DELETE_ONE = 'deleteOne',
|
||||
UPDATE_ONE = 'updateOne',
|
||||
UPDATE_MANY = 'updateMany',
|
||||
FIND_DUPLICATES = 'findDuplicates',
|
||||
RESTORE_MANY = 'restoreMany',
|
||||
RESTORE_ONE = 'restoreOne',
|
||||
MERGE_MANY = 'mergeMany',
|
||||
}
|
||||
|
||||
interface BaseQueryArgs {
|
||||
@@ -77,6 +83,35 @@ export interface DeleteManyQueryArgs {
|
||||
filter: ObjectRecordFilter;
|
||||
}
|
||||
|
||||
export interface UpdateOneQueryArgs {
|
||||
id: string;
|
||||
data: Partial<ObjectRecord>;
|
||||
}
|
||||
|
||||
export interface UpdateManyQueryArgs {
|
||||
filter: ObjectRecordFilter;
|
||||
data: Partial<ObjectRecord>;
|
||||
}
|
||||
|
||||
export interface FindDuplicatesQueryArgs {
|
||||
data?: Partial<ObjectRecord>[];
|
||||
ids?: string[];
|
||||
}
|
||||
|
||||
export interface RestoreManyQueryArgs {
|
||||
filter: ObjectRecordFilter;
|
||||
}
|
||||
|
||||
export interface RestoreOneQueryArgs {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface MergeManyQueryArgs {
|
||||
ids: string[];
|
||||
conflictPriorityIndex: number;
|
||||
dryRun?: boolean;
|
||||
}
|
||||
|
||||
export type CommonQueryArgs =
|
||||
| FindOneQueryArgs
|
||||
| FindManyQueryArgs
|
||||
@@ -86,7 +121,12 @@ export type CommonQueryArgs =
|
||||
| DestroyOneQueryArgs
|
||||
| DestroyManyQueryArgs
|
||||
| DeleteOneQueryArgs
|
||||
| DeleteManyQueryArgs;
|
||||
| DeleteManyQueryArgs
|
||||
| UpdateOneQueryArgs
|
||||
| UpdateManyQueryArgs
|
||||
| FindDuplicatesQueryArgs
|
||||
| RestoreManyQueryArgs
|
||||
| RestoreOneQueryArgs;
|
||||
|
||||
export type CommonInput<T extends CommonQueryArgs> = T & BaseQueryArgs;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { type ObjectRecord } from 'twenty-shared/types';
|
||||
|
||||
import { type CommonFindDuplicatesOutputItem } from 'src/engine/api/common/types/common-find-duplicates-output-item.type';
|
||||
import { type CommonFindManyOutput } from 'src/engine/api/common/types/common-find-many-output.type';
|
||||
import { type CommonGroupByOutputItem } from 'src/engine/api/common/types/common-group-by-output-item.type';
|
||||
|
||||
@@ -7,4 +8,5 @@ export type CommonQueryResult =
|
||||
| ObjectRecord[]
|
||||
| ObjectRecord
|
||||
| CommonGroupByOutputItem[]
|
||||
| CommonFindManyOutput;
|
||||
| CommonFindManyOutput
|
||||
| CommonFindDuplicatesOutputItem[];
|
||||
|
||||
+2
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
|
||||
@@ -114,6 +115,7 @@ export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseR
|
||||
.setFindOptions({
|
||||
select: columnsToSelect,
|
||||
})
|
||||
.take(QUERY_MAX_RECORDS)
|
||||
.getMany()) as ObjectRecord[];
|
||||
|
||||
return typeORMObjectRecordsParser.createConnection({
|
||||
|
||||
+1
-1
@@ -302,7 +302,7 @@ export class QueryRunnerArgsFactory {
|
||||
}
|
||||
}
|
||||
|
||||
private async overrideValueByFieldMetadata(
|
||||
async overrideValueByFieldMetadata(
|
||||
key: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: any,
|
||||
|
||||
+1
-3
@@ -12,7 +12,6 @@ import {
|
||||
import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { CommonCreateManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-create-many-query-runner/common-create-many-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
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';
|
||||
@@ -52,7 +51,6 @@ export class CreateManyResolverFactory
|
||||
const records = await this.commonCreateManyQueryRunnerService.execute(
|
||||
{ ...args, selectedFields },
|
||||
internalContext,
|
||||
CommonQueryNames.CREATE_MANY,
|
||||
);
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
@@ -70,7 +68,7 @@ export class CreateManyResolverFactory
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
return workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-3
@@ -11,7 +11,6 @@ import {
|
||||
import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { CommonCreateOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-create-one-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
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';
|
||||
@@ -49,7 +48,6 @@ export class CreateOneResolverFactory
|
||||
const record = await this.commonCreateOneQueryRunnerService.execute(
|
||||
{ ...args, selectedFields },
|
||||
internalContext,
|
||||
CommonQueryNames.CREATE_ONE,
|
||||
);
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
@@ -65,7 +63,7 @@ export class CreateOneResolverFactory
|
||||
totalCount: 1,
|
||||
});
|
||||
} catch (error) {
|
||||
return workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-3
@@ -12,7 +12,6 @@ import {
|
||||
import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { CommonDeleteManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-delete-many-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
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';
|
||||
@@ -52,7 +51,6 @@ export class DeleteManyResolverFactory
|
||||
const records = await this.commonDeleteManyQueryRunnerService.execute(
|
||||
{ ...args, selectedFields },
|
||||
internalContext,
|
||||
CommonQueryNames.DELETE_MANY,
|
||||
);
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
@@ -70,7 +68,7 @@ export class DeleteManyResolverFactory
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
return workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-3
@@ -11,7 +11,6 @@ import {
|
||||
import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { CommonDeleteOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-delete-one-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
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';
|
||||
@@ -51,7 +50,6 @@ export class DeleteOneResolverFactory
|
||||
const record = await this.commonDeleteOneQueryRunnerService.execute(
|
||||
{ ...args, selectedFields },
|
||||
internalContext,
|
||||
CommonQueryNames.DELETE_ONE,
|
||||
);
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
@@ -67,7 +65,7 @@ export class DeleteOneResolverFactory
|
||||
totalCount: 1,
|
||||
});
|
||||
} catch (error) {
|
||||
return workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-3
@@ -12,7 +12,6 @@ import {
|
||||
import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { CommonDestroyManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-destroy-many-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
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';
|
||||
@@ -53,7 +52,6 @@ export class DestroyManyResolverFactory
|
||||
await this.commonDestroyManyQueryRunnerService.execute(
|
||||
{ ...args, selectedFields },
|
||||
internalContext,
|
||||
CommonQueryNames.DESTROY_MANY,
|
||||
);
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
@@ -71,7 +69,7 @@ export class DestroyManyResolverFactory
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
return workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-3
@@ -11,7 +11,6 @@ import {
|
||||
import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { CommonDestroyOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-destroy-one-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
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';
|
||||
@@ -51,7 +50,6 @@ export class DestroyOneResolverFactory
|
||||
const record = await this.commonDestroyOneQueryRunnerService.execute(
|
||||
{ ...args, selectedFields },
|
||||
internalContext,
|
||||
CommonQueryNames.DESTROY_ONE,
|
||||
);
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
@@ -67,7 +65,7 @@ export class DestroyOneResolverFactory
|
||||
totalCount: 1,
|
||||
});
|
||||
} catch (error) {
|
||||
return workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+49
@@ -1,5 +1,8 @@
|
||||
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 {
|
||||
@@ -8,8 +11,13 @@ import {
|
||||
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
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
|
||||
@@ -19,6 +27,8 @@ export class FindDuplicatesResolverFactory
|
||||
|
||||
constructor(
|
||||
private readonly graphqlQueryRunnerService: GraphqlQueryFindDuplicatesResolverService,
|
||||
private readonly commonFindDuplicatesQueryRunnerService: CommonFindDuplicatesQueryRunnerService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
create(
|
||||
@@ -27,6 +37,45 @@ 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 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,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
|
||||
+1
-3
@@ -11,7 +11,6 @@ import {
|
||||
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { CommonFindManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-find-many-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
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';
|
||||
@@ -55,7 +54,6 @@ export class FindManyResolverFactory
|
||||
} = await this.commonFindManyQueryRunnerService.execute(
|
||||
{ ...args, selectedFields },
|
||||
internalContext,
|
||||
CommonQueryNames.FIND_MANY,
|
||||
);
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
@@ -76,7 +74,7 @@ export class FindManyResolverFactory
|
||||
hasPreviousPage: pageInfo.hasPreviousPage,
|
||||
});
|
||||
} catch (error) {
|
||||
return workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-3
@@ -10,7 +10,6 @@ import {
|
||||
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { CommonFindOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-find-one-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
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';
|
||||
@@ -48,7 +47,6 @@ export class FindOneResolverFactory
|
||||
const record = await this.commonFindOneQueryRunnerService.execute(
|
||||
{ ...args, selectedFields },
|
||||
internalContext,
|
||||
CommonQueryNames.FIND_ONE,
|
||||
);
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
@@ -64,7 +62,7 @@ export class FindOneResolverFactory
|
||||
totalCount: 1,
|
||||
});
|
||||
} catch (error) {
|
||||
return workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-3
@@ -11,7 +11,6 @@ import {
|
||||
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { CommonGroupByQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-group-by-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { GraphqlQueryGroupByResolverService } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/graphql-query-group-by-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';
|
||||
@@ -48,10 +47,9 @@ export class GroupByResolverFactory
|
||||
return await this.commonGroupByQueryRunnerService.execute(
|
||||
{ ...args, selectedFields },
|
||||
internalContext,
|
||||
CommonQueryNames.GROUP_BY,
|
||||
);
|
||||
} catch (error) {
|
||||
return workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+41
-1
@@ -1,15 +1,22 @@
|
||||
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,
|
||||
type Resolver,
|
||||
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
import { 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
|
||||
@@ -19,6 +26,8 @@ export class MergeManyResolverFactory
|
||||
|
||||
constructor(
|
||||
private readonly graphqlQueryRunnerService: GraphqlQueryMergeManyResolverService,
|
||||
private readonly commonMergeManyQueryRunnerService: CommonMergeManyQueryRunnerService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
create(
|
||||
@@ -27,6 +36,37 @@ 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,
|
||||
);
|
||||
|
||||
if (isCommonApiEnabled) {
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
try {
|
||||
const record = await this.commonMergeManyQueryRunnerService.execute(
|
||||
{ ...args, selectedFields },
|
||||
internalContext,
|
||||
);
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(
|
||||
internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
return typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
});
|
||||
} catch (error) {
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
|
||||
+45
@@ -1,5 +1,8 @@
|
||||
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 {
|
||||
@@ -8,8 +11,13 @@ import {
|
||||
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
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
|
||||
@@ -19,6 +27,8 @@ export class RestoreManyResolverFactory
|
||||
|
||||
constructor(
|
||||
private readonly graphqlQueryRunnerService: GraphqlQueryRestoreManyResolverService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly commonRestoreManyQueryRunnerService: CommonRestoreManyQueryRunnerService,
|
||||
) {}
|
||||
|
||||
create(
|
||||
@@ -27,6 +37,41 @@ 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 featureFlagsMap = workspaceDataSource.featureFlagMap;
|
||||
|
||||
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,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
|
||||
+42
@@ -1,5 +1,7 @@
|
||||
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 {
|
||||
@@ -8,8 +10,13 @@ import {
|
||||
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
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
|
||||
@@ -19,6 +26,8 @@ export class RestoreOneResolverFactory
|
||||
|
||||
constructor(
|
||||
private readonly graphqlQueryRunnerService: GraphqlQueryRestoreOneResolverService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly commonRestoreOneQueryRunnerService: CommonRestoreOneQueryRunnerService,
|
||||
) {}
|
||||
|
||||
create(
|
||||
@@ -27,6 +36,39 @@ 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 featureFlagsMap = workspaceDataSource.featureFlagMap;
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
return typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
});
|
||||
} catch (error) {
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
|
||||
+47
@@ -1,5 +1,8 @@
|
||||
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 {
|
||||
@@ -8,15 +11,24 @@ import {
|
||||
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
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
|
||||
implements WorkspaceResolverBuilderFactoryInterface
|
||||
{
|
||||
public static methodName = RESOLVER_METHOD_NAMES.UPDATE_MANY;
|
||||
|
||||
constructor(
|
||||
private readonly graphqlQueryRunnerService: GraphqlQueryUpdateManyResolverService,
|
||||
private readonly commonUpdateManyQueryRunnerService: CommonUpdateManyQueryRunnerService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
create(
|
||||
@@ -25,6 +37,41 @@ 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 featureFlagsMap = workspaceDataSource.featureFlagMap;
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
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,
|
||||
|
||||
+42
@@ -1,5 +1,7 @@
|
||||
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 {
|
||||
@@ -8,8 +10,13 @@ import {
|
||||
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||
|
||||
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
|
||||
@@ -19,6 +26,8 @@ export class UpdateOneResolverFactory
|
||||
|
||||
constructor(
|
||||
private readonly graphqlQueryRunnerService: GraphqlQueryUpdateOneResolverService,
|
||||
private readonly commonUpdateOneQueryRunnerService: CommonUpdateOneQueryRunnerService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
create(
|
||||
@@ -27,6 +36,39 @@ 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 featureFlagsMap = workspaceDataSource.featureFlagMap;
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
return typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName:
|
||||
internalContext.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
});
|
||||
} catch (error) {
|
||||
workspaceQueryRunnerGraphqlApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
const options: WorkspaceQueryRunnerOptions = {
|
||||
authContext: internalContext.authContext,
|
||||
info,
|
||||
|
||||
+26
@@ -106,6 +106,32 @@ export class RestApiCoreController {
|
||||
res.status(200).send(result);
|
||||
}
|
||||
|
||||
@Patch('restore/*')
|
||||
async handleApiRestore(
|
||||
@Req() request: AuthenticatedRequest,
|
||||
@Res() res: Response,
|
||||
) {
|
||||
this.logger.log(
|
||||
`[REST API] Processing RESTORE request to ${request.path} on workspace ${request.workspaceId}`,
|
||||
);
|
||||
const result = await this.restApiCoreService.restore(request);
|
||||
|
||||
res.status(200).send(result);
|
||||
}
|
||||
|
||||
@Patch('*/merge')
|
||||
async handleApiMerge(
|
||||
@Req() request: AuthenticatedRequest,
|
||||
@Res() res: Response,
|
||||
) {
|
||||
this.logger.log(
|
||||
`[REST API] Processing MERGE request to ${request.path} on workspace ${request.workspaceId}`,
|
||||
);
|
||||
const result = await this.restApiCoreService.mergeMany(request);
|
||||
|
||||
res.status(200).send(result);
|
||||
}
|
||||
|
||||
@Patch('*')
|
||||
async handleApiPatch(
|
||||
@Req() request: AuthenticatedRequest,
|
||||
|
||||
-2
@@ -11,7 +11,6 @@ import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { CommonCreateManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-create-many-query-runner/common-create-many-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { parseDepthRestRequest } from 'src/engine/api/rest/input-request-parsers/depth-parser-utils/parse-depth-rest-request.util';
|
||||
import { parseUpsertRestRequest } from 'src/engine/api/rest/input-request-parsers/upsert-parser-utils/parse-upsert-rest-request.util';
|
||||
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
|
||||
@@ -49,7 +48,6 @@ export class RestApiCreateManyHandler extends RestApiBaseHandler {
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
},
|
||||
CommonQueryNames.CREATE_MANY,
|
||||
);
|
||||
|
||||
return this.formatRestResponse(
|
||||
|
||||
-2
@@ -11,7 +11,6 @@ import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { CommonCreateOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-create-one-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { parseDepthRestRequest } from 'src/engine/api/rest/input-request-parsers/depth-parser-utils/parse-depth-rest-request.util';
|
||||
import { parseUpsertRestRequest } from 'src/engine/api/rest/input-request-parsers/upsert-parser-utils/parse-upsert-rest-request.util';
|
||||
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
|
||||
@@ -50,7 +49,6 @@ export class RestApiCreateOneHandler extends RestApiBaseHandler {
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
},
|
||||
CommonQueryNames.CREATE_ONE,
|
||||
);
|
||||
|
||||
return this.formatRestResponse(
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ import { capitalize } from 'twenty-shared/utils';
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { CommonDeleteManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-delete-many-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { parseFilterRestRequest } from 'src/engine/api/rest/input-request-parsers/filter-parser-utils/parse-filter-rest-request.util';
|
||||
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
|
||||
import { workspaceQueryRunnerRestApiExceptionHandler } from 'src/engine/api/rest/utils/workspace-query-runner-rest-api-exception-handler.util';
|
||||
@@ -36,7 +35,6 @@ export class RestApiDeleteManyHandler extends RestApiBaseHandler {
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
},
|
||||
CommonQueryNames.DELETE_MANY,
|
||||
);
|
||||
|
||||
return this.formatRestResponse(
|
||||
|
||||
-4
@@ -6,9 +6,7 @@ import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { CommonDeleteOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-delete-one-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||
import { parseDepthRestRequest } from 'src/engine/api/rest/input-request-parsers/depth-parser-utils/parse-depth-rest-request.util';
|
||||
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
|
||||
import { workspaceQueryRunnerRestApiExceptionHandler } from 'src/engine/api/rest/utils/workspace-query-runner-rest-api-exception-handler.util';
|
||||
|
||||
@@ -37,7 +35,6 @@ export class RestApiDeleteOneHandler extends RestApiBaseHandler {
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
},
|
||||
CommonQueryNames.DELETE_ONE,
|
||||
);
|
||||
|
||||
return this.formatRestResponse(
|
||||
@@ -64,7 +61,6 @@ export class RestApiDeleteOneHandler extends RestApiBaseHandler {
|
||||
|
||||
return {
|
||||
id,
|
||||
depth: parseDepthRestRequest(request),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ import { capitalize } from 'twenty-shared/utils';
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { CommonDestroyManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-destroy-many-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { parseFilterRestRequest } from 'src/engine/api/rest/input-request-parsers/filter-parser-utils/parse-filter-rest-request.util';
|
||||
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
|
||||
import { workspaceQueryRunnerRestApiExceptionHandler } from 'src/engine/api/rest/utils/workspace-query-runner-rest-api-exception-handler.util';
|
||||
@@ -36,7 +35,6 @@ export class RestApiDestroyManyHandler extends RestApiBaseHandler {
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
},
|
||||
CommonQueryNames.DESTROY_MANY,
|
||||
);
|
||||
|
||||
return this.formatRestResponse(
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { CommonDestroyOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-destroy-one-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
|
||||
import { workspaceQueryRunnerRestApiExceptionHandler } from 'src/engine/api/rest/utils/workspace-query-runner-rest-api-exception-handler.util';
|
||||
@@ -37,7 +36,6 @@ export class RestApiDestroyOneHandler extends RestApiBaseHandler {
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
},
|
||||
CommonQueryNames.DESTROY_ONE,
|
||||
);
|
||||
|
||||
return this.formatRestResponse(
|
||||
|
||||
+82
@@ -10,11 +10,93 @@ import {
|
||||
RestApiBaseHandler,
|
||||
} from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { CommonFindDuplicatesQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-find-duplicates-query-runner.service';
|
||||
import { parseDepthRestRequest } from 'src/engine/api/rest/input-request-parsers/depth-parser-utils/parse-depth-rest-request.util';
|
||||
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
|
||||
import { workspaceQueryRunnerRestApiExceptionHandler } from 'src/engine/api/rest/utils/workspace-query-runner-rest-api-exception-handler.util';
|
||||
import { buildDuplicateConditions } from 'src/engine/api/utils/build-duplicate-conditions.utils';
|
||||
|
||||
@Injectable()
|
||||
export class RestApiFindDuplicatesHandler extends RestApiBaseHandler {
|
||||
constructor(
|
||||
private readonly commonFindDuplicatesQueryRunnerService: CommonFindDuplicatesQueryRunnerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async commonHandle(request: AuthenticatedRequest) {
|
||||
try {
|
||||
this.validate(request);
|
||||
|
||||
const { data, ids, depth } = this.parseRequestArgs(request);
|
||||
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
} = await this.buildCommonOptions(request);
|
||||
|
||||
const selectedFields = await this.computeSelectedFields({
|
||||
depth,
|
||||
objectMetadataMapItem: objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
authContext,
|
||||
});
|
||||
|
||||
const duplicateConnections =
|
||||
await this.commonFindDuplicatesQueryRunnerService.execute(
|
||||
{ data, ids, selectedFields },
|
||||
{
|
||||
authContext,
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
},
|
||||
);
|
||||
|
||||
return this.formatRestResponse(
|
||||
duplicateConnections,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
} catch (error) {
|
||||
workspaceQueryRunnerRestApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
private formatRestResponse(
|
||||
duplicateConnections: Array<{
|
||||
records: ObjectRecord[];
|
||||
totalCount: number;
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
}>,
|
||||
objectNameSingular: string,
|
||||
) {
|
||||
return {
|
||||
data: duplicateConnections.map((connection) => ({
|
||||
data: {
|
||||
[`${objectNameSingular}Duplicates`]: connection.records,
|
||||
},
|
||||
totalCount: connection.totalCount,
|
||||
pageInfo: {
|
||||
hasNextPage: connection.hasNextPage,
|
||||
hasPreviousPage: connection.hasPreviousPage,
|
||||
startCursor: connection.startCursor,
|
||||
endCursor: connection.endCursor,
|
||||
},
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
private parseRequestArgs(request: AuthenticatedRequest) {
|
||||
return {
|
||||
data: request.body.data,
|
||||
ids: request.body.ids,
|
||||
depth: parseDepthRestRequest(request),
|
||||
};
|
||||
}
|
||||
|
||||
async handle(request: AuthenticatedRequest) {
|
||||
this.validate(request);
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
} from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { CommonFindManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-find-many-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { parseDepthRestRequest } from 'src/engine/api/rest/input-request-parsers/depth-parser-utils/parse-depth-rest-request.util';
|
||||
import { parseEndingBeforeRestRequest } from 'src/engine/api/rest/input-request-parsers/ending-before-parser-utils/parse-ending-before-rest-request.util';
|
||||
import { parseFilterRestRequest } from 'src/engine/api/rest/input-request-parsers/filter-parser-utils/parse-filter-rest-request.util';
|
||||
@@ -118,7 +117,6 @@ export class RestApiFindManyHandler extends RestApiBaseHandler {
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
},
|
||||
CommonQueryNames.FIND_MANY,
|
||||
);
|
||||
|
||||
return this.formatRestResponse(
|
||||
|
||||
@@ -6,7 +6,6 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { CommonFindOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-find-one-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||
import { parseDepthRestRequest } from 'src/engine/api/rest/input-request-parsers/depth-parser-utils/parse-depth-rest-request.util';
|
||||
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
|
||||
@@ -81,7 +80,6 @@ export class RestApiFindOneHandler extends RestApiBaseHandler {
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
},
|
||||
CommonQueryNames.FIND_ONE,
|
||||
);
|
||||
|
||||
return this.formatRestResponse(
|
||||
|
||||
+1
-3
@@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common';
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { CommonGroupByQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-group-by-query-runner.service';
|
||||
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
|
||||
import { parseAggregateFieldsRestRequest } from 'src/engine/api/rest/input-request-parsers/aggregate-fields-parser-utils/parse-aggregate-fields-rest-request.util';
|
||||
import { parseFilterRestRequest } from 'src/engine/api/rest/input-request-parsers/filter-parser-utils/parse-filter-rest-request.util';
|
||||
import { parseGroupByRestRequest } from 'src/engine/api/rest/input-request-parsers/group-by-parser-utils/parse-group-by-rest-request.util';
|
||||
@@ -44,10 +43,9 @@ export class RestApiGroupByHandler extends RestApiBaseHandler {
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
},
|
||||
CommonQueryNames.GROUP_BY,
|
||||
);
|
||||
} catch (error) {
|
||||
throw workspaceQueryRunnerRestApiExceptionHandler(error);
|
||||
return workspaceQueryRunnerRestApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ObjectRecord } from 'twenty-shared/types';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { CommonMergeManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-merge-many-query-runner.service';
|
||||
import { parseDepthRestRequest } from 'src/engine/api/rest/input-request-parsers/depth-parser-utils/parse-depth-rest-request.util';
|
||||
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
|
||||
import { workspaceQueryRunnerRestApiExceptionHandler } from 'src/engine/api/rest/utils/workspace-query-runner-rest-api-exception-handler.util';
|
||||
|
||||
@Injectable()
|
||||
export class RestApiMergeManyHandler extends RestApiBaseHandler {
|
||||
constructor(
|
||||
private readonly commonMergeManyQueryRunnerService: CommonMergeManyQueryRunnerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async handle(request: AuthenticatedRequest) {
|
||||
try {
|
||||
const { depth, ...restArgs } = await this.parseRequestArgs(request);
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
} = await this.buildCommonOptions(request);
|
||||
|
||||
const selectedFields = await this.computeSelectedFields({
|
||||
depth,
|
||||
objectMetadataMapItem: objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
authContext,
|
||||
});
|
||||
|
||||
const record = await this.commonMergeManyQueryRunnerService.execute(
|
||||
{ ...restArgs, selectedFields },
|
||||
{
|
||||
authContext,
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
},
|
||||
);
|
||||
|
||||
return this.formatRestResponse(
|
||||
record,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
} catch (error) {
|
||||
return workspaceQueryRunnerRestApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
private formatRestResponse(record: ObjectRecord, objectNamePlural: string) {
|
||||
return { data: { [`merge${capitalize(objectNamePlural)}`]: record } };
|
||||
}
|
||||
|
||||
private async parseRequestArgs(request: AuthenticatedRequest) {
|
||||
const depth = parseDepthRestRequest(request);
|
||||
|
||||
return {
|
||||
conflictPriorityIndex: request.body.conflictPriorityIndex,
|
||||
dryRun: request.body.dryRun,
|
||||
ids: request.body.ids,
|
||||
depth,
|
||||
};
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ObjectRecord } from 'twenty-shared/types';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { CommonRestoreManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-restore-many-query-runner.service';
|
||||
import { parseDepthRestRequest } from 'src/engine/api/rest/input-request-parsers/depth-parser-utils/parse-depth-rest-request.util';
|
||||
import { parseFilterRestRequest } from 'src/engine/api/rest/input-request-parsers/filter-parser-utils/parse-filter-rest-request.util';
|
||||
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
|
||||
import { workspaceQueryRunnerRestApiExceptionHandler } from 'src/engine/api/rest/utils/workspace-query-runner-rest-api-exception-handler.util';
|
||||
|
||||
@Injectable()
|
||||
export class RestApiRestoreManyHandler extends RestApiBaseHandler {
|
||||
constructor(
|
||||
private readonly commonRestoreManyQueryRunnerService: CommonRestoreManyQueryRunnerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async handle(request: AuthenticatedRequest): Promise<{
|
||||
data: {
|
||||
[x: string]: ObjectRecord[];
|
||||
};
|
||||
}> {
|
||||
try {
|
||||
const { filter, depth } = this.parseRequestArgs(request);
|
||||
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
} = await this.buildCommonOptions(request);
|
||||
|
||||
const selectedFields = await this.computeSelectedFields({
|
||||
depth,
|
||||
objectMetadataMapItem: objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
authContext,
|
||||
});
|
||||
|
||||
const records = await this.commonRestoreManyQueryRunnerService.execute(
|
||||
{ filter, selectedFields },
|
||||
{
|
||||
authContext,
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
},
|
||||
);
|
||||
|
||||
return this.formatRestResponse(
|
||||
records,
|
||||
objectMetadataItemWithFieldMaps.namePlural,
|
||||
);
|
||||
} catch (error) {
|
||||
return workspaceQueryRunnerRestApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
private formatRestResponse(
|
||||
records: ObjectRecord[],
|
||||
objectNamePlural: string,
|
||||
) {
|
||||
return {
|
||||
data: {
|
||||
[`restore${capitalize(objectNamePlural)}`]: records,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private parseRequestArgs(request: AuthenticatedRequest) {
|
||||
const filter = parseFilterRestRequest(request);
|
||||
|
||||
return {
|
||||
filter,
|
||||
depth: parseDepthRestRequest(request),
|
||||
};
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
|
||||
import { ObjectRecord } from 'twenty-shared/types';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { CommonRestoreOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-restore-one-query-runner.service';
|
||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||
import { parseDepthRestRequest } from 'src/engine/api/rest/input-request-parsers/depth-parser-utils/parse-depth-rest-request.util';
|
||||
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
|
||||
import { workspaceQueryRunnerRestApiExceptionHandler } from 'src/engine/api/rest/utils/workspace-query-runner-rest-api-exception-handler.util';
|
||||
|
||||
@Injectable()
|
||||
export class RestApiRestoreOneHandler extends RestApiBaseHandler {
|
||||
constructor(
|
||||
private readonly commonRestoreOneQueryRunnerService: CommonRestoreOneQueryRunnerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async handle(request: AuthenticatedRequest) {
|
||||
try {
|
||||
const { id, depth } = this.parseRequestArgs(request);
|
||||
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
} = await this.buildCommonOptions(request);
|
||||
|
||||
const selectedFields = await this.computeSelectedFields({
|
||||
depth,
|
||||
objectMetadataMapItem: objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
authContext,
|
||||
});
|
||||
|
||||
const record = await this.commonRestoreOneQueryRunnerService.execute(
|
||||
{ id, selectedFields },
|
||||
{
|
||||
authContext,
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
},
|
||||
);
|
||||
|
||||
return this.formatRestResponse(
|
||||
record,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
} catch (error) {
|
||||
return workspaceQueryRunnerRestApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
private formatRestResponse(record: ObjectRecord, objectNameSingular: string) {
|
||||
return {
|
||||
data: { [`restore${capitalize(objectNameSingular)}`]: record },
|
||||
};
|
||||
}
|
||||
|
||||
private parseRequestArgs(request: AuthenticatedRequest) {
|
||||
const { id } = parseCorePath(request);
|
||||
|
||||
if (!isDefined(id)) {
|
||||
throw new BadRequestException('Record ID not found');
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
depth: parseDepthRestRequest(request),
|
||||
};
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ObjectRecord } from 'twenty-shared/types';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { CommonUpdateManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-update-many-query-runner.service';
|
||||
import { parseDepthRestRequest } from 'src/engine/api/rest/input-request-parsers/depth-parser-utils/parse-depth-rest-request.util';
|
||||
import { parseFilterRestRequest } from 'src/engine/api/rest/input-request-parsers/filter-parser-utils/parse-filter-rest-request.util';
|
||||
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
|
||||
import { workspaceQueryRunnerRestApiExceptionHandler } from 'src/engine/api/rest/utils/workspace-query-runner-rest-api-exception-handler.util';
|
||||
|
||||
@Injectable()
|
||||
export class RestApiUpdateManyHandler extends RestApiBaseHandler {
|
||||
constructor(
|
||||
private readonly commonUpdateManyQueryRunnerService: CommonUpdateManyQueryRunnerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async handle(request: AuthenticatedRequest) {
|
||||
try {
|
||||
const { data, depth, filter } = this.parseRequestArgs(request);
|
||||
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
} = await this.buildCommonOptions(request);
|
||||
|
||||
const selectedFields = await this.computeSelectedFields({
|
||||
depth,
|
||||
objectMetadataMapItem: objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
authContext,
|
||||
});
|
||||
|
||||
const records = await this.commonUpdateManyQueryRunnerService.execute(
|
||||
{ data, filter, selectedFields },
|
||||
{
|
||||
authContext,
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
},
|
||||
);
|
||||
|
||||
return this.formatRestResponse(
|
||||
records,
|
||||
objectMetadataItemWithFieldMaps.namePlural,
|
||||
);
|
||||
} catch (error) {
|
||||
return workspaceQueryRunnerRestApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
private formatRestResponse(
|
||||
records: ObjectRecord[],
|
||||
objectNamePlural: string,
|
||||
) {
|
||||
return {
|
||||
data: {
|
||||
[`update${capitalize(objectNamePlural)}`]: records,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private parseRequestArgs(request: AuthenticatedRequest) {
|
||||
return {
|
||||
data: request.body,
|
||||
depth: parseDepthRestRequest(request),
|
||||
filter: parseFilterRestRequest(request),
|
||||
};
|
||||
}
|
||||
}
|
||||
+63
-1
@@ -5,17 +5,79 @@ import {
|
||||
} from '@nestjs/common';
|
||||
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ObjectRecord } from 'twenty-shared/types';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { CommonUpdateOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-update-one-query-runner.service';
|
||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||
import { parseDepthRestRequest } from 'src/engine/api/rest/input-request-parsers/depth-parser-utils/parse-depth-rest-request.util';
|
||||
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
|
||||
import { workspaceQueryRunnerRestApiExceptionHandler } from 'src/engine/api/rest/utils/workspace-query-runner-rest-api-exception-handler.util';
|
||||
import { getAllSelectableFields } from 'src/engine/api/utils/get-all-selectable-fields.utils';
|
||||
|
||||
@Injectable()
|
||||
export class RestApiUpdateOneHandler extends RestApiBaseHandler {
|
||||
constructor(
|
||||
private readonly commonUpdateOneQueryRunnerService: CommonUpdateOneQueryRunnerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async commonHandle(request: AuthenticatedRequest) {
|
||||
try {
|
||||
const { id, data, depth } = this.parseRequestArgs(request);
|
||||
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
} = await this.buildCommonOptions(request);
|
||||
|
||||
const selectedFields = await this.computeSelectedFields({
|
||||
depth,
|
||||
objectMetadataMapItem: objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
authContext,
|
||||
});
|
||||
|
||||
const record = await this.commonUpdateOneQueryRunnerService.execute(
|
||||
{ id, data, selectedFields },
|
||||
{
|
||||
authContext,
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
},
|
||||
);
|
||||
|
||||
return this.formatRestResponse(
|
||||
record,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
} catch (error) {
|
||||
workspaceQueryRunnerRestApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
private formatRestResponse(record: ObjectRecord, objectNameSingular: string) {
|
||||
return { data: { [`update${capitalize(objectNameSingular)}`]: record } };
|
||||
}
|
||||
|
||||
private parseRequestArgs(request: AuthenticatedRequest) {
|
||||
const { id } = parseCorePath(request);
|
||||
|
||||
if (!id) {
|
||||
throw new BadRequestException('Record ID not found');
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
data: request.body,
|
||||
depth: parseDepthRestRequest(request),
|
||||
};
|
||||
}
|
||||
|
||||
async handle(request: AuthenticatedRequest) {
|
||||
const { id: recordId } = parseCorePath(request);
|
||||
|
||||
|
||||
+23
-3
@@ -1,7 +1,7 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
import { type Request } from 'express';
|
||||
import { isValidUuid } from 'twenty-shared/utils';
|
||||
import { isDefined, isValidUuid } from 'twenty-shared/utils';
|
||||
|
||||
export const parseCorePath = (
|
||||
request: Request,
|
||||
@@ -12,7 +12,10 @@ export const parseCorePath = (
|
||||
.split('/')
|
||||
.filter(Boolean);
|
||||
|
||||
if (queryAction.length > 2) {
|
||||
if (
|
||||
queryAction.length > 2 ||
|
||||
(queryAction.length > 3 && queryAction[0] === 'restore')
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
`Query path '${request.path}' invalid. Valid examples: /rest/companies/id or /rest/companies or /rest/batch/companies`,
|
||||
);
|
||||
@@ -32,10 +35,27 @@ export const parseCorePath = (
|
||||
return { object: queryAction[1] };
|
||||
}
|
||||
|
||||
if (queryAction[1] === 'duplicates' || queryAction[1] === 'group') {
|
||||
if (
|
||||
queryAction[1] === 'duplicates' ||
|
||||
queryAction[1] === 'group' ||
|
||||
queryAction[1] === 'merge'
|
||||
) {
|
||||
return { object: queryAction[0] };
|
||||
}
|
||||
|
||||
if (queryAction[0] === 'restore') {
|
||||
const recordId = queryAction[2];
|
||||
|
||||
if (isDefined(recordId) && !isValidUuid(recordId)) {
|
||||
throw new BadRequestException(`'${recordId}' is not a valid UUID`);
|
||||
}
|
||||
|
||||
return {
|
||||
object: queryAction[1],
|
||||
id: recordId,
|
||||
};
|
||||
}
|
||||
|
||||
const recordId = queryAction[1];
|
||||
|
||||
if (!isValidUuid(recordId)) {
|
||||
|
||||
@@ -13,6 +13,10 @@ import { RestApiFindDuplicatesHandler } from 'src/engine/api/rest/core/handlers/
|
||||
import { RestApiFindManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-find-many.handler';
|
||||
import { RestApiFindOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-find-one.handler';
|
||||
import { RestApiGroupByHandler } from 'src/engine/api/rest/core/handlers/rest-api-group-by.handler';
|
||||
import { RestApiMergeManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-merge-many.handler';
|
||||
import { RestApiRestoreManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-restore-many.handler';
|
||||
import { RestApiRestoreOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-restore-one.handler';
|
||||
import { RestApiUpdateManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-update-many.handler';
|
||||
import { RestApiUpdateOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-update-one.handler';
|
||||
import { CoreQueryBuilderModule } from 'src/engine/api/rest/core/query-builder/core-query-builder.module';
|
||||
import { coreQueryBuilderFactories } from 'src/engine/api/rest/core/query-builder/factories/factories';
|
||||
@@ -33,6 +37,8 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
|
||||
const restApiCoreResolvers = [
|
||||
RestApiCreateOneHandler,
|
||||
RestApiCreateManyHandler,
|
||||
RestApiUpdateOneHandler,
|
||||
RestApiUpdateManyHandler,
|
||||
RestApiFindOneHandler,
|
||||
RestApiFindManyHandler,
|
||||
RestApiFindDuplicatesHandler,
|
||||
@@ -42,6 +48,9 @@ const restApiCoreResolvers = [
|
||||
RestApiDestroyManyHandler,
|
||||
RestApiDeleteOneHandler,
|
||||
RestApiDeleteManyHandler,
|
||||
RestApiRestoreOneHandler,
|
||||
RestApiRestoreManyHandler,
|
||||
RestApiMergeManyHandler,
|
||||
];
|
||||
|
||||
@Module({
|
||||
|
||||
@@ -12,6 +12,10 @@ import { RestApiFindDuplicatesHandler } from 'src/engine/api/rest/core/handlers/
|
||||
import { RestApiFindManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-find-many.handler';
|
||||
import { RestApiFindOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-find-one.handler';
|
||||
import { RestApiGroupByHandler } from 'src/engine/api/rest/core/handlers/rest-api-group-by.handler';
|
||||
import { RestApiMergeManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-merge-many.handler';
|
||||
import { RestApiRestoreManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-restore-many.handler';
|
||||
import { RestApiRestoreOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-restore-one.handler';
|
||||
import { RestApiUpdateManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-update-many.handler';
|
||||
import { RestApiUpdateOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-update-one.handler';
|
||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||
import { parseSoftDeleteRestRequest } from 'src/engine/api/rest/input-request-parsers/soft-delete-parser-utils/parse-soft-delete-rest-request.util';
|
||||
@@ -24,15 +28,19 @@ export class RestApiCoreService {
|
||||
constructor(
|
||||
private readonly restApiCreateOneHandler: RestApiCreateOneHandler,
|
||||
private readonly restApiCreateManyHandler: RestApiCreateManyHandler,
|
||||
private readonly restApiUpdateOneHandler: RestApiUpdateOneHandler,
|
||||
private readonly restApiUpdateManyHandler: RestApiUpdateManyHandler,
|
||||
private readonly restApiFindOneHandler: RestApiFindOneHandler,
|
||||
private readonly restApiFindManyHandler: RestApiFindManyHandler,
|
||||
private readonly restApiFindDuplicatesHandler: RestApiFindDuplicatesHandler,
|
||||
private readonly restApiGroupByHandler: RestApiGroupByHandler,
|
||||
private readonly restApiUpdateOneHandler: RestApiUpdateOneHandler,
|
||||
private readonly restApiDestroyOneHandler: RestApiDestroyOneHandler,
|
||||
private readonly restApiDestroyManyHandler: RestApiDestroyManyHandler,
|
||||
private readonly restApiDeleteOneHandler: RestApiDeleteOneHandler,
|
||||
private readonly restApiDeleteManyHandler: RestApiDeleteManyHandler,
|
||||
private readonly restApiRestoreOneHandler: RestApiRestoreOneHandler,
|
||||
private readonly restApiRestoreManyHandler: RestApiRestoreManyHandler,
|
||||
private readonly restApiMergeManyHandler: RestApiMergeManyHandler,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
@@ -63,6 +71,37 @@ export class RestApiCoreService {
|
||||
}
|
||||
}
|
||||
|
||||
async findDuplicates(request: AuthenticatedRequest) {
|
||||
const isCommonApiEnabled = await this.isCommonApiEnabled(request);
|
||||
|
||||
if (isCommonApiEnabled) {
|
||||
return await this.restApiFindDuplicatesHandler.commonHandle(request);
|
||||
} else {
|
||||
return await this.restApiFindDuplicatesHandler.handle(request);
|
||||
}
|
||||
}
|
||||
|
||||
async update(request: AuthenticatedRequest) {
|
||||
const { id: recordId } = parseCorePath(request);
|
||||
const isCommonApiEnabled = await this.isCommonApiEnabled(request);
|
||||
|
||||
if (isCommonApiEnabled) {
|
||||
if (isDefined(recordId)) {
|
||||
return await this.restApiUpdateOneHandler.commonHandle(request);
|
||||
} else {
|
||||
return await this.restApiUpdateManyHandler.handle(request);
|
||||
}
|
||||
} else {
|
||||
if (isDefined(recordId)) {
|
||||
return await this.restApiUpdateOneHandler.handle(request);
|
||||
} else {
|
||||
throw new BadRequestException(
|
||||
'Activate feature flag to use UpdateMany in the REST API',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async get(request: AuthenticatedRequest) {
|
||||
const { id: recordId } = parseCorePath(request);
|
||||
const isCommonApiEnabled = await this.isCommonApiEnabled(request);
|
||||
@@ -94,14 +133,6 @@ export class RestApiCoreService {
|
||||
}
|
||||
}
|
||||
|
||||
async findDuplicates(request: AuthenticatedRequest) {
|
||||
return await this.restApiFindDuplicatesHandler.handle(request);
|
||||
}
|
||||
|
||||
async update(request: AuthenticatedRequest) {
|
||||
return await this.restApiUpdateOneHandler.handle(request);
|
||||
}
|
||||
|
||||
async delete(request: AuthenticatedRequest) {
|
||||
const { id: recordId } = parseCorePath(request);
|
||||
|
||||
@@ -125,4 +156,34 @@ export class RestApiCoreService {
|
||||
'Activate feature flag IS_COMMON_API_ENABLED to use Delete in the REST API',
|
||||
);
|
||||
}
|
||||
|
||||
async restore(request: AuthenticatedRequest) {
|
||||
const { id: recordId } = parseCorePath(request);
|
||||
|
||||
const isCommonApiEnabled = await this.isCommonApiEnabled(request);
|
||||
|
||||
if (isCommonApiEnabled) {
|
||||
if (isDefined(recordId)) {
|
||||
return await this.restApiRestoreOneHandler.handle(request);
|
||||
} else {
|
||||
return await this.restApiRestoreManyHandler.handle(request);
|
||||
}
|
||||
} else {
|
||||
throw new BadRequestException(
|
||||
'Activate feature flag to use Restore in the REST API',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async mergeMany(request: AuthenticatedRequest) {
|
||||
const isCommonApiEnabled = await this.isCommonApiEnabled(request);
|
||||
|
||||
if (isCommonApiEnabled) {
|
||||
return await this.restApiMergeManyHandler.handle(request);
|
||||
} else {
|
||||
throw new BadRequestException(
|
||||
'Activate feature flag to use Merge in the REST API',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,9 @@ import {
|
||||
computeBatchPath,
|
||||
computeDuplicatesResultPath,
|
||||
computeManyResultPath,
|
||||
computeMergeManyResultPath,
|
||||
computeRestoreManyResultPath,
|
||||
computeRestoreOneResultPath,
|
||||
computeSingleResultPath,
|
||||
} from 'src/engine/core-modules/open-api/utils/path.utils';
|
||||
import {
|
||||
@@ -43,12 +46,12 @@ import {
|
||||
} from 'src/engine/core-modules/open-api/utils/responses.utils';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
|
||||
import { shouldExcludeFromWorkspaceApi } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/should-exclude-from-workspace-api.util';
|
||||
import { getServerUrl } from 'src/utils/get-server-url';
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
|
||||
@Injectable()
|
||||
export class OpenApiService {
|
||||
@@ -125,6 +128,10 @@ export class OpenApiService {
|
||||
paths[`/${item.namePlural}/{id}`] = computeSingleResultPath(item);
|
||||
paths[`/${item.namePlural}/duplicates`] =
|
||||
computeDuplicatesResultPath(item);
|
||||
paths[`/restore/${item.namePlural}/{id}`] =
|
||||
computeRestoreOneResultPath(item);
|
||||
paths[`/restore/${item.namePlural}`] = computeRestoreManyResultPath(item);
|
||||
paths[`/${item.namePlural}/merge`] = computeMergeManyResultPath(item);
|
||||
|
||||
return paths;
|
||||
}, schema.paths as OpenAPIV3_1.PathsObject);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { capitalize } from 'twenty-shared/utils';
|
||||
import {
|
||||
getArrayRequestBody,
|
||||
getFindDuplicatesRequestBody,
|
||||
getMergeManyRequestBody,
|
||||
getRequestBody,
|
||||
getUpdateRequestBody,
|
||||
} from 'src/engine/core-modules/open-api/utils/request-body.utils';
|
||||
@@ -16,6 +17,10 @@ import {
|
||||
getFindManyResponse200,
|
||||
getFindOneResponse200,
|
||||
getJsonResponse,
|
||||
getMergeManyResponse200,
|
||||
getRestoreManyResponse200,
|
||||
getRestoreOneResponse200,
|
||||
getUpdateManyResponse200,
|
||||
getUpdateOneResponse200,
|
||||
} from 'src/engine/core-modules/open-api/utils/responses.utils';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
@@ -94,6 +99,21 @@ export const computeManyResultPath = (
|
||||
'401': { $ref: '#/components/responses/401' },
|
||||
},
|
||||
},
|
||||
patch: {
|
||||
tags: [item.namePlural],
|
||||
summary: `Update Many ${item.namePlural}`,
|
||||
operationId: `updateMany${capitalize(item.namePlural)}`,
|
||||
parameters: [
|
||||
{ $ref: '#/components/parameters/depth' },
|
||||
{ $ref: '#/components/parameters/filter' },
|
||||
],
|
||||
requestBody: getUpdateRequestBody(capitalize(item.nameSingular)),
|
||||
responses: {
|
||||
'200': getUpdateManyResponse200(item),
|
||||
'400': { $ref: '#/components/responses/400' },
|
||||
'401': { $ref: '#/components/responses/401' },
|
||||
},
|
||||
},
|
||||
} as OpenAPIV3_1.PathItemObject;
|
||||
};
|
||||
|
||||
@@ -187,3 +207,64 @@ export const computeDuplicatesResultPath = (
|
||||
},
|
||||
} as OpenAPIV3_1.PathItemObject;
|
||||
};
|
||||
|
||||
export const computeRestoreOneResultPath = (
|
||||
item: ObjectMetadataEntity,
|
||||
): OpenAPIV3_1.PathItemObject => {
|
||||
return {
|
||||
patch: {
|
||||
tags: [item.namePlural],
|
||||
summary: `Restore One ${item.nameSingular}`,
|
||||
operationId: `restoreOne${capitalize(item.nameSingular)}`,
|
||||
parameters: [
|
||||
{ $ref: '#/components/parameters/idPath' },
|
||||
{ $ref: '#/components/parameters/depth' },
|
||||
],
|
||||
responses: {
|
||||
'200': getRestoreOneResponse200(item),
|
||||
'400': { $ref: '#/components/responses/400' },
|
||||
'401': { $ref: '#/components/responses/401' },
|
||||
},
|
||||
},
|
||||
} as OpenAPIV3_1.PathItemObject;
|
||||
};
|
||||
|
||||
export const computeRestoreManyResultPath = (
|
||||
item: ObjectMetadataEntity,
|
||||
): OpenAPIV3_1.PathItemObject => {
|
||||
return {
|
||||
patch: {
|
||||
tags: [item.namePlural],
|
||||
summary: `Restore Many ${item.namePlural}`,
|
||||
operationId: `restoreMany${capitalize(item.namePlural)}`,
|
||||
parameters: [
|
||||
{ $ref: '#/components/parameters/filter' },
|
||||
{ $ref: '#/components/parameters/depth' },
|
||||
],
|
||||
responses: {
|
||||
'200': getRestoreManyResponse200(item),
|
||||
'400': { $ref: '#/components/responses/400' },
|
||||
'401': { $ref: '#/components/responses/401' },
|
||||
},
|
||||
},
|
||||
} as OpenAPIV3_1.PathItemObject;
|
||||
};
|
||||
|
||||
export const computeMergeManyResultPath = (
|
||||
item: ObjectMetadataEntity,
|
||||
): OpenAPIV3_1.PathItemObject => {
|
||||
return {
|
||||
patch: {
|
||||
tags: [item.namePlural],
|
||||
summary: `Merge Many ${item.namePlural}`,
|
||||
operationId: `mergeMany${capitalize(item.namePlural)}`,
|
||||
parameters: [{ $ref: '#/components/parameters/depth' }],
|
||||
requestBody: getMergeManyRequestBody(),
|
||||
responses: {
|
||||
'200': getMergeManyResponse200(item),
|
||||
'400': { $ref: '#/components/responses/400' },
|
||||
'401': { $ref: '#/components/responses/401' },
|
||||
},
|
||||
},
|
||||
} as OpenAPIV3_1.PathItemObject;
|
||||
};
|
||||
|
||||
@@ -73,3 +73,40 @@ export const getFindDuplicatesRequestBody = (name: string) => {
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getMergeManyRequestBody = () => {
|
||||
return {
|
||||
description: 'body',
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
ids: {
|
||||
type: 'array',
|
||||
description: 'The IDs of the records to merge',
|
||||
items: {
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
},
|
||||
},
|
||||
conflictPriorityIndex: {
|
||||
type: 'number',
|
||||
description:
|
||||
'The index of the record to use when conflicts occur',
|
||||
},
|
||||
dryRun: {
|
||||
description:
|
||||
'If true, the merge will not be performed and a preview of the merge will be returned.',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
example: { ids: [v4()], conflictPriorityIndex: 0, dryRun: false },
|
||||
required: ['ids', 'conflictPriorityIndex'],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -81,6 +81,65 @@ export const getFindOneResponse200 = (
|
||||
};
|
||||
};
|
||||
|
||||
export const getRestoreOneResponse200 = (
|
||||
item: Pick<ObjectMetadataEntity, 'nameSingular'>,
|
||||
) => {
|
||||
const schemaRef = `#/components/schemas/${capitalize(item.nameSingular)}ForResponse`;
|
||||
|
||||
return {
|
||||
description: 'Successful operation',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[`restore${capitalize(item.nameSingular)}`]: {
|
||||
$ref: schemaRef,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getRestoreManyResponse200 = (
|
||||
item: Pick<ObjectMetadataEntity, 'nameSingular' | 'namePlural'>,
|
||||
) => {
|
||||
const schemaRef = `#/components/schemas/${capitalize(
|
||||
item.nameSingular,
|
||||
)}ForResponse`;
|
||||
|
||||
return {
|
||||
description: 'Successful operation',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[`restore${capitalize(item.namePlural)}`]: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: schemaRef,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getCreateOneResponse201 = (
|
||||
item: Pick<ObjectMetadataEntity, 'nameSingular'>,
|
||||
fromMetadata = false,
|
||||
@@ -208,6 +267,36 @@ export const getDeleteManyResponse200 = (
|
||||
};
|
||||
};
|
||||
|
||||
export const getUpdateManyResponse200 = (
|
||||
item: Pick<ObjectMetadataEntity, 'namePlural' | 'nameSingular'>,
|
||||
) => {
|
||||
const schemaRef = `#/components/schemas/${capitalize(item.nameSingular)}ForResponse`;
|
||||
|
||||
return {
|
||||
description: 'Successful operation',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[`update${capitalize(item.namePlural)}`]: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: schemaRef,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getDeleteResponse200 = (
|
||||
item: Pick<ObjectMetadataEntity, 'nameSingular'>,
|
||||
fromMetadata = false,
|
||||
@@ -350,3 +439,32 @@ export const getFindDuplicatesResponse200 = (
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getMergeManyResponse200 = (
|
||||
item: Pick<ObjectMetadataEntity, 'nameSingular' | 'namePlural'>,
|
||||
) => {
|
||||
const schemaRef = `#/components/schemas/${capitalize(
|
||||
item.nameSingular,
|
||||
)}ForResponse`;
|
||||
|
||||
return {
|
||||
description: 'Successful operation',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
[`merge${capitalize(item.namePlural)}`]: {
|
||||
$ref: schemaRef,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user