Skip to content

Commit 10f4f15

Browse files
authored
Common - Update one/many, restore one/many, findDuplicates & mergeMany (#15279)
closes twentyhq/core-team-issues#1739
1 parent 8f60f7e commit 10f4f15

File tree

60 files changed

+2346
-74
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2346
-74
lines changed

packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,11 @@ export abstract class CommonBaseQueryRunnerService<
7272
@Inject()
7373
protected readonly commonResultGettersService: CommonResultGettersService;
7474

75+
protected abstract readonly operationName: CommonQueryNames;
76+
7577
public async execute(
7678
args: CommonInput<Args>,
7779
queryRunnerContext: CommonBaseQueryRunnerContext,
78-
operationName: CommonQueryNames,
7980
): Promise<Output> {
8081
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
8182
queryRunnerContext;
@@ -104,7 +105,7 @@ export abstract class CommonBaseQueryRunnerService<
104105
const processedArgs = await this.processArgs(
105106
args,
106107
queryRunnerContext,
107-
operationName,
108+
this.operationName,
108109
commonQueryParser,
109110
);
110111

@@ -121,7 +122,7 @@ export abstract class CommonBaseQueryRunnerService<
121122

122123
return this.enrichResultsWithGettersAndHooks({
123124
results,
124-
operationName,
125+
operationName: this.operationName,
125126
authContext,
126127
objectMetadataItemWithFieldMaps,
127128
objectMetadataMaps,

packages/twenty-server/src/engine/api/common/common-query-runners/common-create-many-query-runner/common-create-many-query-runner.service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/co
2121
import {
2222
CommonExtendedInput,
2323
CommonInput,
24+
CommonQueryNames,
2425
CreateManyQueryArgs,
2526
} from 'src/engine/api/common/types/common-query-args.type';
2627
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
3435
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
3536
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
3637
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
37-
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
38+
import { RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
3839

3940
@Injectable()
4041
export class CommonCreateManyQueryRunnerService extends CommonBaseQueryRunnerService<
4142
CreateManyQueryArgs,
4243
ObjectRecord[]
4344
> {
45+
protected readonly operationName = CommonQueryNames.CREATE_MANY;
4446
async run(
4547
args: CommonExtendedInput<CreateManyQueryArgs>,
4648
queryRunnerContext: CommonExtendedQueryRunnerContext,

packages/twenty-server/src/engine/api/common/common-query-runners/common-create-one-query-runner.service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/co
1111
import {
1212
CommonExtendedInput,
1313
CommonInput,
14+
CommonQueryNames,
1415
CreateManyQueryArgs,
1516
CreateOneQueryArgs,
1617
} from 'src/engine/api/common/types/common-query-args.type';
@@ -29,6 +30,8 @@ export class CommonCreateOneQueryRunnerService extends CommonBaseQueryRunnerServ
2930
super();
3031
}
3132

33+
protected readonly operationName = CommonQueryNames.CREATE_ONE;
34+
3235
async run(
3336
args: CommonExtendedInput<CreateManyQueryArgs>,
3437
queryRunnerContext: CommonExtendedQueryRunnerContext,

packages/twenty-server/src/engine/api/common/common-query-runners/common-delete-many-query-runner.service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/co
1717
import {
1818
CommonExtendedInput,
1919
CommonInput,
20+
CommonQueryNames,
2021
DeleteManyQueryArgs,
2122
} from 'src/engine/api/common/types/common-query-args.type';
2223
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
@@ -30,6 +31,8 @@ export class CommonDeleteManyQueryRunnerService extends CommonBaseQueryRunnerSer
3031
DeleteManyQueryArgs,
3132
ObjectRecord[]
3233
> {
34+
protected readonly operationName = CommonQueryNames.DELETE_MANY;
35+
3336
async run(
3437
args: CommonExtendedInput<DeleteManyQueryArgs>,
3538
queryRunnerContext: CommonExtendedQueryRunnerContext,

packages/twenty-server/src/engine/api/common/common-query-runners/common-delete-one-query-runner.service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/co
1616
import {
1717
CommonExtendedInput,
1818
CommonInput,
19+
CommonQueryNames,
1920
DeleteOneQueryArgs,
2021
} from 'src/engine/api/common/types/common-query-args.type';
2122
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
3334
super();
3435
}
3536

37+
protected readonly operationName = CommonQueryNames.DELETE_ONE;
38+
3639
async run(
3740
args: CommonExtendedInput<DeleteOneQueryArgs>,
3841
queryRunnerContext: CommonExtendedQueryRunnerContext,

packages/twenty-server/src/engine/api/common/common-query-runners/common-destroy-many-query-runner.service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/co
1717
import {
1818
CommonExtendedInput,
1919
CommonInput,
20+
CommonQueryNames,
2021
DestroyManyQueryArgs,
2122
} from 'src/engine/api/common/types/common-query-args.type';
2223
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
@@ -27,6 +28,8 @@ export class CommonDestroyManyQueryRunnerService extends CommonBaseQueryRunnerSe
2728
DestroyManyQueryArgs,
2829
ObjectRecord[]
2930
> {
31+
protected readonly operationName = CommonQueryNames.DESTROY_MANY;
32+
3033
async run(
3134
args: CommonExtendedInput<DestroyManyQueryArgs>,
3235
queryRunnerContext: CommonExtendedQueryRunnerContext,

packages/twenty-server/src/engine/api/common/common-query-runners/common-destroy-one-query-runner.service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/co
1616
import {
1717
CommonExtendedInput,
1818
CommonInput,
19+
CommonQueryNames,
1920
DestroyOneQueryArgs,
2021
} from 'src/engine/api/common/types/common-query-args.type';
2122
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
@@ -31,6 +32,8 @@ export class CommonDestroyOneQueryRunnerService extends CommonBaseQueryRunnerSer
3132
super();
3233
}
3334

35+
protected readonly operationName = CommonQueryNames.DESTROY_ONE;
36+
3437
async run(
3538
args: CommonExtendedInput<DestroyOneQueryArgs>,
3639
queryRunnerContext: CommonExtendedQueryRunnerContext,
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
import { Injectable } from '@nestjs/common';
2+
3+
import isEmpty from 'lodash.isempty';
4+
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
5+
import { ObjectRecord, OrderByDirection } from 'twenty-shared/types';
6+
import { isDefined } from 'twenty-shared/utils';
7+
import { In } from 'typeorm';
8+
9+
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
10+
11+
import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service';
12+
import {
13+
CommonQueryRunnerException,
14+
CommonQueryRunnerExceptionCode,
15+
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
16+
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
17+
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
18+
import { CommonFindDuplicatesOutputItem } from 'src/engine/api/common/types/common-find-duplicates-output-item.type';
19+
import {
20+
CommonExtendedInput,
21+
CommonInput,
22+
CommonQueryNames,
23+
FindDuplicatesQueryArgs,
24+
} from 'src/engine/api/common/types/common-query-args.type';
25+
import { getPageInfo } from 'src/engine/api/common/utils/get-page-info.util';
26+
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
27+
import { buildDuplicateConditions } from 'src/engine/api/utils/build-duplicate-conditions.utils';
28+
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
29+
30+
@Injectable()
31+
export class CommonFindDuplicatesQueryRunnerService extends CommonBaseQueryRunnerService<
32+
FindDuplicatesQueryArgs,
33+
CommonFindDuplicatesOutputItem[]
34+
> {
35+
protected readonly operationName = CommonQueryNames.FIND_DUPLICATES;
36+
37+
async run(
38+
args: CommonExtendedInput<FindDuplicatesQueryArgs>,
39+
queryRunnerContext: CommonExtendedQueryRunnerContext,
40+
): Promise<CommonFindDuplicatesOutputItem[]> {
41+
const {
42+
repository,
43+
objectMetadataItemWithFieldMaps,
44+
objectMetadataMaps,
45+
commonQueryParser,
46+
} = queryRunnerContext;
47+
48+
const existingRecordsQueryBuilder = repository.createQueryBuilder(
49+
objectMetadataItemWithFieldMaps.nameSingular,
50+
);
51+
52+
let objectRecords: Partial<ObjectRecord>[] = [];
53+
54+
const columnsToSelect = buildColumnsToSelect({
55+
select: args.selectedFieldsResult.select,
56+
relations: args.selectedFieldsResult.relations,
57+
objectMetadataItemWithFieldMaps,
58+
objectMetadataMaps,
59+
});
60+
61+
if (isDefined(args.ids)) {
62+
objectRecords = (await existingRecordsQueryBuilder
63+
.where({ id: In(args.ids) })
64+
.setFindOptions({
65+
select: columnsToSelect,
66+
})
67+
.getMany()) as ObjectRecord[];
68+
} else if (args.data && !isEmpty(args.data)) {
69+
objectRecords = args.data;
70+
}
71+
72+
const findDuplicatesOutput: CommonFindDuplicatesOutputItem[] =
73+
await Promise.all(
74+
objectRecords.map(async (record) => {
75+
const duplicateConditions = buildDuplicateConditions(
76+
objectMetadataItemWithFieldMaps,
77+
[record],
78+
record.id,
79+
);
80+
81+
if (isEmpty(duplicateConditions)) {
82+
return {
83+
records: [],
84+
totalCount: 0,
85+
hasNextPage: false,
86+
hasPreviousPage: false,
87+
startCursor: null,
88+
endCursor: null,
89+
};
90+
}
91+
92+
const duplicateRecordsQueryBuilder = repository.createQueryBuilder(
93+
objectMetadataItemWithFieldMaps.nameSingular,
94+
);
95+
96+
commonQueryParser.applyFilterToBuilder(
97+
duplicateRecordsQueryBuilder,
98+
objectMetadataItemWithFieldMaps.nameSingular,
99+
duplicateConditions,
100+
);
101+
102+
const duplicates = (await duplicateRecordsQueryBuilder
103+
.setFindOptions({
104+
select: columnsToSelect,
105+
})
106+
.take(QUERY_MAX_RECORDS)
107+
.getMany()) as ObjectRecord[];
108+
109+
const aggregateQueryBuilder = duplicateRecordsQueryBuilder.clone();
110+
const totalCount = await aggregateQueryBuilder.getCount();
111+
112+
const { startCursor, endCursor } = getPageInfo(
113+
duplicates,
114+
[{ id: OrderByDirection.AscNullsFirst }],
115+
QUERY_MAX_RECORDS,
116+
true,
117+
);
118+
119+
return {
120+
records: duplicates,
121+
totalCount,
122+
hasNextPage: false,
123+
hasPreviousPage: false,
124+
startCursor,
125+
endCursor,
126+
};
127+
}),
128+
);
129+
130+
return findDuplicatesOutput;
131+
}
132+
133+
async computeArgs(
134+
args: CommonInput<FindDuplicatesQueryArgs>,
135+
queryRunnerContext: CommonBaseQueryRunnerContext,
136+
): Promise<CommonInput<FindDuplicatesQueryArgs>> {
137+
const { authContext, objectMetadataItemWithFieldMaps } = queryRunnerContext;
138+
139+
return {
140+
...args,
141+
ids: await Promise.all(
142+
args.ids?.map((id) =>
143+
this.queryRunnerArgsFactory.overrideValueByFieldMetadata(
144+
'id',
145+
id,
146+
objectMetadataItemWithFieldMaps.fieldsById,
147+
objectMetadataItemWithFieldMaps,
148+
),
149+
) ?? [],
150+
),
151+
data: await this.queryRunnerArgsFactory.overrideDataByFieldMetadata({
152+
partialRecordInputs: args.data,
153+
authContext,
154+
objectMetadataItemWithFieldMaps,
155+
shouldBackfillPositionIfUndefined: false,
156+
}),
157+
};
158+
}
159+
160+
async processQueryResult(
161+
queryResult: CommonFindDuplicatesOutputItem[],
162+
objectMetadataItemId: string,
163+
objectMetadataMaps: ObjectMetadataMaps,
164+
authContext: WorkspaceAuthContext,
165+
): Promise<CommonFindDuplicatesOutputItem[]> {
166+
const processedResults = await Promise.all(
167+
queryResult.map(async (result) => {
168+
return {
169+
...result,
170+
records: await this.commonResultGettersService.processRecordArray(
171+
result.records,
172+
objectMetadataItemId,
173+
objectMetadataMaps,
174+
authContext.workspace.id,
175+
),
176+
};
177+
}),
178+
);
179+
180+
return processedResults;
181+
}
182+
183+
async validate(
184+
args: CommonInput<FindDuplicatesQueryArgs>,
185+
_queryRunnerContext: CommonBaseQueryRunnerContext,
186+
): Promise<void> {
187+
if (!args.data && !args.ids) {
188+
throw new CommonQueryRunnerException(
189+
'You have to provide either "data" or "ids" argument',
190+
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
191+
);
192+
}
193+
194+
if (args.data && args.ids) {
195+
throw new CommonQueryRunnerException(
196+
'You cannot provide both "data" and "ids" arguments',
197+
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
198+
);
199+
}
200+
201+
if (!args.ids && isEmpty(args.data)) {
202+
throw new CommonQueryRunnerException(
203+
'The "data" condition can not be empty when "ids" input not provided',
204+
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
205+
);
206+
}
207+
}
208+
}

packages/twenty-server/src/engine/api/common/common-query-runners/common-find-many-query-runner.service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { CommonFindManyOutput } from 'src/engine/api/common/types/common-find-ma
2222
import {
2323
CommonExtendedInput,
2424
CommonInput,
25+
CommonQueryNames,
2526
FindManyQueryArgs,
2627
} from 'src/engine/api/common/types/common-query-args.type';
2728
import { getPageInfo } from 'src/engine/api/common/utils/get-page-info.util';
@@ -36,6 +37,8 @@ export class CommonFindManyQueryRunnerService extends CommonBaseQueryRunnerServi
3637
FindManyQueryArgs,
3738
CommonFindManyOutput
3839
> {
40+
protected readonly operationName = CommonQueryNames.FIND_MANY;
41+
3942
async run(
4043
args: CommonExtendedInput<FindManyQueryArgs>,
4144
queryRunnerContext: CommonExtendedQueryRunnerContext,

packages/twenty-server/src/engine/api/common/common-query-runners/common-find-one-query-runner.service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/co
1818
import {
1919
CommonExtendedInput,
2020
CommonInput,
21+
CommonQueryNames,
2122
FindOneQueryArgs,
2223
} from 'src/engine/api/common/types/common-query-args.type';
2324
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
@@ -28,6 +29,8 @@ export class CommonFindOneQueryRunnerService extends CommonBaseQueryRunnerServic
2829
FindOneQueryArgs,
2930
ObjectRecord
3031
> {
32+
protected readonly operationName = CommonQueryNames.FIND_ONE;
33+
3134
async run(
3235
args: CommonExtendedInput<FindOneQueryArgs>,
3336
queryRunnerContext: CommonExtendedQueryRunnerContext,

0 commit comments

Comments
 (0)