Browse Source

refactor: swagger api

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/5239/head
Pranav C 2 years ago
parent
commit
675a95b132
  1. 4
      packages/nocodb/src/lib/controllers/auditController.ts
  2. 48
      packages/nocodb/src/lib/controllers/swagger/helpers/getPaths.ts
  3. 46
      packages/nocodb/src/lib/controllers/swagger/helpers/getSchemas.ts
  4. 68
      packages/nocodb/src/lib/controllers/swagger/helpers/getSwaggerColumnMetas.ts
  5. 68
      packages/nocodb/src/lib/controllers/swagger/helpers/getSwaggerJSON.ts
  6. 10
      packages/nocodb/src/lib/controllers/swagger/helpers/templates/headers.ts
  7. 217
      packages/nocodb/src/lib/controllers/swagger/helpers/templates/params.ts
  8. 36
      packages/nocodb/src/lib/controllers/swaggerController/index.ts
  9. 93
      packages/nocodb/src/lib/controllers/swaggerController/redocHtml.ts
  10. 87
      packages/nocodb/src/lib/controllers/swaggerController/swaggerHtml.ts
  11. 1
      packages/nocodb/src/lib/services/index.ts
  12. 48
      packages/nocodb/src/lib/services/swaggerService/getPaths.ts
  13. 46
      packages/nocodb/src/lib/services/swaggerService/getSchemas.ts
  14. 68
      packages/nocodb/src/lib/services/swaggerService/getSwaggerColumnMetas.ts
  15. 66
      packages/nocodb/src/lib/services/swaggerService/getSwaggerJSON.ts
  16. 34
      packages/nocodb/src/lib/services/swaggerService/index.ts
  17. 0
      packages/nocodb/src/lib/services/swaggerService/swagger-base.json
  18. 10
      packages/nocodb/src/lib/services/swaggerService/templates/headers.ts
  19. 217
      packages/nocodb/src/lib/services/swaggerService/templates/params.ts
  20. 0
      packages/nocodb/src/lib/services/swaggerService/templates/paths.ts
  21. 0
      packages/nocodb/src/lib/services/swaggerService/templates/schemas.ts

4
packages/nocodb/src/lib/controllers/auditController.ts

@ -1,11 +1,9 @@
import { Request, Response, Router } from 'express';
import Audit from '../models/Audit';
import { AuditOperationSubTypes, AuditOperationTypes } from 'nocodb-sdk';
import Model from '../models/Model';
import { AuditOperationTypes } from 'nocodb-sdk';
import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import DOMPurify from 'isomorphic-dompurify';
import { getAjvValidatorMw } from '../meta/api/helpers';
import { auditService } from '../services';

48
packages/nocodb/src/lib/controllers/swagger/helpers/getPaths.ts

@ -1,48 +0,0 @@
import Noco from '../../../Noco';
import Model from '../../../models/Model';
import Project from '../../../models/Project';
import { getModelPaths, getViewPaths } from './templates/paths';
import { SwaggerColumn } from './getSwaggerColumnMetas';
import { SwaggerView } from './getSwaggerJSON';
export default async function getPaths(
{
project,
model,
columns,
views,
}: {
project: Project;
model: Model;
columns: SwaggerColumn[];
views: SwaggerView[];
},
_ncMeta = Noco.ncMeta
) {
const swaggerPaths = await getModelPaths({
tableName: model.title,
type: model.type,
orgs: 'v1',
columns,
projectName: project.title,
});
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,
projectName: project.title,
})
);
}
return swaggerPaths;
}

46
packages/nocodb/src/lib/controllers/swagger/helpers/getSchemas.ts

