Return graphql errors when exists (#5389)

- throw badRequest with graphql error messages when graphql request
fails
- clean some code

Before
<img width="1470" alt="image"
src="https://github.com/twentyhq/twenty/assets/29927851/0b700d9a-2bbe-41f7-84a9-981dc7dd5344">

After

![image](https://github.com/twentyhq/twenty/assets/29927851/6bbaaf7c-1244-473d-9ae5-4fefc6a1b994)
This commit is contained in:
martmull
2024-05-14 13:21:55 +02:00
committed by GitHub
parent 1bc9b780e5
commit ffdd3a7d4e
9 changed files with 68 additions and 81 deletions

View File

@ -483,6 +483,7 @@ export class WorkspaceQueryRunnerService {
const { workspaceId, userId, objectMetadataItem } = options; const { workspaceId, userId, objectMetadataItem } = options;
assertMutationNotOnRemoteObject(objectMetadataItem); assertMutationNotOnRemoteObject(objectMetadataItem);
assertIsValidUuid(args.id);
const query = await this.workspaceQueryBuilderFactory.deleteOne( const query = await this.workspaceQueryBuilderFactory.deleteOne(
args, args,

View File

@ -98,7 +98,7 @@ export class ApiRestQueryBuilderFactory {
}; };
} }
async create(request): Promise<ApiRestQuery> { async create(request: Request): Promise<ApiRestQuery> {
const objectMetadata = await this.getObjectMetadata(request); const objectMetadata = await this.getObjectMetadata(request);
const depth = computeDepth(request); const depth = computeDepth(request);
@ -109,7 +109,7 @@ export class ApiRestQueryBuilderFactory {
}; };
} }
async update(request): Promise<ApiRestQuery> { async update(request: Request): Promise<ApiRestQuery> {
const objectMetadata = await this.getObjectMetadata(request); const objectMetadata = await this.getObjectMetadata(request);
const depth = computeDepth(request); const depth = computeDepth(request);
@ -128,7 +128,7 @@ export class ApiRestQueryBuilderFactory {
}; };
} }
async get(request): Promise<ApiRestQuery> { async get(request: Request): Promise<ApiRestQuery> {
const objectMetadata = await this.getObjectMetadata(request); const objectMetadata = await this.getObjectMetadata(request);
const depth = computeDepth(request); const depth = computeDepth(request);

View File

@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Request } from 'express';
import { ApiRestQueryVariables } from 'src/engine/api/rest/types/api-rest-query-variables.type'; import { ApiRestQueryVariables } from 'src/engine/api/rest/types/api-rest-query-variables.type';
@Injectable() @Injectable()

View File

@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Request } from 'express';
import { ApiRestQueryVariables } from 'src/engine/api/rest/types/api-rest-query-variables.type'; import { ApiRestQueryVariables } from 'src/engine/api/rest/types/api-rest-query-variables.type';
@Injectable() @Injectable()

View File

