Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
import { Inject, Injectable } from '@nestjs/common';

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

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

import { CommonSelectedFieldsHandler } from 'src/engine/api/common/common-args-handlers/common-query-selected-fields/common-selected-fields.handler';
import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { CommonResultGettersService } from 'src/engine/api/common/common-result-getters/common-result-getters.service';
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
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,
CommonQueryArgs,
CommonQueryNames,
} from 'src/engine/api/common/types/common-query-args.type';
import { CommonQueryResult } from 'src/engine/api/common/types/common-query-result.type';
import { isWorkspaceAuthContext } from 'src/engine/api/common/utils/is-workspace-auth-context.util';
import { OBJECTS_WITH_SETTINGS_PERMISSIONS_REQUIREMENTS } from 'src/engine/api/graphql/graphql-query-runner/constants/objects-with-settings-permissions-requirements';
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory';
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
import { WorkspacePreQueryHookPayload } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type';
import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service';
import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/api-key-role.service';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
Expand All @@ -29,7 +45,10 @@ import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/wo
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';

@Injectable()
export abstract class CommonBaseQueryRunnerService {
export abstract class CommonBaseQueryRunnerService<
Args extends CommonQueryArgs,
Output extends CommonQueryResult,
> {
@Inject()
protected readonly workspaceQueryHookService: WorkspaceQueryHookService;
@Inject()
Expand All @@ -53,82 +72,147 @@ export abstract class CommonBaseQueryRunnerService {
@Inject()
protected readonly commonResultGettersService: CommonResultGettersService;

public async prepareQueryRunnerContext({
authContext,
objectMetadataItemWithFieldMaps,
}: {
authContext: WorkspaceAuthContext;
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
}) {
public async execute(
args: CommonInput<Args>,
queryRunnerContext: CommonBaseQueryRunnerContext,
operationName: CommonQueryNames,
): Promise<Output> {
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
queryRunnerContext;

if (!isWorkspaceAuthContext(authContext)) {
throw new CommonQueryRunnerException(
'Invalid auth context',
CommonQueryRunnerExceptionCode.INVALID_AUTH_CONTEXT,
);
}

await this.validate(args, queryRunnerContext);

if (objectMetadataItemWithFieldMaps.isSystem === true) {
await this.validateSettingsPermissionsOnObjectOrThrow(
authContext,
objectMetadataItemWithFieldMaps,
queryRunnerContext,
);
}

const workspace = authContext.workspace;

const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId: workspace.id,
});
const commonQueryParser = new GraphqlQueryParser(
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);

const { roleId } = await this.getRoleIdAndObjectsPermissions(
authContext,
workspace.id,
const processedArgs = await this.processArgs(
args,
queryRunnerContext,
operationName,
commonQueryParser,
);

const rolePermissionConfig = { unionOf: [roleId] };
const extendedQueryRunnerContext =
await this.prepareExtendedQueryRunnerContext(
authContext,
queryRunnerContext,
);

const repository = workspaceDataSource.getRepository(
objectMetadataItemWithFieldMaps.nameSingular,
rolePermissionConfig,
const results = await this.run(processedArgs, {
...extendedQueryRunnerContext,
commonQueryParser,
});

return this.enrichResultsWithGettersAndHooks({
results,
operationName,
authContext,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
}

protected abstract run(
args: CommonExtendedInput<Args>,
queryRunnerContext: CommonExtendedQueryRunnerContext,
): Promise<Output>;

protected abstract validate(
args: CommonInput<Args>,
queryRunnerContext: CommonBaseQueryRunnerContext,
): Promise<void>;

protected abstract computeArgs(
args: CommonInput<Args>,
queryRunnerContext: CommonBaseQueryRunnerContext,
): Promise<CommonInput<Args>>;

protected abstract processQueryResult(
queryResult: Output,
objectMetadataItemId: string,
objectMetadataMaps: ObjectMetadataMaps,
authContext: WorkspaceAuthContext,
): Promise<Output>;

private async processArgs(
args: CommonInput<Args>,
queryRunnerContext: CommonBaseQueryRunnerContext,
operationName: CommonQueryNames,
commonQueryParser: GraphqlQueryParser,
): Promise<CommonExtendedInput<Args>> {
const selectedFieldsResult = commonQueryParser.parseSelectedFields(
args.selectedFields,
);

const { authContext, objectMetadataItemWithFieldMaps } = queryRunnerContext;
const hookedArgs =
(await this.workspaceQueryHookService.executePreQueryHooks(
authContext,
objectMetadataItemWithFieldMaps.nameSingular,
operationName,
args as WorkspacePreQueryHookPayload<CommonQueryNames>,
)) as CommonInput<Args>;

const computedArgs = await this.computeArgs(hookedArgs, queryRunnerContext);

return {
workspaceDataSource,
repository,
rolePermissionConfig,
...computedArgs,
selectedFieldsResult,
};
}

public async enrichResultsWithGettersAndHooks({
private async enrichResultsWithGettersAndHooks({
results,
operationName,
authContext,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
}: {
results: ObjectRecord[];
results: Output;
operationName: CommonQueryNames;
authContext: WorkspaceAuthContext;
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
objectMetadataMaps: ObjectMetadataMaps;
}): Promise<ObjectRecord[]> {
const resultWithGetters =
await this.commonResultGettersService.processQueryResult(
results,
objectMetadataItemWithFieldMaps.id,
objectMetadataMaps,
authContext.workspace.id,
);
}): Promise<Output> {
const resultWithGetters = await this.processQueryResult(
results,
objectMetadataItemWithFieldMaps.id,
objectMetadataMaps,
authContext,
);

await this.workspaceQueryHookService.executePostQueryHooks(
authContext,
objectMetadataItemWithFieldMaps.nameSingular,
operationName,
resultWithGetters,
resultWithGetters as QueryResultFieldValue,
);

return resultWithGetters;
return resultWithGetters as Output;
}

private async validateSettingsPermissionsOnObjectOrThrow(
authContext: WorkspaceAuthContext,
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
queryRunnerContext: CommonBaseQueryRunnerContext,
) {
const { objectMetadataItemWithFieldMaps } = queryRunnerContext;

const workspace = authContext.workspace;

if (
Expand Down Expand Up @@ -206,4 +290,35 @@ export abstract class CommonBaseQueryRunnerService {

return { roleId, objectsPermissions: objectMetadataPermissions[roleId] };
}

private async prepareExtendedQueryRunnerContext(
authContext: WorkspaceAuthContext,
queryRunnerContext: CommonBaseQueryRunnerContext,
): Promise<Omit<CommonExtendedQueryRunnerContext, 'commonQueryParser'>> {
const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId: authContext.workspace.id,
});

const { roleId } = await this.getRoleIdAndObjectsPermissions(
authContext,
authContext.workspace.id,
);

const rolePermissionConfig = { unionOf: [roleId] };

const repository = workspaceDataSource.getRepository(
queryRunnerContext.objectMetadataItemWithFieldMaps.nameSingular,
rolePermissionConfig,
authContext,
);

return {
...queryRunnerContext,
authContext,
workspaceDataSource,
rolePermissionConfig,
repository,
};
}
}
Loading