@ -1,46 +0,0 @@
import Noco from '../../../Noco';
import Model from '../../../models/Model';
import Project from '../../../models/Project';
import { getModelSchemas, getViewSchemas } from './templates/schemas';
import { SwaggerColumn } from './getSwaggerColumnMetas';
import { SwaggerView } from './getSwaggerJSON';
export default async function getSchemas(
{
project,
model,
columns,
views,
}: {
project: Project;
model: Model;
columns: SwaggerColumn[];
views: SwaggerView[];
},
_ncMeta = Noco.ncMeta
) {
const swaggerSchemas = getModelSchemas({
tableName: model.title,
orgs: 'v1',
projectName: project.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,
projectName: project.title,
})
);
}
return swaggerSchemas;
}

68
packages/nocodb/src/lib/controllers/swagger/helpers/getSwaggerColumnMetas.ts

@ -1,68 +0,0 @@
import { UITypes } from 'nocodb-sdk';
import LinkToAnotherRecordColumn from '../../../models/LinkToAnotherRecordColumn';
import SwaggerTypes from '../../../db/sql-mgr/code/routers/xc-ts/SwaggerTypes';
import Column from '../../../models/Column';
import Noco from '../../../Noco';
import Project from '../../../models/Project';
export default async (
columns: Column[],
project: Project,
ncMeta = Noco.ncMeta
): Promise<SwaggerColumn[]> => {
const dbType = await project.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:
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;
}

68
packages/nocodb/src/lib/controllers/swagger/helpers/getSwaggerJSON.ts

@ -1,68 +0,0 @@
import FormViewColumn from '../../../models/FormViewColumn';
import GalleryViewColumn from '../../../models/GalleryViewColumn';
import Noco from '../../../Noco';
import Model from '../../../models/Model';
import swaggerBase from './swagger-base.json';
import getPaths from './getPaths';
import getSchemas from './getSchemas';
import Project from '../../../models/Project';
import getSwaggerColumnMetas from './getSwaggerColumnMetas';
import { ViewTypes } from 'nocodb-sdk';
import GridViewColumn from '../../../models/GridViewColumn';
import View from '../../../models/View';
export default async function getSwaggerJSON(
project: Project,
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),
project,
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({ project, model, columns, views }, ncMeta);
const schemas = await getSchemas(
{ project, 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>;
}

10
packages/nocodb/src/lib/controllers/swagger/helpers/templates/headers.ts

@ -1,10 +0,0 @@
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',
},
};

217
packages/nocodb/src/lib/controllers/swagger/helpers/templates/params.ts

@ -1,217 +0,0 @@
import { SwaggerColumn } from '../getSwaggerColumnMetas';
import { RelationTypes, UITypes } from 'nocodb-sdk';
import LinkToAnotherRecordColumn from '../../../../models/LinkToAnotherRecordColumn';
export const rowIdParam = {
schema: {
type: 'string',
},
name: 'rowId',
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 relationTypeParam = {
schema: {
type: 'string',
enum: ['mm', 'hm'],
},
name: 'relationType',
in: 'path',
required: true,
};
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 columnNameParam = (columns: SwaggerColumn[]) => {
const columnNames = [];
for (const { column } of columns) {
if (column.uidt !== UITypes.LinkToAnotherRecord || column.system) continue;
columnNames.push(column.title);
}
return {
schema: {
type: 'string',
enum: columnNames,
},
name: 'columnName',
in: 'path',
required: true,
};
};
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) {
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([]));
};

36
packages/nocodb/src/lib/controllers/swaggerController/index.ts

@ -0,0 +1,36 @@
import { Router } from 'express'
import ncMetaAclMw from '../../meta/helpers/ncMetaAclMw'
import getSwaggerHtml from './swaggerHtml'
import getRedocHtml from './redocHtml'
import { swaggerService } from '../../services'
async function swaggerJson(req, res) {
const swagger = await swaggerService.swaggerJson({
projectId: req.params.projectId,
siteUrl: req.ncSiteUrl,
})
res.json(swagger)
}
function swaggerHtml(_, res) {
res.send(getSwaggerHtml({ ncSiteUrl: process.env.NC_PUBLIC_URL || '' }))
}
function redocHtml(_, res) {
res.send(getRedocHtml({ ncSiteUrl: process.env.NC_PUBLIC_URL || '' }))
}
const router = Router({ mergeParams: true })
// todo: auth
router.get(
'/api/v1/db/meta/projects/:projectId/swagger.json',
ncMetaAclMw(swaggerJson, 'swaggerJson'),
)
router.get('/api/v1/db/meta/projects/:projectId/swagger', swaggerHtml)
router.get('/api/v1/db/meta/projects/:projectId/redoc', redocHtml)
export default router

