mirror of https://github.com/nocodb/nocodb
աɨռɢӄաօռɢ
12 months ago
97 changed files with 8882 additions and 5424 deletions
@ -0,0 +1,28 @@
|
||||
<script setup lang="ts"> |
||||
import type { ColumnType } from 'nocodb-sdk' |
||||
import { isVirtualCol } from 'nocodb-sdk' |
||||
|
||||
defineProps<{ |
||||
column: ColumnType |
||||
modelValue: any |
||||
}>() |
||||
|
||||
provide(ReadonlyInj, true) |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="pointer-events-none"> |
||||
<LazySmartsheetRow :row="{ row: { [column.title]: modelValue }, rowMeta: {} }"> |
||||
<LazySmartsheetVirtualCell v-if="isVirtualCol(column)" :model-value="modelValue" class="!text-gray-600" :column="column" /> |
||||
|
||||
<LazySmartsheetCell |
||||
v-else |
||||
:model-value="modelValue" |
||||
class="!text-gray-600" |
||||
:column="column" |
||||
:edit-enabled="false" |
||||
:read-only="true" |
||||
/> |
||||
</LazySmartsheetRow> |
||||
</div> |
||||
</template> |
File diff suppressed because it is too large
Load Diff
@ -1,163 +0,0 @@
|
||||
import { RelationTypes, UITypes } from 'nocodb-sdk'; |
||||
import type LookupColumn from '../models/LookupColumn'; |
||||
import type { BaseModelSqlv2 } from '~/db/BaseModelSqlv2'; |
||||
import type { |
||||
Column, |
||||
FormulaColumn, |
||||
LinkToAnotherRecordColumn, |
||||
Model, |
||||
RollupColumn, |
||||
} from '~/models'; |
||||
import formulaQueryBuilderv2 from '~/db/formulav2/formulaQueryBuilderv2'; |
||||
import genRollupSelectv2 from '~/db/genRollupSelectv2'; |
||||
import { NcError } from '~/helpers/catchError'; |
||||
|
||||
export default async function generateBTLookupSelectQuery({ |
||||
column, |
||||
baseModelSqlv2, |
||||
alias, |
||||
model, |
||||
}: { |
||||
column: Column; |
||||
baseModelSqlv2: BaseModelSqlv2; |
||||
alias: string; |
||||
model: Model; |
||||
}): Promise<any> { |
||||
const knex = baseModelSqlv2.dbDriver; |
||||
|
||||
const rootAlias = alias; |
||||
|
||||
{ |
||||
let aliasCount = 0, |
||||
selectQb; |
||||
const alias = `__nc_lk_${aliasCount++}`; |
||||
const lookup = await column.getColOptions<LookupColumn>(); |
||||
{ |
||||
const relationCol = await lookup.getRelationColumn(); |
||||
const relation = |
||||
await relationCol.getColOptions<LinkToAnotherRecordColumn>(); |
||||
|
||||
// if not belongs to then throw error as we don't support
|
||||
if (relation.type !== RelationTypes.BELONGS_TO) |
||||
NcError.badRequest('HasMany/ManyToMany lookup is not supported'); |
||||
|
||||
const childColumn = await relation.getChildColumn(); |
||||
const parentColumn = await relation.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
selectQb = knex( |
||||
`${baseModelSqlv2.getTnPath(parentModel.table_name)} as ${alias}`, |
||||
).where( |
||||
`${alias}.${parentColumn.column_name}`, |
||||
knex.raw(`??`, [ |
||||
`${rootAlias || baseModelSqlv2.getTnPath(childModel.table_name)}.${ |
||||
childColumn.column_name |
||||
}`,
|
||||
]), |
||||
); |
||||
} |
||||
let lookupColumn = await lookup.getLookupColumn(); |
||||
let prevAlias = alias; |
||||
while (lookupColumn.uidt === UITypes.Lookup) { |
||||
const nestedAlias = `__nc_lk_nested_${aliasCount++}`; |
||||
const nestedLookup = await lookupColumn.getColOptions<LookupColumn>(); |
||||
const relationCol = await nestedLookup.getRelationColumn(); |
||||
const relation = |
||||
await relationCol.getColOptions<LinkToAnotherRecordColumn>(); |
||||
|
||||
// if any of the relation in nested lookup is
|
||||
// not belongs to then throw error as we don't support
|
||||
if (relation.type !== RelationTypes.BELONGS_TO) |
||||
NcError.badRequest('HasMany/ManyToMany lookup is not supported'); |
||||
|
||||
const childColumn = await relation.getChildColumn(); |
||||
const parentColumn = await relation.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
selectQb.join( |
||||
`${baseModelSqlv2.getTnPath(parentModel.table_name)} as ${nestedAlias}`, |
||||
`${nestedAlias}.${parentColumn.column_name}`, |
||||
`${prevAlias}.${childColumn.column_name}`, |
||||
); |
||||
|
||||
lookupColumn = await nestedLookup.getLookupColumn(); |
||||
prevAlias = nestedAlias; |
||||
} |
||||
|
||||
switch (lookupColumn.uidt) { |
||||
case UITypes.Links: |
||||
case UITypes.Rollup: |
||||
{ |
||||
const builder = ( |
||||
await genRollupSelectv2({ |
||||
baseModelSqlv2, |
||||
knex, |
||||
columnOptions: |
||||
(await lookupColumn.getColOptions()) as RollupColumn, |
||||
alias: prevAlias, |
||||
}) |
||||
).builder; |
||||
selectQb.select(builder); |
||||
} |
||||
break; |
||||
case UITypes.LinkToAnotherRecord: |
||||
{ |
||||
const nestedAlias = `__nc_sort${aliasCount++}`; |
||||
const relation = |
||||
await lookupColumn.getColOptions<LinkToAnotherRecordColumn>(); |
||||
if (relation.type !== 'bt') return; |
||||
|
||||
const colOptions = |
||||
(await column.getColOptions()) as LinkToAnotherRecordColumn; |
||||
const childColumn = await colOptions.getChildColumn(); |
||||
const parentColumn = await colOptions.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
selectQb |
||||
.join( |
||||
`${baseModelSqlv2.getTnPath( |
||||
parentModel.table_name, |
||||
)} as ${nestedAlias}`,
|
||||
`${nestedAlias}.${parentColumn.column_name}`, |
||||
`${prevAlias}.${childColumn.column_name}`, |
||||
) |
||||
.select(parentModel?.displayValue?.column_name); |
||||
} |
||||
break; |
||||
case UITypes.Formula: |
||||
{ |
||||
const builder = ( |
||||
await formulaQueryBuilderv2( |
||||
baseModelSqlv2, |
||||
( |
||||
await column.getColOptions<FormulaColumn>() |
||||
).formula, |
||||
null, |
||||
model, |
||||
column, |
||||
) |
||||
).builder; |
||||
|
||||
selectQb.select(builder); |
||||
} |
||||
break; |
||||
default: |
||||
{ |
||||
selectQb.select(`${prevAlias}.${lookupColumn.column_name}`); |
||||
} |
||||
|
||||
break; |
||||
} |
||||
|
||||
return { builder: selectQb }; |
||||
} |
||||
} |
@ -0,0 +1,399 @@
|
||||
import { RelationTypes, UITypes } from 'nocodb-sdk'; |
||||
import type LookupColumn from '../models/LookupColumn'; |
||||
import type { BaseModelSqlv2 } from '~/db/BaseModelSqlv2'; |
||||
import type { |
||||
BarcodeColumn, |
||||
Column, |
||||
FormulaColumn, |
||||
LinksColumn, |
||||
LinkToAnotherRecordColumn, |
||||
QrCodeColumn, |
||||
RollupColumn, |
||||
} from '~/models'; |
||||
import { Model } from '~/models'; |
||||
import formulaQueryBuilderv2 from '~/db/formulav2/formulaQueryBuilderv2'; |
||||
import genRollupSelectv2 from '~/db/genRollupSelectv2'; |
||||
import { getAliasGenerator } from '~/utils'; |
||||
import { NcError } from '~/helpers/catchError'; |
||||
|
||||
const LOOKUP_VAL_SEPARATOR = '___'; |
||||
|
||||
export async function getDisplayValueOfRefTable( |
||||
relationCol: Column<LinkToAnotherRecordColumn | LinksColumn>, |
||||
) { |
||||
return await relationCol |
||||
.getColOptions() |
||||
.then((colOpt) => colOpt.getRelatedTable()) |
||||
.then((model) => model.getColumns()) |
||||
.then((cols) => cols.find((col) => col.pv)); |
||||
} |
||||
|
||||
export default async function generateLookupSelectQuery({ |
||||
column, |
||||
baseModelSqlv2, |
||||
alias, |
||||
model: _model, |
||||
getAlias = getAliasGenerator('__lk_slt_'), |
||||
}: { |
||||
column: Column; |
||||
baseModelSqlv2: BaseModelSqlv2; |
||||
alias: string; |
||||
model: Model; |
||||
getAlias?: ReturnType<typeof getAliasGenerator>; |
||||
}): Promise<any> { |
||||
const knex = baseModelSqlv2.dbDriver; |
||||
|
||||
const rootAlias = alias; |
||||
|
||||
{ |
||||
let selectQb; |
||||
const alias = getAlias(); |
||||
let lookupColOpt: LookupColumn; |
||||
let isBtLookup = true; |
||||
|
||||
if (column.uidt === UITypes.Lookup) { |
||||
lookupColOpt = await column.getColOptions<LookupColumn>(); |
||||
} else if (column.uidt !== UITypes.LinkToAnotherRecord) { |
||||
NcError.badRequest('Invalid field type'); |
||||
} |
||||
|
||||
await column.getColOptions<LookupColumn>(); |
||||
{ |
||||
const relationCol = lookupColOpt |
||||
? await lookupColOpt.getRelationColumn() |
||||
: column; |
||||
const relation = |
||||
await relationCol.getColOptions<LinkToAnotherRecordColumn>(); |
||||
|
||||
// if not belongs to then throw error as we don't support
|
||||
if (relation.type === RelationTypes.BELONGS_TO) { |
||||
const childColumn = await relation.getChildColumn(); |
||||
const parentColumn = await relation.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
selectQb = knex( |
||||
`${baseModelSqlv2.getTnPath(parentModel.table_name)} as ${alias}`, |
||||
).where( |
||||
`${alias}.${parentColumn.column_name}`, |
||||
knex.raw(`??`, [ |
||||
`${rootAlias || baseModelSqlv2.getTnPath(childModel.table_name)}.${ |
||||
childColumn.column_name |
||||
}`,
|
||||
]), |
||||
); |
||||
} |
||||
|
||||
// if not belongs to then throw error as we don't support
|
||||
else if (relation.type === RelationTypes.HAS_MANY) { |
||||
isBtLookup = false; |
||||
const childColumn = await relation.getChildColumn(); |
||||
const parentColumn = await relation.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
selectQb = knex( |
||||
`${baseModelSqlv2.getTnPath(childModel.table_name)} as ${alias}`, |
||||
).where( |
||||
`${alias}.${childColumn.column_name}`, |
||||
knex.raw(`??`, [ |
||||
`${rootAlias || baseModelSqlv2.getTnPath(parentModel.table_name)}.${ |
||||
parentColumn.column_name |
||||
}`,
|
||||
]), |
||||
); |
||||
} |
||||
|
||||
// if not belongs to then throw error as we don't support
|
||||
else if (relation.type === RelationTypes.MANY_TO_MANY) { |
||||
isBtLookup = false; |
||||
const childColumn = await relation.getChildColumn(); |
||||
const parentColumn = await relation.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
selectQb = knex( |
||||
`${baseModelSqlv2.getTnPath(parentModel.table_name)} as ${alias}`, |
||||
); |
||||
|
||||
const mmTableAlias = getAlias(); |
||||
|
||||
const mmModel = await relation.getMMModel(); |
||||
const mmChildCol = await relation.getMMChildColumn(); |
||||
const mmParentCol = await relation.getMMParentColumn(); |
||||
|
||||
selectQb |
||||
.innerJoin( |
||||
baseModelSqlv2.getTnPath(mmModel.table_name, mmTableAlias), |
||||
knex.ref(`${mmTableAlias}.${mmParentCol.column_name}`), |
||||
'=', |
||||
knex.ref(`${alias}.${parentColumn.column_name}`), |
||||
) |
||||
.where( |
||||
knex.ref(`${mmTableAlias}.${mmChildCol.column_name}`), |
||||
'=', |
||||
knex.ref( |
||||
`${ |
||||
rootAlias || baseModelSqlv2.getTnPath(childModel.table_name) |
||||
}.${childColumn.column_name}`,
|
||||
), |
||||
); |
||||
} |
||||
} |
||||
let lookupColumn = lookupColOpt |
||||
? await lookupColOpt.getLookupColumn() |
||||
: await getDisplayValueOfRefTable(column); |
||||
|
||||
// if lookup column is qr code or barcode extract the referencing column
|
||||
if ([UITypes.QrCode, UITypes.Barcode].includes(lookupColumn.uidt)) { |
||||
lookupColumn = await lookupColumn |
||||
.getColOptions<BarcodeColumn | QrCodeColumn>() |
||||
.then((barcode) => barcode.getValueColumn()); |
||||
} |
||||
|
||||
let prevAlias = alias; |
||||
while ( |
||||
lookupColumn.uidt === UITypes.Lookup || |
||||
lookupColumn.uidt === UITypes.LinkToAnotherRecord |
||||
) { |
||||
const nestedAlias = getAlias(); |
||||
|
||||
let relationCol: Column<LinkToAnotherRecordColumn | LinksColumn>; |
||||
let nestedLookupColOpt: LookupColumn; |
||||
|
||||
if (lookupColumn.uidt === UITypes.Lookup) { |
||||
nestedLookupColOpt = await lookupColumn.getColOptions<LookupColumn>(); |
||||
relationCol = await nestedLookupColOpt.getRelationColumn(); |
||||
} else { |
||||
relationCol = lookupColumn; |
||||
} |
||||
|
||||
const relation = |
||||
await relationCol.getColOptions<LinkToAnotherRecordColumn>(); |
||||
|
||||
// if any of the relation in nested lookupColOpt is
|
||||
// not belongs to then throw error as we don't support
|
||||
if (relation.type === RelationTypes.BELONGS_TO) { |
||||
const childColumn = await relation.getChildColumn(); |
||||
const parentColumn = await relation.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
selectQb.join( |
||||
`${baseModelSqlv2.getTnPath( |
||||
parentModel.table_name, |
||||
)} as ${nestedAlias}`,
|
||||
`${nestedAlias}.${parentColumn.column_name}`, |
||||
`${prevAlias}.${childColumn.column_name}`, |
||||
); |
||||
} else if (relation.type === RelationTypes.HAS_MANY) { |
||||
isBtLookup = false; |
||||
const childColumn = await relation.getChildColumn(); |
||||
const parentColumn = await relation.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
selectQb.join( |
||||
`${baseModelSqlv2.getTnPath( |
||||
childModel.table_name, |
||||
)} as ${nestedAlias}`,
|
||||
`${nestedAlias}.${childColumn.column_name}`, |
||||
`${prevAlias}.${parentColumn.column_name}`, |
||||
); |
||||
} else if (relation.type === RelationTypes.MANY_TO_MANY) { |
||||
isBtLookup = false; |
||||
const childColumn = await relation.getChildColumn(); |
||||
const parentColumn = await relation.getParentColumn(); |
||||
const childModel = await childColumn.getModel(); |
||||
await childModel.getColumns(); |
||||
const parentModel = await parentColumn.getModel(); |
||||
await parentModel.getColumns(); |
||||
|
||||
const mmTableAlias = getAlias(); |
||||
|
||||
const mmModel = await relation.getMMModel(); |
||||
const mmChildCol = await relation.getMMChildColumn(); |
||||
const mmParentCol = await relation.getMMParentColumn(); |
||||
|
||||
selectQb |
||||
.innerJoin( |
||||
baseModelSqlv2.getTnPath(mmModel.table_name, mmTableAlias), |
||||
knex.ref(`${mmTableAlias}.${mmChildCol.column_name}`), |
||||
'=', |
||||
knex.ref(`${prevAlias}.${childColumn.column_name}`), |
||||
) |
||||
.innerJoin( |
||||
knex.raw('?? as ??', [ |
||||
baseModelSqlv2.getTnPath(parentModel.table_name), |
||||
nestedAlias, |
||||
]), |
||||
knex.ref(`${mmTableAlias}.${mmParentCol.column_name}`), |
||||
'=', |
||||
knex.ref(`${nestedAlias}.${parentColumn.column_name}`), |
||||
) |
||||
.where( |
||||
knex.ref(`${mmTableAlias}.${mmChildCol.column_name}`), |
||||
'=', |
||||
knex.ref( |
||||
`${alias || baseModelSqlv2.getTnPath(childModel.table_name)}.${ |
||||
childColumn.column_name |
||||
}`,
|
||||
), |
||||
); |
||||
} |
||||
|
||||
if (lookupColumn.uidt === UITypes.Lookup) |
||||
lookupColumn = await nestedLookupColOpt.getLookupColumn(); |
||||
else lookupColumn = await getDisplayValueOfRefTable(relationCol); |
||||
prevAlias = nestedAlias; |
||||
} |
||||
|
||||
{ |
||||
// get basemodel and model of lookup column
|
||||
const model = await lookupColumn.getModel(); |
||||
const baseModelSqlv2 = await Model.getBaseModelSQL({ |
||||
model, |
||||
dbDriver: knex, |
||||
}); |
||||
|
||||
switch (lookupColumn.uidt) { |
||||
case UITypes.Attachment: |
||||
NcError.badRequest( |
||||
'Group by using attachment column is not supported', |
||||
); |
||||
break; |
||||
case UITypes.Links: |
||||
case UITypes.Rollup: |
||||
{ |
||||
const builder = ( |
||||
await genRollupSelectv2({ |
||||
baseModelSqlv2, |
||||
knex, |
||||
columnOptions: |
||||
(await lookupColumn.getColOptions()) as RollupColumn, |
||||
alias: prevAlias, |
||||
}) |
||||
).builder; |
||||
selectQb.select(builder); |
||||
} |
||||
break; |
||||
case UITypes.Formula: |
||||
{ |
||||
const builder = ( |
||||
await formulaQueryBuilderv2( |
||||
baseModelSqlv2, |
||||
( |
||||
await lookupColumn.getColOptions<FormulaColumn>() |
||||
).formula, |
||||
lookupColumn.title, |
||||
model, |
||||
lookupColumn, |
||||
await model.getAliasColMapping(), |
||||
prevAlias, |
||||
) |
||||
).builder; |
||||
|
||||
selectQb.select(builder); |
||||
} |
||||
break; |
||||
case UITypes.DateTime: |
||||
{ |
||||
await baseModelSqlv2.selectObject({ |
||||
qb: selectQb, |
||||
columns: [lookupColumn], |
||||
alias: prevAlias, |
||||
}); |
||||
} |
||||
break; |
||||
default: |
||||
{ |
||||
selectQb.select( |
||||
`${prevAlias}.${lookupColumn.column_name} as ${lookupColumn.title}`, |
||||
); |
||||
} |
||||
|
||||
break; |
||||
} |
||||
} |
||||
// if all relation are belongs to then we don't need to do the aggregation
|
||||
if (isBtLookup) { |
||||
return { |
||||
builder: selectQb, |
||||
}; |
||||
} |
||||
|
||||
const subQueryAlias = getAlias(); |
||||
|
||||
if (baseModelSqlv2.isPg) { |
||||
// alternate approach with array_agg
|
||||
return { |
||||
builder: knex |
||||
.select(knex.raw('json_agg(??)::text', [lookupColumn.title])) |
||||
.from(selectQb.as(subQueryAlias)), |
||||
}; |
||||
/* |
||||
// alternate approach with array_agg
|
||||
return { |
||||
builder: knex |
||||
.select(knex.raw('array_agg(??)', [lookupColumn.title])) |
||||
.from(selectQb), |
||||
};*/ |
||||
// alternate approach with string aggregation
|
||||
// return {
|
||||
// builder: knex
|
||||
// .select(
|
||||
// knex.raw('STRING_AGG(??::text, ?)', [
|
||||
// lookupColumn.title,
|
||||
// LOOKUP_VAL_SEPARATOR,
|
||||
// ]),
|
||||
// )
|
||||
// .from(selectQb.as(subQueryAlias)),
|
||||
// };
|
||||
} else if (baseModelSqlv2.isMySQL) { |
||||
return { |
||||
builder: knex |
||||
.select( |
||||
knex.raw('cast(JSON_ARRAYAGG(??) as NCHAR)', [lookupColumn.title]), |
||||
) |
||||
.from(selectQb.as(subQueryAlias)), |
||||
}; |
||||
|
||||
// return {
|
||||
// builder: knex
|
||||
// .select(
|
||||
// knex.raw('GROUP_CONCAT(?? ORDER BY ?? ASC SEPARATOR ?)', [
|
||||
// lookupColumn.title,
|
||||
// lookupColumn.title,
|
||||
// LOOKUP_VAL_SEPARATOR,
|
||||
// ]),
|
||||
// )
|
||||
// .from(selectQb.as(subQueryAlias)),
|
||||
// };
|
||||
} else if (baseModelSqlv2.isSqlite) { |
||||
// ref: https://stackoverflow.com/questions/13382856/sqlite3-join-group-concat-using-distinct-with-custom-separator
|
||||
// selectQb.orderBy(`${lookupColumn.title}`, 'asc');
|
||||
return { |
||||
builder: knex |
||||
.select( |
||||
knex.raw(`group_concat(??, ?)`, [ |
||||
lookupColumn.title, |
||||
LOOKUP_VAL_SEPARATOR, |
||||
]), |
||||
) |
||||
.from(selectQb.as(subQueryAlias)), |
||||
}; |
||||
} |
||||
|
||||
NcError.notImplemented('Database not supported Group by on Lookup'); |
||||
} |
||||
} |
@ -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; |
||||
} |
@ -0,0 +1,29 @@
|
||||
import { getModelSchemas } 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; |
||||
} |
@ -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; |
||||
} |
@ -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>; |
||||
} |
@ -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": [] |
||||
} |
||||
] |
||||
} |
@ -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,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([])); |
||||
}; |
@ -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`, |
||||
}, |
||||
}, |
||||
}; |
||||
} |
@ -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…
Reference in new issue