Skip to content

Commit dc05f18

Browse files
authored
Common api - Destroy and delete queries (#15177)
Done ⬇️ Gql : move delete and destroy logic to common Rest : - rename 'delete' handler to 'destroy' - create soft delete handlers (named 'delete') : one and many - create destroy many handler - update doc --> Rest api gains NEW soft delete one/many + destroy many capabilities closes : twentyhq/core-team-issues#1579
1 parent ce87ea8 commit dc05f18

File tree

50 files changed

+1791
-595
lines changed

Some content is hidden

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

50 files changed

+1791
-595
lines changed

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

Lines changed: 155 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,33 @@
11
import { Inject, Injectable } from '@nestjs/common';
22

3-
import { type ObjectRecord } from 'twenty-shared/types';
43
import { isDefined } from 'twenty-shared/utils';
4+
import { Omit } from 'zod/v4/core/util.cjs';
55

66
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
7+
import { QueryResultFieldValue } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value';
78

89
import { CommonSelectedFieldsHandler } from 'src/engine/api/common/common-args-handlers/common-query-selected-fields/common-selected-fields.handler';
10+
import {
11+
CommonQueryRunnerException,
12+
CommonQueryRunnerExceptionCode,
13+
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
914
import { CommonResultGettersService } from 'src/engine/api/common/common-result-getters/common-result-getters.service';
10-
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
15+
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
16+
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
17+
import {
18+
CommonExtendedInput,
19+
CommonInput,
20+
CommonQueryArgs,
21+
CommonQueryNames,
22+
} from 'src/engine/api/common/types/common-query-args.type';
23+
import { CommonQueryResult } from 'src/engine/api/common/types/common-query-result.type';
24+
import { isWorkspaceAuthContext } from 'src/engine/api/common/utils/is-workspace-auth-context.util';
1125
import { OBJECTS_WITH_SETTINGS_PERMISSIONS_REQUIREMENTS } from 'src/engine/api/graphql/graphql-query-runner/constants/objects-with-settings-permissions-requirements';
26+
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
1227
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
1328
import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory';
1429
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
30+
import { WorkspacePreQueryHookPayload } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type';
1531
import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service';
1632
import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/api-key-role.service';
1733
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
@@ -29,7 +45,10 @@ import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/wo
2945
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
3046

3147
@Injectable()
32-
export abstract class CommonBaseQueryRunnerService {
48+
export abstract class CommonBaseQueryRunnerService<
49+
Args extends CommonQueryArgs,
50+
Output extends CommonQueryResult,
51+
> {
3352
@Inject()
3453
protected readonly workspaceQueryHookService: WorkspaceQueryHookService;
3554
@Inject()
@@ -53,82 +72,147 @@ export abstract class CommonBaseQueryRunnerService {
5372
@Inject()
5473
protected readonly commonResultGettersService: CommonResultGettersService;
5574

56-
public async prepareQueryRunnerContext({
57-
authContext,
58-
objectMetadataItemWithFieldMaps,
59-
}: {
60-
authContext: WorkspaceAuthContext;
61-
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
62-
}) {
75+
public async execute(
76+
args: CommonInput<Args>,
77+
queryRunnerContext: CommonBaseQueryRunnerContext,
78+
operationName: CommonQueryNames,
79+
): Promise<Output> {
80+
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
81+
queryRunnerContext;
82+
83+
if (!isWorkspaceAuthContext(authContext)) {
84+
throw new CommonQueryRunnerException(
85+
'Invalid auth context',
86+
CommonQueryRunnerExceptionCode.INVALID_AUTH_CONTEXT,
87+
);
88+
}
89+
90+
await this.validate(args, queryRunnerContext);
91+
6392
if (objectMetadataItemWithFieldMaps.isSystem === true) {
6493
await this.validateSettingsPermissionsOnObjectOrThrow(
6594
authContext,
66-
objectMetadataItemWithFieldMaps,
95+
queryRunnerContext,
6796
);
6897
}
6998

70-
const workspace = authContext.workspace;
71-
72-
const workspaceDataSource =
73-
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
74-
workspaceId: workspace.id,
75-
});
99+
const commonQueryParser = new GraphqlQueryParser(
100+
objectMetadataItemWithFieldMaps,
101+
objectMetadataMaps,
102+
);
76103

77-
const { roleId } = await this.getRoleIdAndObjectsPermissions(
78-
authContext,
79-
workspace.id,
104+
const processedArgs = await this.processArgs(
105+
args,
106+
queryRunnerContext,
107+
operationName,
108+
commonQueryParser,
80109
);
81110

82-
const rolePermissionConfig = { unionOf: [roleId] };
111+
const extendedQueryRunnerContext =
112+
await this.prepareExtendedQueryRunnerContext(
113+
authContext,
114+
queryRunnerContext,
115+
);
83116

84-
const repository = workspaceDataSource.getRepository(
85-
objectMetadataItemWithFieldMaps.nameSingular,
86-
rolePermissionConfig,
117+
const results = await this.run(processedArgs, {
118+
...extendedQueryRunnerContext,
119+
commonQueryParser,
120+
});
121+
122+
return this.enrichResultsWithGettersAndHooks({
123+
results,
124+
operationName,
87125
authContext,
126+
objectMetadataItemWithFieldMaps,
127+
objectMetadataMaps,
128+
});
129+
}
130+
131+
protected abstract run(
132+
args: CommonExtendedInput<Args>,
133+
queryRunnerContext: CommonExtendedQueryRunnerContext,
134+
): Promise<Output>;
135+
136+
protected abstract validate(
137+
args: CommonInput<Args>,
138+
queryRunnerContext: CommonBaseQueryRunnerContext,
139+
): Promise<void>;
140+
141+
protected abstract computeArgs(
142+
args: CommonInput<Args>,
143+
queryRunnerContext: CommonBaseQueryRunnerContext,
144+
): Promise<CommonInput<Args>>;
145+
146+
protected abstract processQueryResult(
147+
queryResult: Output,
148+
objectMetadataItemId: string,
149+
objectMetadataMaps: ObjectMetadataMaps,
150+
authContext: WorkspaceAuthContext,
151+
): Promise<Output>;
152+
153+
private async processArgs(
154+
args: CommonInput<Args>,
155+
queryRunnerContext: CommonBaseQueryRunnerContext,
156+
operationName: CommonQueryNames,
157+
commonQueryParser: GraphqlQueryParser,
158+
): Promise<CommonExtendedInput<Args>> {
159+
const selectedFieldsResult = commonQueryParser.parseSelectedFields(
160+
args.selectedFields,
88161
);
89162

163+
const { authContext, objectMetadataItemWithFieldMaps } = queryRunnerContext;
164+
const hookedArgs =
165+
(await this.workspaceQueryHookService.executePreQueryHooks(
166+
authContext,
167+
objectMetadataItemWithFieldMaps.nameSingular,
168+
operationName,
169+
args as WorkspacePreQueryHookPayload<CommonQueryNames>,
170+
)) as CommonInput<Args>;
171+
172+
const computedArgs = await this.computeArgs(hookedArgs, queryRunnerContext);
173+
90174
return {
91-
workspaceDataSource,
92-
repository,
93-
rolePermissionConfig,
175+
...computedArgs,
176+
selectedFieldsResult,
94177
};
95178
}
96179

97-
public async enrichResultsWithGettersAndHooks({
180+
private async enrichResultsWithGettersAndHooks({
98181
results,
99182
operationName,
100183
authContext,
101184
objectMetadataItemWithFieldMaps,
102185
objectMetadataMaps,
103186
}: {
104-
results: ObjectRecord[];
187+
results: Output;
105188
operationName: CommonQueryNames;
106189
authContext: WorkspaceAuthContext;
107190
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
108191
objectMetadataMaps: ObjectMetadataMaps;
109-
}): Promise<ObjectRecord[]> {
110-
const resultWithGetters =
111-
await this.commonResultGettersService.processQueryResult(
112-
results,
113-
objectMetadataItemWithFieldMaps.id,
114-
objectMetadataMaps,
115-
authContext.workspace.id,
116-
);
192+
}): Promise<Output> {
193+
const resultWithGetters = await this.processQueryResult(
194+
results,
195+
objectMetadataItemWithFieldMaps.id,
196+
objectMetadataMaps,
197+
authContext,
198+
);
117199

118200
await this.workspaceQueryHookService.executePostQueryHooks(
119201
authContext,
120202
objectMetadataItemWithFieldMaps.nameSingular,
121203
operationName,
122-
resultWithGetters,
204+
resultWithGetters as QueryResultFieldValue,
123205
);
124206

125-
return resultWithGetters;
207+
return resultWithGetters as Output;
126208
}
127209

128210
private async validateSettingsPermissionsOnObjectOrThrow(
129211
authContext: WorkspaceAuthContext,
130-
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
212+
queryRunnerContext: CommonBaseQueryRunnerContext,
131213
) {
214+
const { objectMetadataItemWithFieldMaps } = queryRunnerContext;
215+
132216
const workspace = authContext.workspace;
133217

134218
if (
@@ -206,4 +290,35 @@ export abstract class CommonBaseQueryRunnerService {
206290

207291
return { roleId, objectsPermissions: objectMetadataPermissions[roleId] };
208292
}
293+
294+
private async prepareExtendedQueryRunnerContext(
295+
authContext: WorkspaceAuthContext,
296+
queryRunnerContext: CommonBaseQueryRunnerContext,
297+
): Promise<Omit<CommonExtendedQueryRunnerContext, 'commonQueryParser'>> {
298+
const workspaceDataSource =
299+
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
300+
workspaceId: authContext.workspace.id,
301+
});
302+
303+
const { roleId } = await this.getRoleIdAndObjectsPermissions(
304+
authContext,
305+
authContext.workspace.id,
306+
);
307+
308+
const rolePermissionConfig = { unionOf: [roleId] };
309+
310+
const repository = workspaceDataSource.getRepository(
311+
queryRunnerContext.objectMetadataItemWithFieldMaps.nameSingular,
312+
rolePermissionConfig,
313+
authContext,
314+
);
315+
316+
return {
317+
...queryRunnerContext,
318+
authContext,
319+
workspaceDataSource,
320+
rolePermissionConfig,
321+
repository,
322+
};
323+
}
209324
}

0 commit comments

Comments
 (0)