93
packages/nocodb/src/lib/controllers/swaggerController/redocHtml.ts

@ -0,0 +1,93 @@
export default ({
ncSiteUrl,
}: {
ncSiteUrl: string;
}): string => `<!DOCTYPE html>
<html>
<head>
<title>NocoDB API Documentation</title>
<!-- needed for adaptive design -->
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="${ncSiteUrl}/css/fonts.montserrat.css" rel="stylesheet">
<!--
Redoc doesn't change outer page styles
-->
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="redoc"></div>
<script src="${ncSiteUrl}/js/redoc.standalone.min.js"></script>
<script>
let initialLocalStorage = {}
try {
initialLocalStorage = JSON.parse(localStorage.getItem('nocodb-gui-v2') || '{}');
} catch (e) {
console.error('Failed to parse local storage', e);
}
const xhttp = new XMLHttpRequest();
xhttp.open("GET", "./swagger.json");
xhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhttp.setRequestHeader("xc-auth", initialLocalStorage && initialLocalStorage.token);
xhttp.onload = function () {
const swaggerJson = this.responseText;
const swagger = JSON.parse(swaggerJson);
Redoc.init(swagger, {
scrollYOffset: 50
}, document.getElementById('redoc'))
};
xhttp.send();
</script>
<script>
console.log('%c🚀 We are Hiring!!! 🚀%c\\n%cJoin the forces http://careers.nocodb.com', 'color:#1348ba;font-size:3rem;padding:20px;', 'display:none', 'font-size:1.5rem;padding:20px')
const linkEl = document.createElement('a')
linkEl.setAttribute('href', "http://careers.nocodb.com")
linkEl.setAttribute('target', '_blank')
linkEl.setAttribute('class', 'we-are-hiring')
linkEl.innerHTML = '🚀 We are Hiring!!! 🚀'
const styleEl = document.createElement('style');
styleEl.innerHTML = \`
.we-are-hiring {
position: fixed;
bottom: 50px;
right: -250px;
opacity: 0;
background: orange;
border-radius: 4px;
padding: 19px;
z-index: 200;
text-decoration: none;
text-transform: uppercase;
color: black;
transition: 1s opacity, 1s right;
display: block;
font-weight: bold;
}
.we-are-hiring.active {
opacity: 1;
right:25px;
}
@media only screen and (max-width: 600px) {
.we-are-hiring {
display: none;
}
}
\`
document.body.appendChild(linkEl, document.body.firstChild)
document.body.appendChild(styleEl, document.body.firstChild)
setTimeout(() => linkEl.classList.add('active'), 2000)
</script>
</body>
</html>`;

87
packages/nocodb/src/lib/controllers/swaggerController/swaggerHtml.ts

