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 { 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 { ModulesModule } from 'src/modules/modules.module';
|
||||||
import { CoreGraphQLApiModule } from 'src/engine/api/graphql/core-graphql-api.module';
|
import { CoreGraphQLApiModule } from 'src/engine/api/graphql/core-graphql-api.module';
|
||||||
import { MetadataGraphQLApiModule } from 'src/engine/api/graphql/metadata-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
|
// Api modules
|
||||||
CoreGraphQLApiModule,
|
CoreGraphQLApiModule,
|
||||||
MetadataGraphQLApiModule,
|
MetadataGraphQLApiModule,
|
||||||
ApiRestModule,
|
RestApiModule,
|
||||||
// Conditional modules
|
// Conditional modules
|
||||||
...AppModule.getConditionalModules(),
|
...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 { 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 { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||||
import { TokenService } from 'src/engine/core-modules/auth/services/token.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 { CreateOneQueryFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/create-one-query.factory';
|
||||||
import { UpdateQueryFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/update-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/api-rest-query-builder/factories/find-one-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/api-rest-query-builder/factories/find-many-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/api-rest-query-builder/factories/delete-variables.factory';
|
import { DeleteVariablesFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/delete-variables.factory';
|
||||||
import { CreateVariablesFactory } from 'src/engine/api/rest/api-rest-query-builder/factories/create-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/api-rest-query-builder/factories/update-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/api-rest-query-builder/factories/get-variables.factory';
|
import { GetVariablesFactory } from 'src/engine/api/rest/rest-api-core-query-builder/factories/get-variables.factory';
|
||||||
import { parsePath } from 'src/engine/api/rest/api-rest-query-builder/utils/parse-path.utils';
|
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/api-rest-query-builder/utils/compute-depth.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 { 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 { 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()
|
@Injectable()
|
||||||
export class ApiRestQueryBuilderFactory {
|
export class CoreQueryBuilderFactory {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly deleteQueryFactory: DeleteQueryFactory,
|
private readonly deleteQueryFactory: DeleteQueryFactory,
|
||||||
private readonly createQueryFactory: CreateQueryFactory,
|
private readonly createOneQueryFactory: CreateOneQueryFactory,
|
||||||
|
private readonly createManyQueryFactory: CreateManyQueryFactory,
|
||||||
private readonly updateQueryFactory: UpdateQueryFactory,
|
private readonly updateQueryFactory: UpdateQueryFactory,
|
||||||
private readonly findOneQueryFactory: FindOneQueryFactory,
|
private readonly findOneQueryFactory: FindOneQueryFactory,
|
||||||
private readonly findManyQueryFactory: FindManyQueryFactory,
|
private readonly findManyQueryFactory: FindManyQueryFactory,
|
||||||
@ -36,7 +39,10 @@ export class ApiRestQueryBuilderFactory {
|
|||||||
private readonly environmentService: EnvironmentService,
|
private readonly environmentService: EnvironmentService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getObjectMetadata(request: Request): Promise<{
|
async getObjectMetadata(
|
||||||
|
request: Request,
|
||||||
|
parsedObject: string,
|
||||||
|
): Promise<{
|
||||||
objectMetadataItems: ObjectMetadataEntity[];
|
objectMetadataItems: ObjectMetadataEntity[];
|
||||||
objectMetadataItem: ObjectMetadataEntity;
|
objectMetadataItem: ObjectMetadataEntity;
|
||||||
}> {
|
}> {
|
||||||
@ -53,8 +59,6 @@ export class ApiRestQueryBuilderFactory {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { object: parsedObject } = parsePath(request);
|
|
||||||
|
|
||||||
const [objectMetadata] = objectMetadataItems.filter(
|
const [objectMetadata] = objectMetadataItems.filter(
|
||||||
(object) => object.namePlural === parsedObject,
|
(object) => object.namePlural === parsedObject,
|
||||||
);
|
);
|
||||||
@ -81,10 +85,11 @@ export class ApiRestQueryBuilderFactory {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(request: Request): Promise<ApiRestQuery> {
|
async delete(request: Request): Promise<Query> {
|
||||||
const objectMetadata = await this.getObjectMetadata(request);
|
const { object: parsedObject } = parseCorePath(request);
|
||||||
|
const objectMetadata = await this.getObjectMetadata(request, parsedObject);
|
||||||
|
|
||||||
const { id } = parsePath(request);
|
const { id } = parseCorePath(request);
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
@ -98,23 +103,36 @@ export class ApiRestQueryBuilderFactory {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(request: Request): Promise<ApiRestQuery> {
|
async createOne(request: Request): Promise<Query> {
|
||||||
const objectMetadata = await this.getObjectMetadata(request);
|
const { object: parsedObject } = parseCorePath(request);
|
||||||
|
const objectMetadata = await this.getObjectMetadata(request, parsedObject);
|
||||||
|
|
||||||
const depth = computeDepth(request);
|
const depth = computeDepth(request);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
query: this.createQueryFactory.create(objectMetadata, depth),
|
query: this.createOneQueryFactory.create(objectMetadata, depth),
|
||||||
variables: this.createVariablesFactory.create(request),
|
variables: this.createVariablesFactory.create(request),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(request: Request): Promise<ApiRestQuery> {
|
async createMany(request: Request): Promise<Query> {
|
||||||
const objectMetadata = await this.getObjectMetadata(request);
|
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 depth = computeDepth(request);
|
||||||
|
|
||||||
const { id } = parsePath(request);
|
const { id } = parseCorePath(request);
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
@ -128,12 +146,13 @@ export class ApiRestQueryBuilderFactory {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(request: Request): Promise<ApiRestQuery> {
|
async get(request: Request): Promise<Query> {
|
||||||
const objectMetadata = await this.getObjectMetadata(request);
|
const { object: parsedObject } = parseCorePath(request);
|
||||||
|
const objectMetadata = await this.getObjectMetadata(request, parsedObject);
|
||||||
|
|
||||||
const depth = computeDepth(request);
|
const depth = computeDepth(request);
|
||||||
|
|
||||||
const { id } = parsePath(request);
|
const { id } = parseCorePath(request);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
query: id
|
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 { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { capitalize } from 'src/utils/capitalize';
|
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()
|
@Injectable()
|
||||||
export class CreateQueryFactory {
|
export class CreateOneQueryFactory {
|
||||||
create(objectMetadata, depth?: number): string {
|
create(objectMetadata, depth?: number): string {
|
||||||
const objectNameSingular = capitalize(
|
const objectNameSingular = capitalize(
|
||||||
objectMetadata.objectMetadataItem.nameSingular,
|
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 { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { capitalize } from 'src/utils/capitalize';
|
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()
|
@Injectable()
|
||||||
export class FindManyQueryFactory {
|
export class FindManyQueryFactory {
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { capitalize } from 'src/utils/capitalize';
|
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()
|
@Injectable()
|
||||||
export class FindOneQueryFactory {
|
export class FindOneQueryFactory {
|
||||||
@ -2,11 +2,11 @@ import { Injectable } from '@nestjs/common';
|
|||||||
|
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
|
|
||||||
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';
|
||||||
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';
|
||||||
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';
|
||||||
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';
|
||||||
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()
|
@Injectable()
|
||||||
export class GetVariablesFactory {
|
export class GetVariablesFactory {
|
||||||
@ -21,7 +21,7 @@ export class GetVariablesFactory {
|
|||||||
id: string | undefined,
|
id: string | undefined,
|
||||||
request: Request,
|
request: Request,
|
||||||
objectMetadata,
|
objectMetadata,
|
||||||
): ApiRestQueryVariables {
|
): QueryVariables {
|
||||||
if (id) {
|
if (id) {
|
||||||
return { filter: { id: { eq: id } } };
|
return { filter: { id: { eq: id } } };
|
||||||
}
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock';
|
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', () => {
|
describe('FilterInputFactory', () => {
|
||||||
const objectMetadata = { objectMetadataItem: objectMetadataItemMock };
|
const objectMetadata = { objectMetadataItem: objectMetadataItemMock };
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
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', () => {
|
describe('LastCursorInputFactory', () => {
|
||||||
let service: LastCursorInputFactory;
|
let service: LastCursorInputFactory;
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
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', () => {
|
describe('LimitInputFactory', () => {
|
||||||
let service: 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 { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||||
|
|
||||||
import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock';
|
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', () => {
|
describe('OrderByInputFactory', () => {
|
||||||
const objectMetadata = { objectMetadataItem: objectMetadataItemMock };
|
const objectMetadata = { objectMetadataItem: objectMetadataItemMock };
|
||||||
@ -2,10 +2,10 @@ import { Injectable } from '@nestjs/common';
|
|||||||
|
|
||||||
import { Request } from 'express';
|
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 { 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/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';
|
||||||
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';
|
||||||
import { FieldValue } from 'src/engine/api/rest/types/api-rest-field-value.type';
|
import { FieldValue } from 'src/engine/api/rest/types/field-value.type';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FilterInputFactory {
|
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', () => {
|
describe('addDefaultConjunctionIfMissing', () => {
|
||||||
it('should add default conjunction if missing', () => {
|
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 { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
import {
|
import {
|
||||||
fieldSelectMock,
|
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', () => {
|
describe('checkFilterQuery', () => {
|
||||||
it('should check filter query', () => {
|
it('should check filter query', () => {
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
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', () => {
|
describe('formatFieldValue', () => {
|
||||||
it('should format fieldNumber value', () => {
|
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', () => {
|
describe('parseBaseFilter', () => {
|
||||||
it('should parse simple filter string test 1', () => {
|
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', () => {
|
describe('parseFilterContent', () => {
|
||||||
it('should parse query filter test 1', () => {
|
it('should parse query filter test 1', () => {
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock';
|
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', () => {
|
describe('parseFilter', () => {
|
||||||
it('should parse string filter test 1', () => {
|
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;
|
export const DEFAULT_CONJUNCTION = Conjunctions.and;
|
||||||
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
import { BadRequestException } from '@nestjs/common';
|
||||||
|
|
||||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
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 = (
|
export const formatFieldValue = (
|
||||||
value: string,
|
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 { 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 { 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/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';
|
||||||
import {
|
import { checkFields } from 'src/engine/api/rest/rest-api-core-query-builder/utils/check-fields.utils';
|
||||||
checkFields,
|
import { formatFieldValue } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/format-field-values.utils';
|
||||||
getFieldType,
|
import { FieldValue } from 'src/engine/api/rest/types/field-value.type';
|
||||||
} from 'src/engine/api/rest/api-rest-query-builder/utils/fields.utils';
|
import { checkFilterEnumValues } from 'src/engine/api/rest/rest-api-core-query-builder/factories/input-factories/filter-utils/check-filter-enum-values';
|
||||||
import { formatFieldValue } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/format-field-values.utils';
|
import { getFieldType } from 'src/engine/api/rest/rest-api-core-query-builder/utils/get-field-type.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';
|
|
||||||
|
|
||||||
export enum Conjunctions {
|
export enum Conjunctions {
|
||||||
or = 'or',
|
or = 'or',
|
||||||
@ -7,7 +7,7 @@ import {
|
|||||||
RecordOrderBy,
|
RecordOrderBy,
|
||||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
} 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;
|
export const DEFAULT_ORDER_DIRECTION = OrderByDirection.AscNullsFirst;
|
||||||
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { capitalize } from 'src/utils/capitalize';
|
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()
|
@Injectable()
|
||||||
export class UpdateQueryFactory {
|
export class UpdateQueryFactory {
|
||||||
@ -2,11 +2,11 @@ import { Injectable } from '@nestjs/common';
|
|||||||
|
|
||||||
import { Request } from 'express';
|
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()
|
@Injectable()
|
||||||
export class UpdateVariablesFactory {
|
export class UpdateVariablesFactory {
|
||||||
create(id: string, request: Request): ApiRestQueryVariables {
|
create(id: string, request: Request): QueryVariables {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
data: request.body,
|
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', () => {
|
describe('computeDepth', () => {
|
||||||
it('should compute depth from query', () => {
|
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,
|
fieldStringMock,
|
||||||
objectMetadataItemMock,
|
objectMetadataItemMock,
|
||||||
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
|
} 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', () => {
|
describe('mapFieldMetadataToGraphqlQuery', () => {
|
||||||
it('should map properly', () => {
|
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 { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||||
|
|
||||||
import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
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 { 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';
|
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 = (
|
export const checkFields = (
|
||||||
objectMetadata: ObjectMetadataInterface,
|
objectMetadata: ObjectMetadataInterface,
|
||||||
fieldNames: string[],
|
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';
|
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 = (
|
export const parseMetadataPath = (
|
||||||
request: Request,
|
request: Request,
|
||||||
): { objectNameSingular: string; objectNamePlural: string; id?: string } => {
|
): { objectNameSingular: string; objectNamePlural: string; id?: string } => {
|
||||||
const queryAction = request.path.replace('/rest/metadata/', '').split('/');
|
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(
|
throw new BadRequestException(
|
||||||
`Query path '${request.path}' invalid. Valid examples: /rest/metadata/fields or /rest/metadata/objects/id`,
|
`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;
|
id?: string;
|
||||||
data?: object | null;
|
data?: object | null;
|
||||||
filter?: object;
|
filter?: object;
|
||||||
orderBy?: object;
|
orderBy?: object;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
lastCursor?: string;
|
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', () => {
|
describe('cleanGraphQLResponse', () => {
|
||||||
it('should remove edges/node from results', () => {
|
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 { 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 { baseSchema } from 'src/engine/core-modules/open-api/utils/base-schema.utils';
|
||||||
import {
|
import {
|
||||||
|
computeBatchPath,
|
||||||
computeManyResultPath,
|
computeManyResultPath,
|
||||||
computeSingleResultPath,
|
computeSingleResultPath,
|
||||||
} from 'src/engine/core-modules/open-api/utils/path.utils';
|
} 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 {
|
import {
|
||||||
computeMetadataSchemaComponents,
|
computeMetadataSchemaComponents,
|
||||||
computeParameterComponents,
|
computeParameterComponents,
|
||||||
@ -21,8 +25,10 @@ import { computeWebhooks } from 'src/engine/core-modules/open-api/utils/computeW
|
|||||||
import { capitalize } from 'src/utils/capitalize';
|
import { capitalize } from 'src/utils/capitalize';
|
||||||
import {
|
import {
|
||||||
getDeleteResponse200,
|
getDeleteResponse200,
|
||||||
getManyResultResponse200,
|
getFindManyResponse200,
|
||||||
getSingleResultSuccessResponse,
|
getCreateOneResponse201,
|
||||||
|
getFindOneResponse200,
|
||||||
|
getUpdateOneResponse200,
|
||||||
} from 'src/engine/core-modules/open-api/utils/responses.utils';
|
} from 'src/engine/core-modules/open-api/utils/responses.utils';
|
||||||
import { getRequestBody } from 'src/engine/core-modules/open-api/utils/request-body.utils';
|
import { getRequestBody } from 'src/engine/core-modules/open-api/utils/request-body.utils';
|
||||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||||
@ -60,6 +66,7 @@ export class OpenApiService {
|
|||||||
}
|
}
|
||||||
schema.paths = objectMetadataItems.reduce((paths, item) => {
|
schema.paths = objectMetadataItems.reduce((paths, item) => {
|
||||||
paths[`/${item.namePlural}`] = computeManyResultPath(item);
|
paths[`/${item.namePlural}`] = computeManyResultPath(item);
|
||||||
|
paths[`/batch/${item.namePlural}`] = computeBatchPath(item);
|
||||||
paths[`/${item.namePlural}/{id}`] = computeSingleResultPath(item);
|
paths[`/${item.namePlural}/{id}`] = computeSingleResultPath(item);
|
||||||
|
|
||||||
return paths;
|
return paths;
|
||||||
@ -86,8 +93,8 @@ export class OpenApiService {
|
|||||||
schemas: computeSchemaComponents(objectMetadataItems),
|
schemas: computeSchemaComponents(objectMetadataItems),
|
||||||
parameters: computeParameterComponents(),
|
parameters: computeParameterComponents(),
|
||||||
responses: {
|
responses: {
|
||||||
'400': getErrorResponses('Invalid request'),
|
'400': get400ErrorResponses(),
|
||||||
'401': getErrorResponses('Unauthorized'),
|
'401': get401ErrorResponses(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -128,7 +135,7 @@ export class OpenApiService {
|
|||||||
summary: `Find Many ${item.namePlural}`,
|
summary: `Find Many ${item.namePlural}`,
|
||||||
parameters: [{ $ref: '#/components/parameters/filter' }],
|
parameters: [{ $ref: '#/components/parameters/filter' }],
|
||||||
responses: {
|
responses: {
|
||||||
'200': getManyResultResponse200(item),
|
'200': getFindManyResponse200(item),
|
||||||
'400': { $ref: '#/components/responses/400' },
|
'400': { $ref: '#/components/responses/400' },
|
||||||
'401': { $ref: '#/components/responses/401' },
|
'401': { $ref: '#/components/responses/401' },
|
||||||
},
|
},
|
||||||
@ -137,9 +144,9 @@ export class OpenApiService {
|
|||||||
tags: [item.namePlural],
|
tags: [item.namePlural],
|
||||||
summary: `Create One ${item.nameSingular}`,
|
summary: `Create One ${item.nameSingular}`,
|
||||||
operationId: `createOne${capitalize(item.nameSingular)}`,
|
operationId: `createOne${capitalize(item.nameSingular)}`,
|
||||||
requestBody: getRequestBody(item),
|
requestBody: getRequestBody(capitalize(item.nameSingular)),
|
||||||
responses: {
|
responses: {
|
||||||
'200': getSingleResultSuccessResponse(item),
|
'200': getCreateOneResponse201(item, true),
|
||||||
'400': { $ref: '#/components/responses/400' },
|
'400': { $ref: '#/components/responses/400' },
|
||||||
'401': { $ref: '#/components/responses/401' },
|
'401': { $ref: '#/components/responses/401' },
|
||||||
},
|
},
|
||||||
@ -151,7 +158,7 @@ export class OpenApiService {
|
|||||||
summary: `Find One ${item.nameSingular}`,
|
summary: `Find One ${item.nameSingular}`,
|
||||||
parameters: [{ $ref: '#/components/parameters/idPath' }],
|
parameters: [{ $ref: '#/components/parameters/idPath' }],
|
||||||
responses: {
|
responses: {
|
||||||
'200': getSingleResultSuccessResponse(item),
|
'200': getFindOneResponse200(item),
|
||||||
'400': { $ref: '#/components/responses/400' },
|
'400': { $ref: '#/components/responses/400' },
|
||||||
'401': { $ref: '#/components/responses/401' },
|
'401': { $ref: '#/components/responses/401' },
|
||||||
},
|
},
|
||||||
@ -162,19 +169,19 @@ export class OpenApiService {
|
|||||||
operationId: `deleteOne${capitalize(item.nameSingular)}`,
|
operationId: `deleteOne${capitalize(item.nameSingular)}`,
|
||||||
parameters: [{ $ref: '#/components/parameters/idPath' }],
|
parameters: [{ $ref: '#/components/parameters/idPath' }],
|
||||||
responses: {
|
responses: {
|
||||||
'200': getDeleteResponse200(item),
|
'200': getDeleteResponse200(item, true),
|
||||||
'400': { $ref: '#/components/responses/400' },
|
'400': { $ref: '#/components/responses/400' },
|
||||||
'401': { $ref: '#/components/responses/401' },
|
'401': { $ref: '#/components/responses/401' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
put: {
|
patch: {
|
||||||
tags: [item.namePlural],
|
tags: [item.namePlural],
|
||||||
summary: `Update One ${item.namePlural}`,
|
summary: `Update One ${item.namePlural}`,
|
||||||
operationId: `updateOne${capitalize(item.nameSingular)}`,
|
operationId: `updateOne${capitalize(item.nameSingular)}`,
|
||||||
parameters: [{ $ref: '#/components/parameters/idPath' }],
|
parameters: [{ $ref: '#/components/parameters/idPath' }],
|
||||||
requestBody: getRequestBody(item),
|
requestBody: getRequestBody(capitalize(item.nameSingular)),
|
||||||
responses: {
|
responses: {
|
||||||
'200': getSingleResultSuccessResponse(item),
|
'200': getUpdateOneResponse200(item, true),
|
||||||
'400': { $ref: '#/components/responses/400' },
|
'400': { $ref: '#/components/responses/400' },
|
||||||
'401': { $ref: '#/components/responses/401' },
|
'401': { $ref: '#/components/responses/401' },
|
||||||
},
|
},
|
||||||
@ -189,8 +196,8 @@ export class OpenApiService {
|
|||||||
schemas: computeMetadataSchemaComponents(metadata),
|
schemas: computeMetadataSchemaComponents(metadata),
|
||||||
parameters: computeParameterComponents(),
|
parameters: computeParameterComponents(),
|
||||||
responses: {
|
responses: {
|
||||||
'400': getErrorResponses('Invalid request'),
|
'400': get400ErrorResponses(),
|
||||||
'401': getErrorResponses('Unauthorized'),
|
'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,
|
computeLimitParameters,
|
||||||
computeOrderByParameters,
|
computeOrderByParameters,
|
||||||
} from 'src/engine/core-modules/open-api/utils/parameters.utils';
|
} 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 { 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/api-rest-query-builder/factories/input-factories/filter-utils/parse-base-filter.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/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';
|
||||||
import { DEFAULT_CONJUNCTION } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/filter-utils/add-default-conjunction.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('computeParameters', () => {
|
||||||
describe('computeLimit', () => {
|
describe('computeLimit', () => {
|
||||||
|
|||||||
@ -105,6 +105,33 @@ const getRequiredFields = (item: ObjectMetadataEntity): string[] => {
|
|||||||
}, [] as 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 = (
|
const computeSchemaComponent = (
|
||||||
item: ObjectMetadataEntity,
|
item: ObjectMetadataEntity,
|
||||||
): OpenAPIV3_1.SchemaObject => {
|
): OpenAPIV3_1.SchemaObject => {
|
||||||
@ -138,6 +165,7 @@ export const computeSchemaComponents = (
|
|||||||
return objectMetadataItems.reduce(
|
return objectMetadataItems.reduce(
|
||||||
(schemas, item) => {
|
(schemas, item) => {
|
||||||
schemas[capitalize(item.nameSingular)] = computeSchemaComponent(item);
|
schemas[capitalize(item.nameSingular)] = computeSchemaComponent(item);
|
||||||
|
schemas[capitalize(item.namePlural)] = computeBatchSchemaComponent(item);
|
||||||
|
|
||||||
return schemas;
|
return schemas;
|
||||||
},
|
},
|
||||||
@ -168,6 +196,7 @@ export const computeMetadataSchemaComponents = (
|
|||||||
case 'object': {
|
case 'object': {
|
||||||
schemas[`${capitalize(item.nameSingular)}`] = {
|
schemas[`${capitalize(item.nameSingular)}`] = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
description: `An object`,
|
||||||
properties: {
|
properties: {
|
||||||
dataSourceId: { type: 'string' },
|
dataSourceId: { type: 'string' },
|
||||||
nameSingular: { 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;
|
return schemas;
|
||||||
@ -208,6 +246,7 @@ export const computeMetadataSchemaComponents = (
|
|||||||
case 'field': {
|
case 'field': {
|
||||||
schemas[`${capitalize(item.nameSingular)}`] = {
|
schemas[`${capitalize(item.nameSingular)}`] = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
description: `A field`,
|
||||||
properties: {
|
properties: {
|
||||||
type: { type: 'string' },
|
type: { type: 'string' },
|
||||||
name: { type: 'string' },
|
name: { type: 'string' },
|
||||||
@ -259,6 +298,15 @@ export const computeMetadataSchemaComponents = (
|
|||||||
defaultValue: { type: 'object' },
|
defaultValue: { type: 'object' },
|
||||||
options: { 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;
|
return schemas;
|
||||||
@ -266,6 +314,7 @@ export const computeMetadataSchemaComponents = (
|
|||||||
case 'relation': {
|
case 'relation': {
|
||||||
schemas[`${capitalize(item.nameSingular)}`] = {
|
schemas[`${capitalize(item.nameSingular)}`] = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
description: 'A relation',
|
||||||
properties: {
|
properties: {
|
||||||
relationType: { type: 'string' },
|
relationType: { type: 'string' },
|
||||||
fromObjectMetadata: {
|
fromObjectMetadata: {
|
||||||
@ -293,6 +342,15 @@ export const computeMetadataSchemaComponents = (
|
|||||||
fromFieldMetadataId: { type: 'string' },
|
fromFieldMetadataId: { type: 'string' },
|
||||||
toFieldMetadataId: { 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';
|
import { OpenAPIV3_1 } from 'openapi-types';
|
||||||
|
|
||||||
export const getErrorResponses = (
|
export const get400ErrorResponses = (): OpenAPIV3_1.ResponseObject => {
|
||||||
description: string,
|
|
||||||
): OpenAPIV3_1.ResponseObject => {
|
|
||||||
return {
|
return {
|
||||||
description,
|
description: 'Bad Request',
|
||||||
content: {
|
content: {
|
||||||
'application/json': {
|
'application/json': {
|
||||||
schema: {
|
schema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
|
statusCode: { type: 'number' },
|
||||||
|
message: { type: 'string' },
|
||||||
error: { 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 { 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 { 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/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';
|
||||||
import { DEFAULT_ORDER_DIRECTION } from 'src/engine/api/rest/api-rest-query-builder/factories/input-factories/order-by-input.factory';
|
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/api-rest-query-builder/factories/input-factories/filter-utils/add-default-conjunction.utils';
|
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 => {
|
export const computeLimitParameters = (): OpenAPIV3_1.ParameterObject => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -5,11 +5,33 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
|||||||
import {
|
import {
|
||||||
getDeleteResponse200,
|
getDeleteResponse200,
|
||||||
getJsonResponse,
|
getJsonResponse,
|
||||||
getManyResultResponse200,
|
getFindManyResponse200,
|
||||||
getSingleResultSuccessResponse,
|
getCreateOneResponse201,
|
||||||
|
getCreateManyResponse201,
|
||||||
|
getFindOneResponse200,
|
||||||
|
getUpdateOneResponse200,
|
||||||
} from 'src/engine/core-modules/open-api/utils/responses.utils';
|
} from 'src/engine/core-modules/open-api/utils/responses.utils';
|
||||||
import { getRequestBody } from 'src/engine/core-modules/open-api/utils/request-body.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 = (
|
export const computeManyResultPath = (
|
||||||
item: ObjectMetadataEntity,
|
item: ObjectMetadataEntity,
|
||||||
): OpenAPIV3_1.PathItemObject => {
|
): OpenAPIV3_1.PathItemObject => {
|
||||||
@ -27,7 +49,7 @@ export const computeManyResultPath = (
|
|||||||
{ $ref: '#/components/parameters/lastCursor' },
|
{ $ref: '#/components/parameters/lastCursor' },
|
||||||
],
|
],
|
||||||
responses: {
|
responses: {
|
||||||
'200': getManyResultResponse200(item),
|
'200': getFindManyResponse200(item),
|
||||||
'400': { $ref: '#/components/responses/400' },
|
'400': { $ref: '#/components/responses/400' },
|
||||||
'401': { $ref: '#/components/responses/401' },
|
'401': { $ref: '#/components/responses/401' },
|
||||||
},
|
},
|
||||||
@ -37,9 +59,9 @@ export const computeManyResultPath = (
|
|||||||
summary: `Create One ${item.nameSingular}`,
|
summary: `Create One ${item.nameSingular}`,
|
||||||
operationId: `createOne${capitalize(item.nameSingular)}`,
|
operationId: `createOne${capitalize(item.nameSingular)}`,
|
||||||
parameters: [{ $ref: '#/components/parameters/depth' }],
|
parameters: [{ $ref: '#/components/parameters/depth' }],
|
||||||
requestBody: getRequestBody(item),
|
requestBody: getRequestBody(capitalize(item.nameSingular)),
|
||||||
responses: {
|
responses: {
|
||||||
'201': getSingleResultSuccessResponse(item),
|
'201': getCreateOneResponse201(item),
|
||||||
'400': { $ref: '#/components/responses/400' },
|
'400': { $ref: '#/components/responses/400' },
|
||||||
'401': { $ref: '#/components/responses/401' },
|
'401': { $ref: '#/components/responses/401' },
|
||||||
},
|
},
|
||||||
@ -61,7 +83,7 @@ export const computeSingleResultPath = (
|
|||||||
{ $ref: '#/components/parameters/depth' },
|
{ $ref: '#/components/parameters/depth' },
|
||||||
],
|
],
|
||||||
responses: {
|
responses: {
|
||||||
'200': getSingleResultSuccessResponse(item),
|
'200': getFindOneResponse200(item),
|
||||||
'400': { $ref: '#/components/responses/400' },
|
'400': { $ref: '#/components/responses/400' },
|
||||||
'401': { $ref: '#/components/responses/401' },
|
'401': { $ref: '#/components/responses/401' },
|
||||||
},
|
},
|
||||||
@ -77,7 +99,7 @@ export const computeSingleResultPath = (
|
|||||||
'401': { $ref: '#/components/responses/401' },
|
'401': { $ref: '#/components/responses/401' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
put: {
|
patch: {
|
||||||
tags: [item.namePlural],
|
tags: [item.namePlural],
|
||||||
summary: `Update One ${item.namePlural}`,
|
summary: `Update One ${item.namePlural}`,
|
||||||
operationId: `UpdateOne${capitalize(item.nameSingular)}`,
|
operationId: `UpdateOne${capitalize(item.nameSingular)}`,
|
||||||
@ -85,9 +107,9 @@ export const computeSingleResultPath = (
|
|||||||
{ $ref: '#/components/parameters/idPath' },
|
{ $ref: '#/components/parameters/idPath' },
|
||||||
{ $ref: '#/components/parameters/depth' },
|
{ $ref: '#/components/parameters/depth' },
|
||||||
],
|
],
|
||||||
requestBody: getRequestBody(item),
|
requestBody: getRequestBody(capitalize(item.nameSingular)),
|
||||||
responses: {
|
responses: {
|
||||||
'200': getSingleResultSuccessResponse(item),
|
'200': getUpdateOneResponse200(item),
|
||||||
'400': { $ref: '#/components/responses/400' },
|
'400': { $ref: '#/components/responses/400' },
|
||||||
'401': { $ref: '#/components/responses/401' },
|
'401': { $ref: '#/components/responses/401' },
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,16 +1,11 @@
|
|||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
export const getRequestBody = (name: string) => {
|
||||||
import { capitalize } from 'src/utils/capitalize';
|
|
||||||
|
|
||||||
export const getRequestBody = (
|
|
||||||
item: Pick<ObjectMetadataEntity, 'nameSingular'>,
|
|
||||||
) => {
|
|
||||||
return {
|
return {
|
||||||
description: 'body',
|
description: 'body',
|
||||||
required: true,
|
required: true,
|
||||||
content: {
|
content: {
|
||||||
'application/json': {
|
'application/json': {
|
||||||
schema: {
|
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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { capitalize } from 'src/utils/capitalize';
|
import { capitalize } from 'src/utils/capitalize';
|
||||||
|
|
||||||
export const getManyResultResponse200 = (
|
export const getFindManyResponse200 = (
|
||||||
item: Pick<ObjectMetadataEntity, 'nameSingular' | 'namePlural'>,
|
item: Pick<ObjectMetadataEntity, 'nameSingular' | 'namePlural'>,
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
@ -28,7 +28,8 @@ export const getManyResultResponse200 = (
|
|||||||
example: {
|
example: {
|
||||||
data: {
|
data: {
|
||||||
[item.namePlural]: [
|
[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'>,
|
item: Pick<ObjectMetadataEntity, 'nameSingular'>,
|
||||||
) => {
|
) => {
|
||||||
return {
|
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'>,
|
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 {
|
return {
|
||||||
description: 'Successful operation',
|
description: 'Successful operation',
|
||||||
@ -77,7 +118,84 @@ export const getDeleteResponse200 = (
|
|||||||
data: {
|
data: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
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',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
id: {
|
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 { useContainer } from 'class-validator';
|
||||||
import '@sentry/tracing';
|
import '@sentry/tracing';
|
||||||
|
|
||||||
|
import { ApplyCorsToExceptions } from 'src/utils/apply-cors-to-exceptions';
|
||||||
|
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
import { generateFrontConfig } from './utils/generate-front-config';
|
import { generateFrontConfig } from './utils/generate-front-config';
|
||||||
@ -38,6 +40,8 @@ const bootstrap = async () => {
|
|||||||
app.use(Sentry.Handlers.tracingHandler());
|
app.use(Sentry.Handlers.tracingHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.useGlobalFilters(new ApplyCorsToExceptions());
|
||||||
|
|
||||||
// Apply validation pipes globally
|
// Apply validation pipes globally
|
||||||
app.useGlobalPipes(
|
app.useGlobalPipes(
|
||||||
new ValidationPipe({
|
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