mirror of https://github.com/nocodb/nocodb
mertmit
2 years ago
6 changed files with 721 additions and 12 deletions
@ -0,0 +1,367 @@
|
||||
import { Request, Response } from 'express'; |
||||
import Project from '../../models/Project'; |
||||
import { ModelTypes, ProjectListType, UITypes } from 'nocodb-sdk'; |
||||
import { PagedResponseImpl } from '../helpers/PagedResponse'; |
||||
import syncMigration from '../helpers/syncMigration'; |
||||
import { IGNORE_TABLES } from '../../utils/common/BaseApiBuilder'; |
||||
import Column from '../../models/Column'; |
||||
import Model from '../../models/Model'; |
||||
import NcHelp from '../../utils/NcHelp'; |
||||
import Base from '../../models/Base'; |
||||
import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2'; |
||||
import getTableNameAlias, { getColumnNameAlias } from '../helpers/getTableName'; |
||||
import LinkToAnotherRecordColumn from '../../models/LinkToAnotherRecordColumn'; |
||||
import ncMetaAclMw from '../helpers/ncMetaAclMw'; |
||||
import { Tele } from 'nc-help'; |
||||
import { NcError } from '../helpers/catchError'; |
||||
import getColumnUiType from '../helpers/getColumnUiType'; |
||||
import mapDefaultPrimaryValue from '../helpers/mapDefaultPrimaryValue'; |
||||
import { extractAndGenerateManyToManyRelations } from './metaDiffApis'; |
||||
import { metaApiMetrics } from '../helpers/apiMetrics'; |
||||
|
||||
export async function baseGet( |
||||
req: Request<any, any, any>, |
||||
res: Response<Base> |
||||
) { |
||||
const base = await Base.get(req.params.baseId); |
||||
|
||||
delete base.config; |
||||
|
||||
res.json(base); |
||||
} |
||||
|
||||
export async function baseUpdate( |
||||
_req: Request<any, any, any>, |
||||
_res: Response<ProjectListType> |
||||
) { |
||||
NcError.badRequest('Base update not yet supported'); |
||||
} |
||||
|
||||
export async function baseList( |
||||
req: Request<any, any, any>, |
||||
res: Response<ProjectListType>, |
||||
next |
||||
) { |
||||
try { |
||||
const projects = await Project.list(req.query); |
||||
|
||||
res // todo: pagination
|
||||
.json( |
||||
new PagedResponseImpl(projects, { |
||||
count: projects.length, |
||||
limit: projects.length, |
||||
}) |
||||
); |
||||
} catch (e) { |
||||
console.log(e); |
||||
next(e); |
||||
} |
||||
} |
||||
|
||||
export async function baseDelete( |
||||
req: Request<any, any, any>, |
||||
res: Response<any> |
||||
) { |
||||
const base = await Base.get(req.params.baseId); |
||||
const result = await base.delete(); |
||||
Tele.emit('evt', { evt_type: 'base:deleted' }); |
||||
res.json(result); |
||||
} |
||||
|
||||
async function baseCreate(req: Request<any, any>, res) { |
||||
// type | base | projectId
|
||||
const baseBody = req.body; |
||||
const base = await Base.createBase(baseBody); |
||||
const project = await base.getProject(); |
||||
await syncMigration(project); |
||||
|
||||
const info = await populateMeta(base, project); |
||||
|
||||
Tele.emit('evt_api_created', info); |
||||
|
||||
delete base.config; |
||||
|
||||
Tele.emit('evt', { |
||||
evt_type: 'base:created' |
||||
}); |
||||
|
||||
res.json(base); |
||||
} |
||||
|
||||
async function populateMeta(base: Base, project: Project): Promise<any> { |
||||
const info = { |
||||
type: 'rest', |
||||
apiCount: 0, |
||||
tablesCount: 0, |
||||
relationsCount: 0, |
||||
viewsCount: 0, |
||||
client: base?.getConnectionConfig()?.client, |
||||
timeTaken: 0, |
||||
}; |
||||
|
||||
const t = process.hrtime(); |
||||
const sqlClient = NcConnectionMgrv2.getSqlClient(base); |
||||
let order = 1; |
||||
const models2: { [tableName: string]: Model } = {}; |
||||
|
||||
const virtualColumnsInsert = []; |
||||
|
||||
/* Get all relations */ |
||||
const relations = (await sqlClient.relationListAll())?.data?.list; |
||||
|
||||
info.relationsCount = relations.length; |
||||
|
||||
let tables = (await sqlClient.tableList())?.data?.list |
||||
?.filter(({ tn }) => !IGNORE_TABLES.includes(tn)) |
||||
?.map((t) => { |
||||
t.order = ++order; |
||||
t.title = getTableNameAlias(t.tn, project.prefix, base); |
||||
t.table_name = t.tn; |
||||
return t; |
||||
}); |
||||
|
||||
/* filter based on prefix */ |
||||
if (project?.prefix) { |
||||
tables = tables.filter((t) => { |
||||
return t?.tn?.startsWith(project?.prefix); |
||||
}); |
||||
} |
||||
|
||||
info.tablesCount = tables.length; |
||||
|
||||
tables.forEach((t) => { |
||||
t.title = getTableNameAlias(t.tn, project.prefix, base); |
||||
}); |
||||
|
||||
relations.forEach((r) => { |
||||
r.title = getTableNameAlias(r.tn, project.prefix, base); |
||||
r.rtitle = getTableNameAlias(r.rtn, project.prefix, base); |
||||
}); |
||||
|
||||
// await this.syncRelations();
|
||||
|
||||
const tableMetasInsert = tables.map((table) => { |
||||
return async () => { |
||||
/* filter relation where this table is present */ |
||||
const tableRelations = relations.filter( |
||||
(r) => r.tn === table.tn || r.rtn === table.tn |
||||
); |
||||
|
||||
const columns: Array< |
||||
Omit<Column, 'column_name' | 'title'> & { |
||||
cn: string; |
||||
system?: boolean; |
||||
} |
||||
> = (await sqlClient.columnList({ tn: table.tn }))?.data?.list; |
||||
|
||||
const hasMany = |
||||
table.type === 'view' |
||||
? [] |
||||
: tableRelations.filter((r) => r.rtn === table.tn); |
||||
const belongsTo = |
||||
table.type === 'view' |
||||
? [] |
||||
: tableRelations.filter((r) => r.tn === table.tn); |
||||
|
||||
mapDefaultPrimaryValue(columns); |
||||
|
||||
// add vitual columns
|
||||
const virtualColumns = [ |
||||
...hasMany.map((hm) => { |
||||
return { |
||||
uidt: UITypes.LinkToAnotherRecord, |
||||
type: 'hm', |
||||
hm, |
||||
title: `${hm.title} List`, |
||||
}; |
||||
}), |
||||
...belongsTo.map((bt) => { |
||||
// find and mark foreign key column
|
||||
const fkColumn = columns.find((c) => c.cn === bt.cn); |
||||
if (fkColumn) { |
||||
fkColumn.uidt = UITypes.ForeignKey; |
||||
fkColumn.system = true; |
||||
} |
||||
|
||||
return { |
||||
uidt: UITypes.LinkToAnotherRecord, |
||||
type: 'bt', |
||||
bt, |
||||
title: `${bt.rtitle}`, |
||||
}; |
||||
}), |
||||
]; |
||||
|
||||
// await Model.insert(project.id, base.id, meta);
|
||||
|
||||
/* create nc_models and its rows if it doesn't exists */ |
||||
models2[table.table_name] = await Model.insert(project.id, base.id, { |
||||
table_name: table.tn || table.table_name, |
||||
title: table.title, |
||||
type: table.type || 'table', |
||||
order: table.order, |
||||
}); |
||||
|
||||
// table crud apis
|
||||
info.apiCount += 5; |
||||
|
||||
let colOrder = 1; |
||||
|
||||
for (const column of columns) { |
||||
await Column.insert({ |
||||
uidt: column.uidt || getColumnUiType(base, column), |
||||
fk_model_id: models2[table.tn].id, |
||||
...column, |
||||
title: getColumnNameAlias(column.cn, base), |
||||
column_name: column.cn, |
||||
order: colOrder++, |
||||
}); |
||||
} |
||||
virtualColumnsInsert.push(async () => { |
||||
const columnNames = {}; |
||||
for (const column of virtualColumns) { |
||||
// generate unique name if there is any duplicate column name
|
||||
let c = 0; |
||||
while (`${column.title}${c || ''}` in columnNames) { |
||||
c++; |
||||
} |
||||
column.title = `${column.title}${c || ''}`; |
||||
columnNames[column.title] = true; |
||||
|
||||
const rel = column.hm || column.bt; |
||||
|
||||
const rel_column_id = (await models2?.[rel.tn]?.getColumns())?.find( |
||||
(c) => c.column_name === rel.cn |
||||
)?.id; |
||||
|
||||
const tnId = models2?.[rel.tn]?.id; |
||||
|
||||
const ref_rel_column_id = ( |
||||
await models2?.[rel.rtn]?.getColumns() |
||||
)?.find((c) => c.column_name === rel.rcn)?.id; |
||||
|
||||
const rtnId = models2?.[rel.rtn]?.id; |
||||
|
||||
try { |
||||
await Column.insert<LinkToAnotherRecordColumn>({ |
||||
project_id: project.id, |
||||
db_alias: base.id, |
||||
fk_model_id: models2[table.tn].id, |
||||
cn: column.cn, |
||||
title: column.title, |
||||
uidt: column.uidt, |
||||
type: column.hm ? 'hm' : column.mm ? 'mm' : 'bt', |
||||
// column_id,
|
||||
fk_child_column_id: rel_column_id, |
||||
fk_parent_column_id: ref_rel_column_id, |
||||
fk_index_name: rel.fkn, |
||||
ur: rel.ur, |
||||
dr: rel.dr, |
||||
order: colOrder++, |
||||
fk_related_model_id: column.hm ? tnId : rtnId, |
||||
system: column.system, |
||||
}); |
||||
|
||||
// nested relations data apis
|
||||
info.apiCount += 5; |
||||
} catch (e) { |
||||
console.log(e); |
||||
} |
||||
} |
||||
}); |
||||
}; |
||||
}); |
||||
|
||||
/* handle xc_tables update in parallel */ |
||||
await NcHelp.executeOperations(tableMetasInsert, base.type); |
||||
await NcHelp.executeOperations(virtualColumnsInsert, base.type); |
||||
await extractAndGenerateManyToManyRelations(Object.values(models2)); |
||||
|
||||
let views: Array<{ order: number; table_name: string; title: string }> = ( |
||||
await sqlClient.viewList() |
||||
)?.data?.list |
||||
// ?.filter(({ tn }) => !IGNORE_TABLES.includes(tn))
|
||||
?.map((v) => { |
||||
v.order = ++order; |
||||
v.table_name = v.view_name; |
||||
v.title = getTableNameAlias(v.view_name, project.prefix, base); |
||||
return v; |
||||
}); |
||||
|
||||
/* filter based on prefix */ |
||||
if (project?.prefix) { |
||||
views = tables.filter((t) => { |
||||
return t?.tn?.startsWith(project?.prefix); |
||||
}); |
||||
} |
||||
|
||||
info.viewsCount = views.length; |
||||
|
||||
const viewMetasInsert = views.map((table) => { |
||||
return async () => { |
||||
const columns = (await sqlClient.columnList({ tn: table.table_name })) |
||||
?.data?.list; |
||||
|
||||
/* create nc_models and its rows if it doesn't exists */ |
||||
models2[table.table_name] = await Model.insert(project.id, base.id, { |
||||
table_name: table.table_name, |
||||
title: getTableNameAlias(table.table_name, project.prefix, base), |
||||
// todo: sanitize
|
||||
type: ModelTypes.VIEW, |
||||
order: table.order, |
||||
}); |
||||
|
||||
let colOrder = 1; |
||||
|
||||
// view apis
|
||||
info.apiCount += 2; |
||||
|
||||
for (const column of columns) { |
||||
await Column.insert({ |
||||
fk_model_id: models2[table.table_name].id, |
||||
...column, |
||||
title: getColumnNameAlias(column.cn, base), |
||||
order: colOrder++, |
||||
uidt: getColumnUiType(base, column), |
||||
}); |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
await NcHelp.executeOperations(viewMetasInsert, base.type); |
||||
|
||||
const t1 = process.hrtime(t); |
||||
const t2 = t1[0] + t1[1] / 1000000000; |
||||
|
||||
(info as any).timeTaken = t2.toFixed(1); |
||||
|
||||
return info; |
||||
} |
||||
|
||||
export default (router) => { |
||||
router.get( |
||||
'/api/v1/db/meta/projects/:projectId/bases/:baseId', |
||||
metaApiMetrics, |
||||
ncMetaAclMw(baseGet, 'baseGet') |
||||
); |
||||
router.patch( |
||||
'/api/v1/db/meta/projects/:projectId/bases/:baseId', |
||||
metaApiMetrics, |
||||
ncMetaAclMw(baseUpdate, 'baseUpdate') |
||||
); |
||||
router.delete( |
||||
'/api/v1/db/meta/projects/:projectId/bases/:baseId', |
||||
metaApiMetrics, |
||||
ncMetaAclMw(baseDelete, 'baseDelete') |
||||
); |
||||
router.post( |
||||
'/api/v1/db/meta/projects/:projectId/bases', |
||||
metaApiMetrics, |
||||
ncMetaAclMw(baseCreate, 'baseCreate') |
||||
); |
||||
router.get( |
||||
'/api/v1/db/meta/projects/:projectId/bases', |
||||
metaApiMetrics, |
||||
ncMetaAclMw(baseList, 'baseList') |
||||
); |
||||
}; |
Loading…
Reference in new issue