From 576492f3c09eae0e5460b273da0f89ceebade631 Mon Sep 17 00:00:00 2001 From: martmull Date: Mon, 18 Dec 2023 13:46:21 +0100 Subject: [PATCH] 3035 improve rest api syntax (#3047) --- .../twenty-docs/docs/developer/rest_api.mdx | 2 +- ...p-field-metadata-to-graphql-query.utils.ts | 4 +- .../src/core/api-rest/api-rest.controller.ts | 12 +---- .../api-rest.controller.utils.spec.ts | 53 +++++++++++++++++++ .../api-rest/api-rest.controller.utils.ts | 36 +++++++++++++ .../core/open-api/utils/responses.utils.ts | 34 ++++-------- 6 files changed, 102 insertions(+), 39 deletions(-) create mode 100644 packages/twenty-server/src/core/api-rest/api-rest.controller.utils.spec.ts create mode 100644 packages/twenty-server/src/core/api-rest/api-rest.controller.utils.ts diff --git a/packages/twenty-docs/docs/developer/rest_api.mdx b/packages/twenty-docs/docs/developer/rest_api.mdx index 153c078bf8..0a2d064aaf 100644 --- a/packages/twenty-docs/docs/developer/rest_api.mdx +++ b/packages/twenty-docs/docs/developer/rest_api.mdx @@ -19,7 +19,7 @@ if needed. ## Programmatic use? You can call the REST API in your application using this endpoint -[https://api.twenty.com](https://api.twenty.com). +[https://api.twenty.com/rest](https://api.twenty.com/rest). You will need to provide your API key as a Bearer token in your `headers.Authorization = 'Bearer '`. diff --git a/packages/twenty-server/src/core/api-rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils.ts b/packages/twenty-server/src/core/api-rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils.ts index c5a0ba8031..a8dc288405 100644 --- a/packages/twenty-server/src/core/api-rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils.ts +++ b/packages/twenty-server/src/core/api-rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils.ts @@ -33,7 +33,7 @@ export const mapFieldMetadataToGraphqlQuery = ( const relationMetadataItem = objectMetadataItems.find( (objectMetadataItem) => objectMetadataItem.id === - (field.toRelationMetadata as any)?.fromObjectMetadata?.id, + (field.toRelationMetadata as any)?.fromObjectMetadataId, ); return `${field.name} @@ -58,7 +58,7 @@ export const mapFieldMetadataToGraphqlQuery = ( const relationMetadataItem = objectMetadataItems.find( (objectMetadataItem) => objectMetadataItem.id === - (field.fromRelationMetadata as any)?.toObjectMetadata?.id, + (field.fromRelationMetadata as any)?.toObjectMetadataId, ); return `${field.name} diff --git a/packages/twenty-server/src/core/api-rest/api-rest.controller.ts b/packages/twenty-server/src/core/api-rest/api-rest.controller.ts index e6371e35e9..3c2559d45d 100644 --- a/packages/twenty-server/src/core/api-rest/api-rest.controller.ts +++ b/packages/twenty-server/src/core/api-rest/api-rest.controller.ts @@ -3,17 +3,7 @@ import { Controller, Delete, Get, Post, Put, Req, Res } from '@nestjs/common'; import { Request, Response } from 'express'; import { ApiRestService } from 'src/core/api-rest/api-rest.service'; -import { ApiRestResponse } from 'src/core/api-rest/types/api-rest-response.type'; - -const handleResult = (res: Response, result: ApiRestResponse) => { - if (result.data.error) { - res - .status(result.data.status || 400) - .send({ error: `${result.data.error}` }); - } else { - res.send(result.data); - } -}; +import { handleResult } from 'src/core/api-rest/api-rest.controller.utils'; @Controller('rest/*') export class ApiRestController { diff --git a/packages/twenty-server/src/core/api-rest/api-rest.controller.utils.spec.ts b/packages/twenty-server/src/core/api-rest/api-rest.controller.utils.spec.ts new file mode 100644 index 0000000000..59fe4249ca --- /dev/null +++ b/packages/twenty-server/src/core/api-rest/api-rest.controller.utils.spec.ts @@ -0,0 +1,53 @@ +import { cleanGraphQLResponse } from 'src/core/api-rest/api-rest.controller.utils'; + +describe('cleanGraphQLResponse', () => { + it('should remove edges/node from results', () => { + const data = { + companies: { + edges: [ + { + node: { id: 'id', createdAt: '2023-01-01' }, + }, + ], + }, + }; + const expectedResult = { + companies: [{ id: 'id', createdAt: '2023-01-01' }], + }; + + expect(cleanGraphQLResponse(data)).toEqual(expectedResult); + }); + it('should remove nested edges/node from results', () => { + const data = { + companies: { + edges: [ + { + node: { + id: 'id', + createdAt: '2023-01-01', + people: { + edges: [{ node: { id: 'id1' } }, { node: { id: 'id2' } }], + }, + }, + }, + ], + }, + }; + const expectedResult = { + companies: [ + { + id: 'id', + createdAt: '2023-01-01', + people: [{ id: 'id1' }, { id: 'id2' }], + }, + ], + }; + + expect(cleanGraphQLResponse(data)).toEqual(expectedResult); + }); + it('should not format when no list returned', () => { + const data = { company: { id: 'id' } }; + + expect(cleanGraphQLResponse(data)).toEqual(data); + }); +}); diff --git a/packages/twenty-server/src/core/api-rest/api-rest.controller.utils.ts b/packages/twenty-server/src/core/api-rest/api-rest.controller.utils.ts new file mode 100644 index 0000000000..e82792b4fa --- /dev/null +++ b/packages/twenty-server/src/core/api-rest/api-rest.controller.utils.ts @@ -0,0 +1,36 @@ +import { Response } from 'express'; + +import { ApiRestResponse } from 'src/core/api-rest/types/api-rest-response.type'; + +// https://gist.github.com/ManUtopiK/469aec75b655d6a4d912aeb3b75af3c9 +export const cleanGraphQLResponse = (input) => { + if (!input) return null; + const output = {}; + const isObject = (obj) => { + return obj !== null && typeof obj === 'object' && !Array.isArray(obj); + }; + + Object.keys(input).forEach((key) => { + if (input[key] && input[key].edges) { + output[key] = input[key].edges.map((edge) => + cleanGraphQLResponse(edge.node), + ); + } else if (isObject(input[key])) { + output[key] = cleanGraphQLResponse(input[key]); + } else if (key !== '__typename') { + output[key] = input[key]; + } + }); + + 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)); + } +}; diff --git a/packages/twenty-server/src/core/open-api/utils/responses.utils.ts b/packages/twenty-server/src/core/open-api/utils/responses.utils.ts index 603a86d241..3dd683cf14 100644 --- a/packages/twenty-server/src/core/open-api/utils/responses.utils.ts +++ b/packages/twenty-server/src/core/open-api/utils/responses.utils.ts @@ -13,21 +13,11 @@ export const getManyResultResponse200 = (item: ObjectMetadataEntity) => { type: 'object', properties: { [item.namePlural]: { - type: 'object', - properties: { - edges: { - type: 'array', - items: { - type: 'object', - properties: { - node: { - $ref: `#/components/schemas/${capitalize( - item.nameSingular, - )}`, - }, - }, - }, - }, + type: 'array', + items: { + $ref: `#/components/schemas/${capitalize( + item.nameSingular, + )}`, }, }, }, @@ -35,16 +25,10 @@ export const getManyResultResponse200 = (item: ObjectMetadataEntity) => { }, example: { data: { - properties: { - [item.namePlural]: { - edges: [ - { - node: `${capitalize(item.nameSingular)}Object`, - }, - '...', - ], - }, - }, + [item.namePlural]: [ + `${capitalize(item.nameSingular)}Object`, + '...', + ], }, }, },