From 1de840f76768f69a86b1a644d8b687883e8a6b58 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Thu, 16 Nov 2023 07:52:27 +0000 Subject: [PATCH] refactor: add v2 apis - swagger --- .../api-docs/api-docs.controller.ts | 39 +- .../src/services/api-docs/api-docs.service.ts | 30 ++ .../api-docs/swagger/templates/paths.ts | 2 +- .../services/api-docs/swaggerV2/getPaths.ts | 45 ++ .../services/api-docs/swaggerV2/getSchemas.ts | 46 ++ .../swaggerV2/getSwaggerColumnMetas.ts | 67 +++ .../api-docs/swaggerV2/getSwaggerJSONV2.ts | 67 +++ .../api-docs/swaggerV2/swagger-base.json | 96 ++++ .../api-docs/swaggerV2/templates/headers.ts | 10 + .../api-docs/swaggerV2/templates/params.ts | 233 ++++++++++ .../api-docs/swaggerV2/templates/paths.ts | 420 ++++++++++++++++++ .../api-docs/swaggerV2/templates/schemas.ts | 85 ++++ 12 files changed, 1127 insertions(+), 13 deletions(-) create mode 100644 packages/nocodb/src/services/api-docs/swaggerV2/getPaths.ts create mode 100644 packages/nocodb/src/services/api-docs/swaggerV2/getSchemas.ts create mode 100644 packages/nocodb/src/services/api-docs/swaggerV2/getSwaggerColumnMetas.ts create mode 100644 packages/nocodb/src/services/api-docs/swaggerV2/getSwaggerJSONV2.ts create mode 100644 packages/nocodb/src/services/api-docs/swaggerV2/swagger-base.json create mode 100644 packages/nocodb/src/services/api-docs/swaggerV2/templates/headers.ts create mode 100644 packages/nocodb/src/services/api-docs/swaggerV2/templates/params.ts create mode 100644 packages/nocodb/src/services/api-docs/swaggerV2/templates/paths.ts create mode 100644 packages/nocodb/src/services/api-docs/swaggerV2/templates/schemas.ts diff --git a/packages/nocodb/src/controllers/api-docs/api-docs.controller.ts b/packages/nocodb/src/controllers/api-docs/api-docs.controller.ts index bea1aadefb..3d8a073627 100644 --- a/packages/nocodb/src/controllers/api-docs/api-docs.controller.ts +++ b/packages/nocodb/src/controllers/api-docs/api-docs.controller.ts @@ -18,10 +18,7 @@ import { MetaApiLimiterGuard } from '~/guards/meta-api-limiter.guard'; export class ApiDocsController { constructor(private readonly apiDocsService: ApiDocsService) {} - @Get([ - '/api/v1/db/meta/projects/:baseId/swagger.json', - '/api/v2/meta/bases/:baseId/swagger.json', - ]) + @Get(['/api/v1/db/meta/projects/:baseId/swagger.json']) @UseGuards(MetaApiLimiterGuard, GlobalGuard) @Acl('swaggerJson') async swaggerJson(@Param('baseId') baseId: string, @Request() req) { @@ -33,21 +30,39 @@ export class ApiDocsController { return swagger; } - @Get([ - '/api/v2/meta/bases/:baseId/swagger', - '/api/v1/db/meta/projects/:baseId/swagger', - ]) + @Get(['/api/v2/meta/bases/:baseId/swagger.json']) + @UseGuards(MetaApiLimiterGuard, GlobalGuard) + @Acl('swaggerJson') + async swaggerJsonV2(@Param('baseId') baseId: string, @Request() req) { + const swagger = await this.apiDocsService.swaggerJsonV2({ + baseId: baseId, + siteUrl: req.ncSiteUrl, + }); + + return swagger; + } + + @Get(['/api/v1/db/meta/projects/:baseId/swagger']) @UseGuards(PublicApiLimiterGuard) swaggerHtml(@Param('baseId') baseId: string, @Response() res) { res.send(getSwaggerHtml({ ncSiteUrl: process.env.NC_PUBLIC_URL || '' })); } @UseGuards(PublicApiLimiterGuard) - @Get([ - '/api/v1/db/meta/projects/:baseId/redoc', - '/api/v2/meta/bases/:baseId/redoc', - ]) + @Get(['/api/v1/db/meta/projects/:baseId/redoc']) redocHtml(@Param('baseId') baseId: string, @Response() res) { res.send(getRedocHtml({ ncSiteUrl: process.env.NC_PUBLIC_URL || '' })); } + + @Get(['/api/v2/meta/bases/:baseId/swagger']) + @UseGuards(PublicApiLimiterGuard) + swaggerHtmlV2(@Param('baseId') baseId: string, @Response() res) { + res.send(getSwaggerHtml({ ncSiteUrl: process.env.NC_PUBLIC_URL || '' })); + } + + @UseGuards(PublicApiLimiterGuard) + @Get(['/api/v2/meta/bases/:baseId/redoc']) + redocHtmlV2(@Param('baseId') baseId: string, @Response() res) { + res.send(getRedocHtml({ ncSiteUrl: process.env.NC_PUBLIC_URL || '' })); + } } diff --git a/packages/nocodb/src/services/api-docs/api-docs.service.ts b/packages/nocodb/src/services/api-docs/api-docs.service.ts index a6b6c0fc9b..2e51b6466a 100644 --- a/packages/nocodb/src/services/api-docs/api-docs.service.ts +++ b/packages/nocodb/src/services/api-docs/api-docs.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; import getSwaggerJSON from './swagger/getSwaggerJSON'; +import getSwaggerJSONV2 from './swaggerV2/getSwaggerJSONV2'; import { NcError } from '~/helpers/catchError'; import { Base, Model } from '~/models'; @@ -32,6 +33,35 @@ export class ApiDocsService { }, ] as any; + return swagger; + } + async swaggerJsonV2(param: { baseId: string; siteUrl: string }) { + const base = await Base.get(param.baseId); + + if (!base) NcError.notFound(); + + const models = await Model.list({ + base_id: param.baseId, + source_id: null, + }); + + const swagger = await getSwaggerJSONV2(base, models); + + swagger.servers = [ + { + url: param.siteUrl, + }, + { + url: '{customUrl}', + variables: { + customUrl: { + default: param.siteUrl, + description: 'Provide custom nocodb app base url', + }, + }, + }, + ] as any; + return swagger; } } diff --git a/packages/nocodb/src/services/api-docs/swagger/templates/paths.ts b/packages/nocodb/src/services/api-docs/swagger/templates/paths.ts index c7cd06ec3e..a95bd752b6 100644 --- a/packages/nocodb/src/services/api-docs/swagger/templates/paths.ts +++ b/packages/nocodb/src/services/api-docs/swagger/templates/paths.ts @@ -667,6 +667,6 @@ function getPaginatedResponseType(type: string) { }, }; } -function isRelationExist(columns: SwaggerColumn[]) { +export function isRelationExist(columns: SwaggerColumn[]) { return columns.some((c) => isLinksOrLTAR(c.column) && !c.column.system); } diff --git a/packages/nocodb/src/services/api-docs/swaggerV2/getPaths.ts b/packages/nocodb/src/services/api-docs/swaggerV2/getPaths.ts new file mode 100644 index 0000000000..bb07894c24 --- /dev/null +++ b/packages/nocodb/src/services/api-docs/swaggerV2/getPaths.ts @@ -0,0 +1,45 @@ +import { getModelPaths } from './templates/paths'; +import type { Base, Model } from '~/models'; +import type { SwaggerColumn } from './getSwaggerColumnMetas'; +import type { SwaggerView } from './getSwaggerJSONV2'; +import Noco from '~/Noco'; + +export default async function getPaths( + { + model, + columns, + views, + }: { + model: Model; + columns: SwaggerColumn[]; + views: SwaggerView[]; + }, + _ncMeta = Noco.ncMeta, +) { + const swaggerPaths = await getModelPaths({ + tableName: model.title, + tableId: model.id, + views, + type: model.type, + columns, + }); + + // for (const { view, columns: viewColumns } of views) { + // const swaggerColumns = columns.filter( + // (c) => viewColumns.find((vc) => vc.fk_column_id === c.column.id)?.show, + // ); + // Object.assign( + // swaggerPaths, + // await getViewPaths({ + // tableName: model.title, + // viewName: view.title, + // type: model.type, + // orgs: 'v1', + // columns: swaggerColumns, + // baseName: base.id, + // }), + // ); + // } + + return swaggerPaths; +} diff --git a/packages/nocodb/src/services/api-docs/swaggerV2/getSchemas.ts b/packages/nocodb/src/services/api-docs/swaggerV2/getSchemas.ts new file mode 100644 index 0000000000..18c75f08cc --- /dev/null +++ b/packages/nocodb/src/services/api-docs/swaggerV2/getSchemas.ts @@ -0,0 +1,46 @@ +import { getModelSchemas, getViewSchemas } from './templates/schemas'; +import type { Base, Model } from '~/models'; + +import type { SwaggerColumn } from './getSwaggerColumnMetas'; +import type { SwaggerView } from './getSwaggerJSONV2'; +import Noco from '~/Noco'; + +export default async function getSchemas( + { + base, + model, + columns, + views, + }: { + base: Base; + model: Model; + columns: SwaggerColumn[]; + views: SwaggerView[]; + }, + _ncMeta = Noco.ncMeta, +) { + const swaggerSchemas = getModelSchemas({ + tableName: model.title, + orgs: 'v1', + baseName: base.title, + columns, + }); + + for (const { view, columns: viewColumns } of views) { + const swaggerColumns = columns.filter( + (c) => viewColumns.find((vc) => vc.fk_column_id === c.column.id)?.show, + ); + Object.assign( + swaggerSchemas, + getViewSchemas({ + tableName: model.title, + viewName: view.title, + orgs: 'v1', + columns: swaggerColumns, + baseName: base.title, + }), + ); + } + + return swaggerSchemas; +} diff --git a/packages/nocodb/src/services/api-docs/swaggerV2/getSwaggerColumnMetas.ts b/packages/nocodb/src/services/api-docs/swaggerV2/getSwaggerColumnMetas.ts new file mode 100644 index 0000000000..660c1d45be --- /dev/null +++ b/packages/nocodb/src/services/api-docs/swaggerV2/getSwaggerColumnMetas.ts @@ -0,0 +1,67 @@ +import { UITypes } from 'nocodb-sdk'; +import type { Base, Column, LinkToAnotherRecordColumn } from '~/models'; +import SwaggerTypes from '~/db/sql-mgr/code/routers/xc-ts/SwaggerTypes'; +import Noco from '~/Noco'; + +export default async ( + columns: Column[], + base: Base, + ncMeta = Noco.ncMeta, +): Promise => { + const dbType = await base.getBases().then((b) => b?.[0]?.type); + return Promise.all( + columns.map(async (c) => { + const field: SwaggerColumn = { + title: c.title, + type: 'object', + virtual: true, + column: c, + }; + + switch (c.uidt) { + case UITypes.LinkToAnotherRecord: + { + const colOpt = await c.getColOptions( + ncMeta, + ); + if (colOpt) { + const relTable = await colOpt.getRelatedTable(ncMeta); + field.type = undefined; + field.$ref = `#/components/schemas/${relTable.title}Request`; + } + } + break; + case UITypes.Formula: + case UITypes.Lookup: + field.type = 'object'; + break; + case UITypes.Rollup: + case UITypes.Links: + field.type = 'number'; + break; + case UITypes.Attachment: + field.type = 'array'; + field.items = { + $ref: `#/components/schemas/Attachment`, + }; + break; + default: + field.virtual = false; + SwaggerTypes.setSwaggerType(c, field, dbType); + break; + } + + return field; + }), + ); +}; + +export interface SwaggerColumn { + type: any; + title: string; + description?: string; + virtual?: boolean; + $ref?: any; + column: Column; + items?: any; +} diff --git a/packages/nocodb/src/services/api-docs/swaggerV2/getSwaggerJSONV2.ts b/packages/nocodb/src/services/api-docs/swaggerV2/getSwaggerJSONV2.ts new file mode 100644 index 0000000000..8e92c13d80 --- /dev/null +++ b/packages/nocodb/src/services/api-docs/swaggerV2/getSwaggerJSONV2.ts @@ -0,0 +1,67 @@ +import { ViewTypes } from 'nocodb-sdk'; +import swaggerBase from './swagger-base.json'; +import getPaths from './getPaths'; +import getSchemas from './getSchemas'; +import getSwaggerColumnMetas from './getSwaggerColumnMetas'; +import type { + Base, + FormViewColumn, + GalleryViewColumn, + GridViewColumn, + Model, + View, +} from '~/models'; +import Noco from '~/Noco'; + +export default async function getSwaggerJSONV2( + base: Base, + models: Model[], + ncMeta = Noco.ncMeta, +) { + // base swagger object + const swaggerObj = { + ...swaggerBase, + paths: {}, + components: { + ...swaggerBase.components, + schemas: { ...swaggerBase.components.schemas }, + }, + }; + + // iterate and populate swagger schema and path for models and views + for (const model of models) { + let paths = {}; + + const columns = await getSwaggerColumnMetas( + await model.getColumns(ncMeta), + base, + ncMeta, + ); + + const views: SwaggerView[] = []; + + for (const view of (await model.getViews(false, ncMeta)) || []) { + if (view.type !== ViewTypes.GRID) continue; + views.push({ + view, + columns: await view.getColumns(ncMeta), + }); + } + + // skip mm tables + if (!model.mm) + paths = await getPaths({ model, columns, views }, ncMeta); + + const schemas = await getSchemas({ base, model, columns, views }, ncMeta); + + Object.assign(swaggerObj.paths, paths); + Object.assign(swaggerObj.components.schemas, schemas); + } + + return swaggerObj; +} + +export interface SwaggerView { + view: View; + columns: Array; +} diff --git a/packages/nocodb/src/services/api-docs/swaggerV2/swagger-base.json b/packages/nocodb/src/services/api-docs/swaggerV2/swagger-base.json new file mode 100644 index 0000000000..3aae176f89 --- /dev/null +++ b/packages/nocodb/src/services/api-docs/swaggerV2/swagger-base.json @@ -0,0 +1,96 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "nocodb", + "version": "2.0" + }, + "servers": [ + { + "url": "http://localhost:8080" + } + ], + "paths": { + }, + "components": { + "schemas": { + "Paginated": { + "title": "Paginated", + "type": "object", + "properties": { + "pageSize": { + "type": "integer" + }, + "totalRows": { + "type": "integer" + }, + "isFirstPage": { + "type": "boolean" + }, + "isLastPage": { + "type": "boolean" + }, + "page": { + "type": "number" + } + } + }, + "Attachment": { + "title": "Attachment", + "type": "object", + "properties": { + "mimetype": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon": { + "type": "string" + } + } + }, + "Groupby": { + "title": "Groupby", + "type": "object", + "properties": { + "count": { + "type": "number", + "description": "count" + }, + "column_name": { + "type": "string", + "description": "the value of the given column" + } + } + } + }, + "securitySchemes": { + "xcAuth": { + "type": "apiKey", + "in": "header", + "name": "xc-auth", + "description": "JWT access token" + }, + "xcToken": { + "type": "apiKey", + "in": "header", + "name": "xc-token", + "description": "API token" + } + } + }, + "security": [ + { + "xcAuth": [] + }, + { + "xcToken": [] + } + ] +} diff --git a/packages/nocodb/src/services/api-docs/swaggerV2/templates/headers.ts b/packages/nocodb/src/services/api-docs/swaggerV2/templates/headers.ts new file mode 100644 index 0000000000..ddc1707d41 --- /dev/null +++ b/packages/nocodb/src/services/api-docs/swaggerV2/templates/headers.ts @@ -0,0 +1,10 @@ +export const csvExportResponseHeader = { + 'nc-export-offset': { + schema: { + type: 'integer', + }, + description: + 'Offset of next set of data which will be helpful if there is large amount of data. It will returns `-1` if all set of data exported.', + example: '1000', + }, +}; diff --git a/packages/nocodb/src/services/api-docs/swaggerV2/templates/params.ts b/packages/nocodb/src/services/api-docs/swaggerV2/templates/params.ts new file mode 100644 index 0000000000..46df2dc15d --- /dev/null +++ b/packages/nocodb/src/services/api-docs/swaggerV2/templates/params.ts @@ -0,0 +1,233 @@ +import { isLinksOrLTAR, RelationTypes, UITypes } from 'nocodb-sdk'; +import type { LinkToAnotherRecordColumn } from '~/models'; +import type { SwaggerColumn } from '../getSwaggerColumnMetas'; +import type { SwaggerView } from '~/services/api-docs/swaggerV2/getSwaggerJSONV2'; + +export const recordIdParam = { + schema: { + type: 'string', + }, + name: 'recordId', + in: 'path', + required: true, + example: 1, + description: + 'Primary key of the record you want to read. If the table have composite primary key then combine them by using `___` and pass it as primary key.', +}; +export const fieldsParam = { + schema: { + type: 'string', + }, + in: 'query', + name: 'fields', + description: + 'Array of field names or comma separated filed names to include in the response objects. In array syntax pass it like `fields[]=field1&fields[]=field2` or alternately `fields=field1,field2`.', +}; +export const sortParam = { + schema: { + type: 'string', + }, + in: 'query', + name: 'sort', + description: + 'Comma separated field names to sort rows, rows will sort in ascending order based on provided columns. To sort in descending order provide `-` prefix along with column name, like `-field`. Example : `sort=field1,-field2`', +}; +export const whereParam = { + schema: { + type: 'string', + }, + in: 'query', + name: 'where', + description: + 'This can be used for filtering rows, which accepts complicated where conditions. For more info visit [here](https://docs.nocodb.com/developer-resources/rest-apis#comparison-operators). Example : `where=(field1,eq,value)`', +}; +export const limitParam = { + schema: { + type: 'number', + minimum: 1, + }, + in: 'query', + name: 'limit', + description: + 'The `limit` parameter used for pagination, the response collection size depends on limit value with default value `25` and maximum value `1000`, which can be overridden by environment variables `DB_QUERY_LIMIT_DEFAULT` and `DB_QUERY_LIMIT_MAX` respectively.', + example: 25, +}; +export const offsetParam = { + schema: { + type: 'number', + minimum: 0, + }, + in: 'query', + name: 'offset', + description: + 'The `offset` parameter used for pagination, the value helps to select collection from a certain index.', + example: 0, +}; + +export const shuffleParam = { + schema: { + type: 'number', + minimum: 0, + maximum: 1, + }, + in: 'query', + name: 'shuffle', + description: + 'The `shuffle` parameter used for pagination, the response will be shuffled if it is set to 1.', + example: 0, +}; + +export const columnNameQueryParam = { + schema: { + type: 'string', + }, + in: 'query', + name: 'column_name', + description: + 'Column name of the column you want to group by, eg. `column_name=column1`', +}; + +export const linkFieldNameParam = (columns: SwaggerColumn[]) => { + const linkColumnIds = []; + const description = [ + '**Links Field Identifier** corresponding to the relation field `Links` established between tables.\n\nLink Columns:', + ]; + for (const { column } of columns) { + if (!isLinksOrLTAR(column) || column.system) continue; + linkColumnIds.push(column.id); + + description.push(`* ${column.id} - ${column.title}`); + } + + return { + schema: { + type: 'string', + enum: linkColumnIds, + }, + name: 'linkFieldId', + in: 'path', + required: true, + description: description.join('\n'), + }; +}; +export const viewIdParams = (views: SwaggerView[]) => { + const viewIds = []; + const description = ['Views:']; + + for (const { view } of views) { + viewIds.push(view.id); + description.push(`* ${view.id} - ${view.title}`); + } + + return { + schema: { + type: 'string', + enum: viewIds, + }, + description: description.join('\n'), + name: 'viewId', + in: 'query', + required: false, + }; +}; + +export const referencedRowIdParam = { + schema: { + type: 'string', + }, + name: 'refRowId', + in: 'path', + required: true, +}; + +export const exportTypeParam = { + schema: { + type: 'string', + enum: ['csv', 'excel'], + }, + name: 'type', + in: 'path', + required: true, +}; + +export const csvExportOffsetParam = { + schema: { + type: 'number', + minimum: 0, + }, + in: 'query', + name: 'offset', + description: + 'Helps to start export from a certain index. You can get the next set of data offset from previous response header named `nc-export-offset`.', + example: 0, +}; + +export const nestedWhereParam = (colName) => ({ + schema: { + type: 'string', + }, + in: 'query', + name: `nested[${colName}][where]`, + description: `This can be used for filtering rows in nested column \`${colName}\`, which accepts complicated where conditions. For more info visit [here](https://docs.nocodb.com/developer-resources/rest-apis#comparison-operators). Example : \`nested[${colName}][where]=(field1,eq,value)\``, +}); + +export const nestedFieldParam = (colName) => ({ + schema: { + type: 'string', + }, + in: 'query', + name: `nested[${colName}][fields]`, + description: `Array of field names or comma separated filed names to include in the in nested column \`${colName}\` result. In array syntax pass it like \`fields[]=field1&fields[]=field2.\`. Example : \`nested[${colName}][fields]=field1,field2\``, +}); +export const nestedSortParam = (colName) => ({ + schema: { + type: 'string', + }, + in: 'query', + name: `nested[${colName}][sort]`, + description: `Comma separated field names to sort rows in nested column \`${colName}\` rows, it will sort in ascending order based on provided columns. To sort in descending order provide \`-\` prefix along with column name, like \`-field\`. Example : \`nested[${colName}][sort]=field1,-field2\``, +}); +export const nestedLimitParam = (colName) => ({ + schema: { + type: 'number', + minimum: 1, + }, + in: 'query', + name: `nested[${colName}][limit]`, + description: `The \`limit\` parameter used for pagination of nested \`${colName}\` rows, the response collection size depends on limit value and default value is \`25\`.`, + example: '25', +}); +export const nestedOffsetParam = (colName) => ({ + schema: { + type: 'number', + minimum: 0, + }, + in: 'query', + name: `nested[${colName}][offset]`, + description: `The \`offset\` parameter used for pagination of nested \`${colName}\` rows, the value helps to select collection from a certain index.`, + example: 0, +}); + +export const getNestedParams = async ( + columns: SwaggerColumn[], +): Promise => { + return await columns.reduce(async (paramsArr, { column }) => { + if (column.uidt === UITypes.LinkToAnotherRecord) { + const colOpt = await column.getColOptions(); + if (colOpt.type !== RelationTypes.BELONGS_TO) { + return [ + ...(await paramsArr), + nestedWhereParam(column.title), + nestedOffsetParam(column.title), + nestedLimitParam(column.title), + nestedFieldParam(column.title), + nestedSortParam(column.title), + ]; + } else { + return [...(await paramsArr), nestedFieldParam(column.title)]; + } + } + + return paramsArr; + }, Promise.resolve([])); +}; diff --git a/packages/nocodb/src/services/api-docs/swaggerV2/templates/paths.ts b/packages/nocodb/src/services/api-docs/swaggerV2/templates/paths.ts new file mode 100644 index 0000000000..a654017011 --- /dev/null +++ b/packages/nocodb/src/services/api-docs/swaggerV2/templates/paths.ts @@ -0,0 +1,420 @@ +import { ModelTypes } from 'nocodb-sdk'; +import { + fieldsParam, + getNestedParams, + limitParam, + linkFieldNameParam, + offsetParam, + recordIdParam, + shuffleParam, + sortParam, + viewIdParams, + whereParam, +} from './params'; +import type { SwaggerColumn } from '../getSwaggerColumnMetas'; +import type { SwaggerView } from '~/services/api-docs/swaggerV2/getSwaggerJSONV2'; +import { isRelationExist } from '~/services/api-docs/swagger/templates/paths'; + +export const getModelPaths = async (ctx: { + tableName: string; + type: ModelTypes; + columns: SwaggerColumn[]; + tableId: string; + views: SwaggerView[]; +}): Promise<{ [path: string]: any }> => ({ + [`/api/v2/tables/${ctx.tableId}/records`]: { + get: { + summary: `${ctx.tableName} list`, + operationId: `${ctx.tableName.toLowerCase()}-db-table-row-list`, + description: `List of all rows from ${ctx.tableName} ${ctx.type} and response data fields can be filtered based on query params.`, + tags: [ctx.tableName], + parameters: [ + viewIdParams(ctx.views), + fieldsParam, + sortParam, + whereParam, + limitParam, + shuffleParam, + offsetParam, + ...(await getNestedParams(ctx.columns)), + ], + responses: { + '200': { + description: 'OK', + content: { + 'application/json': { + schema: getPaginatedResponseType(`${ctx.tableName}Response`), + }, + }, + }, + }, + }, + ...(ctx.type === ModelTypes.TABLE + ? { + post: { + summary: `${ctx.tableName} create`, + description: + 'Insert a new row in table by providing a key value pair object where key refers to the column alias. All the required fields should be included with payload excluding `autoincrement` and column with default value.', + operationId: `${ctx.tableName.toLowerCase()}-create`, + responses: { + '200': { + description: 'OK', + content: { + 'application/json': { + schema: { + $ref: `#/components/schemas/${ctx.tableName}Response`, + }, + }, + }, + }, + '400': { + $ref: '#/components/responses/BadRequest', + }, + }, + tags: [ctx.tableName], + requestBody: { + content: { + 'application/json': { + schema: { + oneOf: [ + { + $ref: `#/components/schemas/${ctx.tableName}Request`, + }, + { + type: 'array', + items: { + $ref: `#/components/schemas/${ctx.tableName}Request`, + }, + }, + ], + }, + }, + }, + }, + }, + patch: { + summary: `${ctx.tableName} update`, + operationId: `${ctx.tableName.toLowerCase()}-update`, + description: + 'Partial update row in table by providing a key value pair object where key refers to the column alias. You need to only include columns which you want to update.', + responses: { + '200': { + description: 'OK', + content: { + 'application/json': { + schema: {}, + }, + }, + }, + '400': { + $ref: '#/components/responses/BadRequest', + }, + }, + tags: [ctx.tableName], + requestBody: { + content: { + 'application/json': { + schema: { + oneOf: [ + { + $ref: `#/components/schemas/${ctx.tableName}Request`, + }, + { + type: 'array', + items: { + $ref: `#/components/schemas/${ctx.tableName}Request`, + }, + }, + ], + }, + }, + }, + }, + }, + delete: { + summary: `${ctx.tableName} delete`, + operationId: `${ctx.tableName.toLowerCase()}-delete`, + responses: { + '200': { + description: 'OK', + }, + }, + tags: [ctx.tableName], + description: + 'Delete a row by using the **primary key** column value.', + requestBody: { + content: { + 'application/json': { + schema: { + oneOf: [ + { + $ref: `#/components/schemas/${ctx.tableName}Request`, + }, + { + type: 'array', + items: { + $ref: `#/components/schemas/${ctx.tableName}Request`, + }, + }, + ], + }, + }, + }, + }, + }, + } + : {}), + }, + [`/api/v2/tables/${ctx.tableId}/records/{recordId}`]: { + get: { + parameters: [recordIdParam, fieldsParam], + summary: `${ctx.tableName} read`, + description: 'Read a row data by using the **primary key** column value.', + operationId: `${ctx.tableName.toLowerCase()}-read`, + tags: [ctx.tableName], + responses: { + '201': { + description: 'Created', + content: { + 'application/json': { + schema: { + $ref: `#/components/schemas/${ctx.tableName}Response`, + }, + }, + }, + }, + }, + }, + }, + [`/api/v2/tables/${ctx.tableId}/records/count`]: { + parameters: [viewIdParams(ctx.views)], + get: { + summary: `${ctx.tableName} count`, + operationId: `${ctx.tableName.toLowerCase()}-count`, + description: 'Get rows count of a table by applying optional filters.', + tags: [ctx.tableName], + parameters: [whereParam], + responses: { + '200': { + description: 'OK', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + count: { + type: 'number', + }, + }, + required: ['list', 'pageInfo'], + }, + examples: { + 'Example 1': { + value: { + count: 3, + }, + }, + }, + }, + }, + }, + '400': { + $ref: '#/components/responses/BadRequest', + }, + }, + }, + }, + + ...(isRelationExist(ctx.columns) + ? { + [`/api/v2/tables/${ctx.tableId}/links/{linkFieldId}/records/{recordId}`]: + { + parameters: [linkFieldNameParam(ctx.columns), recordIdParam], + get: { + summary: 'Link Records list', + operationId: `${ctx.tableName.toLowerCase()}-nested-list`, + description: + 'This API endpoint allows you to retrieve list of linked records for a specific `Link field` and `Record ID`. The response is an array of objects containing Primary Key and its corresponding display value.', + tags: [ctx.tableName], + parameters: [ + fieldsParam, + sortParam, + whereParam, + limitParam, + offsetParam, + { + $ref: '#/components/parameters/xc-auth', + }, + ], + responses: { + '200': { + description: 'OK', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + list: { + type: 'array', + description: 'List of data objects', + items: { + type: 'object', + }, + }, + pageInfo: { + $ref: '#/components/schemas/Paginated', + description: 'Paginated Info', + }, + }, + required: ['list', 'pageInfo'], + }, + }, + }, + }, + '400': { + $ref: '#/components/responses/BadRequest', + }, + }, + }, + post: { + summary: 'Link Records', + operationId: `${ctx.tableName.toLowerCase()}-nested-link`, + responses: { + '200': { + description: 'OK', + content: { + 'application/json': { + schema: {}, + examples: { + 'Example 1': { + value: true, + }, + }, + }, + }, + }, + '400': { + $ref: '#/components/responses/BadRequest', + }, + }, + tags: [ctx.tableName], + requestBody: { + content: { + 'application/json': { + schema: { + oneOf: [ + { + type: 'object', + }, + { + type: 'array', + items: { + type: 'object', + }, + }, + ], + }, + examples: { + 'Example 1': { + value: [ + { + Id: 4, + }, + { + Id: 5, + }, + ], + }, + }, + }, + }, + }, + description: + 'This API endpoint allows you to link records to a specific `Link field` and `Record ID`. The request payload is an array of record-ids from the adjacent table for linking purposes. Note that any existing links, if present, will be unaffected during this operation.', + parameters: [ + recordIdParam, + { + $ref: '#/components/parameters/xc-auth', + }, + ], + }, + delete: { + summary: 'Unlink Records', + operationId: `${ctx.tableName.toLowerCase()}-nested-unlink`, + responses: { + '200': { + description: 'OK', + content: { + 'application/json': { + schema: {}, + examples: { + 'Example 1': { + value: true, + }, + }, + }, + }, + }, + '400': { + $ref: '#/components/responses/BadRequest', + }, + }, + tags: [ctx.tableName], + requestBody: { + content: { + 'application/json': { + schema: { + oneOf: [ + { + type: 'array', + items: { + type: 'object', + }, + }, + ], + }, + examples: { + 'Example 1': { + value: [ + { + Id: 1, + }, + { + Id: 2, + }, + ], + }, + }, + }, + }, + }, + description: + 'This API endpoint allows you to unlink records from a specific `Link field` and `Record ID`. The request payload is an array of record-ids from the adjacent table for unlinking purposes. Note that, \n- duplicated record-ids will be ignored.\n- non-existent record-ids will be ignored.', + parameters: [ + recordIdParam, + { + $ref: '#/components/parameters/xc-auth', + }, + ], + }, + }, + } + : {}), +}); + +function getPaginatedResponseType(type: string) { + return { + type: 'object', + properties: { + list: { + type: 'array', + items: { + $ref: `#/components/schemas/${type}`, + }, + }, + PageInfo: { + $ref: `#/components/schemas/Paginated`, + }, + }, + }; +} diff --git a/packages/nocodb/src/services/api-docs/swaggerV2/templates/schemas.ts b/packages/nocodb/src/services/api-docs/swaggerV2/templates/schemas.ts new file mode 100644 index 0000000000..edf2681133 --- /dev/null +++ b/packages/nocodb/src/services/api-docs/swaggerV2/templates/schemas.ts @@ -0,0 +1,85 @@ +import type { SwaggerColumn } from '../getSwaggerColumnMetas'; + +export const getModelSchemas = (ctx: { + tableName: string; + orgs: string; + baseName: string; + columns: Array; +}) => ({ + [`${ctx.tableName}Response`]: { + title: `${ctx.tableName} Response`, + type: 'object', + description: '', + 'x-internal': false, + properties: { + ...(ctx.columns?.reduce( + (colsObj, { title, virtual, column, ...fieldProps }) => ({ + ...colsObj, + [title]: fieldProps, + }), + {}, + ) || {}), + }, + }, + [`${ctx.tableName}Request`]: { + title: `${ctx.tableName} Request`, + type: 'object', + description: '', + 'x-internal': false, + properties: { + ...(ctx.columns?.reduce( + (colsObj, { title, virtual, column, ...fieldProps }) => ({ + ...colsObj, + ...(virtual + ? {} + : { + [title]: fieldProps, + }), + }), + {}, + ) || {}), + }, + }, +}); +export const getViewSchemas = (ctx: { + tableName: string; + viewName: string; + orgs: string; + baseName: string; + columns: Array; +}) => ({ + [`${ctx.tableName}${ctx.viewName}GridResponse`]: { + title: `${ctx.tableName} : ${ctx.viewName} Response`, + type: 'object', + description: '', + 'x-internal': false, + properties: { + ...(ctx.columns?.reduce( + (colsObj, { title, virtual, column, ...fieldProps }) => ({ + ...colsObj, + [title]: fieldProps, + }), + {}, + ) || {}), + }, + }, + [`${ctx.tableName}${ctx.viewName}GridRequest`]: { + title: `${ctx.tableName} : ${ctx.viewName} Request`, + type: 'object', + description: '', + 'x-internal': false, + properties: { + ...(ctx.columns?.reduce( + (colsObj, { title, virtual, column, ...fieldProps }) => ({ + ...colsObj, + ...(virtual + ? {} + : { + [title]: fieldProps, + }), + }), + {}, + ) || {}), + }, + }, +});