@ -0,0 +1,87 @@
export default ({
ncSiteUrl,
}: {
ncSiteUrl: string;
}): string => `<!DOCTYPE html>
<html>
<head>
<title>NocoDB : API Docs</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<link rel="shortcut icon" href="${ncSiteUrl}/favicon.ico" />
<link rel="stylesheet" href="${ncSiteUrl}/css/swagger-ui-bundle.4.5.2.min.css"/>
<script src="${ncSiteUrl}/js/swagger-ui-bundle.4.5.2.min.js"></script>
</head>
<body>
<div id="app"></div>
<script>
let initialLocalStorage = {}
try {
initialLocalStorage = JSON.parse(localStorage.getItem('nocodb-gui-v2') || '{}');
} catch (e) {
console.error('Failed to parse local storage', e);
}
var xmlhttp = new XMLHttpRequest(); // new HttpRequest instance
xmlhttp.open("GET", "./swagger.json");
xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xmlhttp.setRequestHeader("xc-auth", initialLocalStorage && initialLocalStorage.token);
xmlhttp.onload = function () {
const ui = SwaggerUIBundle({
// url: ,
spec: JSON.parse(xmlhttp.responseText),
dom_id: '#app',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
})
}
xmlhttp.send();
console.log('%c🚀 We are Hiring!!! 🚀%c\\n%cJoin the forces http://careers.nocodb.com', 'color:#1348ba;font-size:3rem;padding:20px;', 'display:none', 'font-size:1.5rem;padding:20px');
const linkEl = document.createElement('a')
linkEl.setAttribute('href', "http://careers.nocodb.com")
linkEl.setAttribute('target', '_blank')
linkEl.setAttribute('class', 'we-are-hiring')
linkEl.innerHTML = '🚀 We are Hiring!!! 🚀'
const styleEl = document.createElement('style');
styleEl.innerHTML = \`
.we-are-hiring {
position: fixed;
bottom: 50px;
right: -250px;
opacity: 0;
background: orange;
border-radius: 4px;
padding: 19px;
z-index: 200;
text-decoration: none;
text-transform: uppercase;
color: black;
transition: 1s opacity, 1s right;
display: block;
font-weight: bold;
}
.we-are-hiring.active {
opacity: 1;
right:25px;
}
@media only screen and (max-width: 600px) {
.we-are-hiring {
display: none;
}
}
\`
document.body.appendChild(linkEl, document.body.firstChild)
document.body.appendChild(styleEl, document.body.firstChild)
setTimeout(() => linkEl.classList.add('active'), 2000)
</script>
</body>
</html>
`;

1
packages/nocodb/src/lib/services/index.ts

@ -30,3 +30,4 @@ export * as dataService from './dataService';
export * as bulkDataService from './dataService/bulkData';
export * as cacheService from './cacheService';
export * as auditService from './auditService';
export * as swaggerService from './swaggerService';

48
packages/nocodb/src/lib/services/swaggerService/getPaths.ts

@ -0,0 +1,48 @@
import Noco from '../../Noco';
import Model from '../../models/Model';
import Project from '../../models/Project';
import { getModelPaths, getViewPaths } from './templates/paths';
import { SwaggerColumn } from './getSwaggerColumnMetas';
import { SwaggerView } from './getSwaggerJSON';
export default async function getPaths(
{
project,
model,
columns,
views,
}: {
project: Project;
model: Model;
columns: SwaggerColumn[];
views: SwaggerView[];
},
_ncMeta = Noco.ncMeta
) {
const swaggerPaths = await getModelPaths({
tableName: model.title,
type: model.type,
orgs: 'v1',
columns,
projectName: project.title,
});
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,
projectName: project.title,
})
);
}
return swaggerPaths;
}

46
packages/nocodb/src/lib/services/swaggerService/getSchemas.ts

@ -0,0 +1,46 @@
import Noco from '../../Noco';
import Model from '../../models/Model';
import Project from '../../models/Project';
import { getModelSchemas, getViewSchemas } from './templates/schemas';
import { SwaggerColumn } from './getSwaggerColumnMetas';
import { SwaggerView } from './getSwaggerJSON';
export default async function getSchemas(
{
project,
model,
columns,
views,
}: {
project: Project;
model: Model;
columns: SwaggerColumn[];
views: SwaggerView[];
},
_ncMeta = Noco.ncMeta
) {
const swaggerSchemas = getModelSchemas({
tableName: model.title,
orgs: 'v1',
projectName: project.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,
projectName: project.title,
})
);
}
return swaggerSchemas;
}

