Browse Source

Merge pull request #6982 from nocodb/nc-fix/update-swagger-json-to-v2

Show v2 apis in swagger
pull/6994/head
Raju Udava 1 year ago committed by GitHub
parent
commit
e03715b995
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
  2. 39
      packages/nocodb/src/controllers/api-docs/api-docs.controller.ts
  3. 30
      packages/nocodb/src/services/api-docs/api-docs.service.ts
  4. 2
      packages/nocodb/src/services/api-docs/swagger/templates/paths.ts
  5. 28
      packages/nocodb/src/services/api-docs/swaggerV2/getPaths.ts
  6. 29
      packages/nocodb/src/services/api-docs/swaggerV2/getSchemas.ts
  7. 67
      packages/nocodb/src/services/api-docs/swaggerV2/getSwaggerColumnMetas.ts
  8. 66
      packages/nocodb/src/services/api-docs/swaggerV2/getSwaggerJSONV2.ts
  9. 128
      packages/nocodb/src/services/api-docs/swaggerV2/swagger-base.json
  10. 10
      packages/nocodb/src/services/api-docs/swaggerV2/templates/headers.ts
  11. 237
      packages/nocodb/src/services/api-docs/swaggerV2/templates/params.ts
  12. 407
      packages/nocodb/src/services/api-docs/swaggerV2/templates/paths.ts
  13. 109
      packages/nocodb/src/services/api-docs/swaggerV2/templates/schemas.ts

2
packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue

@ -514,7 +514,7 @@ const projectDelete = () => {
@click.stop="
() => {
$e('c:base:api-docs')
openLink(`/api/v1/db/meta/projects/${base.id}/swagger`, appInfo.ncSiteUrl)
openLink(`/api/v2/meta/bases/${base.id}/swagger`, appInfo.ncSiteUrl)
}
"
>

39
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 || '' }));
}
}

30
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;
}
}

2
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);
}

28
packages/nocodb/src/services/api-docs/swaggerV2/getPaths.ts

@ -0,0 +1,28 @@
import { getModelPaths } from './templates/paths';
import type { 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,
});
return swaggerPaths;
}

29
packages/nocodb/src/services/api-docs/swaggerV2/getSchemas.ts

@ -0,0 +1,29 @@
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
}: {
base: Base;
model: Model;
columns: SwaggerColumn[];
views: SwaggerView[];
},
_ncMeta = Noco.ncMeta,
) {
const swaggerSchemas = getModelSchemas({
tableName: model.title,
orgs: 'v1',
baseName: base.title,
columns,
});
return swaggerSchemas;
}

67
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<SwaggerColumn[]> => {
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<LinkToAnotherRecordColumn>(
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;
}

66
packages/nocodb/src/services/api-docs/swaggerV2/getSwaggerJSONV2.ts

@ -0,0 +1,66 @@
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<GridViewColumn | GalleryViewColumn | FormViewColumn>;
}

128
packages/nocodb/src/services/api-docs/swaggerV2/swagger-base.json

@ -0,0 +1,128 @@
{
"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"
}
},
"responses": {
"BadRequest": {
"description": "BadReqeust",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"msg": {
"type": "string",
"x-stoplight": {
"id": "p9mk4oi0hbihm"
},
"example": "BadRequest [Error]: <ERROR MESSAGE>"
}
},
"required": [
"msg"
]
},
"examples": {
"Example 1": {
"value": {
"msg": "BadRequest [Error]: <ERROR MESSAGE>"
}
}
}
}
},
"headers": {}
}
}
},
"security": [
{
"xcAuth": []
},
{
"xcToken": []
}
]
}

10
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',
},
};

237
packages/nocodb/src/services/api-docs/swaggerV2/templates/params.ts

@ -0,0 +1,237 @@
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 = [
'Allows you to fetch records that are currently visible within a specific view.\n\nViews:',
];
for (const { view } of views) {
viewIds.push(view.id);
description.push(
`* ${view.id} - ${view.is_default ? 'Default view' : 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<any[]> => {
return await columns.reduce(async (paramsArr, { column }) => {
if (column.uidt === UITypes.LinkToAnotherRecord && !column.system) {
const colOpt = await column.getColOptions<LinkToAnotherRecordColumn>();
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([]));
};

407
packages/nocodb/src/services/api-docs/swaggerV2/templates/paths.ts

@ -0,0 +1,407 @@
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}IdRequest`,
},
{
type: 'array',
items: {
$ref: `#/components/schemas/${ctx.tableName}IdRequest`,
},
},
],
},
},
},
},
},
}
: {}),
},
[`/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,
],
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],
},
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],
},
},
}
: {}),
});
function getPaginatedResponseType(type: string) {
return {
type: 'object',
properties: {
list: {
type: 'array',
items: {
$ref: `#/components/schemas/${type}`,
},
},
PageInfo: {
$ref: `#/components/schemas/Paginated`,
},
},
};
}

109
packages/nocodb/src/services/api-docs/swaggerV2/templates/schemas.ts

@ -0,0 +1,109 @@
import { isSystemColumn } from 'nocodb-sdk';
import type { SwaggerColumn } from '../getSwaggerColumnMetas';
export const getModelSchemas = (ctx: {
tableName: string;
orgs: string;
baseName: string;
columns: Array<SwaggerColumn>;
}) => ({
[`${ctx.tableName}Response`]: {
title: `${ctx.tableName} Response`,
type: 'object',
description: '',
'x-internal': false,
properties: {
...(ctx.columns?.reduce(
(colsObj, { title, virtual, column, ...fieldProps }) => ({
...colsObj,
...(column.system
? {}
: {
[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 || isSystemColumn(column) || column.ai || column.meta?.ag
? {}
: {
[title]: fieldProps,
}),
}),
{},
) || {}),
},
},
[`${ctx.tableName}IdRequest`]: {
title: `${ctx.tableName} Id Request`,
type: 'object',
description: '',
'x-internal': false,
properties: {
...(ctx.columns?.reduce(
(colsObj, { title, virtual, column, ...fieldProps }) => ({
...colsObj,
...(column.pk
? {
[title]: fieldProps,
}
: {}),
}),
{},
) || {}),
},
},
});
export const getViewSchemas = (ctx: {
tableName: string;
viewName: string;
orgs: string;
baseName: string;
columns: Array<SwaggerColumn>;
}) => ({
[`${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,
}),
}),
{},
) || {}),
},
},
});
Loading…
Cancel
Save