diff --git a/packages/twenty-server/src/app.module.ts b/packages/twenty-server/src/app.module.ts index 8a0f0e754..95e8b31a0 100644 --- a/packages/twenty-server/src/app.module.ts +++ b/packages/twenty-server/src/app.module.ts @@ -13,7 +13,7 @@ import { join } from 'path'; import { YogaDriverConfig, YogaDriver } from '@graphql-yoga/nestjs'; -import { ApiRestModule } from 'src/engine/api/rest/api-rest.module'; +import { RestApiModule } from 'src/engine/api/rest/rest-api.module'; import { ModulesModule } from 'src/modules/modules.module'; import { CoreGraphQLApiModule } from 'src/engine/api/graphql/core-graphql-api.module'; import { MetadataGraphQLApiModule } from 'src/engine/api/graphql/metadata-graphql-api.module'; @@ -54,7 +54,7 @@ import { IntegrationsModule } from './engine/integrations/integrations.module'; // Api modules CoreGraphQLApiModule, MetadataGraphQLApiModule, - ApiRestModule, + RestApiModule, // Conditional modules ...AppModule.getConditionalModules(), ], diff --git a/packages/twenty-server/src/engine/api/rest/__tests__/api-rest.service.spec.ts b/packages/twenty-server/src/engine/api/rest/__tests__/api-rest.service.spec.ts deleted file mode 100644 index 83ed4dadf..000000000 --- a/packages/twenty-server/src/engine/api/rest/__tests__/api-rest.service.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { HttpService } from '@nestjs/axios'; - -import { ApiRestService } from 'src/engine/api/rest/api-rest.service'; -import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; -import { TokenService } from 'src/engine/core-modules/auth/services/token.service'; -import { ApiRestQueryBuilderFactory } from 'src/engine/api/rest/api-rest-query-builder/api-rest-query-builder.factory'; - -describe('ApiRestService', () => { - let service: ApiRestService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - ApiRestService, - { - provide: ApiRestQueryBuilderFactory, - useValue: {}, - }, - { - provide: EnvironmentService, - useValue: {}, - }, - { - provide: TokenService, - useValue: {}, - }, - { - provide: HttpService, - useValue: {}, - }, - ], - }).compile(); - - service = module.get(ApiRestService); - }); - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/__tests__/api-rest-query-builder.factory.spec.ts b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/__tests__/api-rest-query-builder.factory.spec.ts deleted file mode 100644 index 579786a21..000000000 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/__tests__/api-rest-query-builder.factory.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { ApiRestQueryBuilderFactory } from 'src/engine/api/rest/api-rest-query-builder/api-rest-query-builder.factory'; -import { DeleteQueryFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/delete-query.factory'; -import { CreateQueryFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/create-query.factory'; -import { UpdateQueryFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/update-query.factory'; -import { FindOneQueryFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/find-one-query.factory'; -import { FindManyQueryFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/find-many-query.factory'; -import { DeleteVariablesFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/delete-variables.factory'; -import { CreateVariablesFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/create-variables.factory'; -import { UpdateVariablesFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/update-variables.factory'; -import { GetVariablesFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/get-variables.factory'; -import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; -import { TokenService } from 'src/engine/core-modules/auth/services/token.service'; -import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; - -describe('ApiRestQueryBuilderFactory', () => { - let service: ApiRestQueryBuilderFactory; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - ApiRestQueryBuilderFactory, - { provide: DeleteQueryFactory, useValue: {} }, - { provide: CreateQueryFactory, useValue: {} }, - { provide: UpdateQueryFactory, useValue: {} }, - { provide: FindOneQueryFactory, useValue: {} }, - { provide: FindManyQueryFactory, useValue: {} }, - { provide: DeleteVariablesFactory, useValue: {} }, - { provide: CreateVariablesFactory, useValue: {} }, - { provide: UpdateVariablesFactory, useValue: {} }, - { provide: GetVariablesFactory, useValue: {} }, - { provide: ObjectMetadataService, useValue: {} }, - { provide: TokenService, useValue: {} }, - { provide: EnvironmentService, useValue: {} }, - ], - }).compile(); - - service = module.get( - ApiRestQueryBuilderFactory, - ); - }); - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/api-rest-query-builder.module.ts b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/api-rest-query-builder.module.ts deleted file mode 100644 index d4c09f901..000000000 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/api-rest-query-builder.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { ApiRestQueryBuilderFactory } from 'src/engine/api/rest/api-rest-query-builder/api-rest-query-builder.factory'; -import { apiRestQueryBuilderFactories } from 'src/engine/api/rest/api-rest-query-builder/factories/factories'; -import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; -import { AuthModule } from 'src/engine/core-modules/auth/auth.module'; - -@Module({ - imports: [ObjectMetadataModule, AuthModule], - providers: [...apiRestQueryBuilderFactories, ApiRestQueryBuilderFactory], - exports: [ApiRestQueryBuilderFactory], -}) -export class ApiRestQueryBuilderModule {} diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/create-variables.factory.ts b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/create-variables.factory.ts deleted file mode 100644 index 9b307e8f8..000000000 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/create-variables.factory.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { Request } from 'express'; - -import { ApiRestQueryVariables } from 'src/engine/api/rest/types/api-rest-query-variables.type'; - -@Injectable() -export class CreateVariablesFactory { - create(request: Request): ApiRestQueryVariables { - return { - data: request.body, - }; - } -} diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/delete-variables.factory.ts b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/delete-variables.factory.ts deleted file mode 100644 index 4cd89b779..000000000 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/delete-variables.factory.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { ApiRestQueryVariables } from 'src/engine/api/rest/types/api-rest-query-variables.type'; - -@Injectable() -export class DeleteVariablesFactory { - create(id: string): ApiRestQueryVariables { - return { - id: id, - }; - } -} diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/factories.ts b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/factories.ts deleted file mode 100644 index a2e3951a9..000000000 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/factories.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { DeleteQueryFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/delete-query.factory'; -import { CreateQueryFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/create-query.factory'; -import { UpdateQueryFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/update-query.factory'; -import { FindOneQueryFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/find-one-query.factory'; -import { FindManyQueryFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/find-many-query.factory'; -import { DeleteVariablesFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/delete-variables.factory'; -import { CreateVariablesFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/create-variables.factory'; -import { UpdateVariablesFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/update-variables.factory'; -import { GetVariablesFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/get-variables.factory'; -import { LastCursorInputFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/last-cursor-input.factory'; -import { LimitInputFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/limit-input.factory'; -import { OrderByInputFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/order-by-input.factory'; -import { FilterInputFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-input.factory'; - -export const apiRestQueryBuilderFactories = [ - DeleteQueryFactory, - CreateQueryFactory, - UpdateQueryFactory, - FindOneQueryFactory, - FindManyQueryFactory, - DeleteVariablesFactory, - CreateVariablesFactory, - UpdateVariablesFactory, - GetVariablesFactory, - LastCursorInputFactory, - LimitInputFactory, - OrderByInputFactory, - FilterInputFactory, -]; diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/__tests__/fields.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/__tests__/fields.utils.spec.ts deleted file mode 100644 index fc0e2bc44..000000000 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/__tests__/fields.utils.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock'; -import { - checkFields, - getFieldType, -} from 'src/engine/api/rest/api-rest-query-builder/utils/fields.utils'; -import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; - -describe('FieldUtils', () => { - describe('getFieldType', () => { - it('should get field type', () => { - expect(getFieldType(objectMetadataItemMock, 'fieldNumber')).toEqual( - FieldMetadataType.NUMBER, - ); - }); - }); - - describe('checkFields', () => { - it('should check field types', () => { - expect(() => - checkFields(objectMetadataItemMock, ['fieldNumber']), - ).not.toThrow(); - - expect(() => - checkFields(objectMetadataItemMock, ['wrongField']), - ).toThrow(); - - expect(() => - checkFields(objectMetadataItemMock, ['fieldNumber', 'wrongField']), - ).toThrow(); - }); - }); -}); diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/__tests__/parse-path.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/__tests__/parse-path.utils.spec.ts deleted file mode 100644 index b81a46d5d..000000000 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/__tests__/parse-path.utils.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { parsePath } from 'src/engine/api/rest/api-rest-query-builder/utils/parse-path.utils'; - -describe('parsePath', () => { - it('should parse object from request path', () => { - const request: any = { path: '/rest/companies/uuid' }; - - expect(parsePath(request)).toEqual({ - object: 'companies', - id: 'uuid', - }); - }); - - it('should parse object from request path', () => { - const request: any = { path: '/rest/companies' }; - - expect(parsePath(request)).toEqual({ - object: 'companies', - id: undefined, - }); - }); -}); diff --git a/packages/twenty-server/src/engine/api/rest/api-rest.controller.ts b/packages/twenty-server/src/engine/api/rest/api-rest.controller.ts deleted file mode 100644 index 921cd8841..000000000 --- a/packages/twenty-server/src/engine/api/rest/api-rest.controller.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Controller, Delete, Get, Post, Put, Req, Res } from '@nestjs/common'; - -import { Request, Response } from 'express'; - -import { ApiRestService } from 'src/engine/api/rest/api-rest.service'; -import { cleanGraphQLResponse } from 'src/engine/api/rest/api-rest.controller.utils'; - -@Controller('rest/*') -export class ApiRestController { - constructor(private readonly apiRestService: ApiRestService) {} - - @Get() - async handleApiGet(@Req() request: Request, @Res() res: Response) { - const result = await this.apiRestService.get(request); - - res.send(cleanGraphQLResponse(result.data)); - } - - @Delete() - async handleApiDelete(@Req() request: Request, @Res() res: Response) { - const result = await this.apiRestService.delete(request); - - res.send(cleanGraphQLResponse(result.data)); - } - - @Post() - async handleApiPost(@Req() request: Request, @Res() res: Response) { - const result = await this.apiRestService.create(request); - - res.send(cleanGraphQLResponse(result.data)); - } - - @Put() - async handleApiPut(@Req() request: Request, @Res() res: Response) { - const result = await this.apiRestService.update(request); - - res.send(cleanGraphQLResponse(result.data)); - } -} diff --git a/packages/twenty-server/src/engine/api/rest/api-rest.module.ts b/packages/twenty-server/src/engine/api/rest/api-rest.module.ts deleted file mode 100644 index 79f492733..000000000 --- a/packages/twenty-server/src/engine/api/rest/api-rest.module.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Module } from '@nestjs/common'; -import { HttpModule } from '@nestjs/axios'; - -import { ApiRestController } from 'src/engine/api/rest/api-rest.controller'; -import { ApiRestService } from 'src/engine/api/rest/api-rest.service'; -import { ApiRestQueryBuilderModule } from 'src/engine/api/rest/api-rest-query-builder/api-rest-query-builder.module'; -import { AuthModule } from 'src/engine/core-modules/auth/auth.module'; -import { ApiRestMetadataController } from 'src/engine/api/rest/metadata-rest.controller'; -import { ApiRestMetadataService } from 'src/engine/api/rest/metadata-rest.service'; - -@Module({ - imports: [ApiRestQueryBuilderModule, AuthModule, HttpModule], - controllers: [ApiRestMetadataController, ApiRestController], - providers: [ApiRestMetadataService, ApiRestService], - exports: [ApiRestMetadataService], -}) -export class ApiRestModule {} diff --git a/packages/twenty-server/src/engine/api/rest/api-rest.service.ts b/packages/twenty-server/src/engine/api/rest/api-rest.service.ts deleted file mode 100644 index 3631f4dcd..000000000 --- a/packages/twenty-server/src/engine/api/rest/api-rest.service.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { HttpService } from '@nestjs/axios'; - -import { Request } from 'express'; -import { AxiosResponse } from 'axios'; - -import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; -import { ApiRestQueryBuilderFactory } from 'src/engine/api/rest/api-rest-query-builder/api-rest-query-builder.factory'; -import { ApiRestQuery } from 'src/engine/api/rest/types/api-rest-query.type'; -import { getServerUrl } from 'src/utils/get-server-url'; - -@Injectable() -export class ApiRestService { - constructor( - private readonly environmentService: EnvironmentService, - private readonly apiRestQueryBuilderFactory: ApiRestQueryBuilderFactory, - private readonly httpService: HttpService, - ) {} - - async callGraphql(request: Request, data: ApiRestQuery) { - const baseUrl = getServerUrl( - request, - this.environmentService.get('SERVER_URL'), - ); - let response: AxiosResponse; - - try { - response = await this.httpService.axiosRef.post( - `${baseUrl}/graphql`, - data, - { - headers: { - 'Content-Type': 'application/json', - Authorization: request.headers.authorization, - }, - }, - ); - } catch (err) { - throw new BadRequestException(err.response.data); - } - - if (response.data.errors?.length) { - throw new BadRequestException(response.data); - } - - return response; - } - - async get(request: Request) { - const data = await this.apiRestQueryBuilderFactory.get(request); - - return await this.callGraphql(request, data); - } - - async delete(request: Request) { - const data = await this.apiRestQueryBuilderFactory.delete(request); - - return await this.callGraphql(request, data); - } - - async create(request: Request) { - const data = await this.apiRestQueryBuilderFactory.create(request); - - return await this.callGraphql(request, data); - } - - async update(request: Request) { - const data = await this.apiRestQueryBuilderFactory.update(request); - - return await this.callGraphql(request, data); - } -} diff --git a/packages/twenty-server/src/engine/api/rest/controllers/rest-api-core-batch.controller.ts b/packages/twenty-server/src/engine/api/rest/controllers/rest-api-core-batch.controller.ts new file mode 100644 index 000000000..6f35ed834 --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/controllers/rest-api-core-batch.controller.ts @@ -0,0 +1,18 @@ +import { Controller, Post, Req, Res } from '@nestjs/common'; + +import { Request, Response } from 'express'; + +import { RestApiCoreService } from 'src/engine/api/rest/services/rest-api-core.service'; +import { cleanGraphQLResponse } from 'src/engine/api/rest/utils/clean-graphql-response.utils'; + +@Controller('rest/batch/*') +export class RestApiCoreBatchController { + constructor(private readonly restApiCoreService: RestApiCoreService) {} + + @Post() + async handleApiPost(@Req() request: Request, @Res() res: Response) { + const result = await this.restApiCoreService.createMany(request); + + res.status(201).send(cleanGraphQLResponse(result.data)); + } +} diff --git a/packages/twenty-server/src/engine/api/rest/controllers/rest-api-core.controller.ts b/packages/twenty-server/src/engine/api/rest/controllers/rest-api-core.controller.ts new file mode 100644 index 000000000..8be1e4e6d --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/controllers/rest-api-core.controller.ts @@ -0,0 +1,58 @@ +import { + Controller, + Delete, + Get, + Patch, + Post, + Put, + Req, + Res, +} from '@nestjs/common'; + +import { Request, Response } from 'express'; + +import { RestApiCoreService } from 'src/engine/api/rest/services/rest-api-core.service'; +import { cleanGraphQLResponse } from 'src/engine/api/rest/utils/clean-graphql-response.utils'; + +@Controller('rest/*') +export class RestApiCoreController { + constructor(private readonly restApiCoreService: RestApiCoreService) {} + + @Get() + async handleApiGet(@Req() request: Request, @Res() res: Response) { + const result = await this.restApiCoreService.get(request); + + res.status(200).send(cleanGraphQLResponse(result.data)); + } + + @Delete() + async handleApiDelete(@Req() request: Request, @Res() res: Response) { + const result = await this.restApiCoreService.delete(request); + + res.status(200).send(cleanGraphQLResponse(result.data)); + } + + @Post() + async handleApiPost(@Req() request: Request, @Res() res: Response) { + const result = await this.restApiCoreService.createOne(request); + + res.status(201).send(cleanGraphQLResponse(result.data)); + } + + @Patch() + async handleApiPatch(@Req() request: Request, @Res() res: Response) { + const result = await this.restApiCoreService.update(request); + + res.status(200).send(cleanGraphQLResponse(result.data)); + } + + // This endpoint is not documented in the OpenAPI schema. + // 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() + async handleApiPut(@Req() request: Request, @Res() res: Response) { + const result = await this.restApiCoreService.update(request); + + res.status(200).send(cleanGraphQLResponse(result.data)); + } +} diff --git a/packages/twenty-server/src/engine/api/rest/controllers/rest-api-metadata.controller.ts b/packages/twenty-server/src/engine/api/rest/controllers/rest-api-metadata.controller.ts new file mode 100644 index 000000000..ac42dba52 --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/controllers/rest-api-metadata.controller.ts @@ -0,0 +1,60 @@ +import { + Controller, + Get, + Delete, + Post, + Req, + Res, + Patch, + Put, +} from '@nestjs/common'; + +import { Request, Response } from 'express'; + +import { RestApiMetadataService } from 'src/engine/api/rest/services/rest-api-metadata.service'; +import { cleanGraphQLResponse } from 'src/engine/api/rest/utils/clean-graphql-response.utils'; + +@Controller('rest/metadata/*') +export class RestApiMetadataController { + constructor( + private readonly restApiMetadataService: RestApiMetadataService, + ) {} + + @Get() + async handleApiGet(@Req() request: Request, @Res() res: Response) { + const result = await this.restApiMetadataService.get(request); + + res.status(200).send(cleanGraphQLResponse(result.data)); + } + + @Delete() + async handleApiDelete(@Req() request: Request, @Res() res: Response) { + const result = await this.restApiMetadataService.delete(request); + + res.status(200).send(cleanGraphQLResponse(result.data)); + } + + @Post() + async handleApiPost(@Req() request: Request, @Res() res: Response) { + const result = await this.restApiMetadataService.create(request); + + res.status(201).send(cleanGraphQLResponse(result.data)); + } + + @Patch() + async handleApiPatch(@Req() request: Request, @Res() res: Response) { + const result = await this.restApiMetadataService.update(request); + + res.status(200).send(cleanGraphQLResponse(result.data)); + } + + // This endpoint is not documented in the OpenAPI schema. + // 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() + async handleApiPut(@Req() request: Request, @Res() res: Response) { + const result = await this.restApiMetadataService.update(request); + + res.status(200).send(cleanGraphQLResponse(result.data)); + } +} diff --git a/packages/twenty-server/src/engine/api/rest/errors/RestApiException.ts b/packages/twenty-server/src/engine/api/rest/errors/RestApiException.ts new file mode 100644 index 000000000..11a72c026 --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/errors/RestApiException.ts @@ -0,0 +1,24 @@ +import { BadRequestException } from '@nestjs/common'; + +import { BaseGraphQLError } from 'src/engine/utils/graphql-errors.util'; + +const formatMessage = (message: BaseGraphQLError) => { + if (message.extensions) { + return message.extensions.response.message || message.extensions.response; + } + + return message.message; +}; + +export class RestApiException extends BadRequestException { + constructor(errors: BaseGraphQLError[]) { + super({ + statusCode: 400, + message: + errors.length === 1 + ? formatMessage(errors[0]) + : JSON.stringify(errors.map((error) => formatMessage(error))), + error: 'Bad Request', + }); + } +} diff --git a/packages/twenty-server/src/engine/api/rest/metadata-rest.controller.ts b/packages/twenty-server/src/engine/api/rest/metadata-rest.controller.ts deleted file mode 100644 index 98b3b088d..000000000 --- a/packages/twenty-server/src/engine/api/rest/metadata-rest.controller.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Controller, Get, Delete, Post, Put, Req, Res } from '@nestjs/common'; - -import { Request, Response } from 'express'; - -import { ApiRestMetadataService } from 'src/engine/api/rest/metadata-rest.service'; -import { cleanGraphQLResponse } from 'src/engine/api/rest/api-rest.controller.utils'; - -@Controller('rest/metadata/*') -export class ApiRestMetadataController { - constructor(private readonly apiRestService: ApiRestMetadataService) {} - - @Get() - async handleApiGet(@Req() request: Request, @Res() res: Response) { - const result = await this.apiRestService.get(request); - - res.send(cleanGraphQLResponse(result.data)); - } - - @Delete() - async handleApiDelete(@Req() request: Request, @Res() res: Response) { - const result = await this.apiRestService.delete(request); - - res.send(cleanGraphQLResponse(result.data)); - } - - @Post() - async handleApiPost(@Req() request: Request, @Res() res: Response) { - const result = await this.apiRestService.create(request); - - res.send(cleanGraphQLResponse(result.data)); - } - - @Put() - async handleApiPut(@Req() request: Request, @Res() res: Response) { - const result = await this.apiRestService.update(request); - - res.send(cleanGraphQLResponse(result.data)); - } -} diff --git a/packages/twenty-server/src/engine/api/rest/metadata-rest.service.ts b/packages/twenty-server/src/engine/api/rest/metadata-rest.service.ts deleted file mode 100644 index 3c6ea223d..000000000 --- a/packages/twenty-server/src/engine/api/rest/metadata-rest.service.ts +++ /dev/null @@ -1,325 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { HttpService } from '@nestjs/axios'; - -import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; -import { ApiRestQueryBuilderFactory } from 'src/engine/api/rest/api-rest-query-builder/api-rest-query-builder.factory'; -import { ApiRestQuery } from 'src/engine/api/rest/types/api-rest-query.type'; -import { TokenService } from 'src/engine/core-modules/auth/services/token.service'; -import { parseMetadataPath } from 'src/engine/api/rest/api-rest-query-builder/utils/parse-path.utils'; -import { capitalize } from 'src/utils/capitalize'; -import { getServerUrl } from 'src/utils/get-server-url'; - -@Injectable() -export class ApiRestMetadataService { - constructor( - private readonly tokenService: TokenService, - private readonly environmentService: EnvironmentService, - private readonly apiRestQueryBuilderFactory: ApiRestQueryBuilderFactory, - private readonly httpService: HttpService, - ) {} - - async callMetadata(request, data: ApiRestQuery) { - const baseUrl = getServerUrl( - request, - this.environmentService.get('SERVER_URL'), - ); - - try { - return await this.httpService.axiosRef.post(`${baseUrl}/metadata`, data, { - headers: { - 'Content-Type': 'application/json', - Authorization: request.headers.authorization, - }, - }); - } catch (err) { - return { - data: { - error: `${err}. Please check your query.`, - status: err.response.status, - }, - }; - } - } - - async fetchMetadataInputFields(request, fieldName: string) { - const query = ` - query { - __type(name: "${fieldName}") { - inputFields { name } - } - } - `; - const data: ApiRestQuery = { - query, - variables: {}, - }; - - const { data: response } = await this.callMetadata(request, data); - const fields = response.data.__type.inputFields.map((field) => field.name); - - return fields; - } - - fetchMetadataFields(objectNamePlural: string) { - const fields = ` - type - name - label - description - icon - isCustom - isActive - isSystem - isNullable - createdAt - updatedAt - fromRelationMetadata { - id - relationType - toObjectMetadata { - id - dataSourceId - nameSingular - namePlural - isSystem - } - toFieldMetadataId - } - toRelationMetadata { - id - relationType - fromObjectMetadata { - id - dataSourceId - nameSingular - namePlural - isSystem - } - fromFieldMetadataId - } - defaultValue - options - `; - - switch (objectNamePlural) { - case 'objects': - return ` - dataSourceId - nameSingular - namePlural - labelSingular - labelPlural - description - icon - isCustom - isActive - isSystem - createdAt - updatedAt - labelIdentifierFieldMetadataId - imageIdentifierFieldMetadataId - fields(paging: { first: 1000 }) { - edges { - node { - id - ${fields} - } - } - } - `; - case 'fields': - return fields; - case 'relations': - return ` - relationType - fromObjectMetadata { - id - dataSourceId - nameSingular - namePlural - isSystem - } - fromObjectMetadataId - toObjectMetadata { - id - dataSourceId - nameSingular - namePlural - isSystem - } - toObjectMetadataId - fromFieldMetadataId - toFieldMetadataId - `; - } - } - - generateFindManyQuery(objectNameSingular: string, objectNamePlural: string) { - const fields = this.fetchMetadataFields(objectNamePlural); - - return ` - query FindMany${capitalize(objectNamePlural)}( - $filter: ${objectNameSingular}Filter, - ) { - ${objectNamePlural}( - filter: $filter, - paging: { first: 1000 } - ) { - edges { - node { - id - ${fields} - } - } - } - } - `; - } - - generateFindOneQuery(objectNameSingular: string, objectNamePlural: string) { - const fields = this.fetchMetadataFields(objectNamePlural); - - return ` - query FindOne${capitalize(objectNameSingular)}( - $id: ID!, - ) { - ${objectNameSingular}(id: $id) { - id - ${fields} - } - } - `; - } - - async get(request) { - try { - await this.tokenService.validateToken(request); - - const { objectNameSingular, objectNamePlural, id } = - parseMetadataPath(request); - - const query = id - ? this.generateFindOneQuery(objectNameSingular, objectNamePlural) - : this.generateFindManyQuery(objectNameSingular, objectNamePlural); - - const data: ApiRestQuery = { - query, - variables: id ? { id } : request.body, - }; - - return await this.callMetadata(request, data); - } catch (err) { - return { data: { error: err, status: err.status } }; - } - } - - async create(request) { - try { - await this.tokenService.validateToken(request); - - const { objectNameSingular: objectName } = parseMetadataPath(request); - const objectNameCapitalized = capitalize(objectName); - - const fieldName = `Create${objectNameCapitalized}Input`; - const fields = await this.fetchMetadataInputFields(request, fieldName); - - const query = ` - mutation Create${objectNameCapitalized}($input: CreateOne${objectNameCapitalized}Input!) { - createOne${objectNameCapitalized}(input: $input) { - id - ${fields.map((field) => field).join('\n')} - } - } - `; - - const data: ApiRestQuery = { - query, - variables: { - input: { - [objectName]: request.body, - }, - }, - }; - - return await this.callMetadata(request, data); - } catch (err) { - return { data: { error: err, status: err.status } }; - } - } - - async update(request) { - try { - await this.tokenService.validateToken(request); - - const { objectNameSingular: objectName, id } = parseMetadataPath(request); - const objectNameCapitalized = capitalize(objectName); - - if (!id) { - throw new BadRequestException( - `update ${objectName} query invalid. Id missing. eg: /rest/metadata/${objectName}/0d4389ef-ea9c-4ae8-ada1-1cddc440fb56`, - ); - } - const fieldName = `Update${objectNameCapitalized}Input`; - const fields = await this.fetchMetadataInputFields(request, fieldName); - - const query = ` - mutation Update${objectNameCapitalized}($input: UpdateOne${objectNameCapitalized}Input!) { - updateOne${objectNameCapitalized}(input: $input) { - id - ${fields.map((field) => field).join('\n')} - } - } - `; - - const data: ApiRestQuery = { - query, - variables: { - input: { - update: request.body, - id, - }, - }, - }; - - return await this.callMetadata(request, data); - } catch (err) { - return { data: { error: err, status: err.status } }; - } - } - - async delete(request) { - try { - await this.tokenService.validateToken(request); - - const { objectNameSingular: objectName, id } = parseMetadataPath(request); - const objectNameCapitalized = capitalize(objectName); - - if (!id) { - throw new BadRequestException( - `delete ${objectName} query invalid. Id missing. eg: /rest/metadata/${objectName}/0d4389ef-ea9c-4ae8-ada1-1cddc440fb56`, - ); - } - - const query = ` - mutation Delete${objectNameCapitalized}($input: DeleteOne${objectNameCapitalized}Input!) { - deleteOne${objectNameCapitalized}(input: $input) { - id - } - } - `; - - const data: ApiRestQuery = { - query, - variables: { - input: { - id, - }, - }, - }; - - return await this.callMetadata(request, data); - } catch (err) { - return { data: { error: err, status: err.status } }; - } - } -} diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/__tests__/core-query-builder.factory.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/__tests__/core-query-builder.factory.spec.ts new file mode 100644 index 000000000..b64197417 --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/__tests__/core-query-builder.factory.spec.ts @@ -0,0 +1,46 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { CoreQueryBuilderFactory } from 'src/engine/api/rest/rest-api-core-query-builder/core-query-builder.factory'; +import { DeleteQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/delete-query.factory'; +import { CreateOneQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/create-one-query.factory'; +import { CreateManyQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/create-many-query.factory'; +import { UpdateQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/update-query.factory'; +import { FindOneQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/find-one-query.factory'; +import { FindManyQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/find-many-query.factory'; +import { DeleteVariablesFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/delete-variables.factory'; +import { CreateVariablesFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/create-variables.factory'; +import { UpdateVariablesFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/update-variables.factory'; +import { GetVariablesFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/get-variables.factory'; +import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; +import { TokenService } from 'src/engine/core-modules/auth/services/token.service'; +import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; + +describe('CoreQueryBuilderFactory', () => { + let service: CoreQueryBuilderFactory; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + CoreQueryBuilderFactory, + { provide: DeleteQueryFactory, useValue: {} }, + { provide: CreateOneQueryFactory, useValue: {} }, + { provide: CreateManyQueryFactory, useValue: {} }, + { provide: UpdateQueryFactory, useValue: {} }, + { provide: FindOneQueryFactory, useValue: {} }, + { provide: FindManyQueryFactory, useValue: {} }, + { provide: DeleteVariablesFactory, useValue: {} }, + { provide: CreateVariablesFactory, useValue: {} }, + { provide: UpdateVariablesFactory, useValue: {} }, + { provide: GetVariablesFactory, useValue: {} }, + { provide: ObjectMetadataService, useValue: {} }, + { provide: TokenService, useValue: {} }, + { provide: EnvironmentService, useValue: {} }, + ], + }).compile(); + + service = module.get(CoreQueryBuilderFactory); + }); + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/api-rest-query-builder.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/core-query-builder.factory.ts similarity index 53% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/api-rest-query-builder.factory.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/core-query-builder.factory.ts index 3f968e02d..d7f0d6d18 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/api-rest-query-builder.factory.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/core-query-builder.factory.ts @@ -2,28 +2,31 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { Request } from 'express'; -import { DeleteQueryFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/delete-query.factory'; +import { DeleteQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/delete-query.factory'; import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { TokenService } from 'src/engine/core-modules/auth/services/token.service'; -import { CreateQueryFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/create-query.factory'; -import { UpdateQueryFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/update-query.factory'; -import { FindOneQueryFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/find-one-query.factory'; -import { FindManyQueryFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/find-many-query.factory'; -import { DeleteVariablesFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/delete-variables.factory'; -import { CreateVariablesFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/create-variables.factory'; -import { UpdateVariablesFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/update-variables.factory'; -import { GetVariablesFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/get-variables.factory'; -import { parsePath } from 'src/engine/api/rest/api-rest-query-builder/utils/parse-path.utils'; -import { computeDepth } from 'src/engine/api/rest/api-rest-query-builder/utils/compute-depth.utils'; +import { CreateOneQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/create-one-query.factory'; +import { UpdateQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/update-query.factory'; +import { FindOneQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/find-one-query.factory'; +import { FindManyQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/find-many-query.factory'; +import { DeleteVariablesFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/delete-variables.factory'; +import { CreateVariablesFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/create-variables.factory'; +import { UpdateVariablesFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/update-variables.factory'; +import { GetVariablesFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/get-variables.factory'; +import { parseCorePath } from 'src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/parse-core-path.utils'; +import { computeDepth } from 'src/engine/api/rest/rest-api-core-query-builder/utils/compute-depth.utils'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { ApiRestQuery } from 'src/engine/api/rest/types/api-rest-query.type'; +import { Query } from 'src/engine/api/rest/types/query.type'; import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; +import { CreateManyQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/create-many-query.factory'; +import { parseCoreBatchPath } from 'src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/parse-core-batch-path.utils'; @Injectable() -export class ApiRestQueryBuilderFactory { +export class CoreQueryBuilderFactory { constructor( private readonly deleteQueryFactory: DeleteQueryFactory, - private readonly createQueryFactory: CreateQueryFactory, + private readonly createOneQueryFactory: CreateOneQueryFactory, + private readonly createManyQueryFactory: CreateManyQueryFactory, private readonly updateQueryFactory: UpdateQueryFactory, private readonly findOneQueryFactory: FindOneQueryFactory, private readonly findManyQueryFactory: FindManyQueryFactory, @@ -36,7 +39,10 @@ export class ApiRestQueryBuilderFactory { private readonly environmentService: EnvironmentService, ) {} - async getObjectMetadata(request: Request): Promise<{ + async getObjectMetadata( + request: Request, + parsedObject: string, + ): Promise<{ objectMetadataItems: ObjectMetadataEntity[]; objectMetadataItem: ObjectMetadataEntity; }> { @@ -53,8 +59,6 @@ export class ApiRestQueryBuilderFactory { ); } - const { object: parsedObject } = parsePath(request); - const [objectMetadata] = objectMetadataItems.filter( (object) => object.namePlural === parsedObject, ); @@ -81,10 +85,11 @@ export class ApiRestQueryBuilderFactory { }; } - async delete(request: Request): Promise { - const objectMetadata = await this.getObjectMetadata(request); + async delete(request: Request): Promise { + const { object: parsedObject } = parseCorePath(request); + const objectMetadata = await this.getObjectMetadata(request, parsedObject); - const { id } = parsePath(request); + const { id } = parseCorePath(request); if (!id) { throw new BadRequestException( @@ -98,23 +103,36 @@ export class ApiRestQueryBuilderFactory { }; } - async create(request: Request): Promise { - const objectMetadata = await this.getObjectMetadata(request); + async createOne(request: Request): Promise { + const { object: parsedObject } = parseCorePath(request); + const objectMetadata = await this.getObjectMetadata(request, parsedObject); const depth = computeDepth(request); return { - query: this.createQueryFactory.create(objectMetadata, depth), + query: this.createOneQueryFactory.create(objectMetadata, depth), variables: this.createVariablesFactory.create(request), }; } - async update(request: Request): Promise { - const objectMetadata = await this.getObjectMetadata(request); + async createMany(request: Request): Promise { + const { object: parsedObject } = parseCoreBatchPath(request); + const objectMetadata = await this.getObjectMetadata(request, parsedObject); + const depth = computeDepth(request); + + return { + query: this.createManyQueryFactory.create(objectMetadata, depth), + variables: this.createVariablesFactory.create(request), + }; + } + + async update(request: Request): Promise { + const { object: parsedObject } = parseCorePath(request); + const objectMetadata = await this.getObjectMetadata(request, parsedObject); const depth = computeDepth(request); - const { id } = parsePath(request); + const { id } = parseCorePath(request); if (!id) { throw new BadRequestException( @@ -128,12 +146,13 @@ export class ApiRestQueryBuilderFactory { }; } - async get(request: Request): Promise { - const objectMetadata = await this.getObjectMetadata(request); + async get(request: Request): Promise { + const { object: parsedObject } = parseCorePath(request); + const objectMetadata = await this.getObjectMetadata(request, parsedObject); const depth = computeDepth(request); - const { id } = parsePath(request); + const { id } = parseCorePath(request); return { query: id diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/core-query-builder.module.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/core-query-builder.module.ts new file mode 100644 index 000000000..8e91f1174 --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/core-query-builder.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; + +import { CoreQueryBuilderFactory } from 'src/engine/api/rest/rest-api-core-query-builder/core-query-builder.factory'; +import { coreQueryBuilderFactories } from 'src/engine/api/rest/rest-api-core-query-builder/factories/factories'; +import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; +import { AuthModule } from 'src/engine/core-modules/auth/auth.module'; + +@Module({ + imports: [ObjectMetadataModule, AuthModule], + providers: [...coreQueryBuilderFactories, CoreQueryBuilderFactory], + exports: [CoreQueryBuilderFactory], +}) +export class CoreQueryBuilderModule {} diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/create-many-query.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/create-many-query.factory.ts new file mode 100644 index 000000000..02c8714f4 --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/create-many-query.factory.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@nestjs/common'; + +import { capitalize } from 'src/utils/capitalize'; +import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/rest-api-core-query-builder/utils/map-field-metadata-to-graphql-query.utils'; + +@Injectable() +export class CreateManyQueryFactory { + create(objectMetadata, depth?: number): string { + const objectNamePlural = capitalize( + objectMetadata.objectMetadataItem.namePlural, + ); + const objectNameSingular = capitalize( + objectMetadata.objectMetadataItem.nameSingular, + ); + + return ` + mutation Create${objectNamePlural}($data: [${objectNameSingular}CreateInput!]) { + create${objectNamePlural}(data: $data) { + id + ${objectMetadata.objectMetadataItem.fields + .map((field) => + mapFieldMetadataToGraphqlQuery( + objectMetadata.objectMetadataItems, + field, + depth, + ), + ) + .join('\n')} + } + } + `; + } +} diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/create-query.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/create-one-query.factory.ts similarity index 87% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/create-query.factory.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/create-one-query.factory.ts index 731567e16..a7135605e 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/create-query.factory.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/create-one-query.factory.ts @@ -1,10 +1,10 @@ import { Injectable } from '@nestjs/common'; import { capitalize } from 'src/utils/capitalize'; -import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils'; +import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/rest-api-core-query-builder/utils/map-field-metadata-to-graphql-query.utils'; @Injectable() -export class CreateQueryFactory { +export class CreateOneQueryFactory { create(objectMetadata, depth?: number): string { const objectNameSingular = capitalize( objectMetadata.objectMetadataItem.nameSingular, diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/create-variables.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/create-variables.factory.ts new file mode 100644 index 000000000..eb4b20ef1 --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/create-variables.factory.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@nestjs/common'; + +import { Request } from 'express'; + +import { QueryVariables } from 'src/engine/api/rest/types/query-variables.type'; + +@Injectable() +export class CreateVariablesFactory { + create(request: Request): QueryVariables { + const data = Array.isArray(request.body) + ? request.body.map((recordData) => { + return { position: 'first', ...recordData }; + }) + : { position: 'first', ...request.body }; + + return { + data, + }; + } +} diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/delete-query.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/delete-query.factory.ts similarity index 100% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/delete-query.factory.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/delete-query.factory.ts diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/delete-variables.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/delete-variables.factory.ts new file mode 100644 index 000000000..c41c2073c --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/delete-variables.factory.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@nestjs/common'; + +import { QueryVariables } from 'src/engine/api/rest/types/query-variables.type'; + +@Injectable() +export class DeleteVariablesFactory { + create(id: string): QueryVariables { + return { + id: id, + }; + } +} diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/factories.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/factories.ts new file mode 100644 index 000000000..d6bbd273f --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/factories.ts @@ -0,0 +1,31 @@ +import { DeleteQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/delete-query.factory'; +import { CreateOneQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/create-one-query.factory'; +import { UpdateQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/update-query.factory'; +import { FindOneQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/find-one-query.factory'; +import { FindManyQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/find-many-query.factory'; +import { DeleteVariablesFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/delete-variables.factory'; +import { CreateVariablesFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/create-variables.factory'; +import { UpdateVariablesFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/update-variables.factory'; +import { GetVariablesFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/get-variables.factory'; +import { LastCursorInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/last-cursor-input.factory'; +import { LimitInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/limit-input.factory'; +import { OrderByInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/order-by-input.factory'; +import { FilterInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-input.factory'; +import { CreateManyQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/create-many-query.factory'; + +export const coreQueryBuilderFactories = [ + DeleteQueryFactory, + CreateOneQueryFactory, + CreateManyQueryFactory, + UpdateQueryFactory, + FindOneQueryFactory, + FindManyQueryFactory, + DeleteVariablesFactory, + CreateVariablesFactory, + UpdateVariablesFactory, + GetVariablesFactory, + LastCursorInputFactory, + LimitInputFactory, + OrderByInputFactory, + FilterInputFactory, +]; diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/find-many-query.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/find-many-query.factory.ts similarity index 94% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/find-many-query.factory.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/find-many-query.factory.ts index b59abac04..92c1741f0 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/find-many-query.factory.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/find-many-query.factory.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { capitalize } from 'src/utils/capitalize'; -import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils'; +import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/rest-api-core-query-builder/utils/map-field-metadata-to-graphql-query.utils'; @Injectable() export class FindManyQueryFactory { diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/find-one-query.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/find-one-query.factory.ts similarity index 91% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/find-one-query.factory.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/find-one-query.factory.ts index 0ab363e40..9286080be 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/find-one-query.factory.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/find-one-query.factory.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { capitalize } from 'src/utils/capitalize'; -import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils'; +import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/rest-api-core-query-builder/utils/map-field-metadata-to-graphql-query.utils'; @Injectable() export class FindOneQueryFactory { diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/get-variables.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/get-variables.factory.ts similarity index 56% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/get-variables.factory.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/get-variables.factory.ts index dfe87ce77..c838ce14e 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/get-variables.factory.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/get-variables.factory.ts @@ -2,11 +2,11 @@ import { Injectable } from '@nestjs/common'; import { Request } from 'express'; -import { LastCursorInputFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/last-cursor-input.factory'; -import { LimitInputFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/limit-input.factory'; -import { OrderByInputFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/order-by-input.factory'; -import { FilterInputFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-input.factory'; -import { ApiRestQueryVariables } from 'src/engine/api/rest/types/api-rest-query-variables.type'; +import { LastCursorInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/last-cursor-input.factory'; +import { LimitInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/limit-input.factory'; +import { OrderByInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/order-by-input.factory'; +import { FilterInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-input.factory'; +import { QueryVariables } from 'src/engine/api/rest/types/query-variables.type'; @Injectable() export class GetVariablesFactory { @@ -21,7 +21,7 @@ export class GetVariablesFactory { id: string | undefined, request: Request, objectMetadata, - ): ApiRestQueryVariables { + ): QueryVariables { if (id) { return { filter: { id: { eq: id } } }; } diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/__tests__/filter-input.factory.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/filter-input.factory.spec.ts similarity index 96% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/__tests__/filter-input.factory.spec.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/filter-input.factory.spec.ts index d4e9e3cf8..a27948d44 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/__tests__/filter-input.factory.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/filter-input.factory.spec.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock'; -import { FilterInputFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-input.factory'; +import { FilterInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-input.factory'; describe('FilterInputFactory', () => { const objectMetadata = { objectMetadataItem: objectMetadataItemMock }; diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/__tests__/last-cursor-input.factory.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/last-cursor-input.factory.spec.ts similarity index 85% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/__tests__/last-cursor-input.factory.spec.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/last-cursor-input.factory.spec.ts index 58a7f1432..e979584fb 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/__tests__/last-cursor-input.factory.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/last-cursor-input.factory.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { LastCursorInputFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/last-cursor-input.factory'; +import { LastCursorInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/last-cursor-input.factory'; describe('LastCursorInputFactory', () => { let service: LastCursorInputFactory; diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/__tests__/limit-input.factory.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/limit-input.factory.spec.ts similarity index 90% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/__tests__/limit-input.factory.spec.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/limit-input.factory.spec.ts index a3e0fcdf6..16ed18507 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/__tests__/limit-input.factory.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/limit-input.factory.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { LimitInputFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/limit-input.factory'; +import { LimitInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/limit-input.factory'; describe('LimitInputFactory', () => { let service: LimitInputFactory; diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/__tests__/order-by-input.factory.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/order-by-input.factory.spec.ts similarity index 96% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/__tests__/order-by-input.factory.spec.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/order-by-input.factory.spec.ts index 7e09cbc73..deedebe78 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/__tests__/order-by-input.factory.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/__tests__/order-by-input.factory.spec.ts @@ -3,7 +3,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock'; -import { OrderByInputFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/order-by-input.factory'; +import { OrderByInputFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/order-by-input.factory'; describe('OrderByInputFactory', () => { const objectMetadata = { objectMetadataItem: objectMetadataItemMock }; diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-input.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-input.factory.ts similarity index 54% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-input.factory.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-input.factory.ts index be2efdfbb..1041212df 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-input.factory.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-input.factory.ts @@ -2,10 +2,10 @@ import { Injectable } from '@nestjs/common'; import { Request } from 'express'; -import { addDefaultConjunctionIfMissing } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/add-default-conjunction.utils'; -import { checkFilterQuery } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/check-filter-query.utils'; -import { parseFilter } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter.utils'; -import { FieldValue } from 'src/engine/api/rest/types/api-rest-field-value.type'; +import { addDefaultConjunctionIfMissing } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/add-default-conjunction.utils'; +import { checkFilterQuery } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/check-filter-query.utils'; +import { parseFilter } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-filter.utils'; +import { FieldValue } from 'src/engine/api/rest/types/field-value.type'; @Injectable() export class FilterInputFactory { diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/add-default-conjunction.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/add-default-conjunction.utils.spec.ts similarity index 81% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/add-default-conjunction.utils.spec.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/add-default-conjunction.utils.spec.ts index 90a58f59c..8adda4835 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/add-default-conjunction.utils.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/add-default-conjunction.utils.spec.ts @@ -1,4 +1,4 @@ -import { addDefaultConjunctionIfMissing } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/add-default-conjunction.utils'; +import { addDefaultConjunctionIfMissing } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/add-default-conjunction.utils'; describe('addDefaultConjunctionIfMissing', () => { it('should add default conjunction if missing', () => { diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/check-filter-enum-values.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/check-filter-enum-values.spec.ts similarity index 84% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/check-filter-enum-values.spec.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/check-filter-enum-values.spec.ts index 7c4f35d0a..4e4694c11 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/check-filter-enum-values.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/check-filter-enum-values.spec.ts @@ -1,4 +1,4 @@ -import { checkFilterEnumValues } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/check-filter-enum-values'; +import { checkFilterEnumValues } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/check-filter-enum-values'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { fieldSelectMock, diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/check-filter-query.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/check-filter-query.utils.spec.ts similarity index 86% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/check-filter-query.utils.spec.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/check-filter-query.utils.spec.ts index 88d08c661..fc1f3e818 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/check-filter-query.utils.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/check-filter-query.utils.spec.ts @@ -1,4 +1,4 @@ -import { checkFilterQuery } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/check-filter-query.utils'; +import { checkFilterQuery } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/check-filter-query.utils'; describe('checkFilterQuery', () => { it('should check filter query', () => { diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/format-field-values.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/format-field-values.utils.spec.ts similarity index 92% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/format-field-values.utils.spec.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/format-field-values.utils.spec.ts index 22ad6baa6..eb94f88b3 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/format-field-values.utils.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/format-field-values.utils.spec.ts @@ -1,5 +1,5 @@ import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { formatFieldValue } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/format-field-values.utils'; +import { formatFieldValue } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/format-field-values.utils'; describe('formatFieldValue', () => { it('should format fieldNumber value', () => { diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/parse-base-filter.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/parse-base-filter.utils.spec.ts similarity index 90% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/parse-base-filter.utils.spec.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/parse-base-filter.utils.spec.ts index d335485ab..c522b00bd 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/parse-base-filter.utils.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/parse-base-filter.utils.spec.ts @@ -1,4 +1,4 @@ -import { parseBaseFilter } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-base-filter.utils'; +import { parseBaseFilter } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-base-filter.utils'; describe('parseBaseFilter', () => { it('should parse simple filter string test 1', () => { diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/parse-filter-content.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/parse-filter-content.utils.spec.ts similarity index 91% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/parse-filter-content.utils.spec.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/parse-filter-content.utils.spec.ts index fc08fc400..60cf46def 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/parse-filter-content.utils.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/parse-filter-content.utils.spec.ts @@ -1,4 +1,4 @@ -import { parseFilterContent } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter-content.utils'; +import { parseFilterContent } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-filter-content.utils'; describe('parseFilterContent', () => { it('should parse query filter test 1', () => { diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/parse-filter.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/parse-filter.utils.spec.ts similarity index 94% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/parse-filter.utils.spec.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/parse-filter.utils.spec.ts index a5c36b91d..54de0817b 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/__tests__/parse-filter.utils.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/__tests__/parse-filter.utils.spec.ts @@ -1,5 +1,5 @@ import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock'; -import { parseFilter } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter.utils'; +import { parseFilter } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-filter.utils'; describe('parseFilter', () => { it('should parse string filter test 1', () => { diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/add-default-conjunction.utils.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/add-default-conjunction.utils.ts similarity index 67% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/add-default-conjunction.utils.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/add-default-conjunction.utils.ts index 61606bb10..03f66456e 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/add-default-conjunction.utils.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/add-default-conjunction.utils.ts @@ -1,4 +1,4 @@ -import { Conjunctions } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter.utils'; +import { Conjunctions } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-filter.utils'; export const DEFAULT_CONJUNCTION = Conjunctions.and; diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/check-filter-enum-values.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/check-filter-enum-values.ts similarity index 100% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/check-filter-enum-values.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/check-filter-enum-values.ts diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/check-filter-query.utils.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/check-filter-query.utils.ts similarity index 100% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/check-filter-query.utils.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/check-filter-query.utils.ts diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/format-field-values.utils.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/format-field-values.utils.ts similarity index 93% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/format-field-values.utils.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/format-field-values.utils.ts index 4271970ce..eeab31d67 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/format-field-values.utils.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/format-field-values.utils.ts @@ -1,7 +1,7 @@ import { BadRequestException } from '@nestjs/common'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { FieldValue } from 'src/engine/api/rest/types/api-rest-field-value.type'; +import { FieldValue } from 'src/engine/api/rest/types/field-value.type'; export const formatFieldValue = ( value: string, diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-base-filter.utils.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-base-filter.utils.ts similarity index 100% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-base-filter.utils.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-base-filter.utils.ts diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter-content.utils.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-filter-content.utils.ts similarity index 100% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter-content.utils.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-filter-content.utils.ts diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter.utils.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-filter.utils.ts similarity index 65% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter.utils.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-filter.utils.ts index 74d5e6179..ac8307d87 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter.utils.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-filter.utils.ts @@ -2,15 +2,13 @@ import { BadRequestException } from '@nestjs/common'; import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; -import { parseFilterContent } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter-content.utils'; -import { parseBaseFilter } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-base-filter.utils'; -import { - checkFields, - getFieldType, -} from 'src/engine/api/rest/api-rest-query-builder/utils/fields.utils'; -import { formatFieldValue } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/format-field-values.utils'; -import { FieldValue } from 'src/engine/api/rest/types/api-rest-field-value.type'; -import { checkFilterEnumValues } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/check-filter-enum-values'; +import { parseFilterContent } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-filter-content.utils'; +import { parseBaseFilter } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-base-filter.utils'; +import { checkFields } from 'src/engine/api/rest/rest-api-core-query-builder/utils/check-fields.utils'; +import { formatFieldValue } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/format-field-values.utils'; +import { FieldValue } from 'src/engine/api/rest/types/field-value.type'; +import { checkFilterEnumValues } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/check-filter-enum-values'; +import { getFieldType } from 'src/engine/api/rest/rest-api-core-query-builder/utils/get-field-type.utils'; export enum Conjunctions { or = 'or', diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/last-cursor-input.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/last-cursor-input.factory.ts similarity index 100% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/last-cursor-input.factory.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/last-cursor-input.factory.ts diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/limit-input.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/limit-input.factory.ts similarity index 100% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/limit-input.factory.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/limit-input.factory.ts diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/order-by-input.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/order-by-input.factory.ts similarity index 95% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/order-by-input.factory.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/order-by-input.factory.ts index 56720b120..2b44cd279 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/input-factories/order-by-input.factory.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/order-by-input.factory.ts @@ -7,7 +7,7 @@ import { RecordOrderBy, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; -import { checkFields } from 'src/engine/api/rest/api-rest-query-builder/utils/fields.utils'; +import { checkFields } from 'src/engine/api/rest/rest-api-core-query-builder/utils/check-fields.utils'; export const DEFAULT_ORDER_DIRECTION = OrderByDirection.AscNullsFirst; diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/update-query.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/update-query.factory.ts similarity index 91% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/update-query.factory.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/update-query.factory.ts index e289c94f2..ca7e9cd03 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/update-query.factory.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/update-query.factory.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { capitalize } from 'src/utils/capitalize'; -import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils'; +import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/rest-api-core-query-builder/utils/map-field-metadata-to-graphql-query.utils'; @Injectable() export class UpdateQueryFactory { diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/update-variables.factory.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/update-variables.factory.ts similarity index 55% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/update-variables.factory.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/update-variables.factory.ts index e5231a666..588288bf9 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/factories/update-variables.factory.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/factories/update-variables.factory.ts @@ -2,11 +2,11 @@ import { Injectable } from '@nestjs/common'; import { Request } from 'express'; -import { ApiRestQueryVariables } from 'src/engine/api/rest/types/api-rest-query-variables.type'; +import { QueryVariables } from 'src/engine/api/rest/types/query-variables.type'; @Injectable() export class UpdateVariablesFactory { - create(id: string, request: Request): ApiRestQueryVariables { + create(id: string, request: Request): QueryVariables { return { id, data: request.body, diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/__tests__/check-fields.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/__tests__/check-fields.utils.spec.ts new file mode 100644 index 000000000..c54bf49bf --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/__tests__/check-fields.utils.spec.ts @@ -0,0 +1,16 @@ +import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock'; +import { checkFields } from 'src/engine/api/rest/rest-api-core-query-builder/utils/check-fields.utils'; + +describe('checkFields', () => { + it('should check field types', () => { + expect(() => + checkFields(objectMetadataItemMock, ['fieldNumber']), + ).not.toThrow(); + + expect(() => checkFields(objectMetadataItemMock, ['wrongField'])).toThrow(); + + expect(() => + checkFields(objectMetadataItemMock, ['fieldNumber', 'wrongField']), + ).toThrow(); + }); +}); diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/__tests__/compute-depth.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/__tests__/compute-depth.utils.spec.ts similarity index 84% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/__tests__/compute-depth.utils.spec.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/__tests__/compute-depth.utils.spec.ts index 3fc57097f..a6909574a 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/__tests__/compute-depth.utils.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/__tests__/compute-depth.utils.spec.ts @@ -1,4 +1,4 @@ -import { computeDepth } from 'src/engine/api/rest/api-rest-query-builder/utils/compute-depth.utils'; +import { computeDepth } from 'src/engine/api/rest/rest-api-core-query-builder/utils/compute-depth.utils'; describe('computeDepth', () => { it('should compute depth from query', () => { diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/__tests__/get-field-type.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/__tests__/get-field-type.utils.spec.ts new file mode 100644 index 000000000..6c09709c2 --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/__tests__/get-field-type.utils.spec.ts @@ -0,0 +1,11 @@ +import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock'; +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { getFieldType } from 'src/engine/api/rest/rest-api-core-query-builder/utils/get-field-type.utils'; + +describe('getFieldType', () => { + it('should get field type', () => { + expect(getFieldType(objectMetadataItemMock, 'fieldNumber')).toEqual( + FieldMetadataType.NUMBER, + ); + }); +}); diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts similarity index 91% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts index fea18cb63..9e6f2e348 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts @@ -5,7 +5,7 @@ import { fieldStringMock, objectMetadataItemMock, } from 'src/engine/api/__mocks__/object-metadata-item.mock'; -import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils'; +import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/rest-api-core-query-builder/utils/map-field-metadata-to-graphql-query.utils'; describe('mapFieldMetadataToGraphqlQuery', () => { it('should map properly', () => { diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/fields.utils.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/check-fields.utils.ts similarity index 80% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/fields.utils.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/check-fields.utils.ts index eca664a7a..00f5ec990 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/fields.utils.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/check-fields.utils.ts @@ -3,21 +3,9 @@ import { BadRequestException } from '@nestjs/common'; import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types'; -import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; -export const getFieldType = ( - objectMetadata: ObjectMetadataInterface, - fieldName: string, -): FieldMetadataType | undefined => { - for (const fieldMetdata of objectMetadata.fields) { - if (fieldName === fieldMetdata.name) { - return fieldMetdata.type; - } - } -}; - export const checkFields = ( objectMetadata: ObjectMetadataInterface, fieldNames: string[], diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/compute-depth.utils.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/compute-depth.utils.ts similarity index 100% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/compute-depth.utils.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/compute-depth.utils.ts diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/get-field-type.utils.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/get-field-type.utils.ts new file mode 100644 index 000000000..9cae4ad8e --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/get-field-type.utils.ts @@ -0,0 +1,10 @@ +import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; + +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; + +export const getFieldType = ( + objectMetadata: ObjectMetadataInterface, + fieldName: string, +): FieldMetadataType | undefined => { + return objectMetadata.fields.find((field) => field.name === fieldName)?.type; +}; diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/map-field-metadata-to-graphql-query.utils.ts similarity index 100% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/map-field-metadata-to-graphql-query.utils.ts diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/__tests__/parse-core-batch-path.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/__tests__/parse-core-batch-path.utils.spec.ts new file mode 100644 index 000000000..041315211 --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/__tests__/parse-core-batch-path.utils.spec.ts @@ -0,0 +1,11 @@ +import { parseCoreBatchPath } from 'src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/parse-core-batch-path.utils'; + +describe('parseCoreBatchPath', () => { + it('should parse object from request path', () => { + const request: any = { path: '/rest/batch/companies' }; + + expect(parseCoreBatchPath(request)).toEqual({ + object: 'companies', + }); + }); +}); diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/__tests__/parse-core-path.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/__tests__/parse-core-path.utils.spec.ts new file mode 100644 index 000000000..0d20d453a --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/__tests__/parse-core-path.utils.spec.ts @@ -0,0 +1,29 @@ +import { parseCorePath } from 'src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/parse-core-path.utils'; + +describe('parseCorePath', () => { + it('should parse object from request path', () => { + const request: any = { path: '/rest/companies/uuid' }; + + expect(parseCorePath(request)).toEqual({ + object: 'companies', + id: 'uuid', + }); + }); + + it('should parse object from request path', () => { + const request: any = { path: '/rest/companies' }; + + expect(parseCorePath(request)).toEqual({ + object: 'companies', + id: undefined, + }); + }); + + it('should throw for wrong request path', () => { + const request: any = { path: '/rest/companies/uuid/toto' }; + + expect(() => parseCorePath(request)).toThrow( + "Query path '/rest/companies/uuid/toto' invalid. Valid examples: /rest/companies/id or /rest/companies", + ); + }); +}); diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/__tests__/parse-metadata-path.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/__tests__/parse-metadata-path.utils.spec.ts new file mode 100644 index 000000000..28b1238c2 --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/__tests__/parse-metadata-path.utils.spec.ts @@ -0,0 +1,39 @@ +import { parseMetadataPath } from 'src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/parse-metadata-path.utils'; + +describe('parseMetadataPath', () => { + it('should parse object from request path with uuid', () => { + const request: any = { path: '/rest/metadata/fields/uuid' }; + + expect(parseMetadataPath(request)).toEqual({ + objectNameSingular: 'field', + objectNamePlural: 'fields', + id: 'uuid', + }); + }); + + it('should parse object from request path', () => { + const request: any = { path: '/rest/metadata/fields' }; + + expect(parseMetadataPath(request)).toEqual({ + objectNameSingular: 'field', + objectNamePlural: 'fields', + id: undefined, + }); + }); + + it('should throw for wrong request path', () => { + const request: any = { path: '/rest/metadata/INVALID' }; + + expect(() => parseMetadataPath(request)).toThrow( + 'Query path \'/rest/metadata/INVALID\' invalid. Metadata path "INVALID" does not exist. Valid examples: /rest/metadata/fields or /rest/metadata/objects or /rest/metadata/relations', + ); + }); + + it('should throw for wrong request path', () => { + const request: any = { path: '/rest/metadata/fields/uuid/toto' }; + + expect(() => parseMetadataPath(request)).toThrow( + "Query path '/rest/metadata/fields/uuid/toto' invalid. Valid examples: /rest/metadata/fields or /rest/metadata/objects/id", + ); + }); +}); diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/parse-core-batch-path.utils.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/parse-core-batch-path.utils.ts new file mode 100644 index 000000000..287e3e5d9 --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/parse-core-batch-path.utils.ts @@ -0,0 +1,5 @@ +import { Request } from 'express'; + +export const parseCoreBatchPath = (request: Request): { object: string } => { + return { object: request.path.replace('/rest/batch/', '') }; +}; diff --git a/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/parse-core-path.utils.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/parse-core-path.utils.ts new file mode 100644 index 000000000..ff0f7b01a --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/parse-core-path.utils.ts @@ -0,0 +1,21 @@ +import { BadRequestException } from '@nestjs/common'; + +import { Request } from 'express'; + +export const parseCorePath = ( + request: Request, +): { object: string; id?: string } => { + const queryAction = request.path.replace('/rest/', '').split('/'); + + if (queryAction.length > 2) { + throw new BadRequestException( + `Query path '${request.path}' invalid. Valid examples: /rest/companies/id or /rest/companies`, + ); + } + + if (queryAction.length === 1) { + return { object: queryAction[0] }; + } + + return { object: queryAction[0], id: queryAction[1] }; +}; diff --git a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/parse-path.utils.ts b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/parse-metadata-path.utils.ts similarity index 77% rename from packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/parse-path.utils.ts rename to packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/parse-metadata-path.utils.ts index b6a307e1d..c706aa2d2 100644 --- a/packages/twenty-server/src/engine/api/rest/api-rest-query-builder/utils/parse-path.utils.ts +++ b/packages/twenty-server/src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/parse-metadata-path.utils.ts @@ -2,30 +2,12 @@ import { BadRequestException } from '@nestjs/common'; import { Request } from 'express'; -export const parsePath = ( - request: Request, -): { object: string; id?: string } => { - const queryAction = request.path.replace('/rest/', '').split('/'); - - if (queryAction.length > 2) { - throw new BadRequestException( - `Query path '${request.path}' invalid. Valid examples: /rest/companies/id or /rest/companies`, - ); - } - - if (queryAction.length === 1) { - return { object: queryAction[0] }; - } - - return { object: queryAction[0], id: queryAction[1] }; -}; - export const parseMetadataPath = ( request: Request, ): { objectNameSingular: string; objectNamePlural: string; id?: string } => { const queryAction = request.path.replace('/rest/metadata/', '').split('/'); - if (queryAction.length > 3 || queryAction.length === 0) { + if (queryAction.length >= 3 || queryAction.length === 0) { throw new BadRequestException( `Query path '${request.path}' invalid. Valid examples: /rest/metadata/fields or /rest/metadata/objects/id`, ); diff --git a/packages/twenty-server/src/engine/api/rest/rest-api.module.ts b/packages/twenty-server/src/engine/api/rest/rest-api.module.ts new file mode 100644 index 000000000..16141805d --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/rest-api.module.ts @@ -0,0 +1,23 @@ +import { Module } from '@nestjs/common'; +import { HttpModule } from '@nestjs/axios'; + +import { RestApiCoreController } from 'src/engine/api/rest/controllers/rest-api-core.controller'; +import { RestApiCoreService } from 'src/engine/api/rest/services/rest-api-core.service'; +import { CoreQueryBuilderModule } from 'src/engine/api/rest/rest-api-core-query-builder/core-query-builder.module'; +import { AuthModule } from 'src/engine/core-modules/auth/auth.module'; +import { RestApiMetadataController } from 'src/engine/api/rest/controllers/rest-api-metadata.controller'; +import { RestApiMetadataService } from 'src/engine/api/rest/services/rest-api-metadata.service'; +import { RestApiCoreBatchController } from 'src/engine/api/rest/controllers/rest-api-core-batch.controller'; +import { RestApiService } from 'src/engine/api/rest/services/rest-api.service'; + +@Module({ + imports: [CoreQueryBuilderModule, AuthModule, HttpModule], + controllers: [ + RestApiMetadataController, + RestApiCoreBatchController, + RestApiCoreController, + ], + providers: [RestApiMetadataService, RestApiCoreService, RestApiService], + exports: [RestApiMetadataService], +}) +export class RestApiModule {} diff --git a/packages/twenty-server/src/engine/api/rest/services/__tests__/core.service.spec.ts b/packages/twenty-server/src/engine/api/rest/services/__tests__/core.service.spec.ts new file mode 100644 index 000000000..45fde1dfd --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/services/__tests__/core.service.spec.ts @@ -0,0 +1,30 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { RestApiCoreService } from 'src/engine/api/rest/services/rest-api-core.service'; +import { CoreQueryBuilderFactory } from 'src/engine/api/rest/rest-api-core-query-builder/core-query-builder.factory'; +import { RestApiService } from 'src/engine/api/rest/services/rest-api.service'; + +describe('RestApiCoreService', () => { + let service: RestApiCoreService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + RestApiCoreService, + { + provide: CoreQueryBuilderFactory, + useValue: {}, + }, + { + provide: RestApiService, + useValue: {}, + }, + ], + }).compile(); + + service = module.get(RestApiCoreService); + }); + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/packages/twenty-server/src/engine/api/rest/services/rest-api-core.service.ts b/packages/twenty-server/src/engine/api/rest/services/rest-api-core.service.ts new file mode 100644 index 000000000..126c64e6a --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/services/rest-api-core.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@nestjs/common'; + +import { Request } from 'express'; + +import { CoreQueryBuilderFactory } from 'src/engine/api/rest/rest-api-core-query-builder/core-query-builder.factory'; +import { + GraphqlApiType, + RestApiService, +} from 'src/engine/api/rest/services/rest-api.service'; + +@Injectable() +export class RestApiCoreService { + constructor( + private readonly coreQueryBuilderFactory: CoreQueryBuilderFactory, + 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); + } +} diff --git a/packages/twenty-server/src/engine/api/rest/services/rest-api-metadata.service.ts b/packages/twenty-server/src/engine/api/rest/services/rest-api-metadata.service.ts new file mode 100644 index 000000000..007c67b2e --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/services/rest-api-metadata.service.ts @@ -0,0 +1,284 @@ +import { BadRequestException, Injectable } from '@nestjs/common'; + +import { Query } from 'src/engine/api/rest/types/query.type'; +import { TokenService } from 'src/engine/core-modules/auth/services/token.service'; +import { capitalize } from 'src/utils/capitalize'; +import { parseMetadataPath } from 'src/engine/api/rest/rest-api-core-query-builder/utils/path-parsers/parse-metadata-path.utils'; +import { + GraphqlApiType, + RestApiService, +} from 'src/engine/api/rest/services/rest-api.service'; + +@Injectable() +export class RestApiMetadataService { + constructor( + private readonly tokenService: TokenService, + private readonly restApiService: RestApiService, + ) {} + + fetchMetadataFields(objectNamePlural: string) { + const fields = ` + type + name + label + description + icon + isCustom + isActive + isSystem + isNullable + createdAt + updatedAt + fromRelationMetadata { + id + relationType + toObjectMetadata { + id + dataSourceId + nameSingular + namePlural + isSystem + } + toFieldMetadataId + } + toRelationMetadata { + id + relationType + fromObjectMetadata { + id + dataSourceId + nameSingular + namePlural + isSystem + } + fromFieldMetadataId + } + defaultValue + options + `; + + switch (objectNamePlural) { + case 'objects': + return ` + dataSourceId + nameSingular + namePlural + labelSingular + labelPlural + description + icon + isCustom + isActive + isSystem + createdAt + updatedAt + labelIdentifierFieldMetadataId + imageIdentifierFieldMetadataId + fields(paging: { first: 1000 }) { + edges { + node { + id + ${fields} + } + } + } + `; + case 'fields': + return fields; + case 'relations': + return ` + relationType + fromObjectMetadata { + id + dataSourceId + nameSingular + namePlural + isSystem + } + fromObjectMetadataId + toObjectMetadata { + id + dataSourceId + nameSingular + namePlural + isSystem + } + toObjectMetadataId + fromFieldMetadataId + toFieldMetadataId + `; + } + } + + generateFindManyQuery(objectNameSingular: string, objectNamePlural: string) { + const fields = this.fetchMetadataFields(objectNamePlural); + + return ` + query FindMany${capitalize(objectNamePlural)}( + $filter: ${objectNameSingular}Filter, + ) { + ${objectNamePlural}( + filter: $filter, + paging: { first: 1000 } + ) { + edges { + node { + id + ${fields} + } + } + } + } + `; + } + + generateFindOneQuery(objectNameSingular: string, objectNamePlural: string) { + const fields = this.fetchMetadataFields(objectNamePlural); + + return ` + query FindOne${capitalize(objectNameSingular)}( + $id: UUID!, + ) { + ${objectNameSingular}(id: $id) { + id + ${fields} + } + } + `; + } + + async get(request) { + await this.tokenService.validateToken(request); + + const { objectNameSingular, objectNamePlural, id } = + parseMetadataPath(request); + + const query = id + ? this.generateFindOneQuery(objectNameSingular, objectNamePlural) + : this.generateFindManyQuery(objectNameSingular, objectNamePlural); + + const data: Query = { + query, + variables: id ? { id } : request.body, + }; + + return await this.restApiService.call( + GraphqlApiType.METADATA, + request, + data, + ); + } + + async create(request) { + await this.tokenService.validateToken(request); + + const { objectNameSingular: objectName, objectNamePlural } = + parseMetadataPath(request); + const objectNameCapitalized = capitalize(objectName); + + const fields = this.fetchMetadataFields(objectNamePlural); + + const query = ` + mutation Create${objectNameCapitalized}($input: CreateOne${objectNameCapitalized}Input!) { + createOne${objectNameCapitalized}(input: $input) { + id + ${fields} + } + } + `; + + const data: Query = { + query, + variables: { + input: { + [objectName]: request.body, + }, + }, + }; + + return await this.restApiService.call( + GraphqlApiType.METADATA, + request, + data, + ); + } + + async update(request) { + await this.tokenService.validateToken(request); + + const { + objectNameSingular: objectName, + objectNamePlural, + id, + } = parseMetadataPath(request); + const objectNameCapitalized = capitalize(objectName); + + if (!id) { + throw new BadRequestException( + `update ${objectName} query invalid. Id missing. eg: /rest/metadata/${objectName}/0d4389ef-ea9c-4ae8-ada1-1cddc440fb56`, + ); + } + const fields = this.fetchMetadataFields(objectNamePlural); + + const query = ` + mutation Update${objectNameCapitalized}($input: UpdateOne${objectNameCapitalized}Input!) { + updateOne${objectNameCapitalized}(input: $input) { + id + ${fields} + } + } + `; + + const data: Query = { + query, + variables: { + input: { + update: request.body, + id, + }, + }, + }; + + return await this.restApiService.call( + GraphqlApiType.METADATA, + request, + data, + ); + } + + async delete(request) { + await this.tokenService.validateToken(request); + + const { objectNameSingular: objectName, id } = parseMetadataPath(request); + const objectNameCapitalized = capitalize(objectName); + + if (!id) { + throw new BadRequestException( + `delete ${objectName} query invalid. Id missing. eg: /rest/metadata/${objectName}/0d4389ef-ea9c-4ae8-ada1-1cddc440fb56`, + ); + } + + const query = ` + mutation Delete${objectNameCapitalized}($input: DeleteOne${objectNameCapitalized}Input!) { + deleteOne${objectNameCapitalized}(input: $input) { + id + } + } + `; + + const data: Query = { + query, + variables: { + input: { + id, + }, + }, + }; + + return await this.restApiService.call( + GraphqlApiType.METADATA, + request, + data, + ); + } +} diff --git a/packages/twenty-server/src/engine/api/rest/services/rest-api.service.ts b/packages/twenty-server/src/engine/api/rest/services/rest-api.service.ts new file mode 100644 index 000000000..1107a2598 --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/services/rest-api.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@nestjs/common'; +import { HttpService } from '@nestjs/axios'; + +import { Request } from 'express'; +import { AxiosResponse } from 'axios'; + +import { Query } from 'src/engine/api/rest/types/query.type'; +import { getServerUrl } from 'src/utils/get-server-url'; +import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; +import { RestApiException } from 'src/engine/api/rest/errors/RestApiException'; + +export enum GraphqlApiType { + CORE = 'core', + METADATA = 'metadata', +} + +@Injectable() +export class RestApiService { + constructor( + private readonly environmentService: EnvironmentService, + private readonly httpService: HttpService, + ) {} + + async call(graphqlApiType: GraphqlApiType, request: Request, data: Query) { + const baseUrl = getServerUrl( + request, + this.environmentService.get('SERVER_URL'), + ); + let response: AxiosResponse; + const url = `${baseUrl}/${ + graphqlApiType === GraphqlApiType.CORE + ? 'graphql' + : GraphqlApiType.METADATA + }`; + + try { + response = await this.httpService.axiosRef.post(url, data, { + headers: { + 'Content-Type': 'application/json', + Authorization: request.headers.authorization, + }, + }); + } catch (err) { + throw new RestApiException(err.response.data.errors); + } + + if (response.data.errors?.length) { + throw new RestApiException(response.data.errors); + } + + return response; + } +} diff --git a/packages/twenty-server/src/engine/api/rest/types/api-rest-query.type.ts b/packages/twenty-server/src/engine/api/rest/types/api-rest-query.type.ts deleted file mode 100644 index ea3b2cfa6..000000000 --- a/packages/twenty-server/src/engine/api/rest/types/api-rest-query.type.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type ApiRestQuery = { - query: string; - variables: object; -}; diff --git a/packages/twenty-server/src/engine/api/rest/types/api-rest-field-value.type.ts b/packages/twenty-server/src/engine/api/rest/types/field-value.type.ts similarity index 100% rename from packages/twenty-server/src/engine/api/rest/types/api-rest-field-value.type.ts rename to packages/twenty-server/src/engine/api/rest/types/field-value.type.ts diff --git a/packages/twenty-server/src/engine/api/rest/types/api-rest-query-variables.type.ts b/packages/twenty-server/src/engine/api/rest/types/query-variables.type.ts similarity index 71% rename from packages/twenty-server/src/engine/api/rest/types/api-rest-query-variables.type.ts rename to packages/twenty-server/src/engine/api/rest/types/query-variables.type.ts index c4e6b0525..2554be3e4 100644 --- a/packages/twenty-server/src/engine/api/rest/types/api-rest-query-variables.type.ts +++ b/packages/twenty-server/src/engine/api/rest/types/query-variables.type.ts @@ -1,8 +1,9 @@ -export type ApiRestQueryVariables = { +export type QueryVariables = { id?: string; data?: object | null; filter?: object; orderBy?: object; limit?: number; lastCursor?: string; + input?: object; }; diff --git a/packages/twenty-server/src/engine/api/rest/types/query.type.ts b/packages/twenty-server/src/engine/api/rest/types/query.type.ts new file mode 100644 index 000000000..1af013c8a --- /dev/null +++ b/packages/twenty-server/src/engine/api/rest/types/query.type.ts @@ -0,0 +1,6 @@ +import { QueryVariables } from 'src/engine/api/rest/types/query-variables.type'; + +export type Query = { + query: string; + variables: QueryVariables; +}; diff --git a/packages/twenty-server/src/engine/api/rest/__tests__/api-rest.controller.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/utils/__tests__/clean-graphql-response.utils.spec.ts similarity index 92% rename from packages/twenty-server/src/engine/api/rest/__tests__/api-rest.controller.utils.spec.ts rename to packages/twenty-server/src/engine/api/rest/utils/__tests__/clean-graphql-response.utils.spec.ts index 4655a731f..2dd422282 100644 --- a/packages/twenty-server/src/engine/api/rest/__tests__/api-rest.controller.utils.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/utils/__tests__/clean-graphql-response.utils.spec.ts @@ -1,4 +1,4 @@ -import { cleanGraphQLResponse } from 'src/engine/api/rest/api-rest.controller.utils'; +import { cleanGraphQLResponse } from 'src/engine/api/rest/utils/clean-graphql-response.utils'; describe('cleanGraphQLResponse', () => { it('should remove edges/node from results', () => { diff --git a/packages/twenty-server/src/engine/api/rest/api-rest.controller.utils.ts b/packages/twenty-server/src/engine/api/rest/utils/clean-graphql-response.utils.ts similarity index 100% rename from packages/twenty-server/src/engine/api/rest/api-rest.controller.utils.ts rename to packages/twenty-server/src/engine/api/rest/utils/clean-graphql-response.utils.ts diff --git a/packages/twenty-server/src/engine/core-modules/open-api/open-api.service.ts b/packages/twenty-server/src/engine/core-modules/open-api/open-api.service.ts index 58fe617ca..c833377e1 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/open-api.service.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/open-api.service.ts @@ -7,10 +7,14 @@ import { TokenService } from 'src/engine/core-modules/auth/services/token.servic import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { baseSchema } from 'src/engine/core-modules/open-api/utils/base-schema.utils'; import { + computeBatchPath, computeManyResultPath, computeSingleResultPath, } from 'src/engine/core-modules/open-api/utils/path.utils'; -import { getErrorResponses } from 'src/engine/core-modules/open-api/utils/get-error-responses.utils'; +import { + get400ErrorResponses, + get401ErrorResponses, +} from 'src/engine/core-modules/open-api/utils/get-error-responses.utils'; import { computeMetadataSchemaComponents, computeParameterComponents, @@ -21,8 +25,10 @@ import { computeWebhooks } from 'src/engine/core-modules/open-api/utils/computeW import { capitalize } from 'src/utils/capitalize'; import { getDeleteResponse200, - getManyResultResponse200, - getSingleResultSuccessResponse, + getFindManyResponse200, + getCreateOneResponse201, + getFindOneResponse200, + getUpdateOneResponse200, } from 'src/engine/core-modules/open-api/utils/responses.utils'; import { getRequestBody } from 'src/engine/core-modules/open-api/utils/request-body.utils'; import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; @@ -60,6 +66,7 @@ export class OpenApiService { } schema.paths = objectMetadataItems.reduce((paths, item) => { paths[`/${item.namePlural}`] = computeManyResultPath(item); + paths[`/batch/${item.namePlural}`] = computeBatchPath(item); paths[`/${item.namePlural}/{id}`] = computeSingleResultPath(item); return paths; @@ -86,8 +93,8 @@ export class OpenApiService { schemas: computeSchemaComponents(objectMetadataItems), parameters: computeParameterComponents(), responses: { - '400': getErrorResponses('Invalid request'), - '401': getErrorResponses('Unauthorized'), + '400': get400ErrorResponses(), + '401': get401ErrorResponses(), }, }; @@ -128,7 +135,7 @@ export class OpenApiService { summary: `Find Many ${item.namePlural}`, parameters: [{ $ref: '#/components/parameters/filter' }], responses: { - '200': getManyResultResponse200(item), + '200': getFindManyResponse200(item), '400': { $ref: '#/components/responses/400' }, '401': { $ref: '#/components/responses/401' }, }, @@ -137,9 +144,9 @@ export class OpenApiService { tags: [item.namePlural], summary: `Create One ${item.nameSingular}`, operationId: `createOne${capitalize(item.nameSingular)}`, - requestBody: getRequestBody(item), + requestBody: getRequestBody(capitalize(item.nameSingular)), responses: { - '200': getSingleResultSuccessResponse(item), + '200': getCreateOneResponse201(item, true), '400': { $ref: '#/components/responses/400' }, '401': { $ref: '#/components/responses/401' }, }, @@ -151,7 +158,7 @@ export class OpenApiService { summary: `Find One ${item.nameSingular}`, parameters: [{ $ref: '#/components/parameters/idPath' }], responses: { - '200': getSingleResultSuccessResponse(item), + '200': getFindOneResponse200(item), '400': { $ref: '#/components/responses/400' }, '401': { $ref: '#/components/responses/401' }, }, @@ -162,19 +169,19 @@ export class OpenApiService { operationId: `deleteOne${capitalize(item.nameSingular)}`, parameters: [{ $ref: '#/components/parameters/idPath' }], responses: { - '200': getDeleteResponse200(item), + '200': getDeleteResponse200(item, true), '400': { $ref: '#/components/responses/400' }, '401': { $ref: '#/components/responses/401' }, }, }, - put: { + patch: { tags: [item.namePlural], summary: `Update One ${item.namePlural}`, operationId: `updateOne${capitalize(item.nameSingular)}`, parameters: [{ $ref: '#/components/parameters/idPath' }], - requestBody: getRequestBody(item), + requestBody: getRequestBody(capitalize(item.nameSingular)), responses: { - '200': getSingleResultSuccessResponse(item), + '200': getUpdateOneResponse200(item, true), '400': { $ref: '#/components/responses/400' }, '401': { $ref: '#/components/responses/401' }, }, @@ -189,8 +196,8 @@ export class OpenApiService { schemas: computeMetadataSchemaComponents(metadata), parameters: computeParameterComponents(), responses: { - '400': getErrorResponses('Invalid request'), - '401': getErrorResponses('Unauthorized'), + '400': get400ErrorResponses(), + '401': get401ErrorResponses(), }, }; diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts index 25bce40f6..c01c66c3d 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts @@ -40,6 +40,17 @@ describe('computeSchemaComponents', () => { }, }, }, + ObjectsName: { + description: 'A list of objectsName', + example: { + fieldNumber: '', + }, + items: { + $ref: '#/components/schemas/ObjectName', + }, + required: ['fieldNumber'], + type: 'array', + }, }); }); }); diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts index 06c4684c9..c5b1a3794 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts @@ -8,10 +8,10 @@ import { computeLimitParameters, computeOrderByParameters, } from 'src/engine/core-modules/open-api/utils/parameters.utils'; -import { DEFAULT_ORDER_DIRECTION } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/order-by-input.factory'; -import { FilterComparators } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-base-filter.utils'; -import { Conjunctions } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter.utils'; -import { DEFAULT_CONJUNCTION } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/add-default-conjunction.utils'; +import { DEFAULT_ORDER_DIRECTION } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/order-by-input.factory'; +import { FilterComparators } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-base-filter.utils'; +import { Conjunctions } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-filter.utils'; +import { DEFAULT_CONJUNCTION } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/add-default-conjunction.utils'; describe('computeParameters', () => { describe('computeLimit', () => { diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts index 83bf41b9e..f95011dfe 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts @@ -105,6 +105,33 @@ const getRequiredFields = (item: ObjectMetadataEntity): string[] => { }, [] as string[]); }; +const computeBatchSchemaComponent = ( + item: ObjectMetadataEntity, +): OpenAPIV3_1.SchemaObject => { + const result = { + type: 'array', + description: `A list of ${item.namePlural}`, + items: { $ref: `#/components/schemas/${capitalize(item.nameSingular)}` }, + example: [{}], + } as OpenAPIV3_1.SchemaObject; + + const requiredFields = getRequiredFields(item); + + if (requiredFields?.length) { + result.required = requiredFields; + result.example = requiredFields.reduce( + (example, requiredField) => { + example[requiredField] = ''; + + return example; + }, + {} as Record, + ); + } + + return result; +}; + const computeSchemaComponent = ( item: ObjectMetadataEntity, ): OpenAPIV3_1.SchemaObject => { @@ -138,6 +165,7 @@ export const computeSchemaComponents = ( return objectMetadataItems.reduce( (schemas, item) => { schemas[capitalize(item.nameSingular)] = computeSchemaComponent(item); + schemas[capitalize(item.namePlural)] = computeBatchSchemaComponent(item); return schemas; }, @@ -168,6 +196,7 @@ export const computeMetadataSchemaComponents = ( case 'object': { schemas[`${capitalize(item.nameSingular)}`] = { type: 'object', + description: `An object`, properties: { dataSourceId: { type: 'string' }, nameSingular: { type: 'string' }, @@ -201,6 +230,15 @@ export const computeMetadataSchemaComponents = ( }, }, }, + example: {}, + }; + schemas[`${capitalize(item.namePlural)}`] = { + type: 'array', + description: `A list of ${item.namePlural}`, + items: { + $ref: `#/components/schemas/${capitalize(item.nameSingular)}`, + }, + example: [{}], }; return schemas; @@ -208,6 +246,7 @@ export const computeMetadataSchemaComponents = ( case 'field': { schemas[`${capitalize(item.nameSingular)}`] = { type: 'object', + description: `A field`, properties: { type: { type: 'string' }, name: { type: 'string' }, @@ -259,6 +298,15 @@ export const computeMetadataSchemaComponents = ( defaultValue: { type: 'object' }, options: { type: 'object' }, }, + example: {}, + }; + schemas[`${capitalize(item.namePlural)}`] = { + type: 'array', + description: `A list of ${item.namePlural}`, + items: { + $ref: `#/components/schemas/${capitalize(item.nameSingular)}`, + }, + example: [{}], }; return schemas; @@ -266,6 +314,7 @@ export const computeMetadataSchemaComponents = ( case 'relation': { schemas[`${capitalize(item.nameSingular)}`] = { type: 'object', + description: 'A relation', properties: { relationType: { type: 'string' }, fromObjectMetadata: { @@ -293,6 +342,15 @@ export const computeMetadataSchemaComponents = ( fromFieldMetadataId: { type: 'string' }, toFieldMetadataId: { type: 'string' }, }, + example: {}, + }; + schemas[`${capitalize(item.namePlural)}`] = { + type: 'array', + description: `A list of ${item.namePlural}`, + items: { + $ref: `#/components/schemas/${capitalize(item.nameSingular)}`, + }, + example: [{}], }; } } diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/get-error-responses.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/get-error-responses.utils.ts index acd0c1700..13f48a682 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/get-error-responses.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/get-error-responses.utils.ts @@ -1,17 +1,45 @@ import { OpenAPIV3_1 } from 'openapi-types'; -export const getErrorResponses = ( - description: string, -): OpenAPIV3_1.ResponseObject => { +export const get400ErrorResponses = (): OpenAPIV3_1.ResponseObject => { return { - description, + description: 'Bad Request', content: { 'application/json': { schema: { type: 'object', properties: { + statusCode: { type: 'number' }, + message: { type: 'string' }, error: { type: 'string' }, }, + example: { + statusCode: 400, + message: 'error message', + error: 'Bad Request', + }, + }, + }, + }, + }; +}; + +export const get401ErrorResponses = (): OpenAPIV3_1.ResponseObject => { + return { + description: 'Unauthorized', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + statusCode: { type: 'number' }, + message: { type: 'string' }, + error: { type: 'string' }, + }, + example: { + statusCode: 401, + message: 'Token invalid.', + error: 'Unauthorized', + }, }, }, }, diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts index 03f7319e7..6034d751e 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts @@ -2,10 +2,10 @@ import { OpenAPIV3_1 } from 'openapi-types'; import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; -import { FilterComparators } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-base-filter.utils'; -import { Conjunctions } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/parse-filter.utils'; -import { DEFAULT_ORDER_DIRECTION } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/order-by-input.factory'; -import { DEFAULT_CONJUNCTION } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/add-default-conjunction.utils'; +import { FilterComparators } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-base-filter.utils'; +import { Conjunctions } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/parse-filter.utils'; +import { DEFAULT_ORDER_DIRECTION } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/order-by-input.factory'; +import { DEFAULT_CONJUNCTION } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/add-default-conjunction.utils'; export const computeLimitParameters = (): OpenAPIV3_1.ParameterObject => { return { diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/path.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/path.utils.ts index 497e95e77..083c57a4c 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/path.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/path.utils.ts @@ -5,11 +5,33 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat import { getDeleteResponse200, getJsonResponse, - getManyResultResponse200, - getSingleResultSuccessResponse, + getFindManyResponse200, + getCreateOneResponse201, + getCreateManyResponse201, + getFindOneResponse200, + getUpdateOneResponse200, } from 'src/engine/core-modules/open-api/utils/responses.utils'; import { getRequestBody } from 'src/engine/core-modules/open-api/utils/request-body.utils'; +export const computeBatchPath = ( + item: ObjectMetadataEntity, +): OpenAPIV3_1.PathItemObject => { + return { + post: { + tags: [item.namePlural], + summary: `Create Many ${item.namePlural}`, + operationId: `createMany${capitalize(item.namePlural)}`, + parameters: [{ $ref: '#/components/parameters/depth' }], + requestBody: getRequestBody(capitalize(item.namePlural)), + responses: { + '201': getCreateManyResponse201(item), + '400': { $ref: '#/components/responses/400' }, + '401': { $ref: '#/components/responses/401' }, + }, + }, + } as OpenAPIV3_1.PathItemObject; +}; + export const computeManyResultPath = ( item: ObjectMetadataEntity, ): OpenAPIV3_1.PathItemObject => { @@ -27,7 +49,7 @@ export const computeManyResultPath = ( { $ref: '#/components/parameters/lastCursor' }, ], responses: { - '200': getManyResultResponse200(item), + '200': getFindManyResponse200(item), '400': { $ref: '#/components/responses/400' }, '401': { $ref: '#/components/responses/401' }, }, @@ -37,9 +59,9 @@ export const computeManyResultPath = ( summary: `Create One ${item.nameSingular}`, operationId: `createOne${capitalize(item.nameSingular)}`, parameters: [{ $ref: '#/components/parameters/depth' }], - requestBody: getRequestBody(item), + requestBody: getRequestBody(capitalize(item.nameSingular)), responses: { - '201': getSingleResultSuccessResponse(item), + '201': getCreateOneResponse201(item), '400': { $ref: '#/components/responses/400' }, '401': { $ref: '#/components/responses/401' }, }, @@ -61,7 +83,7 @@ export const computeSingleResultPath = ( { $ref: '#/components/parameters/depth' }, ], responses: { - '200': getSingleResultSuccessResponse(item), + '200': getFindOneResponse200(item), '400': { $ref: '#/components/responses/400' }, '401': { $ref: '#/components/responses/401' }, }, @@ -77,7 +99,7 @@ export const computeSingleResultPath = ( '401': { $ref: '#/components/responses/401' }, }, }, - put: { + patch: { tags: [item.namePlural], summary: `Update One ${item.namePlural}`, operationId: `UpdateOne${capitalize(item.nameSingular)}`, @@ -85,9 +107,9 @@ export const computeSingleResultPath = ( { $ref: '#/components/parameters/idPath' }, { $ref: '#/components/parameters/depth' }, ], - requestBody: getRequestBody(item), + requestBody: getRequestBody(capitalize(item.nameSingular)), responses: { - '200': getSingleResultSuccessResponse(item), + '200': getUpdateOneResponse200(item), '400': { $ref: '#/components/responses/400' }, '401': { $ref: '#/components/responses/401' }, }, diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/request-body.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/request-body.utils.ts index 6ba3b3023..904a18fb7 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/request-body.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/request-body.utils.ts @@ -1,16 +1,11 @@ -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { capitalize } from 'src/utils/capitalize'; - -export const getRequestBody = ( - item: Pick, -) => { +export const getRequestBody = (name: string) => { return { description: 'body', required: true, content: { 'application/json': { schema: { - $ref: `#/components/schemas/${capitalize(item.nameSingular)}`, + $ref: `#/components/schemas/${name}`, }, }, }, diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/responses.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/responses.utils.ts index c3905e2d2..7baae995e 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/responses.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/responses.utils.ts @@ -1,7 +1,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { capitalize } from 'src/utils/capitalize'; -export const getManyResultResponse200 = ( +export const getFindManyResponse200 = ( item: Pick, ) => { return { @@ -28,7 +28,8 @@ export const getManyResultResponse200 = ( example: { data: { [item.namePlural]: [ - `${capitalize(item.nameSingular)}Object`, + `${capitalize(item.nameSingular)}Object1`, + `${capitalize(item.nameSingular)}Object2`, '...', ], }, @@ -39,7 +40,7 @@ export const getManyResultResponse200 = ( }; }; -export const getSingleResultSuccessResponse = ( +export const getFindOneResponse200 = ( item: Pick, ) => { return { @@ -58,14 +59,54 @@ export const getSingleResultSuccessResponse = ( }, }, }, + example: { + data: { + [item.nameSingular]: `${capitalize(item.nameSingular)}Object`, + }, + }, }, }, }, }; }; -export const getDeleteResponse200 = ( +export const getCreateOneResponse201 = ( item: Pick, + fromMetadata = false, +) => { + const one = fromMetadata ? 'One' : ''; + + return { + description: 'Successful operation', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + data: { + type: 'object', + properties: { + [`create${one}${capitalize(item.nameSingular)}`]: { + $ref: `#/components/schemas/${capitalize(item.nameSingular)}`, + }, + }, + }, + }, + example: { + data: { + [`create${one}${capitalize(item.nameSingular)}`]: `${capitalize( + item.nameSingular, + )}Object`, + }, + }, + }, + }, + }, + }; +}; + +export const getCreateManyResponse201 = ( + item: Pick, ) => { return { description: 'Successful operation', @@ -77,7 +118,84 @@ export const getDeleteResponse200 = ( data: { type: 'object', properties: { - [item.nameSingular]: { + [`create${capitalize(item.namePlural)}`]: { + type: 'array', + items: { + $ref: `#/components/schemas/${capitalize( + item.nameSingular, + )}`, + }, + }, + }, + }, + }, + example: { + data: { + [`create${capitalize(item.namePlural)}`]: [ + `${capitalize(item.nameSingular)}Object1`, + `${capitalize(item.nameSingular)}Object2`, + '...', + ], + }, + }, + }, + }, + }, + }; +}; + +export const getUpdateOneResponse200 = ( + item: Pick, + fromMetadata = false, +) => { + const one = fromMetadata ? 'One' : ''; + + return { + description: 'Successful operation', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + data: { + type: 'object', + properties: { + [`update${one}${capitalize(item.nameSingular)}`]: { + $ref: `#/components/schemas/${capitalize(item.nameSingular)}`, + }, + }, + }, + }, + example: { + data: { + [`update${one}${capitalize(item.nameSingular)}`]: `${capitalize( + item.nameSingular, + )}Object`, + }, + }, + }, + }, + }, + }; +}; + +export const getDeleteResponse200 = ( + item: Pick, + fromMetadata = false, +) => { + const one = fromMetadata ? 'One' : ''; + + return { + description: 'Successful operation', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + data: { + type: 'object', + properties: { + [`delete${one}${capitalize(item.nameSingular)}`]: { type: 'object', properties: { id: { @@ -89,6 +207,13 @@ export const getDeleteResponse200 = ( }, }, }, + example: { + data: { + [`delete${one}${capitalize(item.nameSingular)}`]: { + id: 'ffe75ac3-9786-4846-b56f-640685c3631e', + }, + }, + }, }, }, }, diff --git a/packages/twenty-server/src/main.ts b/packages/twenty-server/src/main.ts index a4d519253..66a11993b 100644 --- a/packages/twenty-server/src/main.ts +++ b/packages/twenty-server/src/main.ts @@ -8,6 +8,8 @@ import bytes from 'bytes'; import { useContainer } from 'class-validator'; import '@sentry/tracing'; +import { ApplyCorsToExceptions } from 'src/utils/apply-cors-to-exceptions'; + import { AppModule } from './app.module'; import { generateFrontConfig } from './utils/generate-front-config'; @@ -38,6 +40,8 @@ const bootstrap = async () => { app.use(Sentry.Handlers.tracingHandler()); } + app.useGlobalFilters(new ApplyCorsToExceptions()); + // Apply validation pipes globally app.useGlobalPipes( new ValidationPipe({ diff --git a/packages/twenty-server/src/utils/apply-cors-to-exceptions.ts b/packages/twenty-server/src/utils/apply-cors-to-exceptions.ts new file mode 100644 index 000000000..24910c9f4 --- /dev/null +++ b/packages/twenty-server/src/utils/apply-cors-to-exceptions.ts @@ -0,0 +1,26 @@ +import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common'; + +import { Response } from 'express'; + +// In case of exception in middleware run before the CORS middleware (eg: JSON Middleware that checks the request body), +// the CORS headers are missing in the response. +// This class add CORS headers to exception response to avoid misleading CORS error +@Catch() +export class ApplyCorsToExceptions implements ExceptionFilter { + catch(exception: any, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + + response.header('Access-Control-Allow-Origin', '*'); + response.header( + 'Access-Control-Allow-Methods', + 'GET,HEAD,PUT,PATCH,POST,DELETE', + ); + response.header( + 'Access-Control-Allow-Headers', + 'Origin, X-Requested-With, Content-Type, Accept', + ); + + response.status(exception.getStatus()).json(exception.response); + } +}