@ -3,7 +3,7 @@ import { Controller, Delete, Get, Post, Put, Req, Res } from '@nestjs/common';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { ApiRestService } from 'src/engine/api/rest/api-rest.service'; import { ApiRestService } from 'src/engine/api/rest/api-rest.service';
import { handleResult } from 'src/engine/api/rest/api-rest.controller.utils'; import { cleanGraphQLResponse } from 'src/engine/api/rest/api-rest.controller.utils';
@Controller('rest/*') @Controller('rest/*')
export class ApiRestController { export class ApiRestController {
@ -11,21 +11,29 @@ export class ApiRestController {
@Get() @Get()
async handleApiGet(@Req() request: Request, @Res() res: Response) { async handleApiGet(@Req() request: Request, @Res() res: Response) {
handleResult(res, await this.apiRestService.get(request)); const result = await this.apiRestService.get(request);
res.send(cleanGraphQLResponse(result.data));
} }
@Delete() @Delete()
async handleApiDelete(@Req() request: Request, @Res() res: Response) { async handleApiDelete(@Req() request: Request, @Res() res: Response) {
handleResult(res, await this.apiRestService.delete(request)); const result = await this.apiRestService.delete(request);
res.send(cleanGraphQLResponse(result.data));
} }
@Post() @Post()
async handleApiPost(@Req() request: Request, @Res() res: Response) { async handleApiPost(@Req() request: Request, @Res() res: Response) {
handleResult(res, await this.apiRestService.create(request)); const result = await this.apiRestService.create(request);
res.send(cleanGraphQLResponse(result.data));
} }
@Put() @Put()
async handleApiPut(@Req() request: Request, @Res() res: Response) { async handleApiPut(@Req() request: Request, @Res() res: Response) {
handleResult(res, await this.apiRestService.update(request)); const result = await this.apiRestService.update(request);
res.send(cleanGraphQLResponse(result.data));
} }
} }

View File

@ -1,12 +1,8 @@
import { Response } from 'express';
import { ApiRestResponse } from 'src/engine/api/rest/types/api-rest-response.type';
// https://gist.github.com/ManUtopiK/469aec75b655d6a4d912aeb3b75af3c9 // https://gist.github.com/ManUtopiK/469aec75b655d6a4d912aeb3b75af3c9
export const cleanGraphQLResponse = (input) => { export const cleanGraphQLResponse = (input: any) => {
if (!input) return null; if (!input) return null;
const output = {}; const output = {};
const isObject = (obj) => { const isObject = (obj: any) => {
return obj !== null && typeof obj === 'object' && !Array.isArray(obj); return obj !== null && typeof obj === 'object' && !Array.isArray(obj);
}; };
@ -24,13 +20,3 @@ export const cleanGraphQLResponse = (input) => {
return output; return output;
}; };
export const handleResult = (res: Response, result: ApiRestResponse) => {
if (result.data.error) {
res
.status(result.data.status || 400)
.send({ error: `${result.data.error}` });
} else {
res.send(cleanGraphQLResponse(result.data));
}
};

View File

@ -1,87 +1,72 @@
import { Injectable } from '@nestjs/common'; import { BadRequestException, Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios'; import { HttpService } from '@nestjs/axios';
import { Request } from 'express'; import { Request } from 'express';
import { AxiosResponse } from 'axios';
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; 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 { ApiRestQueryBuilderFactory } from 'src/engine/api/rest/api-rest-query-builder/api-rest-query-builder.factory';
import { TokenService } from 'src/engine/core-modules/auth/services/token.service';
import { ApiRestResponse } from 'src/engine/api/rest/types/api-rest-response.type';
import { ApiRestQuery } from 'src/engine/api/rest/types/api-rest-query.type'; import { ApiRestQuery } from 'src/engine/api/rest/types/api-rest-query.type';
import { getServerUrl } from 'src/utils/get-server-url'; import { getServerUrl } from 'src/utils/get-server-url';
@Injectable() @Injectable()
export class ApiRestService { export class ApiRestService {
constructor( constructor(
private readonly tokenService: TokenService,
private readonly environmentService: EnvironmentService, private readonly environmentService: EnvironmentService,
private readonly apiRestQueryBuilderFactory: ApiRestQueryBuilderFactory, private readonly apiRestQueryBuilderFactory: ApiRestQueryBuilderFactory,
private readonly httpService: HttpService, private readonly httpService: HttpService,
) {} ) {}
async callGraphql( async callGraphql(request: Request, data: ApiRestQuery) {
request: Request,
data: ApiRestQuery,
): Promise<ApiRestResponse> {
const baseUrl = getServerUrl( const baseUrl = getServerUrl(
request, request,
this.environmentService.get('SERVER_URL'), this.environmentService.get('SERVER_URL'),
); );
let response: AxiosResponse;
try { try {
return await this.httpService.axiosRef.post(`${baseUrl}/graphql`, data, { response = await this.httpService.axiosRef.post(
headers: { `${baseUrl}/graphql`,
'Content-Type': 'application/json', data,
Authorization: request.headers.authorization, {
headers: {
'Content-Type': 'application/json',
Authorization: request.headers.authorization,
},
}, },
}); );
} catch (err) { } catch (err) {
return { throw new BadRequestException(err.response.data);
data: {
error: `${err}. Please check your query.`,
status: err.response.status,
},
};
} }
if (response.data.errors?.length) {
throw new BadRequestException(response.data);
}
return response;
} }
async get(request: Request): Promise<ApiRestResponse> { async get(request: Request) {
try { const data = await this.apiRestQueryBuilderFactory.get(request);
const data = await this.apiRestQueryBuilderFactory.get(request);
return await this.callGraphql(request, data); return await this.callGraphql(request, data);
} catch (err) {
return { data: { error: err, status: err.status } };
}
} }
async delete(request: Request): Promise<ApiRestResponse> { async delete(request: Request) {
try { const data = await this.apiRestQueryBuilderFactory.delete(request);
const data = await this.apiRestQueryBuilderFactory.delete(request);
return await this.callGraphql(request, data); return await this.callGraphql(request, data);
} catch (err) {
return { data: { error: err, status: err.status } };
}
} }
async create(request: Request): Promise<ApiRestResponse> { async create(request: Request) {
try { const data = await this.apiRestQueryBuilderFactory.create(request);
const data = await this.apiRestQueryBuilderFactory.create(request);
return await this.callGraphql(request, data); return await this.callGraphql(request, data);
} catch (err) {
return { data: { error: err, status: err.status } };
}
} }
async update(request: Request): Promise<ApiRestResponse> { async update(request: Request) {
try { const data = await this.apiRestQueryBuilderFactory.update(request);
const data = await this.apiRestQueryBuilderFactory.update(request);
return await this.callGraphql(request, data); return await this.callGraphql(request, data);
} catch (err) {
return { data: { error: err, status: err.status } };
}
} }
} }

View File

@ -2,8 +2,8 @@ import { Controller, Get, Delete, Post, Put, Req, Res } from '@nestjs/common';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { handleResult } from 'src/engine/api/rest/api-rest.controller.utils';
import { ApiRestMetadataService } from 'src/engine/api/rest/metadata-rest.service'; import { ApiRestMetadataService } from 'src/engine/api/rest/metadata-rest.service';
import { cleanGraphQLResponse } from 'src/engine/api/rest/api-rest.controller.utils';
@Controller('rest/metadata/*') @Controller('rest/metadata/*')
export class ApiRestMetadataController { export class ApiRestMetadataController {
@ -11,21 +11,29 @@ export class ApiRestMetadataController {
@Get() @Get()
async handleApiGet(@Req() request: Request, @Res() res: Response) { async handleApiGet(@Req() request: Request, @Res() res: Response) {
handleResult(res, await this.apiRestService.get(request)); const result = await this.apiRestService.get(request);
res.send(cleanGraphQLResponse(result.data));
} }
@Delete() @Delete()
async handleApiDelete(@Req() request: Request, @Res() res: Response) { async handleApiDelete(@Req() request: Request, @Res() res: Response) {
handleResult(res, await this.apiRestService.delete(request)); const result = await this.apiRestService.delete(request);
res.send(cleanGraphQLResponse(result.data));
} }
@Post() @Post()
async handleApiPost(@Req() request: Request, @Res() res: Response) { async handleApiPost(@Req() request: Request, @Res() res: Response) {
handleResult(res, await this.apiRestService.create(request)); const result = await this.apiRestService.create(request);
res.send(cleanGraphQLResponse(result.data));
} }
@Put() @Put()
async handleApiPut(@Req() request: Request, @Res() res: Response) { async handleApiPut(@Req() request: Request, @Res() res: Response) {
handleResult(res, await this.apiRestService.update(request)); const result = await this.apiRestService.update(request);
res.send(cleanGraphQLResponse(result.data));
} }
} }

View File

@ -1,5 +0,0 @@
import { HttpException } from '@nestjs/common';
export type ApiRestResponse = {
data: { error?: HttpException | string; status?: number };
};