mirror of https://github.com/nocodb/nocodb
Pranav C
2 years ago
21 changed files with 707 additions and 460 deletions
@ -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; |
||||
} |
@ -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; |
||||
} |
@ -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; |
||||
} |
@ -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>; |
||||
} |
@ -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', |
||||
}, |
||||
}; |
@ -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([])); |
||||
}; |
@ -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 |
@ -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>`;
|
@ -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> |
||||
`;
|
@ -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; |
||||
} |
@ -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; |
||||
} |
@ -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; |
||||
} |
@ -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>; |
||||
} |
@ -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,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', |
||||
}, |
||||
}; |
@ -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([])); |
||||
}; |
Loading…
Reference in new issue