68
packages/nocodb/src/lib/services/swaggerService/getSwaggerColumnMetas.ts

@ -0,0 +1,68 @@
import { UITypes } from 'nocodb-sdk';
import LinkToAnotherRecordColumn from '../../models/LinkToAnotherRecordColumn';
import SwaggerTypes from '../../db/sql-mgr/code/routers/xc-ts/SwaggerTypes';
import Column from '../../models/Column';
import Noco from '../../Noco';
import Project from '../../models/Project';
export default async (
columns: Column[],
project: Project,
ncMeta = Noco.ncMeta
): Promise<SwaggerColumn[]> => {
const dbType = await project.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:
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/lib/services/swaggerService/getSwaggerJSON.ts

@ -0,0 +1,66 @@
import { Model, Project, View } from '../../models'
import FormViewColumn from '../../models/FormViewColumn';
import GalleryViewColumn from '../../models/GalleryViewColumn';
import Noco from '../../Noco';
import swaggerBase from './swagger-base.json';
import getPaths from './getPaths';
import getSchemas from './getSchemas';
import getSwaggerColumnMetas from './getSwaggerColumnMetas';
import { ViewTypes } from 'nocodb-sdk';
import GridViewColumn from '../../models/GridViewColumn';
export default async function getSwaggerJSON(
project: Project,
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),
project,
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({ project, model, columns, views }, ncMeta);
const schemas = await getSchemas(
{ project, 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>;
}

34
packages/nocodb/src/lib/services/swaggerService/index.ts

@ -0,0 +1,34 @@
import { NcError } from '../../meta/helpers/catchError'
import Model from '../../models/Model'
import Project from '../../models/Project'
import getSwaggerJSON from './getSwaggerJSON'
export async function swaggerJson(param:{projectId:string; siteUrl:string}){
const project = await Project.get(param.projectId);
if (!project) NcError.notFound();
const models = await Model.list({
project_id: param.projectId,
base_id: null,
});
const swagger = await getSwaggerJSON(project, models);
swagger.servers = [
{
url: param.siteUrl,
},
{
url: '{customUrl}',
variables: {
customUrl: {
default: param.siteUrl,
description: 'Provide custom nocodb app base url',
},
},
},
] as any;
return swagger;
}

0
packages/nocodb/src/lib/controllers/swagger/helpers/swagger-base.json → packages/nocodb/src/lib/services/swaggerService/swagger-base.json

10
packages/nocodb/src/lib/services/swaggerService/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',
},
};

217
packages/nocodb/src/lib/services/swaggerService/templates/params.ts

@ -0,0 +1,217 @@
import { SwaggerColumn } from '../getSwaggerColumnMetas';
import { RelationTypes, UITypes } from 'nocodb-sdk';
import LinkToAnotherRecordColumn from '../../../models/LinkToAnotherRecordColumn';
export const rowIdParam = {
schema: {
type: 'string',
},
name: 'rowId',
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 relationTypeParam = {
schema: {
type: 'string',
enum: ['mm', 'hm'],
},
name: 'relationType',
in: 'path',
required: true,
};
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 columnNameParam = (columns: SwaggerColumn[]) => {
const columnNames = [];
for (const { column } of columns) {
if (column.uidt !== UITypes.LinkToAnotherRecord || column.system) continue;
columnNames.push(column.title);
}
return {
schema: {
type: 'string',
enum: columnNames,
},
name: 'columnName',
in: 'path',
required: true,
};
};
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) {
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([]));
};

0
packages/nocodb/src/lib/controllers/swagger/helpers/templates/paths.ts → packages/nocodb/src/lib/services/swaggerService/templates/paths.ts

0
packages/nocodb/src/lib/controllers/swagger/helpers/templates/schemas.ts → packages/nocodb/src/lib/services/swaggerService/templates/schemas.ts

Loading…
Cancel
Save