From 9c9ceb7cd8770f32743681bf0c7e905c4881db1f Mon Sep 17 00:00:00 2001 From: Pranav C Date: Sun, 26 Feb 2023 14:24:09 +0530 Subject: [PATCH] feat(wip): controller-service implementation Signed-off-by: Pranav C --- packages/nc-gui/components.d.ts | 1 + .../src/lib/controllers/tableController.ts | 410 ++---------------- packages/nocodb/src/lib/models/ProjectUser.ts | 2 +- packages/nocodb/src/lib/services/index.ts | 2 + .../nocodb/src/lib/services/projectService.ts | 42 ++ .../nocodb/src/lib/services/tableService.ts | 348 +++++++++++++++ 6 files changed, 428 insertions(+), 377 deletions(-) create mode 100644 packages/nocodb/src/lib/services/index.ts create mode 100644 packages/nocodb/src/lib/services/projectService.ts create mode 100644 packages/nocodb/src/lib/services/tableService.ts diff --git a/packages/nc-gui/components.d.ts b/packages/nc-gui/components.d.ts index 78f37aba7f..372758da9a 100644 --- a/packages/nc-gui/components.d.ts +++ b/packages/nc-gui/components.d.ts @@ -89,6 +89,7 @@ declare module '@vue/runtime-core' { IcTwotoneWidthNormal: typeof import('~icons/ic/twotone-width-normal')['default'] LogosGoogleGmail: typeof import('~icons/logos/google-gmail')['default'] LogosMysqlIcon: typeof import('~icons/logos/mysql-icon')['default'] + LogosOracle: typeof import('~icons/logos/oracle')['default'] LogosPostgresql: typeof import('~icons/logos/postgresql')['default'] LogosRedditIcon: typeof import('~icons/logos/reddit-icon')['default'] LogosSnowflakeIcon: typeof import('~icons/logos/snowflake-icon')['default'] diff --git a/packages/nocodb/src/lib/controllers/tableController.ts b/packages/nocodb/src/lib/controllers/tableController.ts index c4ca42d0b5..728cc1816a 100644 --- a/packages/nocodb/src/lib/controllers/tableController.ts +++ b/packages/nocodb/src/lib/controllers/tableController.ts @@ -1,404 +1,62 @@ import { Request, Response, Router } from 'express'; -import Model from '../models/Model'; -import { Tele } from 'nc-help'; -import { PagedResponseImpl } from '../meta/helpers/PagedResponse'; -import DOMPurify from 'isomorphic-dompurify'; -import { - AuditOperationSubTypes, - AuditOperationTypes, - isVirtualCol, - ModelTypes, - NormalColumnRequestType, - TableListType, - TableReqType, - TableType, - UITypes, -} from 'nocodb-sdk'; -import ProjectMgrv2 from '../db/sql-mgr/v2/ProjectMgrv2'; -import Project from '../models/Project'; -import Audit from '../models/Audit'; -import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; +import { TableListType, TableReqType, TableType } from 'nocodb-sdk'; import { getAjvValidatorMw } from '../meta/api/helpers'; -import { xcVisibilityMetaGet } from './modelVisibilityController'; -import View from '../models/View'; -import getColumnPropsFromUIDT from '../meta/helpers/getColumnPropsFromUIDT'; -import mapDefaultDisplayValue from '../meta/helpers/mapDefaultDisplayValue'; -import { NcError } from '../meta/helpers/catchError'; -import getTableNameAlias, { getColumnNameAlias } from '../meta/helpers/getTableName'; -import Column from '../models/Column'; -import NcConnectionMgrv2 from '../utils/common/NcConnectionMgrv2'; -import getColumnUiType from '../meta/helpers/getColumnUiType'; -import LinkToAnotherRecordColumn from '../models/LinkToAnotherRecordColumn'; +import { tableUpdate } from '../meta/api/tableApis'; import { metaApiMetrics } from '../meta/helpers/apiMetrics'; +import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; +import { PagedResponseImpl } from '../meta/helpers/PagedResponse'; -export async function tableGet(req: Request, res: Response) { - const table = await Model.getWithInfo({ - id: req.params.tableId, - }); - - // todo: optimise - const viewList = await xcVisibilityMetaGet(table.project_id, [table]); - - //await View.list(req.params.tableId) - table.views = viewList.filter((table: any) => { - return Object.keys((req as any).session?.passport?.user?.roles).some( - (role) => - (req as any)?.session?.passport?.user?.roles[role] && - !table.disabled[role] - ); - }); - - res.json(table); -} - -export async function tableReorder(req: Request, res: Response) { - res.json(Model.updateOrder(req.params.tableId, req.body.order)); -} +import { tableService } from '../services'; export async function tableList(req: Request, res: Response) { - const viewList = await xcVisibilityMetaGet(req.params.projectId); - - // todo: optimise - const tableViewMapping = viewList.reduce((o, view: any) => { - o[view.fk_model_id] = o[view.fk_model_id] || 0; - if ( - Object.keys((req as any).session?.passport?.user?.roles).some( - (role) => - (req as any)?.session?.passport?.user?.roles[role] && - !view.disabled[role] - ) - ) { - o[view.fk_model_id]++; - } - return o; - }, {}); - - const tableList = ( - await Model.list({ - project_id: req.params.projectId, - base_id: req.params.baseId, - }) - ).filter((t) => tableViewMapping[t.id]); - res.json( new PagedResponseImpl( - req.query?.includeM2M === 'true' - ? tableList - : (tableList.filter((t) => !t.mm) as Model[]) + await tableService.getAccessibleTables({ + projectId: req.params.projectId, + baseId: req.params.baseId, + includeM2M: req.query?.includeM2M === 'true', + roles: (req as any).session?.passport?.user?.roles, + }) ) ); } export async function tableCreate(req: Request, res) { - const project = await Project.getWithInfo(req.params.projectId); - let base = project.bases[0]; - - if (req.params.baseId) { - base = project.bases.find((b) => b.id === req.params.baseId); - } - - if ( - !req.body.table_name || - (project.prefix && project.prefix === req.body.table_name) - ) { - NcError.badRequest( - 'Missing table name `table_name` property in request body' - ); - } - - if (base.is_meta && project.prefix) { - if (!req.body.table_name.startsWith(project.prefix)) { - req.body.table_name = `${project.prefix}_${req.body.table_name}`; - } - } - - req.body.table_name = DOMPurify.sanitize(req.body.table_name); - - // validate table name - if (/^\s+|\s+$/.test(req.body.table_name)) { - NcError.badRequest( - 'Leading or trailing whitespace not allowed in table names' - ); - } - - if ( - !(await Model.checkTitleAvailable({ - table_name: req.body.table_name, - project_id: project.id, - base_id: base.id, - })) - ) { - NcError.badRequest('Duplicate table name'); - } - - if (!req.body.title) { - req.body.title = getTableNameAlias( - req.body.table_name, - project.prefix, - base - ); - } - - if ( - !(await Model.checkAliasAvailable({ - title: req.body.title, - project_id: project.id, - base_id: base.id, - })) - ) { - NcError.badRequest('Duplicate table alias'); - } - - const sqlMgr = await ProjectMgrv2.getSqlMgr(project); - - const sqlClient = await NcConnectionMgrv2.getSqlClient(base); - - let tableNameLengthLimit = 255; - const sqlClientType = sqlClient.knex.clientType(); - if (sqlClientType === 'mysql2' || sqlClientType === 'mysql') { - tableNameLengthLimit = 64; - } else if (sqlClientType === 'pg') { - tableNameLengthLimit = 63; - } else if (sqlClientType === 'mssql') { - tableNameLengthLimit = 128; - } - - if (req.body.table_name.length > tableNameLengthLimit) { - NcError.badRequest(`Table name exceeds ${tableNameLengthLimit} characters`); - } - - const mxColumnLength = Column.getMaxColumnNameLength(sqlClientType); - - for (const column of req.body.columns) { - if (column.column_name.length > mxColumnLength) { - NcError.badRequest( - `Column name ${column.column_name} exceeds ${mxColumnLength} characters` - ); - } - } - - req.body.columns = req.body.columns?.map((c) => ({ - ...getColumnPropsFromUIDT(c as any, base), - cn: c.column_name, - })); - await sqlMgr.sqlOpPlus(base, 'tableCreate', { - ...req.body, - tn: req.body.table_name, + const result = tableService.createTable({ + projectId: req.params.projectId, + baseId: req.params.baseId, + table: req.body, + user: (req as any).session?.passport?.user, }); - const columns: Array< - Omit & { - cn: string; - system?: boolean; - } - > = (await sqlClient.columnList({ tn: req.body.table_name }))?.data?.list; - - const tables = await Model.list({ - project_id: project.id, - base_id: base.id, - }); - - await Audit.insert({ - project_id: project.id, - base_id: base.id, - op_type: AuditOperationTypes.TABLE, - op_sub_type: AuditOperationSubTypes.CREATED, - user: (req as any)?.user?.email, - description: `created table ${req.body.table_name} with alias ${req.body.title} `, - ip: (req as any).clientIp, - }).then(() => {}); - - mapDefaultDisplayValue(req.body.columns); - - Tele.emit('evt', { evt_type: 'table:created' }); - - res.json( - await Model.insert(project.id, base.id, { - ...req.body, - columns: columns.map((c, i) => { - const colMetaFromReq = req.body?.columns?.find( - (c1) => c.cn === c1.column_name - ) as NormalColumnRequestType; - return { - ...colMetaFromReq, - uidt: - (colMetaFromReq?.uidt as string) || - c.uidt || - getColumnUiType(base, c), - ...c, - dtxp: [UITypes.MultiSelect, UITypes.SingleSelect].includes( - colMetaFromReq.uidt as any - ) - ? colMetaFromReq.dtxp - : c.dtxp, - title: colMetaFromReq?.title || getColumnNameAlias(c.cn, base), - column_name: c.cn, - order: i + 1, - } as NormalColumnRequestType; - }), - order: +(tables?.pop()?.order ?? 0) + 1, - }) - ); + res.json(result); } -export async function tableUpdate(req: Request, res) { - const model = await Model.get(req.params.tableId); - - const project = await Project.getWithInfo( - req.body.project_id || (req as any).ncProjectId - ); - const base = project.bases.find((b) => b.id === model.base_id); - - if (model.project_id !== project.id) { - NcError.badRequest('Model does not belong to project'); - } - - // if meta present update meta and return - // todo: allow user to update meta and other prop in single api call - if ('meta' in req.body) { - await Model.updateMeta(req.params.tableId, req.body.meta); - - return res.json({ msg: 'success' }); - } - - if (!req.body.table_name) { - NcError.badRequest( - 'Missing table name `table_name` property in request body' - ); - } - - if (base.is_meta && project.prefix) { - if (!req.body.table_name.startsWith(project.prefix)) { - req.body.table_name = `${project.prefix}${req.body.table_name}`; - } - } - - req.body.table_name = DOMPurify.sanitize(req.body.table_name); - - // validate table name - if (/^\s+|\s+$/.test(req.body.table_name)) { - NcError.badRequest( - 'Leading or trailing whitespace not allowed in table names' - ); - } - - if ( - !(await Model.checkTitleAvailable({ - table_name: req.body.table_name, - project_id: project.id, - base_id: base.id, - })) - ) { - NcError.badRequest('Duplicate table name'); - } - - if (!req.body.title) { - req.body.title = getTableNameAlias( - req.body.table_name, - project.prefix, - base - ); - } - - if ( - !(await Model.checkAliasAvailable({ - title: req.body.title, - project_id: project.id, - base_id: base.id, - })) - ) { - NcError.badRequest('Duplicate table alias'); - } - - const sqlMgr = await ProjectMgrv2.getSqlMgr(project); - const sqlClient = await NcConnectionMgrv2.getSqlClient(base); - - let tableNameLengthLimit = 255; - const sqlClientType = sqlClient.knex.clientType(); - if (sqlClientType === 'mysql2' || sqlClientType === 'mysql') { - tableNameLengthLimit = 64; - } else if (sqlClientType === 'pg') { - tableNameLengthLimit = 63; - } else if (sqlClientType === 'mssql') { - tableNameLengthLimit = 128; - } - - if (req.body.table_name.length > tableNameLengthLimit) { - NcError.badRequest(`Table name exceeds ${tableNameLengthLimit} characters`); - } - - await Model.updateAliasAndTableName( - req.params.tableId, - req.body.title, - req.body.table_name - ); - - await sqlMgr.sqlOpPlus(base, 'tableRename', { - ...req.body, - tn: req.body.table_name, - tn_old: model.table_name, +export async function tableGet(req: Request, res: Response) { + const table = await tableService.getTableWithAccessibleViews({ + tableId: req.params.tableId, + user: (req as any).session?.passport?.user, }); - Tele.emit('evt', { evt_type: 'table:updated' }); - - res.json({ msg: 'success' }); + res.json(table); } export async function tableDelete(req: Request, res: Response) { - const table = await Model.getByIdOrName({ id: req.params.tableId }); - await table.getColumns(); - - const relationColumns = table.columns.filter( - (c) => c.uidt === UITypes.LinkToAnotherRecord - ); - - if (relationColumns?.length) { - const referredTables = await Promise.all( - relationColumns.map(async (c) => - c - .getColOptions() - .then((opt) => opt.getRelatedTable()) - .then() - ) - ); - NcError.badRequest( - `Table can't be deleted since Table is being referred in following tables : ${referredTables.join( - ', ' - )}. Delete LinkToAnotherRecord columns and try again.` - ); - } - - const project = await Project.getWithInfo(table.project_id); - const base = project.bases.find((b) => b.id === table.base_id); - const sqlMgr = await ProjectMgrv2.getSqlMgr(project); - (table as any).tn = table.table_name; - table.columns = table.columns.filter((c) => !isVirtualCol(c)); - table.columns.forEach((c) => { - (c as any).cn = c.column_name; + const result = await tableService.deleteTable({ + tableId: req.params.tableId, + user: (req as any).session?.passport?.user, }); - if (table.type === ModelTypes.TABLE) { - await sqlMgr.sqlOpPlus(base, 'tableDelete', table); - } else if (table.type === ModelTypes.VIEW) { - await sqlMgr.sqlOpPlus(base, 'viewDelete', { - ...table, - view_name: table.table_name, - }); - } - - await Audit.insert({ - project_id: project.id, - base_id: base.id, - op_type: AuditOperationTypes.TABLE, - op_sub_type: AuditOperationSubTypes.DELETED, - user: (req as any)?.user?.email, - description: `Deleted ${table.type} ${table.table_name} with alias ${table.title} `, - ip: (req as any).clientIp, - }).then(() => {}); - - Tele.emit('evt', { evt_type: 'table:deleted' }); + res.json(result); +} - res.json(await table.delete()); +export async function tableReorder(req: Request, res: Response) { + res.json( + await tableService.reorderTable({ + tableId: req.params.tableId, + order: req.body.order, + }) + ); } const router = Router({ mergeParams: true }); diff --git a/packages/nocodb/src/lib/models/ProjectUser.ts b/packages/nocodb/src/lib/models/ProjectUser.ts index d62f5490e8..d701853b2d 100644 --- a/packages/nocodb/src/lib/models/ProjectUser.ts +++ b/packages/nocodb/src/lib/models/ProjectUser.ts @@ -219,7 +219,7 @@ export default class ProjectUser { static async getProjectsList( userId: string, - _params: any, + _params?: any, ncMeta = Noco.ncMeta ): Promise { // todo: pagination diff --git a/packages/nocodb/src/lib/services/index.ts b/packages/nocodb/src/lib/services/index.ts new file mode 100644 index 0000000000..afe5d99b06 --- /dev/null +++ b/packages/nocodb/src/lib/services/index.ts @@ -0,0 +1,2 @@ +// export * as projectService from './projectService'; +export * as tableService from './tableService'; diff --git a/packages/nocodb/src/lib/services/projectService.ts b/packages/nocodb/src/lib/services/projectService.ts new file mode 100644 index 0000000000..1520f6de21 --- /dev/null +++ b/packages/nocodb/src/lib/services/projectService.ts @@ -0,0 +1,42 @@ +// // services/project.service.ts +// import { OrgUserRoles, ProjectType } from 'nocodb-sdk' +// import Project from '../models/Project' +// import ProjectUser from '../models/ProjectUser' +// import { customAlphabet } from 'nanoid' +// import { NcError } from './helpers/catchError' +// +// const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4) +// +// export async function getProjectWithInfo(projectId: string) { +// const project = await Project.getWithInfo(projectId) +// return project +// } +// +// export function sanitizeProject(project: any) { +// const sanitizedProject = { ...project } +// sanitizedProject.bases?.forEach((b: any) => { +// ['config'].forEach((k) => delete b[k]) +// }) +// return sanitizedProject +// } +// +// export async function updateProject(projectId: string, data: Partial) { +// const project = await Project.getWithInfo(projectId) +// if (data?.title && project.title !== data.title && (await Project.getByTitle(data.title))) { +// NcError.badRequest('Project title already in use') +// } +// const result = await Project.update(projectId, data) +// return result +// } +// +// export async function listProjects(user: any) { +// const projects = user?.roles?.includes(OrgUserRoles.SUPER_ADMIN) +// ? await Project.list() +// : await ProjectUser.getProjectsList(user.id) +// return projects as ProjectType[] +// } +// +// export async function softDeleteProject(projectId: string) { +// const result = await Project.softDelete(projectId) +// return result +// } diff --git a/packages/nocodb/src/lib/services/tableService.ts b/packages/nocodb/src/lib/services/tableService.ts new file mode 100644 index 0000000000..2bf2c4ca9f --- /dev/null +++ b/packages/nocodb/src/lib/services/tableService.ts @@ -0,0 +1,348 @@ +// @ts-ignore +import DOMPurify from 'isomorphic-dompurify'; +import { + AuditOperationSubTypes, + AuditOperationTypes, + isVirtualCol, + ModelTypes, + NormalColumnRequestType, + TableReqType, + UITypes, +} from 'nocodb-sdk'; +import ProjectMgrv2 from '../db/sql-mgr/v2/ProjectMgrv2'; +import { NcError } from '../meta/helpers/catchError'; +import getColumnPropsFromUIDT from '../meta/helpers/getColumnPropsFromUIDT'; +import getColumnUiType from '../meta/helpers/getColumnUiType'; +import getTableNameAlias, { + getColumnNameAlias, +} from '../meta/helpers/getTableName'; +import mapDefaultDisplayValue from '../meta/helpers/mapDefaultDisplayValue'; +import Audit from '../models/Audit'; +import Column from '../models/Column'; +import LinkToAnotherRecordColumn from '../models/LinkToAnotherRecordColumn'; +import Model from '../models/Model'; +import ModelRoleVisibility from '../models/ModelRoleVisibility'; +import Project from '../models/Project'; +import User from '../models/User'; +import View from '../models/View'; +import NcConnectionMgrv2 from '../utils/common/NcConnectionMgrv2'; +import { Tele } from 'nc-help'; + +export function reorderTable(param: { tableId: string; order: any }) { + return Model.updateOrder(param.tableId, param.order); +} + +export async function deleteTable(param: { tableId: string; user: User }) { + const table = await Model.getByIdOrName({ id: param.tableId }); + await table.getColumns(); + + const relationColumns = table.columns.filter( + (c) => c.uidt === UITypes.LinkToAnotherRecord + ); + + if (relationColumns?.length) { + const referredTables = await Promise.all( + relationColumns.map(async (c) => + c + .getColOptions() + .then((opt) => opt.getRelatedTable()) + .then() + ) + ); + NcError.badRequest( + `Table can't be deleted since Table is being referred in following tables : ${referredTables.join( + ', ' + )}. Delete LinkToAnotherRecord columns and try again.` + ); + } + + const project = await Project.getWithInfo(table.project_id); + const base = project.bases.find((b) => b.id === table.base_id); + const sqlMgr = await ProjectMgrv2.getSqlMgr(project); + (table as any).tn = table.table_name; + table.columns = table.columns.filter((c) => !isVirtualCol(c)); + table.columns.forEach((c) => { + (c as any).cn = c.column_name; + }); + + if (table.type === ModelTypes.TABLE) { + await sqlMgr.sqlOpPlus(base, 'tableDelete', table); + } else if (table.type === ModelTypes.VIEW) { + await sqlMgr.sqlOpPlus(base, 'viewDelete', { + ...table, + view_name: table.table_name, + }); + } + + await Audit.insert({ + project_id: project.id, + base_id: base.id, + op_type: AuditOperationTypes.TABLE, + op_sub_type: AuditOperationSubTypes.DELETED, + user: param.user?.email, + description: `Deleted ${table.type} ${table.table_name} with alias ${table.title} `, + // ip: (req as any).clientIp, + }).then(() => {}); + + Tele.emit('evt', { evt_type: 'table:deleted' }); + + return table.delete(); +} + +export async function getTableWithAccessibleViews(param: { + tableId: string; + user: User; +}) { + const table = await Model.getWithInfo({ + id: param.tableId, + }); + + // todo: optimise + const viewList = await xcVisibilityMetaGet(table.project_id, [table]); + + //await View.list(req.params.tableId) + table.views = viewList.filter((table: any) => { + return Object.keys(param.user?.roles).some( + (role) => param.user?.roles[role] && !table.disabled[role] + ); + }); + + return table; +} + +export async function xcVisibilityMetaGet( + projectId, + _models: Model[] = null, + includeM2M = true + // type: 'table' | 'tableAndViews' | 'views' = 'table' +) { + // todo: move to + const roles = ['owner', 'creator', 'viewer', 'editor', 'commenter', 'guest']; + + const defaultDisabled = roles.reduce((o, r) => ({ ...o, [r]: false }), {}); + + let models = + _models || + (await Model.list({ + project_id: projectId, + base_id: undefined, + })); + + models = includeM2M ? models : (models.filter((t) => !t.mm) as Model[]); + + const result = await models.reduce(async (_obj, model) => { + const obj = await _obj; + + const views = await model.getViews(); + for (const view of views) { + obj[view.id] = { + ptn: model.table_name, + _ptn: model.title, + ptype: model.type, + tn: view.title, + _tn: view.title, + table_meta: model.meta, + ...view, + disabled: { ...defaultDisabled }, + }; + } + + return obj; + }, Promise.resolve({})); + + const disabledList = await ModelRoleVisibility.list(projectId); + + for (const d of disabledList) { + if (result[d.fk_view_id]) + result[d.fk_view_id].disabled[d.role] = !!d.disabled; + } + + return Object.values(result); +} + +export async function getAccessibleTables(param: { + projectId: string; + baseId: string; + includeM2M?: boolean; + roles: Record; +}) { + const viewList = await xcVisibilityMetaGet(param.projectId); + + // todo: optimise + const tableViewMapping = viewList.reduce((o, view: any) => { + o[view.fk_model_id] = o[view.fk_model_id] || 0; + if ( + Object.keys(param.roles).some( + (role) => param.roles[role] && !view.disabled[role] + ) + ) { + o[view.fk_model_id]++; + } + return o; + }, {}); + + const tableList = ( + await Model.list({ + project_id: param.projectId, + base_id: param.baseId, + }) + ).filter((t) => tableViewMapping[t.id]); + + return param.includeM2M + ? tableList + : (tableList.filter((t) => !t.mm) as Model[]); +} + +export async function createTable(args: { + projectId: string; + baseId?: string; + table: TableReqType; + user: User; +}) { + const project = await Project.getWithInfo(args.projectId); + let base = project.bases[0]; + + if (args.baseId) { + base = project.bases.find((b) => b.id === args.baseId); + } + + if ( + !args.table.table_name || + (project.prefix && project.prefix === args.table.table_name) + ) { + NcError.badRequest( + 'Missing table name `table_name` property in request body' + ); + } + + if (base.is_meta && project.prefix) { + if (!args.table.table_name.startsWith(project.prefix)) { + args.table.table_name = `${project.prefix}_${args.table.table_name}`; + } + } + + args.table.table_name = DOMPurify.sanitize(args.table.table_name); + + // validate table name + if (/^\s+|\s+$/.test(args.table.table_name)) { + NcError.badRequest( + 'Leading or trailing whitespace not allowed in table names' + ); + } + + if ( + !(await Model.checkTitleAvailable({ + table_name: args.table.table_name, + project_id: project.id, + base_id: base.id, + })) + ) { + NcError.badRequest('Duplicate table name'); + } + + if (!args.table.title) { + args.table.title = getTableNameAlias( + args.table.table_name, + project.prefix, + base + ); + } + + if ( + !(await Model.checkAliasAvailable({ + title: args.table.title, + project_id: project.id, + base_id: base.id, + })) + ) { + NcError.badRequest('Duplicate table alias'); + } + + const sqlMgr = await ProjectMgrv2.getSqlMgr(project); + + const sqlClient = await NcConnectionMgrv2.getSqlClient(base); + + let tableNameLengthLimit = 255; + const sqlClientType = sqlClient.knex.clientType(); + if (sqlClientType === 'mysql2' || sqlClientType === 'mysql') { + tableNameLengthLimit = 64; + } else if (sqlClientType === 'pg') { + tableNameLengthLimit = 63; + } else if (sqlClientType === 'mssql') { + tableNameLengthLimit = 128; + } + + if (args.table.table_name.length > tableNameLengthLimit) { + NcError.badRequest(`Table name exceeds ${tableNameLengthLimit} characters`); + } + + const mxColumnLength = Column.getMaxColumnNameLength(sqlClientType); + + for (const column of args.table.columns) { + if (column.column_name.length > mxColumnLength) { + NcError.badRequest( + `Column name ${column.column_name} exceeds ${mxColumnLength} characters` + ); + } + } + + args.table.columns = args.table.columns?.map((c) => ({ + ...getColumnPropsFromUIDT(c as any, base), + cn: c.column_name, + })); + await sqlMgr.sqlOpPlus(base, 'tableCreate', { + ...args.table, + tn: args.table.table_name, + }); + + const columns: Array< + Omit & { + cn: string; + system?: boolean; + } + > = (await sqlClient.columnList({ tn: args.table.table_name }))?.data?.list; + + const tables = await Model.list({ + project_id: project.id, + base_id: base.id, + }); + + await Audit.insert({ + project_id: project.id, + base_id: base.id, + op_type: AuditOperationTypes.TABLE, + op_sub_type: AuditOperationSubTypes.CREATED, + user: args.user?.email, + description: `created table ${args.table.table_name} with alias ${args.table.title} `, + // ip: (req as any).clientIp, + }).then(() => {}); + + mapDefaultDisplayValue(args.table.columns); + + Tele.emit('evt', { evt_type: 'table:created' }); + + const result = await Model.insert(project.id, base.id, { + ...args.table, + columns: columns.map((c, i) => { + const colMetaFromReq = args.table?.columns?.find( + (c1) => c.cn === c1.column_name + ); + return { + ...colMetaFromReq, + uidt: colMetaFromReq?.uidt || c.uidt || getColumnUiType(base, c), + ...c, + dtxp: [UITypes.MultiSelect, UITypes.SingleSelect].includes( + colMetaFromReq.uidt as any + ) + ? colMetaFromReq.dtxp + : c.dtxp, + title: colMetaFromReq?.title || getColumnNameAlias(c.cn, base), + column_name: c.cn, + order: i + 1, + } as NormalColumnRequestType; + }), + order: +(tables?.pop()?.order ?? 0) + 1, + }); + + return result; +}