mirror of https://github.com/nocodb/nocodb
Pranav C
2 years ago
6 changed files with 428 additions and 377 deletions
@ -0,0 +1,2 @@
|
||||
// export * as projectService from './projectService';
|
||||
export * as tableService from './tableService'; |
@ -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<ProjectType>) {
|
||||
// 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
|
||||
// }
|
@ -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<LinkToAnotherRecordColumn>() |
||||
.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 = <View[]>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<string, boolean>; |
||||
}) { |
||||
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<Column, 'column_name' | 'title'> & { |
||||
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; |
||||
} |
Loading…
Reference in new issue