Revert "Revert "[4/n]: migrate the RESTAPI GET /rest/* to use TwentyORM direc…" (#11349)
This commit is contained in:
@ -9,6 +9,7 @@ import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
|
||||
describe('WorkspaceSchemaFactory', () => {
|
||||
let service: WorkspaceSchemaFactory;
|
||||
@ -49,6 +50,10 @@ describe('WorkspaceSchemaFactory', () => {
|
||||
provide: FeatureFlagService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: TwentyConfigService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
import { WhereExpressionBuilder } from 'typeorm';
|
||||
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
|
||||
@ -4,7 +4,6 @@ import {
|
||||
ObjectRecordOrderBy,
|
||||
OrderByDirection,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import {
|
||||
@ -18,14 +17,9 @@ import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspac
|
||||
|
||||
export class GraphqlQueryOrderFieldParser {
|
||||
private fieldMetadataMapByName: FieldMetadataMap;
|
||||
private featureFlagsMap: FeatureFlagMap;
|
||||
|
||||
constructor(
|
||||
fieldMetadataMapByName: FieldMetadataMap,
|
||||
featureFlagsMap: FeatureFlagMap,
|
||||
) {
|
||||
constructor(fieldMetadataMapByName: FieldMetadataMap) {
|
||||
this.fieldMetadataMapByName = fieldMetadataMapByName;
|
||||
this.featureFlagsMap = featureFlagsMap;
|
||||
}
|
||||
|
||||
parse(
|
||||
|
||||
@ -21,6 +21,10 @@ import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metada
|
||||
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 { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
|
||||
export class GraphqlQueryParser {
|
||||
private fieldMetadataMapByName: FieldMetadataMap;
|
||||
@ -47,7 +51,6 @@ export class GraphqlQueryParser {
|
||||
);
|
||||
this.orderFieldParser = new GraphqlQueryOrderFieldParser(
|
||||
this.fieldMetadataMapByName,
|
||||
featureFlagsMap,
|
||||
);
|
||||
}
|
||||
|
||||
@ -125,8 +128,9 @@ export class GraphqlQueryParser {
|
||||
)?.fieldsByName;
|
||||
|
||||
if (!parentFields) {
|
||||
throw new Error(
|
||||
throw new GraphqlQueryRunnerException(
|
||||
`Could not find object metadata for ${parentObjectMetadata.nameSingular}`,
|
||||
GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,8 @@ import { GraphQLSchema, printSchema } from 'graphql';
|
||||
import { gql } from 'graphql-tag';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface';
|
||||
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
@ -21,6 +23,7 @@ import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceSchemaFactory {
|
||||
@ -32,6 +35,7 @@ export class WorkspaceSchemaFactory {
|
||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
) {}
|
||||
|
||||
async createGraphQLSchema(authContext: AuthContext): Promise<GraphQLSchema> {
|
||||
@ -44,7 +48,10 @@ export class WorkspaceSchemaFactory {
|
||||
authContext.workspace.id,
|
||||
);
|
||||
|
||||
if (isNewRelationEnabled) {
|
||||
if (
|
||||
isNewRelationEnabled &&
|
||||
this.twentyConfigService.get('NODE_ENV') !== NodeEnvironment.test
|
||||
) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
chalk.yellow('🚧 New relation schema generation is enabled 🚧'),
|
||||
|
||||
@ -22,6 +22,7 @@ import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
@Controller('rest/*')
|
||||
@UseGuards(JwtAuthGuard, WorkspaceAuthGuard)
|
||||
@UseFilters(RestApiExceptionFilter)
|
||||
export class RestApiCoreController {
|
||||
constructor(
|
||||
private readonly restApiCoreService: RestApiCoreService,
|
||||
@ -38,15 +39,12 @@ export class RestApiCoreController {
|
||||
|
||||
@Get()
|
||||
async handleApiGet(@Req() request: Request, @Res() res: Response) {
|
||||
const result = await this.restApiCoreService.get(request);
|
||||
const result = await this.restApiCoreServiceV2.get(request);
|
||||
|
||||
res.status(200).send(cleanGraphQLResponse(result.data.data));
|
||||
res.status(200).send(result);
|
||||
}
|
||||
|
||||
@Delete()
|
||||
// We should move this exception filter to RestApiCoreController class level
|
||||
// when all endpoints are migrated to v2
|
||||
@UseFilters(RestApiExceptionFilter)
|
||||
async handleApiDelete(@Req() request: Request, @Res() res: Response) {
|
||||
const result = await this.restApiCoreServiceV2.delete(request);
|
||||
|
||||
@ -54,7 +52,6 @@ export class RestApiCoreController {
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UseFilters(RestApiExceptionFilter)
|
||||
async handleApiPost(@Req() request: Request, @Res() res: Response) {
|
||||
const result = await this.restApiCoreServiceV2.createOne(request);
|
||||
|
||||
@ -73,7 +70,6 @@ export class RestApiCoreController {
|
||||
// We keep it to avoid a breaking change since it initially used PUT instead of PATCH,
|
||||
// and because the PUT verb is often used as a PATCH.
|
||||
@Put()
|
||||
@UseFilters(RestApiExceptionFilter)
|
||||
async handleApiPut(@Req() request: Request, @Res() res: Response) {
|
||||
const result = await this.restApiCoreServiceV2.update(request);
|
||||
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
@Injectable()
|
||||
export class RestApiCreateOneHandler extends RestApiBaseHandler {
|
||||
async handle(request: Request) {
|
||||
const { objectMetadataNameSingular, objectMetadata, repository } =
|
||||
await this.getRepositoryAndMetadataOrFail(request);
|
||||
|
||||
const overriddenBody = await this.recordInputTransformerService.process({
|
||||
recordInput: request.body,
|
||||
objectMetadataMapItem: objectMetadata.objectMetadataMapItem,
|
||||
});
|
||||
|
||||
const recordExists =
|
||||
isDefined(overriddenBody.id) &&
|
||||
(await repository.exists({
|
||||
where: {
|
||||
id: overriddenBody.id,
|
||||
},
|
||||
}));
|
||||
|
||||
if (recordExists) {
|
||||
throw new BadRequestException('Record already exists');
|
||||
}
|
||||
|
||||
const createdRecord = await repository.save(overriddenBody);
|
||||
|
||||
this.apiEventEmitterService.emitCreateEvents(
|
||||
[createdRecord],
|
||||
this.getAuthContextFromRequest(request),
|
||||
objectMetadata.objectMetadataMapItem,
|
||||
);
|
||||
|
||||
const records = await this.getRecord({
|
||||
recordIds: [createdRecord.id],
|
||||
repository,
|
||||
objectMetadata,
|
||||
depth: this.depthInputFactory.create(request),
|
||||
});
|
||||
|
||||
const record = records[0];
|
||||
|
||||
if (!isDefined(record)) {
|
||||
throw new Error('Created record not found');
|
||||
}
|
||||
|
||||
return this.formatResult({
|
||||
operation: 'create',
|
||||
objectNameSingular: objectMetadataNameSingular,
|
||||
data: record,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||
|
||||
@Injectable()
|
||||
export class RestApiDeleteOneHandler extends RestApiBaseHandler {
|
||||
async handle(request: Request) {
|
||||
const { id: recordId } = parseCorePath(request);
|
||||
|
||||
if (!recordId) {
|
||||
throw new BadRequestException('Record ID not found');
|
||||
}
|
||||
|
||||
const { objectMetadataNameSingular, objectMetadata, repository } =
|
||||
await this.getRepositoryAndMetadataOrFail(request);
|
||||
const recordToDelete = await repository.findOneOrFail({
|
||||
where: { id: recordId },
|
||||
});
|
||||
|
||||
await repository.delete(recordId);
|
||||
|
||||
this.apiEventEmitterService.emitDestroyEvents(
|
||||
[recordToDelete],
|
||||
this.getAuthContextFromRequest(request),
|
||||
objectMetadata.objectMetadataMapItem,
|
||||
);
|
||||
|
||||
return this.formatResult({
|
||||
operation: 'delete',
|
||||
objectNameSingular: objectMetadataNameSingular,
|
||||
data: {
|
||||
id: recordToDelete.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
@Injectable()
|
||||
export class RestApiGetManyHandler extends RestApiBaseHandler {
|
||||
async handle(request: Request) {
|
||||
const {
|
||||
objectMetadataNameSingular,
|
||||
objectMetadataNamePlural,
|
||||
repository,
|
||||
dataSource,
|
||||
objectMetadata,
|
||||
objectMetadataItemWithFieldsMaps,
|
||||
} = await this.getRepositoryAndMetadataOrFail(request);
|
||||
|
||||
const { records, isForwardPagination, hasMoreRecords, totalCount } =
|
||||
await this.findRecords({
|
||||
request,
|
||||
repository,
|
||||
dataSource,
|
||||
objectMetadata,
|
||||
objectMetadataNameSingular,
|
||||
objectMetadataItemWithFieldsMaps,
|
||||
});
|
||||
|
||||
return this.formatPaginatedResult(
|
||||
records,
|
||||
objectMetadataNamePlural,
|
||||
isForwardPagination,
|
||||
hasMoreRecords,
|
||||
totalCount,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||
|
||||
@Injectable()
|
||||
export class RestApiGetOneHandler extends RestApiBaseHandler {
|
||||
async handle(request: Request) {
|
||||
const { id: recordId } = parseCorePath(request);
|
||||
|
||||
if (!isDefined(recordId)) {
|
||||
throw new BadRequestException(
|
||||
'No recordId provided in rest api get one query',
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
objectMetadataNameSingular,
|
||||
repository,
|
||||
dataSource,
|
||||
objectMetadata,
|
||||
objectMetadataItemWithFieldsMaps,
|
||||
} = await this.getRepositoryAndMetadataOrFail(request);
|
||||
|
||||
const { records } = await this.findRecords({
|
||||
request,
|
||||
recordId,
|
||||
repository,
|
||||
dataSource,
|
||||
objectMetadata,
|
||||
objectMetadataNameSingular,
|
||||
objectMetadataItemWithFieldsMaps,
|
||||
});
|
||||
|
||||
const record = records?.[0];
|
||||
|
||||
if (!isDefined(record)) {
|
||||
throw new BadRequestException('Record not found');
|
||||
}
|
||||
|
||||
return this.formatResult({
|
||||
operation: 'findOne',
|
||||
objectNameSingular: objectMetadataNameSingular,
|
||||
data: record,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||
|
||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||
|
||||
@Injectable()
|
||||
export class RestApiUpdateOneHandler extends RestApiBaseHandler {
|
||||
async handle(request: Request) {
|
||||
const { id: recordId } = parseCorePath(request);
|
||||
|
||||
if (!recordId) {
|
||||
throw new BadRequestException('Record ID not found');
|
||||
}
|
||||
|
||||
const { objectMetadataNameSingular, objectMetadata, repository } =
|
||||
await this.getRepositoryAndMetadataOrFail(request);
|
||||
|
||||
const recordToUpdate = await repository.findOneOrFail({
|
||||
where: { id: recordId },
|
||||
});
|
||||
|
||||
const overriddenBody = await this.recordInputTransformerService.process({
|
||||
recordInput: request.body,
|
||||
objectMetadataMapItem: objectMetadata.objectMetadataMapItem,
|
||||
});
|
||||
|
||||
const updatedRecord = await repository.save({
|
||||
...recordToUpdate,
|
||||
...overriddenBody,
|
||||
});
|
||||
|
||||
this.apiEventEmitterService.emitUpdateEvents(
|
||||
[recordToUpdate],
|
||||
[updatedRecord],
|
||||
Object.keys(request.body),
|
||||
this.getAuthContextFromRequest(request),
|
||||
objectMetadata.objectMetadataMapItem,
|
||||
);
|
||||
|
||||
const records = await this.getRecord({
|
||||
recordIds: [updatedRecord.id],
|
||||
repository,
|
||||
objectMetadata,
|
||||
depth: this.depthInputFactory.create(request),
|
||||
});
|
||||
|
||||
const record = records[0];
|
||||
|
||||
if (!isDefined(record)) {
|
||||
throw new Error('Updated record not found');
|
||||
}
|
||||
|
||||
return this.formatResult({
|
||||
operation: 'update',
|
||||
objectNameSingular: objectMetadataNameSingular,
|
||||
data: record,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,416 @@
|
||||
import { BadRequestException, Inject } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import { In, ObjectLiteral, SelectQueryBuilder } from 'typeorm';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
ObjectRecord,
|
||||
OrderByDirection,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import { CoreQueryBuilderFactory } from 'src/engine/api/rest/core/query-builder/core-query-builder.factory';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { GetVariablesFactory } from 'src/engine/api/rest/core/query-builder/factories/get-variables.factory';
|
||||
import {
|
||||
Depth,
|
||||
DepthInputFactory,
|
||||
MAX_DEPTH,
|
||||
} from 'src/engine/api/rest/input-factories/depth-input.factory';
|
||||
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||
import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||
import { formatResult as formatGetManyData } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { QueryVariables } from 'src/engine/api/rest/core/types/query-variables.type';
|
||||
|
||||
export interface PageInfo {
|
||||
hasNextPage?: boolean;
|
||||
hasPreviousPage?: boolean;
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
}
|
||||
|
||||
interface FormatResultParams<T> {
|
||||
operation: 'delete' | 'create' | 'update' | 'findOne' | 'findMany';
|
||||
objectNameSingular?: string;
|
||||
objectNamePlural?: string;
|
||||
data: T;
|
||||
pageInfo?: PageInfo;
|
||||
totalCount?: number;
|
||||
}
|
||||
|
||||
export interface FormatResult {
|
||||
data: {
|
||||
[operation: string]: object;
|
||||
};
|
||||
pageInfo?: PageInfo;
|
||||
totalCount?: number;
|
||||
}
|
||||
|
||||
export abstract class RestApiBaseHandler {
|
||||
@Inject()
|
||||
protected readonly recordInputTransformerService: RecordInputTransformerService;
|
||||
@Inject()
|
||||
protected readonly coreQueryBuilderFactory: CoreQueryBuilderFactory;
|
||||
@Inject()
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager;
|
||||
@Inject()
|
||||
protected readonly getVariablesFactory: GetVariablesFactory;
|
||||
@Inject()
|
||||
protected readonly depthInputFactory: DepthInputFactory;
|
||||
@Inject()
|
||||
protected readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService;
|
||||
@Inject()
|
||||
protected readonly apiEventEmitterService: ApiEventEmitterService;
|
||||
|
||||
protected abstract handle(request: Request): Promise<FormatResult>;
|
||||
|
||||
public async getRepositoryAndMetadataOrFail(request: Request) {
|
||||
const { workspace, apiKey, userWorkspaceId } = request;
|
||||
const { object: parsedObject } = parseCorePath(request);
|
||||
|
||||
const objectMetadata = await this.coreQueryBuilderFactory.getObjectMetadata(
|
||||
request,
|
||||
parsedObject,
|
||||
);
|
||||
|
||||
if (!objectMetadata) {
|
||||
throw new BadRequestException('Object metadata not found');
|
||||
}
|
||||
|
||||
if (!workspace?.id) {
|
||||
throw new BadRequestException('Workspace not found');
|
||||
}
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||
workspaceId: workspace.id,
|
||||
shouldFailIfMetadataNotFound: false,
|
||||
});
|
||||
|
||||
const objectMetadataNameSingular =
|
||||
objectMetadata.objectMetadataMapItem.nameSingular;
|
||||
|
||||
const objectMetadataItemWithFieldsMaps =
|
||||
getObjectMetadataMapItemByNameSingular(
|
||||
objectMetadata.objectMetadataMaps,
|
||||
objectMetadataNameSingular,
|
||||
);
|
||||
|
||||
const shouldBypassPermissionChecks = !!apiKey;
|
||||
|
||||
const roleId =
|
||||
await this.workspacePermissionsCacheService.getRoleIdFromUserWorkspaceId({
|
||||
workspaceId: workspace.id,
|
||||
userWorkspaceId,
|
||||
});
|
||||
|
||||
const repository = dataSource.getRepository<ObjectRecord>(
|
||||
objectMetadataNameSingular,
|
||||
shouldBypassPermissionChecks,
|
||||
roleId,
|
||||
);
|
||||
|
||||
return {
|
||||
objectMetadataNameSingular,
|
||||
objectMetadataNamePlural: objectMetadata.objectMetadataMapItem.namePlural,
|
||||
objectMetadata,
|
||||
repository,
|
||||
dataSource,
|
||||
objectMetadataItemWithFieldsMaps,
|
||||
};
|
||||
}
|
||||
|
||||
getRelations({
|
||||
objectMetadata,
|
||||
depth,
|
||||
}: {
|
||||
objectMetadata: {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
||||
};
|
||||
depth: Depth | undefined;
|
||||
}) {
|
||||
if (!isDefined(depth) || depth === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const relations: string[] = [];
|
||||
|
||||
objectMetadata.objectMetadataMapItem.fields.forEach((field) => {
|
||||
if (field.type === FieldMetadataType.RELATION) {
|
||||
if (
|
||||
depth === MAX_DEPTH &&
|
||||
isDefined(field.relationTargetObjectMetadataId)
|
||||
) {
|
||||
const relationTargetObjectMetadata =
|
||||
objectMetadata.objectMetadataMaps.byId[
|
||||
field.relationTargetObjectMetadataId
|
||||
];
|
||||
const depth2Relations = this.getRelations({
|
||||
objectMetadata: {
|
||||
objectMetadataMaps: objectMetadata.objectMetadataMaps,
|
||||
objectMetadataMapItem: relationTargetObjectMetadata,
|
||||
},
|
||||
depth: 1,
|
||||
});
|
||||
|
||||
depth2Relations.forEach((depth2Relation) => {
|
||||
relations.push(`${field.name}.${depth2Relation}`);
|
||||
});
|
||||
} else {
|
||||
relations.push(`${field.name}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return relations;
|
||||
}
|
||||
|
||||
async getRecord({
|
||||
recordIds,
|
||||
repository,
|
||||
objectMetadata,
|
||||
depth,
|
||||
}: {
|
||||
recordIds: string[];
|
||||
repository: WorkspaceRepository<ObjectLiteral>;
|
||||
objectMetadata: {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
||||
};
|
||||
depth: Depth | undefined;
|
||||
}) {
|
||||
const relations = this.getRelations({
|
||||
objectMetadata,
|
||||
depth: depth,
|
||||
});
|
||||
|
||||
const unorderedRecords = await repository.find({
|
||||
where: { id: In(recordIds) },
|
||||
relations,
|
||||
});
|
||||
|
||||
const recordMap = new Map(unorderedRecords.map((r) => [r.id, r]));
|
||||
|
||||
const orderedRecords = recordIds.map((id) => recordMap.get(id));
|
||||
|
||||
return orderedRecords;
|
||||
}
|
||||
|
||||
public getAuthContextFromRequest(request: Request): AuthContext {
|
||||
return {
|
||||
user: request.user,
|
||||
workspace: request.workspace,
|
||||
apiKey: request.apiKey,
|
||||
workspaceMemberId: request.workspaceMemberId,
|
||||
userWorkspaceId: request.userWorkspaceId,
|
||||
};
|
||||
}
|
||||
|
||||
public formatResult<T>({
|
||||
operation,
|
||||
objectNameSingular,
|
||||
objectNamePlural,
|
||||
data,
|
||||
pageInfo,
|
||||
totalCount,
|
||||
}: FormatResultParams<T>) {
|
||||
let prefix: string;
|
||||
|
||||
if (operation === 'findOne') {
|
||||
prefix = objectNameSingular || '';
|
||||
} else if (operation === 'findMany') {
|
||||
prefix = objectNamePlural || '';
|
||||
} else {
|
||||
prefix = operation + capitalize(objectNameSingular || '');
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
[prefix]: data,
|
||||
},
|
||||
...(isDefined(pageInfo) ? { pageInfo } : {}),
|
||||
...(isDefined(totalCount) ? { totalCount } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
formatPaginatedResult(
|
||||
finalRecords: any[],
|
||||
objectMetadataNamePlural: string,
|
||||
isForwardPagination: boolean,
|
||||
hasMoreRecords: boolean,
|
||||
totalCount: number,
|
||||
) {
|
||||
const hasPreviousPage = !isForwardPagination && hasMoreRecords;
|
||||
|
||||
return this.formatResult({
|
||||
operation: 'findMany',
|
||||
objectNamePlural: objectMetadataNamePlural,
|
||||
data: isForwardPagination ? finalRecords : finalRecords.reverse(),
|
||||
pageInfo: {
|
||||
hasNextPage: isForwardPagination && hasMoreRecords,
|
||||
...(hasPreviousPage ? { hasPreviousPage } : {}),
|
||||
startCursor:
|
||||
finalRecords.length > 0
|
||||
? Buffer.from(JSON.stringify({ id: finalRecords[0].id })).toString(
|
||||
'base64',
|
||||
)
|
||||
: null,
|
||||
endCursor:
|
||||
finalRecords.length > 0
|
||||
? Buffer.from(
|
||||
JSON.stringify({
|
||||
id: finalRecords[finalRecords.length - 1].id,
|
||||
}),
|
||||
).toString('base64')
|
||||
: null,
|
||||
},
|
||||
totalCount,
|
||||
});
|
||||
}
|
||||
|
||||
async findRecords({
|
||||
request,
|
||||
recordId,
|
||||
repository,
|
||||
dataSource,
|
||||
objectMetadata,
|
||||
objectMetadataNameSingular,
|
||||
objectMetadataItemWithFieldsMaps,
|
||||
}: {
|
||||
request: Request;
|
||||
recordId?: string;
|
||||
repository: WorkspaceRepository<ObjectLiteral>;
|
||||
dataSource: WorkspaceDataSource;
|
||||
objectMetadata: any;
|
||||
objectMetadataNameSingular: string;
|
||||
objectMetadataItemWithFieldsMaps:
|
||||
| ObjectMetadataItemWithFieldMaps
|
||||
| undefined;
|
||||
}) {
|
||||
const qb = repository.createQueryBuilder(objectMetadataNameSingular);
|
||||
|
||||
const inputs = this.getVariablesFactory.create(
|
||||
recordId,
|
||||
request,
|
||||
objectMetadata,
|
||||
);
|
||||
|
||||
const fieldMetadataMapByName =
|
||||
objectMetadataItemWithFieldsMaps?.fieldsByName || {};
|
||||
const fieldMetadataMapByJoinColumnName =
|
||||
objectMetadataItemWithFieldsMaps?.fieldsByJoinColumnName || {};
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
fieldMetadataMapByName,
|
||||
fieldMetadataMapByJoinColumnName,
|
||||
objectMetadata.objectMetadataMaps,
|
||||
dataSource.featureFlagMap,
|
||||
);
|
||||
|
||||
const filters = this.computeFilters(inputs);
|
||||
|
||||
let selectQueryBuilder = isDefined(filters)
|
||||
? graphqlQueryParser.applyFilterToBuilder(
|
||||
qb,
|
||||
objectMetadataNameSingular,
|
||||
filters,
|
||||
)
|
||||
: qb;
|
||||
|
||||
const totalCount = await this.getTotalCount(selectQueryBuilder);
|
||||
|
||||
const isForwardPagination = !inputs.endingBefore;
|
||||
|
||||
selectQueryBuilder = graphqlQueryParser.applyOrderToBuilder(
|
||||
selectQueryBuilder,
|
||||
[...(inputs.orderBy || []), { id: OrderByDirection.AscNullsFirst }],
|
||||
objectMetadataNameSingular,
|
||||
isForwardPagination,
|
||||
);
|
||||
|
||||
if (inputs.first) {
|
||||
selectQueryBuilder = selectQueryBuilder.limit(inputs.first);
|
||||
}
|
||||
|
||||
if (inputs.last) {
|
||||
selectQueryBuilder = selectQueryBuilder.limit(inputs.last);
|
||||
}
|
||||
|
||||
const recordIds = await selectQueryBuilder
|
||||
.select(`${objectMetadataNameSingular}.id`)
|
||||
.getMany();
|
||||
|
||||
const records = await this.getRecord({
|
||||
recordIds: recordIds.map((record) => record.id),
|
||||
repository,
|
||||
objectMetadata,
|
||||
depth: this.depthInputFactory.create(request),
|
||||
});
|
||||
|
||||
const hasMoreRecords = records.length < totalCount;
|
||||
|
||||
return {
|
||||
records: formatGetManyData<ObjectLiteral[]>(
|
||||
records,
|
||||
objectMetadataItemWithFieldsMaps as any,
|
||||
objectMetadata.objectMetadataMaps,
|
||||
dataSource.featureFlagMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||
),
|
||||
totalCount,
|
||||
hasMoreRecords,
|
||||
isForwardPagination,
|
||||
};
|
||||
}
|
||||
|
||||
async getTotalCount(
|
||||
query: SelectQueryBuilder<ObjectLiteral>,
|
||||
): Promise<number> {
|
||||
const countQuery = query.clone();
|
||||
|
||||
return await countQuery.getCount();
|
||||
}
|
||||
|
||||
computeFilters(inputs: QueryVariables) {
|
||||
let appliedFilters = inputs.filter;
|
||||
|
||||
if (inputs.startingAfter) {
|
||||
appliedFilters = {
|
||||
and: [
|
||||
appliedFilters || {},
|
||||
{ id: { gt: this.parseCursor(inputs.startingAfter).id } },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (inputs.endingBefore) {
|
||||
appliedFilters = {
|
||||
and: [
|
||||
appliedFilters || {},
|
||||
{ id: { lt: this.parseCursor(inputs.endingBefore).id } },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return appliedFilters;
|
||||
}
|
||||
|
||||
private parseCursor = (cursor: string) => {
|
||||
try {
|
||||
return JSON.parse(Buffer.from(cursor ?? '', 'base64').toString());
|
||||
} catch (error) {
|
||||
throw new BadRequestException(`Invalid cursor: ${cursor}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -3,17 +3,8 @@ import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
|
||||
import { CreateManyQueryFactory } from 'src/engine/api/rest/core/query-builder/factories/create-many-query.factory';
|
||||
import { CreateOneQueryFactory } from 'src/engine/api/rest/core/query-builder/factories/create-one-query.factory';
|
||||
import { CreateVariablesFactory } from 'src/engine/api/rest/core/query-builder/factories/create-variables.factory';
|
||||
import { DeleteQueryFactory } from 'src/engine/api/rest/core/query-builder/factories/delete-query.factory';
|
||||
import { DeleteVariablesFactory } from 'src/engine/api/rest/core/query-builder/factories/delete-variables.factory';
|
||||
import { FindDuplicatesQueryFactory } from 'src/engine/api/rest/core/query-builder/factories/find-duplicates-query.factory';
|
||||
import { FindDuplicatesVariablesFactory } from 'src/engine/api/rest/core/query-builder/factories/find-duplicates-variables.factory';
|
||||
import { FindManyQueryFactory } from 'src/engine/api/rest/core/query-builder/factories/find-many-query.factory';
|
||||
import { FindOneQueryFactory } from 'src/engine/api/rest/core/query-builder/factories/find-one-query.factory';
|
||||
import { GetVariablesFactory } from 'src/engine/api/rest/core/query-builder/factories/get-variables.factory';
|
||||
import { UpdateQueryFactory } from 'src/engine/api/rest/core/query-builder/factories/update-query.factory';
|
||||
import { UpdateVariablesFactory } from 'src/engine/api/rest/core/query-builder/factories/update-variables.factory';
|
||||
import { computeDepth } from 'src/engine/api/rest/core/query-builder/utils/compute-depth.utils';
|
||||
import { parseCoreBatchPath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-batch-path.utils';
|
||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||
@ -26,21 +17,14 @@ import { getObjectMetadataMapItemByNamePlural } from 'src/engine/metadata-module
|
||||
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
import { CreateVariablesFactory } from 'src/engine/api/rest/core/query-builder/factories/create-variables.factory';
|
||||
|
||||
@Injectable()
|
||||
export class CoreQueryBuilderFactory {
|
||||
constructor(
|
||||
private readonly deleteQueryFactory: DeleteQueryFactory,
|
||||
private readonly createOneQueryFactory: CreateOneQueryFactory,
|
||||
private readonly createManyQueryFactory: CreateManyQueryFactory,
|
||||
private readonly updateQueryFactory: UpdateQueryFactory,
|
||||
private readonly findOneQueryFactory: FindOneQueryFactory,
|
||||
private readonly findManyQueryFactory: FindManyQueryFactory,
|
||||
private readonly findDuplicatesQueryFactory: FindDuplicatesQueryFactory,
|
||||
private readonly deleteVariablesFactory: DeleteVariablesFactory,
|
||||
private readonly createVariablesFactory: CreateVariablesFactory,
|
||||
private readonly updateVariablesFactory: UpdateVariablesFactory,
|
||||
private readonly getVariablesFactory: GetVariablesFactory,
|
||||
private readonly findDuplicatesVariablesFactory: FindDuplicatesVariablesFactory,
|
||||
private readonly accessTokenService: AccessTokenService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
@ -113,38 +97,6 @@ export class CoreQueryBuilderFactory {
|
||||
};
|
||||
}
|
||||
|
||||
async delete(request: Request): Promise<Query> {
|
||||
const { object: parsedObject } = parseCorePath(request);
|
||||
const objectMetadata = await this.getObjectMetadata(request, parsedObject);
|
||||
|
||||
const { id } = parseCorePath(request);
|
||||
|
||||
if (!id) {
|
||||
throw new BadRequestException(
|
||||
`delete ${objectMetadata.objectMetadataMapItem.nameSingular} query invalid. Id missing. eg: /rest/${objectMetadata.objectMetadataMapItem.namePlural}/0d4389ef-ea9c-4ae8-ada1-1cddc440fb56`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
query: this.deleteQueryFactory.create(
|
||||
objectMetadata.objectMetadataMapItem,
|
||||
),
|
||||
variables: this.deleteVariablesFactory.create(id),
|
||||
};
|
||||
}
|
||||
|
||||
async createOne(request: Request): Promise<Query> {
|
||||
const { object: parsedObject } = parseCorePath(request);
|
||||
const objectMetadata = await this.getObjectMetadata(request, parsedObject);
|
||||
|
||||
const depth = computeDepth(request);
|
||||
|
||||
return {
|
||||
query: this.createOneQueryFactory.create(objectMetadata, depth),
|
||||
variables: this.createVariablesFactory.create(request),
|
||||
};
|
||||
}
|
||||
|
||||
async createMany(request: Request): Promise<Query> {
|
||||
const { object: parsedObject } = parseCoreBatchPath(request);
|
||||
const objectMetadata = await this.getObjectMetadata(request, parsedObject);
|
||||
@ -156,42 +108,6 @@ export class CoreQueryBuilderFactory {
|
||||
};
|
||||
}
|
||||
|
||||
async update(request: Request): Promise<Query> {
|
||||
const { object: parsedObject } = parseCorePath(request);
|
||||
const objectMetadata = await this.getObjectMetadata(request, parsedObject);
|
||||
|
||||
const depth = computeDepth(request);
|
||||
|
||||
const { id } = parseCorePath(request);
|
||||
|
||||
if (!id) {
|
||||
throw new BadRequestException(
|
||||
`update ${objectMetadata.objectMetadataMapItem.nameSingular} query invalid. Id missing. eg: /rest/${objectMetadata.objectMetadataMapItem.namePlural}/0d4389ef-ea9c-4ae8-ada1-1cddc440fb56`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
query: this.updateQueryFactory.create(objectMetadata, depth),
|
||||
variables: this.updateVariablesFactory.create(id, request),
|
||||
};
|
||||
}
|
||||
|
||||
async get(request: Request): Promise<Query> {
|
||||
const { object: parsedObject } = parseCorePath(request);
|
||||
const objectMetadata = await this.getObjectMetadata(request, parsedObject);
|
||||
|
||||
const depth = computeDepth(request);
|
||||
|
||||
const { id } = parseCorePath(request);
|
||||
|
||||
return {
|
||||
query: id
|
||||
? this.findOneQueryFactory.create(objectMetadata, depth)
|
||||
: this.findManyQueryFactory.create(objectMetadata, depth),
|
||||
variables: this.getVariablesFactory.create(id, request, objectMetadata),
|
||||
};
|
||||
}
|
||||
|
||||
async findDuplicates(request: Request): Promise<Query> {
|
||||
const { object: parsedObject } = parseCorePath(request);
|
||||
const objectMetadata = await this.getObjectMetadata(request, parsedObject);
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils';
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class CreateOneQueryFactory {
|
||||
create(
|
||||
objectMetadata: {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
||||
},
|
||||
depth?: number,
|
||||
): string {
|
||||
const objectNameSingular = capitalize(
|
||||
objectMetadata.objectMetadataMapItem.nameSingular,
|
||||
);
|
||||
|
||||
return `
|
||||
mutation Create${objectNameSingular}($data: ${objectNameSingular}CreateInput!) {
|
||||
create${objectNameSingular}(data: $data) {
|
||||
id
|
||||
${objectMetadata.objectMetadataMapItem.fields
|
||||
.map((field) =>
|
||||
mapFieldMetadataToGraphqlQuery(
|
||||
objectMetadata.objectMetadataMaps,
|
||||
field,
|
||||
depth,
|
||||
),
|
||||
)
|
||||
.join('\n')}
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteQueryFactory {
|
||||
create(objectMetadataMapItem: ObjectMetadataItemWithFieldMaps): string {
|
||||
const objectNameSingular = capitalize(objectMetadataMapItem.nameSingular);
|
||||
|
||||
return `
|
||||
mutation Delete${objectNameSingular}($id: ID!) {
|
||||
delete${objectNameSingular}(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { QueryVariables } from 'src/engine/api/rest/core/types/query-variables.type';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteVariablesFactory {
|
||||
create(id: string): QueryVariables {
|
||||
return {
|
||||
id,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,28 +1,14 @@
|
||||
import { CreateManyQueryFactory } from 'src/engine/api/rest/core/query-builder/factories/create-many-query.factory';
|
||||
import { CreateOneQueryFactory } from 'src/engine/api/rest/core/query-builder/factories/create-one-query.factory';
|
||||
import { CreateVariablesFactory } from 'src/engine/api/rest/core/query-builder/factories/create-variables.factory';
|
||||
import { DeleteQueryFactory } from 'src/engine/api/rest/core/query-builder/factories/delete-query.factory';
|
||||
import { DeleteVariablesFactory } from 'src/engine/api/rest/core/query-builder/factories/delete-variables.factory';
|
||||
import { FindDuplicatesQueryFactory } from 'src/engine/api/rest/core/query-builder/factories/find-duplicates-query.factory';
|
||||
import { FindDuplicatesVariablesFactory } from 'src/engine/api/rest/core/query-builder/factories/find-duplicates-variables.factory';
|
||||
import { FindManyQueryFactory } from 'src/engine/api/rest/core/query-builder/factories/find-many-query.factory';
|
||||
import { FindOneQueryFactory } from 'src/engine/api/rest/core/query-builder/factories/find-one-query.factory';
|
||||
import { GetVariablesFactory } from 'src/engine/api/rest/core/query-builder/factories/get-variables.factory';
|
||||
import { UpdateQueryFactory } from 'src/engine/api/rest/core/query-builder/factories/update-query.factory';
|
||||
import { UpdateVariablesFactory } from 'src/engine/api/rest/core/query-builder/factories/update-variables.factory';
|
||||
import { CreateVariablesFactory } from 'src/engine/api/rest/core/query-builder/factories/create-variables.factory';
|
||||
import { inputFactories } from 'src/engine/api/rest/input-factories/factories';
|
||||
|
||||
export const coreQueryBuilderFactories = [
|
||||
DeleteQueryFactory,
|
||||
CreateOneQueryFactory,
|
||||
CreateManyQueryFactory,
|
||||
UpdateQueryFactory,
|
||||
FindOneQueryFactory,
|
||||
FindManyQueryFactory,
|
||||
FindDuplicatesQueryFactory,
|
||||
DeleteVariablesFactory,
|
||||
CreateVariablesFactory,
|
||||
UpdateVariablesFactory,
|
||||
GetVariablesFactory,
|
||||
FindDuplicatesVariablesFactory,
|
||||
...inputFactories,
|
||||
|
||||
@ -1,65 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils';
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class FindManyQueryFactory {
|
||||
create(
|
||||
objectMetadata: {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
||||
},
|
||||
depth?: number,
|
||||
): string {
|
||||
const objectNameSingular = capitalize(
|
||||
objectMetadata.objectMetadataMapItem.nameSingular,
|
||||
);
|
||||
const objectNamePlural = objectMetadata.objectMetadataMapItem.namePlural;
|
||||
|
||||
return `
|
||||
query FindMany${capitalize(objectNamePlural)}(
|
||||
$filter: ${objectNameSingular}FilterInput,
|
||||
$orderBy: [${objectNameSingular}OrderByInput],
|
||||
$startingAfter: String,
|
||||
$endingBefore: String,
|
||||
$first: Int,
|
||||
$last: Int
|
||||
) {
|
||||
${objectNamePlural}(
|
||||
filter: $filter,
|
||||
orderBy: $orderBy,
|
||||
first: $first,
|
||||
last: $last,
|
||||
after: $startingAfter,
|
||||
before: $endingBefore
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
${objectMetadata.objectMetadataMapItem.fields
|
||||
.map((field) =>
|
||||
mapFieldMetadataToGraphqlQuery(
|
||||
objectMetadata.objectMetadataMaps,
|
||||
field,
|
||||
depth,
|
||||
),
|
||||
)
|
||||
.join('\n')}
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils';
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class FindOneQueryFactory {
|
||||
create(
|
||||
objectMetadata: {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
||||
},
|
||||
depth?: number,
|
||||
): string {
|
||||
const objectNameSingular =
|
||||
objectMetadata.objectMetadataMapItem.nameSingular;
|
||||
|
||||
return `
|
||||
query FindOne${capitalize(objectNameSingular)}(
|
||||
$filter: ${capitalize(objectNameSingular)}FilterInput!,
|
||||
) {
|
||||
${objectNameSingular}(filter: $filter) {
|
||||
id
|
||||
${objectMetadata.objectMetadataMapItem.fields
|
||||
.map((field) =>
|
||||
mapFieldMetadataToGraphqlQuery(
|
||||
objectMetadata.objectMetadataMaps,
|
||||
field,
|
||||
depth,
|
||||
),
|
||||
)
|
||||
.join('\n')}
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@ -33,13 +33,15 @@ export class GetVariablesFactory {
|
||||
return { filter: { id: { eq: id } } };
|
||||
}
|
||||
|
||||
const filter = this.filterInputFactory.create(request, objectMetadata);
|
||||
const limit = this.limitInputFactory.create(request);
|
||||
const orderBy = this.orderByInputFactory.create(request, objectMetadata);
|
||||
const endingBefore = this.endingBeforeInputFactory.create(request);
|
||||
const startingAfter = this.startingAfterInputFactory.create(request);
|
||||
|
||||
return {
|
||||
filter: this.filterInputFactory.create(request, objectMetadata),
|
||||
orderBy: this.orderByInputFactory.create(request, objectMetadata),
|
||||
filter,
|
||||
orderBy,
|
||||
first: !endingBefore ? limit : undefined,
|
||||
last: endingBefore ? limit : undefined,
|
||||
startingAfter,
|
||||
|
||||
@ -1,40 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils';
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class UpdateQueryFactory {
|
||||
create(
|
||||
objectMetadata: {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
||||
},
|
||||
depth?: number,
|
||||
): string {
|
||||
const objectNameSingular =
|
||||
objectMetadata.objectMetadataMapItem.nameSingular;
|
||||
|
||||
return `
|
||||
mutation Update${capitalize(
|
||||
objectNameSingular,
|
||||
)}($id: ID!, $data: ${capitalize(objectNameSingular)}UpdateInput!) {
|
||||
update${capitalize(objectNameSingular)}(id: $id, data: $data) {
|
||||
id
|
||||
${Object.values(objectMetadata.objectMetadataMapItem.fieldsById)
|
||||
.map((field) =>
|
||||
mapFieldMetadataToGraphqlQuery(
|
||||
objectMetadata.objectMetadataMaps,
|
||||
field,
|
||||
depth,
|
||||
),
|
||||
)
|
||||
.join('\n')}
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
|
||||
import { QueryVariables } from 'src/engine/api/rest/core/types/query-variables.type';
|
||||
|
||||
@Injectable()
|
||||
export class UpdateVariablesFactory {
|
||||
create(id: string, request: Request): QueryVariables {
|
||||
return {
|
||||
id,
|
||||
data: request.body,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,197 +1,44 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
||||
import { CoreQueryBuilderFactory } from 'src/engine/api/rest/core/query-builder/core-query-builder.factory';
|
||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service';
|
||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { RestApiDeleteOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-delete-one.handler';
|
||||
import { RestApiCreateOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-create-one.handler';
|
||||
import { RestApiUpdateOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-update-one.handler';
|
||||
import { RestApiGetOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-get-one.handler';
|
||||
import { RestApiGetManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-get-many.handler';
|
||||
|
||||
@Injectable()
|
||||
export class RestApiCoreServiceV2 {
|
||||
constructor(
|
||||
private readonly coreQueryBuilderFactory: CoreQueryBuilderFactory,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly recordInputTransformerService: RecordInputTransformerService,
|
||||
protected readonly apiEventEmitterService: ApiEventEmitterService,
|
||||
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||
private readonly restApiDeleteOneHandler: RestApiDeleteOneHandler,
|
||||
private readonly restApiCreateOneHandler: RestApiCreateOneHandler,
|
||||
private readonly restApiUpdateOneHandler: RestApiUpdateOneHandler,
|
||||
private readonly restApiGetOneHandler: RestApiGetOneHandler,
|
||||
private readonly restApiGetManyHandler: RestApiGetManyHandler,
|
||||
) {}
|
||||
|
||||
async delete(request: Request) {
|
||||
const { id: recordId } = parseCorePath(request);
|
||||
|
||||
if (!recordId) {
|
||||
throw new BadRequestException('Record ID not found');
|
||||
}
|
||||
|
||||
const { objectMetadataNameSingular, objectMetadata, repository } =
|
||||
await this.getRepositoryAndMetadataOrFail(request);
|
||||
const recordToDelete = await repository.findOneOrFail({
|
||||
where: { id: recordId },
|
||||
});
|
||||
|
||||
await repository.delete(recordId);
|
||||
|
||||
this.apiEventEmitterService.emitDestroyEvents(
|
||||
[recordToDelete],
|
||||
this.getAuthContextFromRequest(request),
|
||||
objectMetadata.objectMetadataMapItem,
|
||||
);
|
||||
|
||||
return this.formatResult('delete', objectMetadataNameSingular, {
|
||||
id: recordToDelete.id,
|
||||
});
|
||||
return await this.restApiDeleteOneHandler.handle(request);
|
||||
}
|
||||
|
||||
async createOne(request: Request) {
|
||||
const { objectMetadataNameSingular, objectMetadata, repository } =
|
||||
await this.getRepositoryAndMetadataOrFail(request);
|
||||
|
||||
const overriddenBody = await this.recordInputTransformerService.process({
|
||||
recordInput: request.body,
|
||||
objectMetadataMapItem: objectMetadata.objectMetadataMapItem,
|
||||
});
|
||||
|
||||
const recordExists =
|
||||
isDefined(overriddenBody.id) &&
|
||||
(await repository.exists({
|
||||
where: {
|
||||
id: overriddenBody.id,
|
||||
},
|
||||
}));
|
||||
|
||||
if (recordExists) {
|
||||
throw new BadRequestException('Record already exists');
|
||||
}
|
||||
|
||||
const createdRecord = await repository.save(overriddenBody);
|
||||
|
||||
this.apiEventEmitterService.emitCreateEvents(
|
||||
[createdRecord],
|
||||
this.getAuthContextFromRequest(request),
|
||||
objectMetadata.objectMetadataMapItem,
|
||||
);
|
||||
|
||||
return this.formatResult(
|
||||
'create',
|
||||
objectMetadataNameSingular,
|
||||
createdRecord,
|
||||
);
|
||||
return await this.restApiCreateOneHandler.handle(request);
|
||||
}
|
||||
|
||||
async update(request: Request) {
|
||||
return await this.restApiUpdateOneHandler.handle(request);
|
||||
}
|
||||
|
||||
async get(request: Request) {
|
||||
const { id: recordId } = parseCorePath(request);
|
||||
|
||||
if (!recordId) {
|
||||
throw new BadRequestException('Record ID not found');
|
||||
if (isDefined(recordId)) {
|
||||
return await this.restApiGetOneHandler.handle(request);
|
||||
} else {
|
||||
return await this.restApiGetManyHandler.handle(request);
|
||||
}
|
||||
|
||||
const { objectMetadataNameSingular, objectMetadata, repository } =
|
||||
await this.getRepositoryAndMetadataOrFail(request);
|
||||
|
||||
const recordToUpdate = await repository.findOneOrFail({
|
||||
where: { id: recordId },
|
||||
});
|
||||
|
||||
const overriddenBody = await this.recordInputTransformerService.process({
|
||||
recordInput: request.body,
|
||||
objectMetadataMapItem: objectMetadata.objectMetadataMapItem,
|
||||
});
|
||||
|
||||
const updatedRecord = await repository.save({
|
||||
...recordToUpdate,
|
||||
...overriddenBody,
|
||||
});
|
||||
|
||||
this.apiEventEmitterService.emitUpdateEvents(
|
||||
[recordToUpdate],
|
||||
[updatedRecord],
|
||||
Object.keys(request.body),
|
||||
this.getAuthContextFromRequest(request),
|
||||
objectMetadata.objectMetadataMapItem,
|
||||
);
|
||||
|
||||
return this.formatResult(
|
||||
'update',
|
||||
objectMetadataNameSingular,
|
||||
updatedRecord,
|
||||
);
|
||||
}
|
||||
|
||||
private formatResult<T>(
|
||||
operation: 'delete' | 'create' | 'update' | 'find',
|
||||
objectNameSingular: string,
|
||||
data: T,
|
||||
) {
|
||||
const result = {
|
||||
data: {
|
||||
[operation + capitalize(objectNameSingular)]: data,
|
||||
},
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async getRepositoryAndMetadataOrFail(request: Request) {
|
||||
const { workspace, apiKey, userWorkspaceId } = request;
|
||||
const { object: parsedObject } = parseCorePath(request);
|
||||
|
||||
const objectMetadata = await this.coreQueryBuilderFactory.getObjectMetadata(
|
||||
request,
|
||||
parsedObject,
|
||||
);
|
||||
|
||||
if (!objectMetadata) {
|
||||
throw new BadRequestException('Object metadata not found');
|
||||
}
|
||||
|
||||
if (!workspace?.id) {
|
||||
throw new BadRequestException('Workspace not found');
|
||||
}
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||
workspaceId: workspace.id,
|
||||
shouldFailIfMetadataNotFound: false,
|
||||
});
|
||||
|
||||
const objectMetadataNameSingular =
|
||||
objectMetadata.objectMetadataMapItem.nameSingular;
|
||||
|
||||
const shouldBypassPermissionChecks = !!apiKey;
|
||||
|
||||
const roleId =
|
||||
await this.workspacePermissionsCacheService.getRoleIdFromUserWorkspaceId({
|
||||
workspaceId: workspace.id,
|
||||
userWorkspaceId,
|
||||
});
|
||||
|
||||
const repository = dataSource.getRepository<ObjectRecord>(
|
||||
objectMetadataNameSingular,
|
||||
shouldBypassPermissionChecks,
|
||||
roleId,
|
||||
);
|
||||
|
||||
return {
|
||||
objectMetadataNameSingular,
|
||||
objectMetadata,
|
||||
repository,
|
||||
};
|
||||
}
|
||||
|
||||
private getAuthContextFromRequest(request: Request): AuthContext {
|
||||
return {
|
||||
user: request.user,
|
||||
workspace: request.workspace,
|
||||
apiKey: request.apiKey,
|
||||
workspaceMemberId: request.workspaceMemberId,
|
||||
userWorkspaceId: request.userWorkspaceId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
|
||||
import { RestApiDeleteOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-delete-one.handler';
|
||||
import { RestApiCreateOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-create-one.handler';
|
||||
import { RestApiUpdateOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-update-one.handler';
|
||||
import { RestApiGetOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-get-one.handler';
|
||||
import { RestApiGetManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-get-many.handler';
|
||||
import { CoreQueryBuilderModule } from 'src/engine/api/rest/core/query-builder/core-query-builder.module';
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||
import { RecordTransformerModule } from 'src/engine/core-modules/record-transformer/record-transformer.module';
|
||||
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
||||
import { RestApiCoreController } from 'src/engine/api/rest/core/controllers/rest-api-core.controller';
|
||||
import { coreQueryBuilderFactories } from 'src/engine/api/rest/core/query-builder/factories/factories';
|
||||
import { RestApiCoreBatchController } from 'src/engine/api/rest/core/controllers/rest-api-core-batch.controller';
|
||||
import { RestApiCoreServiceV2 } from 'src/engine/api/rest/core/rest-api-core-v2.service';
|
||||
import { RestApiCoreService } from 'src/engine/api/rest/core/rest-api-core.service';
|
||||
import { RestApiService } from 'src/engine/api/rest/rest-api.service';
|
||||
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||
|
||||
const restApiCoreResolvers = [
|
||||
RestApiDeleteOneHandler,
|
||||
RestApiCreateOneHandler,
|
||||
RestApiUpdateOneHandler,
|
||||
RestApiGetOneHandler,
|
||||
RestApiGetManyHandler,
|
||||
];
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CoreQueryBuilderModule,
|
||||
WorkspaceCacheStorageModule,
|
||||
AuthModule,
|
||||
HttpModule,
|
||||
TwentyORMModule,
|
||||
RecordTransformerModule,
|
||||
WorkspacePermissionsCacheModule,
|
||||
],
|
||||
controllers: [RestApiCoreController, RestApiCoreBatchController],
|
||||
providers: [
|
||||
RestApiService,
|
||||
RestApiCoreService,
|
||||
RestApiCoreServiceV2,
|
||||
ApiEventEmitterService,
|
||||
...coreQueryBuilderFactories,
|
||||
...restApiCoreResolvers,
|
||||
],
|
||||
})
|
||||
export class RestApiCoreModule {}
|
||||
@ -15,36 +15,12 @@ export class RestApiCoreService {
|
||||
private readonly restApiService: RestApiService,
|
||||
) {}
|
||||
|
||||
async get(request: Request) {
|
||||
const data = await this.coreQueryBuilderFactory.get(request);
|
||||
|
||||
return await this.restApiService.call(GraphqlApiType.CORE, request, data);
|
||||
}
|
||||
|
||||
async delete(request: Request) {
|
||||
const data = await this.coreQueryBuilderFactory.delete(request);
|
||||
|
||||
return await this.restApiService.call(GraphqlApiType.CORE, request, data);
|
||||
}
|
||||
|
||||
async createOne(request: Request) {
|
||||
const data = await this.coreQueryBuilderFactory.createOne(request);
|
||||
|
||||
return await this.restApiService.call(GraphqlApiType.CORE, request, data);
|
||||
}
|
||||
|
||||
async createMany(request: Request) {
|
||||
const data = await this.coreQueryBuilderFactory.createMany(request);
|
||||
|
||||
return await this.restApiService.call(GraphqlApiType.CORE, request, data);
|
||||
}
|
||||
|
||||
async update(request: Request) {
|
||||
const data = await this.coreQueryBuilderFactory.update(request);
|
||||
|
||||
return await this.restApiService.call(GraphqlApiType.CORE, request, data);
|
||||
}
|
||||
|
||||
async findDuplicates(request: Request) {
|
||||
const data = await this.coreQueryBuilderFactory.findDuplicates(request);
|
||||
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { ObjectRecordOrderBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
export type QueryVariables = {
|
||||
id?: string;
|
||||
ids?: string[];
|
||||
data?: object | null;
|
||||
filter?: object;
|
||||
orderBy?: object;
|
||||
orderBy?: ObjectRecordOrderBy;
|
||||
last?: number;
|
||||
first?: number;
|
||||
startingAfter?: string;
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
|
||||
export const MAX_DEPTH = 2;
|
||||
|
||||
export type Depth = 0 | 1 | 2;
|
||||
|
||||
const ALLOWED_DEPTH_VALUES: Depth[] = [0, 1, MAX_DEPTH];
|
||||
|
||||
@Injectable()
|
||||
export class DepthInputFactory {
|
||||
create(request: Request): Depth {
|
||||
if (!request.query.depth) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const depth = +request.query.depth as Depth;
|
||||
|
||||
if (isNaN(depth) || !ALLOWED_DEPTH_VALUES.includes(depth)) {
|
||||
throw new BadRequestException(
|
||||
`'depth=${
|
||||
request.query.depth
|
||||
}' parameter invalid. Allowed values are ${ALLOWED_DEPTH_VALUES.join(
|
||||
', ',
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return depth;
|
||||
}
|
||||
}
|
||||
@ -3,11 +3,13 @@ import { EndingBeforeInputFactory } from 'src/engine/api/rest/input-factories/en
|
||||
import { LimitInputFactory } from 'src/engine/api/rest/input-factories/limit-input.factory';
|
||||
import { OrderByInputFactory } from 'src/engine/api/rest/input-factories/order-by-input.factory';
|
||||
import { FilterInputFactory } from 'src/engine/api/rest/input-factories/filter-input.factory';
|
||||
import { DepthInputFactory } from 'src/engine/api/rest/input-factories/depth-input.factory';
|
||||
|
||||
export const inputFactories = [
|
||||
StartingAfterInputFactory,
|
||||
DepthInputFactory,
|
||||
EndingBeforeInputFactory,
|
||||
FilterInputFactory,
|
||||
LimitInputFactory,
|
||||
OrderByInputFactory,
|
||||
FilterInputFactory,
|
||||
StartingAfterInputFactory,
|
||||
];
|
||||
|
||||
@ -1,51 +1,23 @@
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
||||
import { RestApiCoreBatchController } from 'src/engine/api/rest/core/controllers/rest-api-core-batch.controller';
|
||||
import { RestApiCoreController } from 'src/engine/api/rest/core/controllers/rest-api-core.controller';
|
||||
import { CoreQueryBuilderModule } from 'src/engine/api/rest/core/query-builder/core-query-builder.module';
|
||||
import { RestApiCoreServiceV2 } from 'src/engine/api/rest/core/rest-api-core-v2.service';
|
||||
import { RestApiCoreService } from 'src/engine/api/rest/core/rest-api-core.service';
|
||||
import { EndingBeforeInputFactory } from 'src/engine/api/rest/input-factories/ending-before-input.factory';
|
||||
import { LimitInputFactory } from 'src/engine/api/rest/input-factories/limit-input.factory';
|
||||
import { StartingAfterInputFactory } from 'src/engine/api/rest/input-factories/starting-after-input.factory';
|
||||
import { MetadataQueryBuilderModule } from 'src/engine/api/rest/metadata/query-builder/metadata-query-builder.module';
|
||||
import { RestApiMetadataController } from 'src/engine/api/rest/metadata/rest-api-metadata.controller';
|
||||
import { RestApiMetadataService } from 'src/engine/api/rest/metadata/rest-api-metadata.service';
|
||||
import { RestApiService } from 'src/engine/api/rest/rest-api.service';
|
||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||
import { RecordTransformerModule } from 'src/engine/core-modules/record-transformer/record-transformer.module';
|
||||
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
import { RestApiCoreModule } from 'src/engine/api/rest/core/rest-api-core.module';
|
||||
import { RestApiService } from 'src/engine/api/rest/rest-api.service';
|
||||
import { RestApiMetadataController } from 'src/engine/api/rest/metadata/rest-api-metadata.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CoreQueryBuilderModule,
|
||||
MetadataQueryBuilderModule,
|
||||
WorkspaceCacheStorageModule,
|
||||
AuthModule,
|
||||
HttpModule,
|
||||
TwentyORMModule,
|
||||
RecordTransformerModule,
|
||||
WorkspacePermissionsCacheModule,
|
||||
RestApiCoreModule,
|
||||
],
|
||||
controllers: [
|
||||
RestApiMetadataController,
|
||||
RestApiCoreBatchController,
|
||||
RestApiCoreController,
|
||||
],
|
||||
providers: [
|
||||
RestApiMetadataService,
|
||||
RestApiCoreService,
|
||||
RestApiCoreServiceV2,
|
||||
RestApiService,
|
||||
StartingAfterInputFactory,
|
||||
EndingBeforeInputFactory,
|
||||
LimitInputFactory,
|
||||
ApiEventEmitterService,
|
||||
],
|
||||
exports: [RestApiMetadataService],
|
||||
controllers: [RestApiMetadataController],
|
||||
providers: [RestApiService, RestApiMetadataService],
|
||||
})
|
||||
export class RestApiModule {}
|
||||
|
||||
Reference in New Issue
Block a user