4655 batch endpoints on the rest api (#5411)
- add POST rest/batch/<OBJECT> endpoint - rearrange rest api code with Twenty quality standard - unify REST API error format - Added PATCH verb to update objects - In openapi schema, we replaced PUT with PATCH verb to comply with REST standard - fix openApi schema to match the REST api ### Batch Create  ### Replace PUT by PATCH in open Api  ### Error format unification   
This commit is contained in:
@ -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(),
|
||||
],
|
||||
|
||||
@ -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>(ApiRestService);
|
||||
});
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -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>(
|
||||
ApiRestQueryBuilderFactory,
|
||||
);
|
||||
});
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -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 {}
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
];
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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 {}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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',
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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 } };
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>(CoreQueryBuilderFactory);
|
||||
});
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -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<ApiRestQuery> {
|
||||
const objectMetadata = await this.getObjectMetadata(request);
|
||||
async delete(request: Request): Promise<Query> {
|
||||
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<ApiRestQuery> {
|
||||
const objectMetadata = await this.getObjectMetadata(request);
|
||||
async createOne(request: Request): Promise<Query> {
|
||||
const { object: parsedObject } = parseCorePath(request);
|
||||
const objectMetadata = await this.getObjectMetadata(request, parsedObject);
|
||||
|
||||
const depth = computeDepth(request);
|
||||
|
||||
return {
|
||||
query: this.createQueryFactory.create(objectMetadata, depth),
|
||||
query: this.createOneQueryFactory.create(objectMetadata, depth),
|
||||
variables: this.createVariablesFactory.create(request),
|
||||
};
|
||||
}
|
||||
|
||||
async update(request: Request): Promise<ApiRestQuery> {
|
||||
const objectMetadata = await this.getObjectMetadata(request);
|
||||
async createMany(request: Request): Promise<Query> {
|
||||
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<Query> {
|
||||
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<ApiRestQuery> {
|
||||
const objectMetadata = await this.getObjectMetadata(request);
|
||||
async get(request: Request): Promise<Query> {
|
||||
const { object: parsedObject } = parseCorePath(request);
|
||||
const objectMetadata = await this.getObjectMetadata(request, parsedObject);
|
||||
|
||||
const depth = computeDepth(request);
|
||||
|
||||
const { id } = parsePath(request);
|
||||
const { id } = parseCorePath(request);
|
||||
|
||||
return {
|
||||
query: id
|
||||
@ -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 {}
|
||||
@ -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')}
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
];
|
||||
@ -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 {
|
||||
@ -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 {
|
||||
@ -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 } } };
|
||||
}
|
||||
@ -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 };
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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 };
|
||||
@ -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 {
|
||||
@ -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', () => {
|
||||
@ -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,
|
||||
@ -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', () => {
|
||||
@ -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', () => {
|
||||
@ -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', () => {
|
||||
@ -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', () => {
|
||||
@ -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', () => {
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
@ -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',
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
@ -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,
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
@ -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', () => {
|
||||
@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -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', () => {
|
||||
@ -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[],
|
||||
@ -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;
|
||||
};
|
||||
@ -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',
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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",
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -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",
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,5 @@
|
||||
import { Request } from 'express';
|
||||
|
||||
export const parseCoreBatchPath = (request: Request): { object: string } => {
|
||||
return { object: request.path.replace('/rest/batch/', '') };
|
||||
};
|
||||
@ -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] };
|
||||
};
|
||||
@ -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`,
|
||||
);
|
||||
@ -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 {}
|
||||
@ -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>(RestApiCoreService);
|
||||
});
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
export type ApiRestQuery = {
|
||||
query: string;
|
||||
variables: object;
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
import { QueryVariables } from 'src/engine/api/rest/types/query-variables.type';
|
||||
|
||||
export type Query = {
|
||||
query: string;
|
||||
variables: QueryVariables;
|
||||
};
|
||||
@ -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', () => {
|
||||
@ -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(),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -40,6 +40,17 @@ describe('computeSchemaComponents', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
ObjectsName: {
|
||||
description: 'A list of objectsName',
|
||||
example: {
|
||||
fieldNumber: '',
|
||||
},
|
||||
items: {
|
||||
$ref: '#/components/schemas/ObjectName',
|
||||
},
|
||||
required: ['fieldNumber'],
|
||||
type: 'array',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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', () => {
|
||||
|
||||
@ -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<string, string>,
|
||||
);
|
||||
}
|
||||
|
||||
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: [{}],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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' },
|
||||
},
|
||||
|
||||
@ -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<ObjectMetadataEntity, 'nameSingular'>,
|
||||
) => {
|
||||
export const getRequestBody = (name: string) => {
|
||||
return {
|
||||
description: 'body',
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: `#/components/schemas/${capitalize(item.nameSingular)}`,
|
||||
$ref: `#/components/schemas/${name}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -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<ObjectMetadataEntity, 'nameSingular' | 'namePlural'>,
|
||||
) => {
|
||||
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<ObjectMetadataEntity, 'nameSingular'>,
|
||||
) => {
|
||||
return {
|
||||
@ -58,14 +59,54 @@ export const getSingleResultSuccessResponse = (
|
||||
},
|
||||
},
|
||||
},
|
||||
example: {
|
||||
data: {
|
||||
[item.nameSingular]: `${capitalize(item.nameSingular)}Object`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getDeleteResponse200 = (
|
||||
export const getCreateOneResponse201 = (
|
||||
item: Pick<ObjectMetadataEntity, 'nameSingular'>,
|
||||
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<ObjectMetadataEntity, 'nameSingular' | 'namePlural'>,
|
||||
) => {
|
||||
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<ObjectMetadataEntity, 'nameSingular'>,
|
||||
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<ObjectMetadataEntity, 'nameSingular'>,
|
||||
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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -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({
|
||||
|
||||
26
packages/twenty-server/src/utils/apply-cors-to-exceptions.ts
Normal file
26
packages/twenty-server/src/utils/apply-cors-to-exceptions.ts
Normal file
@ -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>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user