From 89adb8dafad8bd366dfa2256beeed72e14cc499e Mon Sep 17 00:00:00 2001 From: Pranav C Date: Tue, 28 Feb 2023 17:21:53 +0530 Subject: [PATCH] refcator: handler to service-controller - project, column, view, hook, base (WIP) Signed-off-by: Pranav C --- .../src/lib/controllers/filterController.ts | 130 ++++------- .../src/lib/controllers/projectController.ts | 177 +++------------ .../src/lib/controllers/sortController.ts | 50 +++-- .../src/lib/controllers/viewController.ts | 83 +++---- packages/nocodb/src/lib/models/Hook.ts | 8 +- packages/nocodb/src/lib/models/Project.ts | 2 +- packages/nocodb/src/lib/models/View.ts | 3 +- packages/nocodb/src/lib/models/index.ts | 36 +++ .../src/lib/services/apiTokenService.ts | 30 +++ .../nocodb/src/lib/services/baseService.ts | 78 +++++++ .../nocodb/src/lib/services/filterService.ts | 57 +++++ .../nocodb/src/lib/services/hookService.ts | 123 ++++++++++ packages/nocodb/src/lib/services/index.ts | 6 +- .../nocodb/src/lib/services/projectService.ts | 211 ++++++++++++++---- .../nocodb/src/lib/services/sortService.ts | 33 +++ .../nocodb/src/lib/services/viewService.ts | 85 +++++++ 16 files changed, 757 insertions(+), 355 deletions(-) create mode 100644 packages/nocodb/src/lib/models/index.ts create mode 100644 packages/nocodb/src/lib/services/apiTokenService.ts create mode 100644 packages/nocodb/src/lib/services/baseService.ts create mode 100644 packages/nocodb/src/lib/services/hookService.ts create mode 100644 packages/nocodb/src/lib/services/sortService.ts create mode 100644 packages/nocodb/src/lib/services/viewService.ts diff --git a/packages/nocodb/src/lib/controllers/filterController.ts b/packages/nocodb/src/lib/controllers/filterController.ts index 6ca705b88b..d83f3e22b7 100644 --- a/packages/nocodb/src/lib/controllers/filterController.ts +++ b/packages/nocodb/src/lib/controllers/filterController.ts @@ -1,119 +1,73 @@ import { Request, Response, Router } from 'express'; -// @ts-ignore -import Model from '../models/Model'; -import { Tele } from 'nc-help'; -// @ts-ignore -import { PagedResponseImpl } from '../meta/helpers/PagedResponse'; -// @ts-ignore -import { Table, TableList, TableListParams, TableReq } from 'nocodb-sdk'; -// @ts-ignore -import ProjectMgrv2 from '../db/sql-mgr/v2/ProjectMgrv2'; -// @ts-ignore -import Project from '../models/Project'; -import Filter from '../models/Filter'; +import { FilterReqType } from 'nocodb-sdk'; +import { getAjvValidatorMw } from '../meta/api/helpers'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import { metaApiMetrics } from '../meta/helpers/apiMetrics'; -import { getAjvValidatorMw } from '../meta/api/helpers'; + +import { filterService } from '../services'; // @ts-ignore export async function filterGet(req: Request, res: Response) { - res.json(await Filter.get(req.params.filterId)); + res.json(await filterService.filterGet({ filterId: req.params.filterId })); } // @ts-ignore -export async function filterList( - req: Request, - res: Response, - next -) { +export async function filterList(req: Request, res: Response) { res.json( - await Filter.rootFilterList({ + await filterService.filterList({ viewId: req.params.viewId, }) ); } + // @ts-ignore -export async function filterChildrenRead( - req: Request, - res: Response, - next -) { - try { - const filter = await Filter.parentFilterList({ - parentId: req.params.filterParentId, - }); +export async function filterChildrenRead(req: Request, res: Response) { + const filter = await filterService.filterChildrenList({ + filterId: req.params.filterParentId, + }); - res.json(filter); - } catch (e) { - console.log(e); - next(e); - } + res.json(filter); } -export async function filterCreate( - req: Request, - res, - next -) { - try { - const filter = await Filter.insert({ - ...req.body, - fk_view_id: req.params.viewId, - }); +export async function filterCreate(req: Request, res) { + const filter = await filterService.filterCreate({ + filter: req.body, + viewId: req.params.viewId, + }); + res.json(filter); +} - Tele.emit('evt', { evt_type: 'filter:created' }); - res.json(filter); - } catch (e) { - console.log(e); - next(e); - } +export async function filterUpdate(req, res) { + const filter = await filterService.filterUpdate({ + filterId: req.params.filterId, + filter: req.body, + }); + res.json(filter); } -// @ts-ignore -export async function filterUpdate(req, res, next) { - try { - const filter = await Filter.update(req.params.filterId, { - ...req.body, - fk_view_id: req.params.viewId, - }); - Tele.emit('evt', { evt_type: 'filter:updated' }); - res.json(filter); - } catch (e) { - console.log(e); - next(e); - } +export async function filterDelete(req: Request, res: Response) { + const filter = await filterService.filterDelete({ + filterId: req.params.filterId, + }); + res.json(filter); } -// @ts-ignore -export async function filterDelete(req: Request, res: Response, next) { - try { - const filter = await Filter.delete(req.params.filterId); - Tele.emit('evt', { evt_type: 'filter:deleted' }); - res.json(filter); - } catch (e) { - console.log(e); - next(e); - } +export async function hookFilterList(req: Request, res: Response) { + res.json( + await filterService.hookFilterList({ + hookId: req.params.hookId, + }) + ); } -export async function hookFilterList( - req: Request, - res: Response +export async function hookFilterCreate( + req: Request, + res ) { - const filter = await Filter.rootFilterListByHook({ + const filter = await filterService.hookFilterCreate({ + filter: req.body, hookId: req.params.hookId, }); - - res.json(filter); -} - -export async function hookFilterCreate(req: Request, res) { - const filter = await Filter.insert({ - ...req.body, - fk_hook_id: req.params.hookId, - }); - - Tele.emit('evt', { evt_type: 'hookFilter:created' }); res.json(filter); } diff --git a/packages/nocodb/src/lib/controllers/projectController.ts b/packages/nocodb/src/lib/controllers/projectController.ts index 89351269b8..5e65b8efdd 100644 --- a/packages/nocodb/src/lib/controllers/projectController.ts +++ b/packages/nocodb/src/lib/controllers/projectController.ts @@ -1,27 +1,20 @@ import { Request, Response } from 'express'; -import { OrgUserRoles, ProjectType } from 'nocodb-sdk'; +import { ProjectType } from 'nocodb-sdk'; import Project from '../models/Project'; import { ProjectListType } from 'nocodb-sdk'; -import DOMPurify from 'isomorphic-dompurify'; import { packageVersion } from '../utils/packageVersion'; import { Tele } from 'nc-help'; import { PagedResponseImpl } from '../meta/helpers/PagedResponse'; -import syncMigration from '../meta/helpers/syncMigration'; import NcConnectionMgrv2 from '../utils/common/NcConnectionMgrv2'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ProjectUser from '../models/ProjectUser'; -import { customAlphabet } from 'nanoid'; import Noco from '../Noco'; import isDocker from 'is-docker'; -import { NcError } from '../meta/helpers/catchError'; import { metaApiMetrics } from '../meta/helpers/apiMetrics'; -import { extractPropsAndSanitize } from '../meta/helpers/extractProps'; -import NcConfigFactory from '../utils/NcConfigFactory'; -import { promisify } from 'util'; -import { getAjvValidatorMw, populateMeta } from '../meta/api/helpers'; +import { getAjvValidatorMw } from '../meta/api/helpers'; import Filter from '../models/Filter'; -const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4); +import { projectService } from '../services'; // // Project CRUD @@ -29,13 +22,12 @@ export async function projectGet( req: Request, res: Response ) { - const project = await Project.getWithInfo(req.params.projectId); - - // delete datasource connection details - project.bases?.forEach((b) => { - ['config'].forEach((k) => delete b[k]); + const project = await projectService.getProjectWithInfo({ + projectId: req.params.projectId, }); + projectService.sanitizeProject(project); + res.json(project); } @@ -43,154 +35,49 @@ export async function projectUpdate( req: Request, res: Response ) { - const project = await Project.getWithInfo(req.params.projectId); - - const data: Partial = extractPropsAndSanitize(req?.body, [ - 'title', - 'meta', - 'color', - ]); - - if ( - data?.title && - project.title !== data.title && - (await Project.getByTitle(data.title)) - ) { - NcError.badRequest('Project title already in use'); - } + const project = await projectService.projectUpdate({ + projectId: req.params.projectId, + project: req.body, + }); - const result = await Project.update(req.params.projectId, data); - Tele.emit('evt', { evt_type: 'project:update' }); - res.json(result); + res.json(project); } export async function projectList( req: Request & { user: { id: string; roles: string } }, - res: Response, - next + res: Response ) { - try { - const projects = req.user?.roles?.includes(OrgUserRoles.SUPER_ADMIN) - ? await Project.list(req.query) - : await ProjectUser.getProjectsList(req.user.id, req.query); + const projects = await projectService.projectList({ + user: req.user, + query: req.query, + }); - res // todo: pagination - .json( - new PagedResponseImpl(projects as ProjectType[], { - count: projects.length, - limit: projects.length, - }) - ); - } catch (e) { - console.log(e); - next(e); - } + res.json( + new PagedResponseImpl(projects as ProjectType[], { + count: projects.length, + limit: projects.length, + }) + ); } export async function projectDelete( req: Request, - res: Response + res: Response ) { - const result = await Project.softDelete(req.params.projectId); - Tele.emit('evt', { evt_type: 'project:deleted' }); - res.json(result); -} - -// -// - -async function projectCreate(req: Request, res) { - const projectBody = req.body; - if (!projectBody.external) { - const ranId = nanoid(); - projectBody.prefix = `nc_${ranId}__`; - projectBody.is_meta = true; - if (process.env.NC_MINIMAL_DBS) { - // if env variable NC_MINIMAL_DBS is set, then create a SQLite file/connection for each project - // each file will be named as nc_.db - const fs = require('fs'); - const toolDir = NcConfigFactory.getToolDir(); - const nanoidv2 = customAlphabet( - '1234567890abcdefghijklmnopqrstuvwxyz', - 14 - ); - if (!(await promisify(fs.exists)(`${toolDir}/nc_minimal_dbs`))) { - await promisify(fs.mkdir)(`${toolDir}/nc_minimal_dbs`); - } - const dbId = nanoidv2(); - const projectTitle = DOMPurify.sanitize(projectBody.title); - projectBody.prefix = ''; - projectBody.bases = [ - { - type: 'sqlite3', - config: { - client: 'sqlite3', - connection: { - client: 'sqlite3', - database: projectTitle, - connection: { - filename: `${toolDir}/nc_minimal_dbs/${projectTitle}_${dbId}.db`, - }, - }, - }, - inflection_column: 'camelize', - inflection_table: 'camelize', - }, - ]; - } else { - const db = Noco.getConfig().meta?.db; - projectBody.bases = [ - { - type: db?.client, - config: null, - is_meta: true, - inflection_column: 'camelize', - inflection_table: 'camelize', - }, - ]; - } - } else { - if (process.env.NC_CONNECT_TO_EXTERNAL_DB_DISABLED) { - NcError.badRequest('Connecting to external db is disabled'); - } - projectBody.is_meta = false; - } - - if (projectBody?.title.length > 50) { - NcError.badRequest('Project title exceeds 50 characters'); - } - - if (await Project.getByTitle(projectBody?.title)) { - NcError.badRequest('Project title already in use'); - } - - projectBody.title = DOMPurify.sanitize(projectBody.title); - projectBody.slug = projectBody.title; - - const project = await Project.createProject(projectBody); - await ProjectUser.insert({ - fk_user_id: (req as any).user.id, - project_id: project.id, - roles: 'owner', + const deleted = await projectService.projectSoftDelete({ + projectId: req.params.projectId, }); - await syncMigration(project); - - // populate metadata if existing table - for (const base of await project.getBases()) { - const info = await populateMeta(base, project); + res.json(deleted); +} - Tele.emit('evt_api_created', info); - delete base.config; - } - Tele.emit('evt', { - evt_type: 'project:created', - xcdb: !projectBody.external, +async function projectCreate(req: Request, res) { + const project = await projectService.projectCreate({ + project: req.body, + user: req['user'], }); - Tele.emit('evt', { evt_type: 'project:rest' }); - res.json(project); } diff --git a/packages/nocodb/src/lib/controllers/sortController.ts b/packages/nocodb/src/lib/controllers/sortController.ts index c1ee743b51..c9ee69515a 100644 --- a/packages/nocodb/src/lib/controllers/sortController.ts +++ b/packages/nocodb/src/lib/controllers/sortController.ts @@ -1,52 +1,52 @@ import { Request, Response, Router } from 'express'; -// @ts-ignore -import Model from '../models/Model'; -import { Tele } from 'nc-help'; -// @ts-ignore +import { getAjvValidatorMw } from '../meta/api/helpers' import { PagedResponseImpl } from '../meta/helpers/PagedResponse'; -import { SortListType, SortType, TableType } from 'nocodb-sdk'; -// @ts-ignore -import ProjectMgrv2 from '../db/sql-mgr/v2/ProjectMgrv2'; -// @ts-ignore -import Project from '../models/Project'; -import Sort from '../models/Sort'; +import { SortListType, SortReqType } from 'nocodb-sdk'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import { metaApiMetrics } from '../meta/helpers/apiMetrics'; -import { getAjvValidatorMw } from '../meta/api/helpers'; -// @ts-ignore -export async function sortGet(req: Request, res: Response) {} +import { sortService } from '../services'; // @ts-ignore export async function sortList( req: Request, res: Response ) { - const sortList = await Sort.list({ viewId: req.params.viewId }); + const sortList = await sortService.sortList({ + viewId: req.params.viewId, + }); res.json({ sorts: new PagedResponseImpl(sortList), }); } // @ts-ignore -export async function sortCreate(req: Request, res) { - const sort = await Sort.insert({ - ...req.body, - fk_view_id: req.params.viewId, - } as Sort); - Tele.emit('evt', { evt_type: 'sort:created' }); +export async function sortCreate(req: Request, res) { + const sort = await sortService.sortCreate({ + sort: req.body, + viewId: req.params.viewId, + }); res.json(sort); } export async function sortUpdate(req, res) { - const sort = await Sort.update(req.params.sortId, req.body); - Tele.emit('evt', { evt_type: 'sort:updated' }); + const sort = await sortService.sortUpdate({ + sortId: req.params.sortId, + sort: req.body, + }); res.json(sort); } export async function sortDelete(req: Request, res: Response) { - Tele.emit('evt', { evt_type: 'sort:deleted' }); - const sort = await Sort.delete(req.params.sortId); + const sort = await sortService.sortDelete({ + sortId: req.params.sortId, + }); + res.json(sort); +} +export async function sortGet(req: Request, res: Response) { + const sort = await sortService.sortGet({ + sortId: req.params.sortId, + }); res.json(sort); } @@ -62,11 +62,13 @@ router.post( getAjvValidatorMw('swagger.json#/components/schemas/SortReq'), ncMetaAclMw(sortCreate, 'sortCreate') ); + router.get( '/api/v1/db/meta/sorts/:sortId', metaApiMetrics, ncMetaAclMw(sortGet, 'sortGet') ); + router.patch( '/api/v1/db/meta/sorts/:sortId', metaApiMetrics, diff --git a/packages/nocodb/src/lib/controllers/viewController.ts b/packages/nocodb/src/lib/controllers/viewController.ts index 4cc86a2d54..e0a1cc6631 100644 --- a/packages/nocodb/src/lib/controllers/viewController.ts +++ b/packages/nocodb/src/lib/controllers/viewController.ts @@ -1,43 +1,21 @@ import { Request, Response, Router } from 'express'; -// @ts-ignore -import Model from '../models/Model'; -import { Tele } from 'nc-help'; -// @ts-ignore import { PagedResponseImpl } from '../meta/helpers/PagedResponse'; -// @ts-ignore -import { Table, TableReq, ViewList } from 'nocodb-sdk'; -// @ts-ignore -import ProjectMgrv2 from '../db/sql-mgr/v2/ProjectMgrv2'; -// @ts-ignore -import Project from '../models/Project'; -import View from '../models/View'; +import { View } from '../models'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; -import { xcVisibilityMetaGet } from './modelVisibilityController'; import { metaApiMetrics } from '../meta/helpers/apiMetrics'; +import { viewService } from '../services'; + // @ts-ignore -export async function viewGet(req: Request, res: Response) {} +export async function viewGet(req: Request, res: Response) {} // @ts-ignore export async function viewList( req: Request, - res: Response + res: Response ) { - const model = await Model.get(req.params.tableId); - - const viewList = await xcVisibilityMetaGet( - // req.params.projectId, - // req.params.baseId, - model.project_id, - [model] - ); - - //await View.list(req.params.tableId) - const filteredViewList = viewList.filter((view: any) => { - return Object.keys((req as any).session?.passport?.user?.roles).some( - (role) => - (req as any)?.session?.passport?.user?.roles[role] && - !view.disabled[role] - ); + const filteredViewList = await viewService.viewList({ + tableId: req.params.tableId, + user: (req as any).session?.passport?.user, }); res.json(new PagedResponseImpl(filteredViewList)); @@ -48,8 +26,7 @@ export async function shareView( req: Request, res: Response ) { - Tele.emit('evt', { evt_type: 'sharedView:generated-link' }); - res.json(await View.share(req.params.viewId)); + res.json(await viewService.shareView({ viewId: req.params.viewId })); } // @ts-ignore @@ -57,48 +34,56 @@ export async function viewCreate(req: Request, res, next) {} // @ts-ignore export async function viewUpdate(req, res) { - const result = await View.update(req.params.viewId, req.body); - Tele.emit('evt', { evt_type: 'vtable:updated', show_as: result.type }); + const result = await viewService.viewUpdate({ + viewId: req.params.viewId, + view: req.body, + }); res.json(result); } // @ts-ignore export async function viewDelete(req: Request, res: Response, next) { - const result = await View.delete(req.params.viewId); - Tele.emit('evt', { evt_type: 'vtable:deleted' }); + const result = await viewService.viewDelete({ viewId: req.params.viewId }); res.json(result); } async function shareViewUpdate(req: Request, res) { - Tele.emit('evt', { evt_type: 'sharedView:updated' }); - res.json(await View.update(req.params.viewId, req.body)); + res.json( + await viewService.shareViewUpdate({ + viewId: req.params.viewId, + sharedView: req.body, + }) + ); } async function shareViewDelete(req: Request, res) { - Tele.emit('evt', { evt_type: 'sharedView:deleted' }); - res.json(await View.sharedViewDelete(req.params.viewId)); + res.json(await viewService.shareViewDelete({ viewId: req.params.viewId })); } async function showAllColumns(req: Request, res) { res.json( - await View.showAllColumns( - req.params.viewId, - (req.query?.ignoreIds || []) - ) + await viewService.showAllColumns({ + viewId: req.params.viewId, + ignoreIds: (req.query?.ignoreIds || []), + }) ); } async function hideAllColumns(req: Request, res) { res.json( - await View.hideAllColumns( - req.params.viewId, - (req.query?.ignoreIds || []) - ) + await viewService.hideAllColumns({ + viewId: req.params.viewId, + ignoreIds: (req.query?.ignoreIds || []), + }) ); } async function shareViewList(req: Request, res) { - res.json(await View.shareViewList(req.params.tableId)); + res.json( + await viewService.shareViewList({ + tableId: req.params.tableId, + }) + ); } const router = Router({ mergeParams: true }); diff --git a/packages/nocodb/src/lib/models/Hook.ts b/packages/nocodb/src/lib/models/Hook.ts index 7c32d09bbd..47a640d8d9 100644 --- a/packages/nocodb/src/lib/models/Hook.ts +++ b/packages/nocodb/src/lib/models/Hook.ts @@ -1,4 +1,4 @@ -import { HookType } from 'nocodb-sdk'; +import { BoolType, HookType } from 'nocodb-sdk' import { CacheDelDirection, CacheGetType, @@ -21,16 +21,16 @@ export default class Hook implements HookType { type?: string; event?: 'after' | 'before'; operation?: 'insert' | 'delete' | 'update'; - async?: boolean; + async?: BoolType; payload?: string; url?: string; headers?: string; - condition?: boolean; + condition?: BoolType; notification?: string; retries?: number; retry_interval?: number; timeout?: number; - active?: boolean; + active?: BoolType; project_id?: string; base_id?: string; diff --git a/packages/nocodb/src/lib/models/Project.ts b/packages/nocodb/src/lib/models/Project.ts index 5ee799d1ab..a3774b96d9 100644 --- a/packages/nocodb/src/lib/models/Project.ts +++ b/packages/nocodb/src/lib/models/Project.ts @@ -1,6 +1,6 @@ import Base from './/Base'; import Noco from '../Noco'; -import { BoolType, MetaType, ProjectType } from 'nocodb-sdk'; +import { BoolType, MetaType, ProjectType } from 'nocodb-sdk' import { CacheDelDirection, CacheGetType, diff --git a/packages/nocodb/src/lib/models/View.ts b/packages/nocodb/src/lib/models/View.ts index 41e17d6d67..e453da37ea 100644 --- a/packages/nocodb/src/lib/models/View.ts +++ b/packages/nocodb/src/lib/models/View.ts @@ -15,6 +15,7 @@ import GridViewColumn from './GridViewColumn'; import Sort from './Sort'; import Filter from './Filter'; import { + BoolType, ColumnReqType, isSystemColumn, UITypes, @@ -942,7 +943,7 @@ export default class View implements ViewType { body: { title?: string; order?: number; - show_system_fields?: boolean; + show_system_fields?: BoolType; lock_type?: string; password?: string; uuid?: string; diff --git a/packages/nocodb/src/lib/models/index.ts b/packages/nocodb/src/lib/models/index.ts new file mode 100644 index 0000000000..534c8ce025 --- /dev/null +++ b/packages/nocodb/src/lib/models/index.ts @@ -0,0 +1,36 @@ +export { default as ApiToken } from './ApiToken'; +export { default as Audit } from './Audit'; +export { default as BarcodeColumn } from './BarcodeColumn'; +export { default as Base } from './Base'; +export { default as Column } from './Column'; +export { default as Filter } from './Filter'; +export { default as FormulaColumn } from './FormulaColumn'; +export { default as FormView } from './FormView'; +export { default as FormViewColumn } from './FormViewColumn'; +export { default as GalleryView } from './GalleryView'; +export { default as GalleryViewColumn } from './GalleryViewColumn'; +export { default as GridView } from './GridView'; +export { default as GridViewColumn } from './GridViewColumn'; +export { default as Hook } from './Hook'; +export { default as HookFilter } from './HookFilter'; +export { default as HookLog } from './HookLog'; +export { default as KanbanView } from './KanbanView'; +export { default as KanbanViewColumn } from './KanbanViewColumn'; +export { default as LinkToAnotherRecordColumn } from './LinkToAnotherRecordColumn'; +export { default as LookupColumn } from './LookupColumn'; +export { default as MapView } from './MapView'; +export { default as MapViewColumn } from './MapViewColumn'; +export { default as Model } from './Model'; +export { default as ModelRoleVisibility } from './ModelRoleVisibility'; +export { default as Plugin } from './Plugin'; +export { default as Project } from './Project'; +export { default as ProjectUser } from './ProjectUser'; +export { default as QrCodeColumn } from './QrCodeColumn'; +export { default as RollupColumn } from './RollupColumn'; +export { default as SelectOption } from './SelectOption'; +export { default as Sort } from './Sort'; +export { default as Store } from './Store'; +export { default as SyncLogs } from './SyncLogs'; +export { default as SyncSource } from './SyncSource'; +export { default as User } from './User'; +export { default as View } from './View'; diff --git a/packages/nocodb/src/lib/services/apiTokenService.ts b/packages/nocodb/src/lib/services/apiTokenService.ts new file mode 100644 index 0000000000..aa106328f6 --- /dev/null +++ b/packages/nocodb/src/lib/services/apiTokenService.ts @@ -0,0 +1,30 @@ +import { ApiTokenReqType, OrgUserRoles } from 'nocodb-sdk'; +import { Tele } from 'nc-help'; +import { NcError } from '../meta/helpers/catchError'; +import ApiToken from '../models/ApiToken'; +import User from '../models/User'; + +export async function apiTokenList(param: { userId: string }) { + return ApiToken.list(param.userId); +} +export async function apiTokenCreate(param: { + userId: string; + tokenBody: ApiTokenReqType; +}) { + Tele.emit('evt', { evt_type: 'apiToken:created' }); + return ApiToken.insert({ ...param.tokenBody, fk_user_id: param.userId }); +} + +export async function apiTokenDelete(param: { token; user: User }) { + const apiToken = await ApiToken.getByToken(param.token); + if ( + !param.user.roles.includes(OrgUserRoles.SUPER_ADMIN) && + apiToken.fk_user_id !== param.user.id + ) { + NcError.notFound('Token not found'); + } + Tele.emit('evt', { evt_type: 'apiToken:deleted' }); + + // todo: verify token belongs to the user + return await ApiToken.delete(param.token); +} diff --git a/packages/nocodb/src/lib/services/baseService.ts b/packages/nocodb/src/lib/services/baseService.ts new file mode 100644 index 0000000000..1f390198c0 --- /dev/null +++ b/packages/nocodb/src/lib/services/baseService.ts @@ -0,0 +1,78 @@ +import Project from '../models/Project'; +import { BaseReqType } from 'nocodb-sdk'; +import { syncBaseMigration } from '../meta/helpers/syncMigration'; +import Base from '../models/Base'; +import { Tele } from 'nc-help'; +import { populateMeta } from '../meta/api/helpers'; + +export async function baseGetWithConfig(param: { baseId: any }) { + const base = await Base.get(param.baseId); + + base.config = base.getConnectionConfig(); + + return base; +} + +export async function baseUpdate(param: { + baseId: string; + base: BaseReqType; + projectId: string; +}) { + const baseBody = param.base; + const project = await Project.getWithInfo(param.projectId); + const base = await Base.updateBase(param.baseId, { + ...baseBody, + type: baseBody.config?.client, + projectId: project.id, + id: param.baseId, + }); + + delete base.config; + + Tele.emit('evt', { + evt_type: 'base:updated', + }); + + return base; +} + +export async function baseList(param: { projectId: string }) { + const bases = await Base.list({ projectId: param.projectId }); + + return bases; +} + +export async function baseDelete(param: { baseId: string }) { + const base = await Base.get(param.baseId); + await base.delete(); + Tele.emit('evt', { evt_type: 'base:deleted' }); + return true; +} + +export async function baseCreate(param: { + projectId: string; + base: BaseReqType; +}) { + // type | base | projectId + const baseBody = param.base; + const project = await Project.getWithInfo(param.projectId); + const base = await Base.createBase({ + ...baseBody, + type: baseBody.config?.client, + projectId: project.id, + }); + + await syncBaseMigration(project, base); + + const info = await populateMeta(base, project); + + Tele.emit('evt_api_created', info); + + delete base.config; + + Tele.emit('evt', { + evt_type: 'base:created', + }); + + return base; +} diff --git a/packages/nocodb/src/lib/services/filterService.ts b/packages/nocodb/src/lib/services/filterService.ts index dd7e8799af..8b40b4b813 100644 --- a/packages/nocodb/src/lib/services/filterService.ts +++ b/packages/nocodb/src/lib/services/filterService.ts @@ -1,4 +1,61 @@ +import { FilterReqType } from 'nocodb-sdk' import Filter from '../models/Filter' +import { Tele } from 'nc-help'; + +export async function hookFilterCreate(param: { filter: FilterReqType; hookId: any }) { + const filter = await Filter.insert({ + ...param.filter, + fk_hook_id: param.hookId, + }) + + Tele.emit('evt', { evt_type: 'hookFilter:created' }) + return filter +} + + +export async function hookFilterList(param: { hookId: any }) { + return Filter.rootFilterListByHook({ hookId: param.hookId }) +} + +export async function filterDelete(param: { filterId: string }) { + await Filter.delete(param.filterId) + Tele.emit('evt', { evt_type: 'filter:deleted' }) + return true; +} + + +export async function filterCreate(param: { + filter: FilterReqType + viewId: string +}) { + const filter = await Filter.insert({ + ...param.filter, + fk_view_id: param.viewId, + }); + + Tele.emit('evt', { evt_type: 'filter:created' }); + + return filter +} +export async function filterUpdate(param: { + filter: FilterReqType + filterId: string +}) { + const filter = await Filter.update(param.filterId, param.filter) + + + Tele.emit('evt', { evt_type: 'filter:updated' }) + + return filter +} + + +export async function filterChildrenList(param: { filterId: any }) { + return Filter.parentFilterList({ + parentId: param.filterId, + }) +} + export async function filterGet(param: { filterId: string }) { const filter = await Filter.get(param.filterId) diff --git a/packages/nocodb/src/lib/services/hookService.ts b/packages/nocodb/src/lib/services/hookService.ts new file mode 100644 index 0000000000..36de0ceb15 --- /dev/null +++ b/packages/nocodb/src/lib/services/hookService.ts @@ -0,0 +1,123 @@ +import { Tele } from 'nc-help'; +import catchError from '../helpers/catchError'; +import { Request, Response, Router } from 'express'; +import { Hook, Model } from '../models'; +import { HookListType, HookReqType, HookTestReqType, HookType, TableReqType } from 'nocodb-sdk' +import { PagedResponseImpl } from '../helpers/PagedResponse'; +import { invokeWebhook } from '../helpers/webhookHelpers'; +import populateSamplePayload from '../helpers/populateSamplePayload'; +import ncMetaAclMw from '../helpers/ncMetaAclMw'; +import { metaApiMetrics } from '../helpers/apiMetrics'; +import { getAjvValidatorMw } from './helpers'; + +export async function hookList( +param: { + tableId: string; +} +) { + // todo: pagination + await Hook.list({ fk_model_id: param.tableId })) +} + +export async function hookCreate( +param: { + tableId: string; + hook: HookReqType +} +) { + Tele.emit('evt', { evt_type: 'webhooks:created' }); + const hook = await Hook.insert({ + ...param.hook, + fk_model_id: param.tableId, + }); + return hook; +} + +export async function hookDelete( +param :{ + hookId: string; +} +) { + Tele.emit('evt', { evt_type: 'webhooks:deleted' }); + await Hook.delete(param.hookId); + return true; +} + +export async function hookUpdate( + param: { + hookId: string; + hook: HookReqType; + } +) { + Tele.emit('evt', { evt_type: 'webhooks:updated' }); + + return await Hook.update(param.hookId, param.hook) +} + +export async function hookTest(param: { + tableId: string; + hookTest: HookTestReqType; +}) { + const model = await Model.getByIdOrName({ id: param.tableId }); + + const { + hook, + payload: { data, user }, + } = param.hookTest; + await invokeWebhook( + new Hook(hook), + model, + data, + user, + (hook as any)?.filters, + true + ); + + Tele.emit('evt', { evt_type: 'webhooks:tested' }); + + return true +} +export async function tableSampleData(param:{ + tableId: string; + operation: 'insert' | 'update'; +}) { + const model = await Model.getByIdOrName({ id: param.tableId }); + + return await populateSamplePayload(model, false, param.operation); +} + +const router = Router({ mergeParams: true }); +router.get( + '/api/v1/db/meta/tables/:tableId/hooks', + metaApiMetrics, + ncMetaAclMw(hookList, 'hookList') +); +router.post( + '/api/v1/db/meta/tables/:tableId/hooks/test', + metaApiMetrics, + getAjvValidatorMw('swagger.json#/components/schemas/HookTestReq'), + ncMetaAclMw(hookTest, 'hookTest') +); +router.post( + '/api/v1/db/meta/tables/:tableId/hooks', + metaApiMetrics, + getAjvValidatorMw('swagger.json#/components/schemas/HookReq'), + ncMetaAclMw(hookCreate, 'hookCreate') +); +router.delete( + '/api/v1/db/meta/hooks/:hookId', + metaApiMetrics, + ncMetaAclMw(hookDelete, 'hookDelete') +); +router.patch( + '/api/v1/db/meta/hooks/:hookId', + metaApiMetrics, + getAjvValidatorMw('swagger.json#/components/schemas/HookReq'), + ncMetaAclMw(hookUpdate, 'hookUpdate') +); +router.get( + '/api/v1/db/meta/tables/:tableId/hooks/samplePayload/:operation', + metaApiMetrics, + catchError(tableSampleData) +); +export default router; diff --git a/packages/nocodb/src/lib/services/index.ts b/packages/nocodb/src/lib/services/index.ts index 45cc1853f4..2d53935889 100644 --- a/packages/nocodb/src/lib/services/index.ts +++ b/packages/nocodb/src/lib/services/index.ts @@ -1,4 +1,8 @@ -// export * as projectService from './projectService'; +export * as projectService from './projectService'; export * as tableService from './tableService'; export * as columnService from './columnService'; export * as filterService from './filterService'; +export * as sortService from './sortService'; +export * as baseService from './baseService'; +export * as apiTokenService from './apiTokenService'; +export * as viewService from './viewService'; diff --git a/packages/nocodb/src/lib/services/projectService.ts b/packages/nocodb/src/lib/services/projectService.ts index 1520f6de21..131df14557 100644 --- a/packages/nocodb/src/lib/services/projectService.ts +++ b/packages/nocodb/src/lib/services/projectService.ts @@ -1,42 +1,169 @@ -// // 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 -// } +import DOMPurify from 'isomorphic-dompurify'; +import { OrgUserRoles, ProjectReqType, } from 'nocodb-sdk'; +import { promisify } from 'util'; +import { populateMeta } from '../meta/api/helpers'; +import { extractPropsAndSanitize } from '../meta/helpers/extractProps'; +import syncMigration from '../meta/helpers/syncMigration'; +import Project from '../models/Project'; +import ProjectUser from '../models/ProjectUser'; +import { customAlphabet } from 'nanoid'; +import Noco from '../Noco'; +import NcConfigFactory from '../utils/NcConfigFactory'; +import { NcError } from '../meta/helpers/catchError'; + +export async function projectCreate(param: { + project: ProjectReqType; + user: any; +}) { + const projectBody: ProjectReqType & Record = param.project; + if (!projectBody.external) { + const ranId = nanoid(); + projectBody.prefix = `nc_${ranId}__`; + projectBody.is_meta = true; + if (process.env.NC_MINIMAL_DBS) { + // if env variable NC_MINIMAL_DBS is set, then create a SQLite file/connection for each project + // each file will be named as nc_.db + const fs = require('fs'); + const toolDir = NcConfigFactory.getToolDir(); + const nanoidv2 = customAlphabet( + '1234567890abcdefghijklmnopqrstuvwxyz', + 14 + ); + if (!(await promisify(fs.exists)(`${toolDir}/nc_minimal_dbs`))) { + await promisify(fs.mkdir)(`${toolDir}/nc_minimal_dbs`); + } + const dbId = nanoidv2(); + const projectTitle = DOMPurify.sanitize(projectBody.title); + projectBody.prefix = ''; + projectBody.bases = [ + { + type: 'sqlite3', + config: { + client: 'sqlite3', + connection: { + client: 'sqlite3', + database: projectTitle, + connection: { + filename: `${toolDir}/nc_minimal_dbs/${projectTitle}_${dbId}.db`, + }, + }, + }, + inflection_column: 'camelize', + inflection_table: 'camelize', + }, + ]; + } else { + const db = Noco.getConfig().meta?.db; + projectBody.bases = [ + { + type: db?.client, + config: null, + is_meta: true, + inflection_column: 'camelize', + inflection_table: 'camelize', + }, + ]; + } + } else { + if (process.env.NC_CONNECT_TO_EXTERNAL_DB_DISABLED) { + NcError.badRequest('Connecting to external db is disabled'); + } + projectBody.is_meta = false; + } + + if (projectBody?.title.length > 50) { + NcError.badRequest('Project title exceeds 50 characters'); + } + + if (await Project.getByTitle(projectBody?.title)) { + NcError.badRequest('Project title already in use'); + } + + projectBody.title = DOMPurify.sanitize(projectBody.title); + projectBody.slug = projectBody.title; + + const project = await Project.createProject(projectBody); + await ProjectUser.insert({ + fk_user_id: (param as any).user.id, + project_id: project.id, + roles: 'owner', + }); + + await syncMigration(project); + + // populate metadata if existing table + for (const base of await project.getBases()) { + const info = await populateMeta(base, project); + + Tele.emit('evt_api_created', info); + delete base.config; + } + + Tele.emit('evt', { + evt_type: 'project:created', + xcdb: !projectBody.external, + }); + + Tele.emit('evt', { evt_type: 'project:rest' }); + + project; +} + +const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4); + +import { Tele } from 'nc-help'; + +export async function getProjectWithInfo(param: { projectId: string }) { + const project = await Project.getWithInfo(param.projectId); + return project; +} + +export async function projectSoftDelete(param: { projectId: any }) { + await Project.softDelete(param.projectId); + Tele.emit('evt', { evt_type: 'project:deleted' }); + return true; +} + +export function sanitizeProject(project: any) { + const sanitizedProject = { ...project }; + sanitizedProject.bases?.forEach((b: any) => { + ['config'].forEach((k) => delete b[k]); + }); + return sanitizedProject; +} + +export async function projectUpdate(param: { + projectId: string; + project: ProjectReqType; +}) { + const project = await Project.getWithInfo(param.projectId); + + const data: Partial = extractPropsAndSanitize( + param?.project as Project, + ['title', 'meta', 'color'] + ); + + if ( + data?.title && + project.title !== data.title && + (await Project.getByTitle(data.title)) + ) { + NcError.badRequest('Project title already in use'); + } + + const result = await Project.update(param.projectId, data); + Tele.emit('evt', { evt_type: 'project:update' }); + + return result; +} + +export async function projectList(param: { + user: { id: string; roles: string }; + query?: any; +}) { + const projects = param.user?.roles?.includes(OrgUserRoles.SUPER_ADMIN) + ? await Project.list(param.query) + : await ProjectUser.getProjectsList(param.user.id, param.query); + + return projects; +} diff --git a/packages/nocodb/src/lib/services/sortService.ts b/packages/nocodb/src/lib/services/sortService.ts new file mode 100644 index 0000000000..4d69e278c8 --- /dev/null +++ b/packages/nocodb/src/lib/services/sortService.ts @@ -0,0 +1,33 @@ +import { SortReqType } from 'nocodb-sdk'; +import Sort from '../models/Sort'; +import { Tele } from 'nc-help'; + +export async function sortGet(param: { sortId: string }) { + return Sort.get(param.sortId); +} + + +export async function sortDelete(param: { sortId: string }) { + await Sort.delete(param.sortId); + Tele.emit('evt', { evt_type: 'sort:deleted' }); + return true; +} + +export async function sortUpdate(param: { sortId: any; sort: SortReqType }) { + const sort = await Sort.update(param.sortId, param.sort); + Tele.emit('evt', { evt_type: 'sort:updated' }); + return sort; +} + +export async function sortCreate(param: { viewId: any; sort: SortReqType }) { + const sort = await Sort.insert({ + ...param.sort, + fk_view_id: param.viewId, + } as Sort); + Tele.emit('evt', { evt_type: 'sort:created' }); + return sort; +} + +export async function sortList(param: { viewId: string }) { + return Sort.list({ viewId: param.viewId }); +} diff --git a/packages/nocodb/src/lib/services/viewService.ts b/packages/nocodb/src/lib/services/viewService.ts new file mode 100644 index 0000000000..4d8cf2f1bc --- /dev/null +++ b/packages/nocodb/src/lib/services/viewService.ts @@ -0,0 +1,85 @@ +import { Model, View } from '../models'; +import { Tele } from 'nc-help'; +import { SharedViewReqType, ViewReqType } from 'nocodb-sdk'; +import { xcVisibilityMetaGet } from '../meta/api/modelVisibilityApis'; + + +export async function viewList(param: { + tableId: string; + user: { + roles: Record; + }; +}) { + const model = await Model.get(param.tableId); + + const viewList = await xcVisibilityMetaGet( + // param.projectId, + // param.baseId, + model.project_id, + [model] + ); + + // todo: user roles + //await View.list(param.tableId) + const filteredViewList = viewList.filter((view: any) => { + return Object.keys(param?.user?.roles).some( + (role) => param?.user?.roles[role] && !view.disabled[role] + ); + }); + + return filteredViewList; +} + +// @ts-ignore +export async function shareView(param: { viewId: string }) { + Tele.emit('evt', { evt_type: 'sharedView:generated-link' }); + return await View.share(param.viewId); +} + +// @ts-ignore +export async function viewUpdate(param: { viewId: string; view: ViewReqType }) { + const result = await View.update(param.viewId, param.view); + Tele.emit('evt', { evt_type: 'vtable:updated', show_as: result.type }); + return result; +} + +// @ts-ignore +export async function viewDelete(param: { viewId: string }) { + await View.delete(param.viewId); + Tele.emit('evt', { evt_type: 'vtable:deleted' }); + return true; +} + +export async function shareViewUpdate(param: { + viewId: string; + sharedView: SharedViewReqType; +}) { + Tele.emit('evt', { evt_type: 'sharedView:updated' }); + return await View.update(param.viewId, param.sharedView); +} + +export async function shareViewDelete(param: { viewId: string }) { + Tele.emit('evt', { evt_type: 'sharedView:deleted' }); + await View.sharedViewDelete(param.viewId); + return true; +} + +export async function showAllColumns(param: { + viewId: string; + ignoreIds?: string[]; +}) { + await View.showAllColumns(param.viewId, param.ignoreIds || []); + return true; +} + +export async function hideAllColumns(param: { + viewId: string; + ignoreIds?: string[]; +}) { + await View.hideAllColumns(param.viewId, param.ignoreIds || []); + return true; +} + +export async function shareViewList(param: { tableId: string }) { + return await View.shareViewList(param.tableId); +}