feat: REST endpoints for metadata API (#3912)
* parse metadata path * metadata rest api * add queryAction condition and return object singular/plural * handle GET endpoint for metadata * FindOne and FindMany query for metadata endpoint * Request all objects and nest fields in object request --------- Co-authored-by: martmull <martmull@hotmail.fr>
This commit is contained in:
@ -19,3 +19,59 @@ export const parsePath = (
|
|||||||
|
|
||||||
return { object: queryAction[0], id: queryAction[1] };
|
return { object: queryAction[0], id: queryAction[1] };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const parseMetadataPath = (
|
||||||
|
request: Request,
|
||||||
|
): { objectNameSingular: string; objectNamePlural: string; id?: string } => {
|
||||||
|
const queryAction = request.path.replace('/rest/metadata/', '').split('/');
|
||||||
|
|
||||||
|
if (queryAction.length > 3 || queryAction.length === 0) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`Query path '${request.path}' invalid. Valid examples: /rest/metadata/fields or /rest/metadata/objects/id`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!['fields', 'objects', 'relations'].includes(queryAction[0])) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`Query path '${request.path}' invalid. Metadata path "${queryAction[0]}" does not exist. Valid examples: /rest/metadata/fields or /rest/metadata/objects or /rest/metadata/relations`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const objectName = queryAction[0];
|
||||||
|
|
||||||
|
if (queryAction.length === 2) {
|
||||||
|
switch (objectName) {
|
||||||
|
case 'fields':
|
||||||
|
return {
|
||||||
|
objectNameSingular: 'field',
|
||||||
|
objectNamePlural: objectName,
|
||||||
|
id: queryAction[1],
|
||||||
|
};
|
||||||
|
case 'objects':
|
||||||
|
return {
|
||||||
|
objectNameSingular: 'object',
|
||||||
|
objectNamePlural: objectName,
|
||||||
|
id: queryAction[1],
|
||||||
|
};
|
||||||
|
case 'relations':
|
||||||
|
return {
|
||||||
|
objectNameSingular: 'relation',
|
||||||
|
objectNamePlural: objectName,
|
||||||
|
id: queryAction[1],
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return { objectNameSingular: '', objectNamePlural: '', id: '' };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (objectName) {
|
||||||
|
case 'fields':
|
||||||
|
return { objectNameSingular: 'field', objectNamePlural: objectName };
|
||||||
|
case 'objects':
|
||||||
|
return { objectNameSingular: 'object', objectNamePlural: objectName };
|
||||||
|
case 'relations':
|
||||||
|
return { objectNameSingular: 'relation', objectNamePlural: objectName };
|
||||||
|
default:
|
||||||
|
return { objectNameSingular: '', objectNamePlural: '' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@ -5,10 +5,12 @@ import { ApiRestController } from 'src/core/api-rest/api-rest.controller';
|
|||||||
import { ApiRestService } from 'src/core/api-rest/api-rest.service';
|
import { ApiRestService } from 'src/core/api-rest/api-rest.service';
|
||||||
import { ApiRestQueryBuilderModule } from 'src/core/api-rest/api-rest-query-builder/api-rest-query-builder.module';
|
import { ApiRestQueryBuilderModule } from 'src/core/api-rest/api-rest-query-builder/api-rest-query-builder.module';
|
||||||
import { AuthModule } from 'src/core/auth/auth.module';
|
import { AuthModule } from 'src/core/auth/auth.module';
|
||||||
|
import { ApiRestMetadataController } from 'src/core/api-rest/metadata-rest.controller';
|
||||||
|
import { ApiRestMetadataService } from 'src/core/api-rest/metadata-rest.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ApiRestQueryBuilderModule, AuthModule, HttpModule],
|
imports: [ApiRestQueryBuilderModule, AuthModule, HttpModule],
|
||||||
controllers: [ApiRestController],
|
controllers: [ApiRestMetadataController, ApiRestController],
|
||||||
providers: [ApiRestService],
|
providers: [ApiRestMetadataService, ApiRestService],
|
||||||
})
|
})
|
||||||
export class ApiRestModule {}
|
export class ApiRestModule {}
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { Controller, Get, Delete, Post, Put, Req, Res } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { Request, Response } from 'express';
|
||||||
|
|
||||||
|
import { handleResult } from 'src/core/api-rest/api-rest.controller.utils';
|
||||||
|
import { ApiRestMetadataService } from 'src/core/api-rest/metadata-rest.service';
|
||||||
|
|
||||||
|
@Controller('rest/metadata/*')
|
||||||
|
export class ApiRestMetadataController {
|
||||||
|
constructor(private readonly apiRestService: ApiRestMetadataService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async handleApiGet(@Req() request: Request, @Res() res: Response) {
|
||||||
|
handleResult(res, await this.apiRestService.get(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete()
|
||||||
|
async handleApiDelete(@Req() request: Request, @Res() res: Response) {
|
||||||
|
handleResult(res, await this.apiRestService.delete(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
async handleApiPost(@Req() request: Request, @Res() res: Response) {
|
||||||
|
handleResult(res, await this.apiRestService.create(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put()
|
||||||
|
async handleApiPut(@Req() request: Request, @Res() res: Response) {
|
||||||
|
handleResult(res, await this.apiRestService.update(request));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,323 @@
|
|||||||
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
|
import { HttpService } from '@nestjs/axios';
|
||||||
|
|
||||||
|
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||||
|
import { ApiRestQueryBuilderFactory } from 'src/core/api-rest/api-rest-query-builder/api-rest-query-builder.factory';
|
||||||
|
import { ApiRestQuery } from 'src/core/api-rest/types/api-rest-query.type';
|
||||||
|
import { TokenService } from 'src/core/auth/services/token.service';
|
||||||
|
import { parseMetadataPath } from 'src/core/api-rest/api-rest-query-builder/utils/parse-path.utils';
|
||||||
|
import { capitalize } from 'src/utils/capitalize';
|
||||||
|
|
||||||
|
@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 =
|
||||||
|
this.environmentService.getServerUrl() ||
|
||||||
|
`${request.protocol}://${request.get('host')}`;
|
||||||
|
|
||||||
|
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 } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user