Browse Source

refactor: move ajv validator from express middleware to service layer

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/5239/head
Pranav C 2 years ago
parent
commit
b7da5eaf40
  1. 36
      packages/nocodb/src/lib/controllers/apiTokenController.ts
  2. 13
      packages/nocodb/src/lib/controllers/auditController.ts
  3. 100
      packages/nocodb/src/lib/controllers/baseController.ts
  4. 2
      packages/nocodb/src/lib/controllers/columnController.ts
  5. 6
      packages/nocodb/src/lib/controllers/dataApis/dataAliasExportApis.ts
  6. 4
      packages/nocodb/src/lib/controllers/filterController.ts
  7. 2
      packages/nocodb/src/lib/controllers/formViewColumnController.ts
  8. 50
      packages/nocodb/src/lib/controllers/formViewController.ts
  9. 3
      packages/nocodb/src/lib/controllers/galleryViewController.ts
  10. 2
      packages/nocodb/src/lib/controllers/gridViewColumnController.ts
  11. 35
      packages/nocodb/src/lib/controllers/gridViewController.ts
  12. 4
      packages/nocodb/src/lib/controllers/hookController.ts
  13. 3
      packages/nocodb/src/lib/controllers/hookFilterController.ts
  14. 5
      packages/nocodb/src/lib/controllers/kanbanViewController.ts
  15. 2
      packages/nocodb/src/lib/controllers/modelVisibilityController.ts
  16. 2
      packages/nocodb/src/lib/controllers/orgLicenseController.ts
  17. 56
      packages/nocodb/src/lib/controllers/orgTokenController.ts
  18. 3
      packages/nocodb/src/lib/controllers/orgUserController.ts
  19. 4
      packages/nocodb/src/lib/controllers/pluginController.ts
  20. 2
      packages/nocodb/src/lib/controllers/projectController.ts
  21. 316
      packages/nocodb/src/lib/controllers/projectUserController.ts
  22. 97
      packages/nocodb/src/lib/controllers/sharedBaseController.ts
  23. 3
      packages/nocodb/src/lib/controllers/sortController.ts
  24. 117
      packages/nocodb/src/lib/controllers/tableController.ts
  25. 22
      packages/nocodb/src/lib/controllers/userController/index.ts
  26. 6
      packages/nocodb/src/lib/meta/api/helpers/apiHelpers.ts
  27. 164
      packages/nocodb/src/lib/models/Hook.ts
  28. 6
      packages/nocodb/src/lib/services/apiTokenService.ts
  29. 11
      packages/nocodb/src/lib/services/auditService.ts
  30. 6
      packages/nocodb/src/lib/services/baseService.ts
  31. 3
      packages/nocodb/src/lib/services/columnService.ts
  32. 2
      packages/nocodb/src/lib/services/dataService/index.ts
  33. 7
      packages/nocodb/src/lib/services/filterService.ts
  34. 6
      packages/nocodb/src/lib/services/formViewColumnService.ts
  35. 5
      packages/nocodb/src/lib/services/formViewService.ts
  36. 5
      packages/nocodb/src/lib/services/galleryViewService.ts
  37. 3
      packages/nocodb/src/lib/services/gridViewColumnService.ts
  38. 4
      packages/nocodb/src/lib/services/gridViewService.ts
  39. 5
      packages/nocodb/src/lib/services/hookFilterService.ts
  40. 10
      packages/nocodb/src/lib/services/hookService.ts
  41. 10
      packages/nocodb/src/lib/services/kanbanViewService.ts
  42. 7
      packages/nocodb/src/lib/services/modelVisibilityService.ts
  43. 3
      packages/nocodb/src/lib/services/orgLicenseService.ts
  44. 6
      packages/nocodb/src/lib/services/orgTokenService.ts
  45. 5
      packages/nocodb/src/lib/services/orgUserService.ts
  46. 5
      packages/nocodb/src/lib/services/pluginService.ts
  47. 4
      packages/nocodb/src/lib/services/projectService.ts
  48. 11
      packages/nocodb/src/lib/services/projectUserService.ts
  49. 5
      packages/nocodb/src/lib/services/sharedBaseService.ts
  50. 5
      packages/nocodb/src/lib/services/sortService.ts
  51. 21
      packages/nocodb/src/lib/services/tableService.ts
  52. 16
      packages/nocodb/src/lib/services/userService/index.ts

36
packages/nocodb/src/lib/controllers/apiTokenController.ts

@ -1,31 +1,28 @@
import { Request, Response, Router } from 'express'; import { Request, Response, Router } from 'express';
import { OrgUserRoles } from 'nocodb-sdk';
import { Tele } from 'nc-help';
import { NcError } from '../meta/helpers/catchError';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import ApiToken from '../models/ApiToken';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import { getAjvValidatorMw } from '../meta/api/helpers'; import { apiTokenService } from '../services';
export async function apiTokenList(req: Request, res: Response) { export async function apiTokenList(req: Request, res: Response) {
res.json(await ApiToken.list(req['user'].id)); res.json(await apiTokenService.apiTokenList({ userId: req['user'].id }));
} }
export async function apiTokenCreate(req: Request, res: Response) { export async function apiTokenCreate(req: Request, res: Response) {
Tele.emit('evt', { evt_type: 'apiToken:created' }); res.json(
res.json(await ApiToken.insert({ ...req.body, fk_user_id: req['user'].id })); await apiTokenService.apiTokenCreate({
tokenBody: req.body,
userId: req['user'].id,
})
);
} }
export async function apiTokenDelete(req: Request, res: Response) {
const apiToken = await ApiToken.getByToken(req.params.token);
if (
!req['user'].roles.includes(OrgUserRoles.SUPER_ADMIN) &&
apiToken.fk_user_id !== req['user'].id
) {
NcError.notFound('Token not found');
}
Tele.emit('evt', { evt_type: 'apiToken:deleted' });
// todo: verify token belongs to the user export async function apiTokenDelete(req: Request, res: Response) {
res.json(await ApiToken.delete(req.params.token)); res.json(
await apiTokenService.apiTokenDelete({
token: req.params.token,
user: req['user'],
})
);
} }
// todo: add reset token api to regenerate token // todo: add reset token api to regenerate token
@ -41,7 +38,6 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/projects/:projectId/api-tokens', '/api/v1/db/meta/projects/:projectId/api-tokens',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ApiTokenReq'),
ncMetaAclMw(apiTokenCreate, 'apiTokenCreate') ncMetaAclMw(apiTokenCreate, 'apiTokenCreate')
); );
router.delete( router.delete(

13
packages/nocodb/src/lib/controllers/auditController.ts

@ -1,18 +1,17 @@
import { Request, Response, Router } from 'express'; import { Request, Response, Router } from 'express';
import Audit from '../models/Audit'; import Audit from '../models/Audit';
import { AuditOperationTypes } from 'nocodb-sdk';
import { PagedResponseImpl } from '../meta/helpers/PagedResponse'; import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { getAjvValidatorMw } from '../meta/api/helpers';
import { auditService } from '../services'; import { auditService } from '../services';
export async function commentRow(req: Request<any, any>, res) { export async function commentRow(req: Request<any, any>, res) {
res.json( res.json(
await Audit.insert({ await auditService.commentRow({
...req.body, rowId: req.params.rowId,
user: (req as any).user, user: (req as any).user,
op_type: AuditOperationTypes.COMMENT, body: {
...req.body,
},
}) })
); );
} }
@ -60,12 +59,10 @@ router.get(
); );
router.post( router.post(
'/api/v1/db/meta/audits/comments', '/api/v1/db/meta/audits/comments',
getAjvValidatorMw('swagger.json#/components/schemas/CommentReq'),
ncMetaAclMw(commentRow, 'commentRow') ncMetaAclMw(commentRow, 'commentRow')
); );
router.post( router.post(
'/api/v1/db/meta/audits/rows/:rowId/update', '/api/v1/db/meta/audits/rows/:rowId/update',
getAjvValidatorMw('swagger.json#/components/schemas/AuditRowUpdateReq'),
ncMetaAclMw(auditRowUpdate, 'auditRowUpdate') ncMetaAclMw(auditRowUpdate, 'auditRowUpdate')
); );
router.get( router.get(

100
packages/nocodb/src/lib/controllers/baseController.ts

@ -1,104 +1,66 @@
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import Project from '../models/Project';
import { BaseListType } from 'nocodb-sdk'; import { BaseListType } from 'nocodb-sdk';
import { PagedResponseImpl } from '../meta/helpers/PagedResponse'; import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
import { syncBaseMigration } from '../meta/helpers/syncMigration';
import Base from '../models/Base'; import Base from '../models/Base';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { Tele } from 'nc-help';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import { getAjvValidatorMw, populateMeta } from '../meta/api/helpers';
export async function baseGet( import { baseService } from '../services';
req: Request<any, any, any>,
res: Response<Base>
) {
const base = await Base.get(req.params.baseId);
base.config = base.getConnectionConfig(); async function baseGet(req: Request<any>, res: Response<Base>) {
const base = await baseService.baseGetWithConfig({
baseId: req.params.baseId,
});
res.json(base); res.json(base);
} }
export async function baseUpdate( async function baseUpdate(req: Request<any, any, any>, res: Response<any>) {
req: Request<any, any, any>, const base = await baseService.baseUpdate({
res: Response<any> baseId: req.params.baseId,
) { base: req.body,
const baseBody = req.body; projectId: req.params.projectId,
const project = await Project.getWithInfo(req.params.projectId);
const base = await Base.updateBase(req.params.baseId, {
...baseBody,
type: baseBody.config?.client,
projectId: project.id,
id: req.params.baseId,
});
delete base.config;
Tele.emit('evt', {
evt_type: 'base:updated',
}); });
res.json(base); res.json(base);
} }
export async function baseList( async function baseList(
req: Request<any, any, any>, req: Request<any, any, any>,
res: Response<BaseListType>, res: Response<BaseListType>
next
) { ) {
try { const bases = await baseService.baseList({
const bases = await Base.list({ projectId: req.params.projectId }); projectId: req.params.projectId,
});
res // todo: pagination res // todo: pagination
.json({ .json({
bases: new PagedResponseImpl(bases, { bases: new PagedResponseImpl(bases, {
count: bases.length, count: bases.length,
limit: bases.length, limit: bases.length,
}), }),
}); });
} catch (e) {
console.log(e);
next(e);
}
} }
export async function baseDelete( export async function baseDelete(
req: Request<any, any, any>, req: Request<any, any, any>,
res: Response<any> res: Response<any>
) { ) {
const base = await Base.get(req.params.baseId); const result = await baseService.baseDelete({
const result = await base.delete(); baseId: req.params.baseId,
Tele.emit('evt', { evt_type: 'base:deleted' }); });
res.json(result); res.json(result);
} }
async function baseCreate(req: Request<any, any>, res) { async function baseCreate(req: Request<any, any>, res) {
// type | base | projectId const base = await baseService.baseCreate({
const baseBody = req.body; projectId: req.params.projectId,
const project = await Project.getWithInfo(req.params.projectId); base: req.body,
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',
}); });
res.json(base); res.json(base);
} }
export default (router) => { const initRoutes = (router) => {
router.get( router.get(
'/api/v1/db/meta/projects/:projectId/bases/:baseId', '/api/v1/db/meta/projects/:projectId/bases/:baseId',
metaApiMetrics, metaApiMetrics,
@ -107,7 +69,6 @@ export default (router) => {
router.patch( router.patch(
'/api/v1/db/meta/projects/:projectId/bases/:baseId', '/api/v1/db/meta/projects/:projectId/bases/:baseId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/BaseReq'),
ncMetaAclMw(baseUpdate, 'baseUpdate') ncMetaAclMw(baseUpdate, 'baseUpdate')
); );
router.delete( router.delete(
@ -118,7 +79,6 @@ export default (router) => {
router.post( router.post(
'/api/v1/db/meta/projects/:projectId/bases', '/api/v1/db/meta/projects/:projectId/bases',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/BaseReq'),
ncMetaAclMw(baseCreate, 'baseCreate') ncMetaAclMw(baseCreate, 'baseCreate')
); );
router.get( router.get(
@ -127,3 +87,5 @@ export default (router) => {
ncMetaAclMw(baseList, 'baseList') ncMetaAclMw(baseList, 'baseList')
); );
}; };
export default initRoutes;

2
packages/nocodb/src/lib/controllers/columnController.ts

@ -1,6 +1,5 @@
import { Request, Response, Router } from 'express'; import { Request, Response, Router } from 'express';
import { ColumnReqType, TableType, UITypes } from 'nocodb-sdk'; import { ColumnReqType, TableType, UITypes } from 'nocodb-sdk';
import { getAjvValidatorMw } from '../meta/api/helpers';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { columnService } from '../services'; import { columnService } from '../services';
@ -45,7 +44,6 @@ const router = Router({ mergeParams: true });
router.post( router.post(
'/api/v1/db/meta/tables/:tableId/columns/', '/api/v1/db/meta/tables/:tableId/columns/',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ColumnReq'),
ncMetaAclMw(columnAdd, 'columnAdd') ncMetaAclMw(columnAdd, 'columnAdd')
); );

6
packages/nocodb/src/lib/controllers/dataApis/dataAliasExportApis.ts

@ -15,7 +15,11 @@ async function excelDataExport(req: Request, res: Response) {
if (!targetView) { if (!targetView) {
targetView = await View.getDefaultView(model.id); targetView = await View.getDefaultView(model.id);
} }
const { offset, elapsed, data } = await extractXlsxData({ view: targetView, query: req.query, siteUrl: (req as any).ncSiteUrl }); const { offset, elapsed, data } = await extractXlsxData({
view: targetView,
query: req.query,
siteUrl: (req as any).ncSiteUrl,
});
const wb = XLSX.utils.book_new(); const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, data, targetView.title); XLSX.utils.book_append_sheet(wb, data, targetView.title);
const buf = XLSX.write(wb, { type: 'base64', bookType: 'xlsx' }); const buf = XLSX.write(wb, { type: 'base64', bookType: 'xlsx' });

4
packages/nocodb/src/lib/controllers/filterController.ts

@ -1,6 +1,5 @@
import { Request, Response, Router } from 'express'; import { Request, Response, Router } from 'express';
import { FilterReqType } from 'nocodb-sdk'; import { FilterReqType } from 'nocodb-sdk';
import { getAjvValidatorMw } from '../meta/api/helpers';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
@ -80,7 +79,6 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/views/:viewId/filters', '/api/v1/db/meta/views/:viewId/filters',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
ncMetaAclMw(filterCreate, 'filterCreate') ncMetaAclMw(filterCreate, 'filterCreate')
); );
@ -91,7 +89,6 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/hooks/:hookId/filters', '/api/v1/db/meta/hooks/:hookId/filters',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
ncMetaAclMw(hookFilterCreate, 'filterCreate') ncMetaAclMw(hookFilterCreate, 'filterCreate')
); );
@ -103,7 +100,6 @@ router.get(
router.patch( router.patch(
'/api/v1/db/meta/filters/:filterId', '/api/v1/db/meta/filters/:filterId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
ncMetaAclMw(filterUpdate, 'filterUpdate') ncMetaAclMw(filterUpdate, 'filterUpdate')
); );
router.delete( router.delete(

2
packages/nocodb/src/lib/controllers/formViewColumnController.ts

@ -1,7 +1,6 @@
import { Request, Response, Router } from 'express'; import { Request, Response, Router } from 'express';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import { getAjvValidatorMw } from '../meta/api/helpers';
import { formViewColumnService } from '../services'; import { formViewColumnService } from '../services';
export async function columnUpdate(req: Request, res: Response) { export async function columnUpdate(req: Request, res: Response) {
@ -17,7 +16,6 @@ const router = Router({ mergeParams: true });
router.patch( router.patch(
'/api/v1/db/meta/form-columns/:formViewColumnId', '/api/v1/db/meta/form-columns/:formViewColumnId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FormColumnReq'),
ncMetaAclMw(columnUpdate, 'columnUpdate') ncMetaAclMw(columnUpdate, 'columnUpdate')
); );
export default router; export default router;

50
packages/nocodb/src/lib/controllers/formViewController.ts

@ -1,50 +1,37 @@
import { Request, Response, Router } from 'express'; import { Request, Response, Router } from 'express';
// @ts-ignore import { FormType } from 'nocodb-sdk';
import Model from '../models/Model';
import { Tele } from 'nc-help';
// @ts-ignore
import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
import { FormType, ViewTypes } 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 FormView from '../models/FormView';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import { getAjvValidatorMw } from '../meta/api/helpers'; import { formViewService } from '../services';
// @ts-ignore
export async function formViewGet(req: Request, res: Response<FormType>) { export async function formViewGet(req: Request, res: Response<FormType>) {
const formViewData = await FormView.getWithInfo(req.params.formViewId); const formViewData = await formViewService.formViewGet({
formViewId: req.params.formViewId,
});
res.json(formViewData); res.json(formViewData);
} }
export async function formViewCreate(req: Request<any, any>, res) { export async function formViewCreate(req: Request<any, any>, res) {
Tele.emit('evt', { evt_type: 'vtable:created', show_as: 'form' }); const view = await formViewService.formViewCreate({
const view = await View.insert({ body: req.body,
...req.body, tableId: req.params.tableId,
// todo: sanitize
fk_model_id: req.params.tableId,
type: ViewTypes.FORM,
}); });
res.json(view); res.json(view);
} }
// @ts-ignore
export async function formViewUpdate(req, res) { export async function formViewUpdate(req, res) {
Tele.emit('evt', { evt_type: 'view:updated', type: 'grid' }); res.json(
res.json(await FormView.update(req.params.formViewId, req.body)); await formViewService.formViewUpdate({
formViewId: req.params.formViewId,
body: req.body,
})
);
} }
// @ts-ignore
export async function formViewDelete(req: Request, res: Response, next) {}
const router = Router({ mergeParams: true }); const router = Router({ mergeParams: true });
router.post( router.post(
'/api/v1/db/meta/tables/:tableId/forms', '/api/v1/db/meta/tables/:tableId/forms',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FormCreateReq'),
ncMetaAclMw(formViewCreate, 'formViewCreate') ncMetaAclMw(formViewCreate, 'formViewCreate')
); );
router.get( router.get(
@ -55,12 +42,7 @@ router.get(
router.patch( router.patch(
'/api/v1/db/meta/forms/:formViewId', '/api/v1/db/meta/forms/:formViewId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FormReq'),
ncMetaAclMw(formViewUpdate, 'formViewUpdate') ncMetaAclMw(formViewUpdate, 'formViewUpdate')
); );
router.delete(
'/api/v1/db/meta/forms/:formViewId',
metaApiMetrics,
ncMetaAclMw(formViewDelete, 'formViewDelete')
);
export default router; export default router;

3
packages/nocodb/src/lib/controllers/galleryViewController.ts

@ -2,7 +2,6 @@ import { Request, Response, Router } from 'express';
import { GalleryType } from 'nocodb-sdk'; import { GalleryType } from 'nocodb-sdk';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import { getAjvValidatorMw } from '../meta/api/helpers';
import { galleryViewService } from '../services'; import { galleryViewService } from '../services';
export async function galleryViewGet(req: Request, res: Response<GalleryType>) { export async function galleryViewGet(req: Request, res: Response<GalleryType>) {
@ -35,13 +34,11 @@ const router = Router({ mergeParams: true });
router.post( router.post(
'/api/v1/db/meta/tables/:tableId/galleries', '/api/v1/db/meta/tables/:tableId/galleries',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/GalleryReq'),
ncMetaAclMw(galleryViewCreate, 'galleryViewCreate') ncMetaAclMw(galleryViewCreate, 'galleryViewCreate')
); );
router.patch( router.patch(
'/api/v1/db/meta/galleries/:galleryViewId', '/api/v1/db/meta/galleries/:galleryViewId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/GalleryReq'),
ncMetaAclMw(galleryViewUpdate, 'galleryViewUpdate') ncMetaAclMw(galleryViewUpdate, 'galleryViewUpdate')
); );
router.get( router.get(

2
packages/nocodb/src/lib/controllers/gridViewColumnController.ts

@ -1,7 +1,6 @@
import { Request, Response, Router } from 'express'; import { Request, Response, Router } from 'express';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import { getAjvValidatorMw } from '../meta/api/helpers';
import { gridViewColumnService } from '../services'; import { gridViewColumnService } from '../services';
export async function columnList(req: Request, res: Response) { export async function columnList(req: Request, res: Response) {
@ -30,7 +29,6 @@ router.get(
router.patch( router.patch(
'/api/v1/db/meta/grid-columns/:gridViewColumnId', '/api/v1/db/meta/grid-columns/:gridViewColumnId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/GridColumnReq'),
ncMetaAclMw(gridColumnUpdate, 'gridColumnUpdate') ncMetaAclMw(gridColumnUpdate, 'gridColumnUpdate')
); );
export default router; export default router;

35
packages/nocodb/src/lib/controllers/gridViewController.ts

@ -1,42 +1,29 @@
import { Request, Router } from 'express'; import { Request, Router } from 'express';
// @ts-ignore
import Model from '../models/Model';
import { Tele } from 'nc-help';
// @ts-ignore
import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
import { ViewTypes } 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 ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import GridView from '../models/GridView'; import { gridViewService } from '../services';
import { getAjvValidatorMw } from '../meta/api/helpers';
// @ts-ignore export async function gridViewCreate(req: Request<any>, res) {
export async function gridViewCreate(req: Request<any, any>, res) { const view = await gridViewService.gridViewCreate({
const view = await View.insert({ grid: req.body,
...req.body, tableId: req.params.tableId,
// todo: sanitize
fk_model_id: req.params.tableId,
type: ViewTypes.GRID,
}); });
Tele.emit('evt', { evt_type: 'vtable:created', show_as: 'grid' });
res.json(view); res.json(view);
} }
export async function gridViewUpdate(req, res) { export async function gridViewUpdate(req, res) {
Tele.emit('evt', { evt_type: 'view:updated', type: 'grid' }); res.json(
res.json(await GridView.update(req.params.viewId, req.body)); await gridViewService.gridViewUpdate({
viewId: req.params.viewId,
grid: req.body,
})
);
} }
const router = Router({ mergeParams: true }); const router = Router({ mergeParams: true });
router.post( router.post(
'/api/v1/db/meta/tables/:tableId/grids/', '/api/v1/db/meta/tables/:tableId/grids/',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/GridReq'),
ncMetaAclMw(gridViewCreate, 'gridViewCreate') ncMetaAclMw(gridViewCreate, 'gridViewCreate')
); );
router.patch( router.patch(

4
packages/nocodb/src/lib/controllers/hookController.ts

@ -4,7 +4,6 @@ import { HookListType, HookType } from 'nocodb-sdk';
import { PagedResponseImpl } from '../meta/helpers/PagedResponse'; import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import { getAjvValidatorMw } from '../meta/api/helpers';
import { hookService } from '../services'; import { hookService } from '../services';
export async function hookList( export async function hookList(
@ -74,13 +73,11 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/tables/:tableId/hooks/test', '/api/v1/db/meta/tables/:tableId/hooks/test',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/HookTestReq'),
ncMetaAclMw(hookTest, 'hookTest') ncMetaAclMw(hookTest, 'hookTest')
); );
router.post( router.post(
'/api/v1/db/meta/tables/:tableId/hooks', '/api/v1/db/meta/tables/:tableId/hooks',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/HookReq'),
ncMetaAclMw(hookCreate, 'hookCreate') ncMetaAclMw(hookCreate, 'hookCreate')
); );
router.delete( router.delete(
@ -91,7 +88,6 @@ router.delete(
router.patch( router.patch(
'/api/v1/db/meta/hooks/:hookId', '/api/v1/db/meta/hooks/:hookId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/HookReq'),
ncMetaAclMw(hookUpdate, 'hookUpdate') ncMetaAclMw(hookUpdate, 'hookUpdate')
); );
router.get( router.get(

3
packages/nocodb/src/lib/controllers/hookFilterController.ts

@ -2,7 +2,6 @@ import { Request, Response, Router } from 'express';
import { T } from 'nc-help'; import { T } from 'nc-help';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import { getAjvValidatorMw } from '../meta/api/helpers';
import { hookFilterService } from '../services'; import { hookFilterService } from '../services';
export async function filterGet(req: Request, res: Response) { export async function filterGet(req: Request, res: Response) {
@ -66,7 +65,6 @@ router.get(
router.post( router.post(
'/hooks/:hookId/filters/', '/hooks/:hookId/filters/',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
ncMetaAclMw(filterCreate, 'filterCreate') ncMetaAclMw(filterCreate, 'filterCreate')
); );
router.get( router.get(
@ -77,7 +75,6 @@ router.get(
router.patch( router.patch(
'/hooks/:hookId/filters/:filterId', '/hooks/:hookId/filters/:filterId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
ncMetaAclMw(filterUpdate, 'filterUpdate') ncMetaAclMw(filterUpdate, 'filterUpdate')
); );
router.delete( router.delete(

5
packages/nocodb/src/lib/controllers/kanbanViewController.ts

@ -5,7 +5,8 @@ import KanbanView from '../models/KanbanView';
import { T } from 'nc-help'; import { T } from 'nc-help';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import { getAjvValidatorMw } from '../meta/api/helpers';
// todo: map to service
export async function kanbanViewGet(req: Request, res: Response<KanbanType>) { export async function kanbanViewGet(req: Request, res: Response<KanbanType>) {
res.json(await KanbanView.get(req.params.kanbanViewId)); res.json(await KanbanView.get(req.params.kanbanViewId));
@ -32,13 +33,11 @@ const router = Router({ mergeParams: true });
router.post( router.post(
'/api/v1/db/meta/tables/:tableId/kanbans', '/api/v1/db/meta/tables/:tableId/kanbans',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/KanbanReq'),
ncMetaAclMw(kanbanViewCreate, 'kanbanViewCreate') ncMetaAclMw(kanbanViewCreate, 'kanbanViewCreate')
); );
router.patch( router.patch(
'/api/v1/db/meta/kanbans/:kanbanViewId', '/api/v1/db/meta/kanbans/:kanbanViewId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/KanbanUpdateReq'),
ncMetaAclMw(kanbanViewUpdate, 'kanbanViewUpdate') ncMetaAclMw(kanbanViewUpdate, 'kanbanViewUpdate')
); );
router.get( router.get(

2
packages/nocodb/src/lib/controllers/modelVisibilityController.ts

@ -1,7 +1,6 @@
import { Router } from 'express'; import { Router } from 'express';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import { getAjvValidatorMw } from '../meta/api/helpers';
import { modelVisibilityService } from '../services'; import { modelVisibilityService } from '../services';
async function xcVisibilityMetaSetAll(req, res) { async function xcVisibilityMetaSetAll(req, res) {
@ -30,7 +29,6 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/projects/:projectId/visibility-rules', '/api/v1/db/meta/projects/:projectId/visibility-rules',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/VisibilityRuleReq'),
ncMetaAclMw(xcVisibilityMetaSetAll, 'modelVisibilitySet') ncMetaAclMw(xcVisibilityMetaSetAll, 'modelVisibilitySet')
); );
export default router; export default router;

2
packages/nocodb/src/lib/controllers/orgLicenseController.ts

@ -2,7 +2,6 @@ import { Router } from 'express';
import { OrgUserRoles } from 'nocodb-sdk'; import { OrgUserRoles } from 'nocodb-sdk';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { getAjvValidatorMw } from '../meta/api/helpers';
import { orgLicenseService } from '../services'; import { orgLicenseService } from '../services';
async function licenseGet(_req, res) { async function licenseGet(_req, res) {
@ -26,7 +25,6 @@ router.get(
router.post( router.post(
'/api/v1/license', '/api/v1/license',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/LicenseReq'),
ncMetaAclMw(licenseSet, 'licenseSet', { ncMetaAclMw(licenseSet, 'licenseSet', {
allowedRoles: [OrgUserRoles.SUPER_ADMIN], allowedRoles: [OrgUserRoles.SUPER_ADMIN],
blockApiTokenAccess: true, blockApiTokenAccess: true,

56
packages/nocodb/src/lib/controllers/orgTokenController.ts

@ -1,56 +1,35 @@
import { Request, Response, Router } from 'express'; import { Request, Response, Router } from 'express';
import { OrgUserRoles } from 'nocodb-sdk';
import ApiToken from '../models/ApiToken';
import { Tele } from 'nc-help';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import { NcError } from '../meta/helpers/catchError';
import getHandler from '../meta/helpers/getHandler'; import getHandler from '../meta/helpers/getHandler';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
import { apiTokenListEE } from '../meta/api/ee/orgTokenApis'; import { apiTokenListEE } from '../meta/api/ee/orgTokenApis';
import { getAjvValidatorMw } from '../meta/api/helpers'; import { orgTokenService } from '../services';
async function apiTokenList(req, res) { async function apiTokenList(req, res) {
const fk_user_id = req.user.id;
let includeUnmappedToken = false;
if (req['user'].roles.includes(OrgUserRoles.SUPER_ADMIN)) {
includeUnmappedToken = true;
}
res.json( res.json(
new PagedResponseImpl( await orgTokenService.apiTokenList({
await ApiToken.listWithCreatedBy({ query: req.query,
...req.query, user: req['user'],
fk_user_id, })
includeUnmappedToken,
}),
{
...req.query,
count: await ApiToken.count({
includeUnmappedToken,
fk_user_id,
}),
}
)
); );
} }
export async function apiTokenCreate(req: Request, res: Response) { export async function apiTokenCreate(req: Request, res: Response) {
Tele.emit('evt', { evt_type: 'org:apiToken:created' }); res.json(
res.json(await ApiToken.insert({ ...req.body, fk_user_id: req['user'].id })); await orgTokenService.apiTokenCreate({
apiToken: req.body,
user: req['user'],
})
);
} }
export async function apiTokenDelete(req: Request, res: Response) { export async function apiTokenDelete(req: Request, res: Response) {
const fk_user_id = req['user'].id; res.json(
const apiToken = await ApiToken.getByToken(req.params.token); await orgTokenService.apiTokenDelete({
if ( token: req.params.token,
!req['user'].roles.includes(OrgUserRoles.SUPER_ADMIN) && user: req['user'],
apiToken.fk_user_id !== fk_user_id })
) { );
NcError.notFound('Token not found');
}
Tele.emit('evt', { evt_type: 'org:apiToken:deleted' });
res.json(await ApiToken.delete(req.params.token));
} }
const router = Router({ mergeParams: true }); const router = Router({ mergeParams: true });
@ -66,7 +45,6 @@ router.get(
router.post( router.post(
'/api/v1/tokens', '/api/v1/tokens',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ApiTokenReq'),
ncMetaAclMw(apiTokenCreate, 'apiTokenCreate', { ncMetaAclMw(apiTokenCreate, 'apiTokenCreate', {
// allowedRoles: [OrgUserRoles.SUPER], // allowedRoles: [OrgUserRoles.SUPER],
blockApiTokenAccess: true, blockApiTokenAccess: true,

3
packages/nocodb/src/lib/controllers/orgUserController.ts

@ -2,7 +2,6 @@ import { Router } from 'express';
import { OrgUserRoles } from 'nocodb-sdk'; import { OrgUserRoles } from 'nocodb-sdk';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { getAjvValidatorMw } from '../meta/api/helpers';
import { orgUserService } from '../services'; import { orgUserService } from '../services';
async function userList(req, res) { async function userList(req, res) {
@ -87,7 +86,6 @@ router.get(
router.patch( router.patch(
'/api/v1/users/:userId', '/api/v1/users/:userId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/OrgUserReq'),
ncMetaAclMw(userUpdate, 'userUpdate', { ncMetaAclMw(userUpdate, 'userUpdate', {
allowedRoles: [OrgUserRoles.SUPER_ADMIN], allowedRoles: [OrgUserRoles.SUPER_ADMIN],
blockApiTokenAccess: true, blockApiTokenAccess: true,
@ -104,7 +102,6 @@ router.delete(
router.post( router.post(
'/api/v1/users', '/api/v1/users',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/OrgUserReq'),
ncMetaAclMw(userAdd, 'userAdd', { ncMetaAclMw(userAdd, 'userAdd', {
allowedRoles: [OrgUserRoles.SUPER_ADMIN], allowedRoles: [OrgUserRoles.SUPER_ADMIN],
blockApiTokenAccess: true, blockApiTokenAccess: true,

4
packages/nocodb/src/lib/controllers/pluginController.ts

@ -3,7 +3,6 @@ import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
import { PluginType } from 'nocodb-sdk'; import { PluginType } from 'nocodb-sdk';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import { getAjvValidatorMw } from '../meta/api/helpers';
import { pluginService } from '../services'; import { pluginService } from '../services';
export async function pluginList(_req: Request, res: Response) { export async function pluginList(_req: Request, res: Response) {
@ -42,8 +41,6 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/plugins/test', '/api/v1/db/meta/plugins/test',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/PluginTestReq'),
ncMetaAclMw(pluginTest, 'pluginTest') ncMetaAclMw(pluginTest, 'pluginTest')
); );
router.get( router.get(
@ -54,7 +51,6 @@ router.get(
router.patch( router.patch(
'/api/v1/db/meta/plugins/:pluginId', '/api/v1/db/meta/plugins/:pluginId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/PluginReq'),
ncMetaAclMw(pluginUpdate, 'pluginUpdate') ncMetaAclMw(pluginUpdate, 'pluginUpdate')
); );
router.get( router.get(

2
packages/nocodb/src/lib/controllers/projectController.ts

@ -11,7 +11,6 @@ import ProjectUser from '../models/ProjectUser';
import Noco from '../Noco'; import Noco from '../Noco';
import isDocker from 'is-docker'; import isDocker from 'is-docker';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import { getAjvValidatorMw } from '../meta/api/helpers';
import Filter from '../models/Filter'; import Filter from '../models/Filter';
import { projectService } from '../services'; import { projectService } from '../services';
@ -155,7 +154,6 @@ export default (router) => {
router.post( router.post(
'/api/v1/db/meta/projects', '/api/v1/db/meta/projects',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ProjectReq'),
ncMetaAclMw(projectCreate, 'projectCreate') ncMetaAclMw(projectCreate, 'projectCreate')
); );
router.get( router.get(

316
packages/nocodb/src/lib/controllers/projectUserController.ts

@ -1,305 +1,59 @@
import { OrgUserRoles } from 'nocodb-sdk';
import { Tele } from 'nc-help';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { Router } from 'express'; import { Router } from 'express';
import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
import ProjectUser from '../models/ProjectUser';
import validator from 'validator';
import { NcError } from '../meta/helpers/catchError';
import { v4 as uuidv4 } from 'uuid';
import User from '../models/User';
import Audit from '../models/Audit';
import NocoCache from '../cache/NocoCache';
import { CacheGetType, CacheScope, MetaTable } from '../utils/globals';
import * as ejs from 'ejs';
import NcPluginMgrv2 from '../meta/helpers/NcPluginMgrv2';
import Noco from '../Noco';
import { PluginCategory } from 'nocodb-sdk';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import { randomTokenString } from '../meta/helpers/stringHelpers'; import { projectUserService } from '../services';
import { getAjvValidatorMw } from '../meta/api/helpers';
async function userList(req, res) { async function userList(req, res) {
res.json({ res.json({
users: new PagedResponseImpl( users: await projectUserService.userList({
await ProjectUser.getUsersList({ projectId: req.params.projectId,
...req.query, query: req.query,
project_id: req.params.projectId, }),
}),
{
...req.query,
count: await ProjectUser.getUsersCount(req.query),
}
),
}); });
} }
async function userInvite(req, res, next): Promise<any> { async function userInvite(req, res): Promise<any> {
const emails = (req.body.email || '') res.json(
.toLowerCase() await projectUserService.userInvite({
.split(/\s*,\s*/) projectId: req.params.projectId,
.map((v) => v.trim()); projectUser: req.body,
req,
// check for invalid emails })
const invalidEmails = emails.filter((v) => !validator.isEmail(v)); );
if (!emails.length) {
return NcError.badRequest('Invalid email address');
}
if (invalidEmails.length) {
NcError.badRequest('Invalid email address : ' + invalidEmails.join(', '));
}
const invite_token = uuidv4();
const error = [];
for (const email of emails) {
// add user to project if user already exist
const user = await User.getByEmail(email);
if (user) {
// check if this user has been added to this project
const projectUser = await ProjectUser.get(req.params.projectId, user.id);
if (projectUser) {
NcError.badRequest(
`${user.email} with role ${projectUser.roles} already exists in this project`
);
}
await ProjectUser.insert({
project_id: req.params.projectId,
fk_user_id: user.id,
roles: req.body.roles || 'editor',
});
const cachedUser = await NocoCache.get(
`${CacheScope.USER}:${email}___${req.params.projectId}`,
CacheGetType.TYPE_OBJECT
);
if (cachedUser) {
cachedUser.roles = req.body.roles || 'editor';
await NocoCache.set(
`${CacheScope.USER}:${email}___${req.params.projectId}`,
cachedUser
);
}
await Audit.insert({
project_id: req.params.projectId,
op_type: 'AUTHENTICATION',
op_sub_type: 'INVITE',
user: req.user.email,
description: `invited ${email} to ${req.params.projectId} project `,
ip: req.clientIp,
});
} else {
try {
// create new user with invite token
const { id } = await User.insert({
invite_token,
invite_token_expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
email,
roles: OrgUserRoles.VIEWER,
token_version: randomTokenString(),
});
// add user to project
await ProjectUser.insert({
project_id: req.params.projectId,
fk_user_id: id,
roles: req.body.roles,
});
const count = await User.count();
Tele.emit('evt', { evt_type: 'project:invite', count });
await Audit.insert({
project_id: req.params.projectId,
op_type: 'AUTHENTICATION',
op_sub_type: 'INVITE',
user: req.user.email,
description: `invited ${email} to ${req.params.projectId} project `,
ip: req.clientIp,
});
// in case of single user check for smtp failure
// and send back token if failed
if (
emails.length === 1 &&
!(await sendInviteEmail(email, invite_token, req))
) {
return res.json({ invite_token, email });
} else {
sendInviteEmail(email, invite_token, req);
}
} catch (e) {
console.log(e);
if (emails.length === 1) {
return next(e);
} else {
error.push({ email, error: e.message });
}
}
}
}
if (emails.length === 1) {
res.json({
msg: 'success',
});
} else {
return res.json({ invite_token, emails, error });
}
} }
// @ts-ignore // @ts-ignore
async function projectUserUpdate(req, res, next): Promise<any> { async function projectUserUpdate(req, res, next): Promise<any> {
if (!req?.body?.project_id) { res.json(
return next(new Error('Missing project id in request body.')); await projectUserService.projectUserUpdate({
} projectUser: req.body,
projectId: req.params.projectId,
if ( userId: req.params.userId,
req.session?.passport?.user?.roles?.owner && req,
req.session?.passport?.user?.id === req.params.userId && })
req.body.roles.indexOf('owner') === -1 );
) {
NcError.badRequest("Super admin can't remove Super role themselves");
}
try {
const user = await User.get(req.params.userId);
if (!user) {
NcError.badRequest(`User with id '${req.params.userId}' doesn't exist`);
}
// todo: handle roles which contains super
if (
!req.session?.passport?.user?.roles?.owner &&
req.body.roles.indexOf('owner') > -1
) {
NcError.forbidden('Insufficient privilege to add super admin role.');
}
await ProjectUser.update(
req.params.projectId,
req.params.userId,
req.body.roles
);
await Audit.insert({
op_type: 'AUTHENTICATION',
op_sub_type: 'ROLES_MANAGEMENT',
user: req.user.email,
description: `updated roles for ${user.email} with ${req.body.roles} `,
ip: req.clientIp,
});
res.json({
msg: 'User details updated successfully',
});
} catch (e) {
next(e);
}
} }
async function projectUserDelete(req, res): Promise<any> { async function projectUserDelete(req, res): Promise<any> {
const project_id = req.params.projectId; await projectUserService.projectUserDelete({
projectId: req.params.projectId,
if (req.session?.passport?.user?.id === req.params.userId) { userId: req.params.userId,
NcError.badRequest("Admin can't delete themselves!"); req,
} });
if (!req.session?.passport?.user?.roles?.owner) {
const user = await User.get(req.params.userId);
if (user.roles?.split(',').includes('super'))
NcError.forbidden('Insufficient privilege to delete a super admin user.');
const projectUser = await ProjectUser.get(project_id, req.params.userId);
if (projectUser?.roles?.split(',').includes('super'))
NcError.forbidden('Insufficient privilege to delete a owner user.');
}
await ProjectUser.delete(project_id, req.params.userId);
res.json({ res.json({
msg: 'success', msg: 'success',
}); });
} }
async function projectUserInviteResend(req, res): Promise<any> { async function projectUserInviteResend(req, res): Promise<any> {
const user = await User.get(req.params.userId); res.json(
await projectUserService.projectUserInviteResend({
if (!user) { projectId: req.params.projectId,
NcError.badRequest(`User with id '${req.params.userId}' not found`); userId: req.params.userId,
} projectUser: req.body,
req,
req.body.roles = user.roles; })
const invite_token = uuidv4(); );
await User.update(user.id, {
invite_token,
invite_token_expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
});
const pluginData = await Noco.ncMeta.metaGet2(null, null, MetaTable.PLUGIN, {
category: PluginCategory.EMAIL,
active: true,
});
if (!pluginData) {
NcError.badRequest(
`No Email Plugin is found. Please go to App Store to configure first or copy the invitation URL to users instead.`
);
}
await sendInviteEmail(user.email, invite_token, req);
await Audit.insert({
op_type: 'AUTHENTICATION',
op_sub_type: 'RESEND_INVITE',
user: user.email,
description: `resent a invite to ${user.email} `,
ip: req.clientIp,
project_id: req.params.projectId,
});
res.json({ msg: 'success' });
}
export async function sendInviteEmail(
email: string,
token: string,
req: any
): Promise<any> {
try {
const template = (await import('./userController/ui/emailTemplates/invite'))
.default;
const emailAdapter = await NcPluginMgrv2.emailAdapter();
if (emailAdapter) {
await emailAdapter.mailSend({
to: email,
subject: 'Verify email',
html: ejs.render(template, {
signupLink: `${req.ncSiteUrl}${
Noco.getConfig()?.dashboardPath
}#/signup/${token}`,
projectName: req.body?.projectName,
roles: (req.body?.roles || '')
.split(',')
.map((r) => r.replace(/^./, (m) => m.toUpperCase()))
.join(', '),
adminEmail: req.session?.passport?.user?.email,
}),
});
return true;
}
} catch (e) {
console.log(
'Warning : `mailSend` failed, Please configure emailClient configuration.',
e.message
);
throw e;
}
} }
const router = Router({ mergeParams: true }); const router = Router({ mergeParams: true });
@ -311,13 +65,11 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/projects/:projectId/users', '/api/v1/db/meta/projects/:projectId/users',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ProjectUserReq'),
ncMetaAclMw(userInvite, 'userInvite') ncMetaAclMw(userInvite, 'userInvite')
); );
router.patch( router.patch(
'/api/v1/db/meta/projects/:projectId/users/:userId', '/api/v1/db/meta/projects/:projectId/users/:userId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/ProjectUserReq'),
ncMetaAclMw(projectUserUpdate, 'projectUserUpdate') ncMetaAclMw(projectUserUpdate, 'projectUserUpdate')
); );
router.delete( router.delete(

97
packages/nocodb/src/lib/controllers/sharedBaseController.ts

@ -1,93 +1,44 @@
import { Router } from 'express'; import { Router } from 'express';
import { Tele } from 'nc-help';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { v4 as uuidv4 } from 'uuid'; import { sharedBaseService } from '../services';
import Project from '../models/Project';
import { NcError } from '../meta/helpers/catchError';
import { getAjvValidatorMw } from '../meta/api/helpers';
// todo: load from config
const config = {
dashboardPath: '/nc',
};
async function createSharedBaseLink(req, res): Promise<any> { async function createSharedBaseLink(req, res): Promise<any> {
const project = await Project.get(req.params.projectId); const sharedBase = await sharedBaseService.createSharedBaseLink({
projectId: req.params.projectId,
let roles = req.body?.roles; roles: req.body?.roles,
if (!roles || (roles !== 'editor' && roles !== 'viewer')) {
roles = 'viewer';
}
if (!project) {
NcError.badRequest('Invalid project id');
}
const data: any = {
uuid: uuidv4(),
password: req.body?.password, password: req.body?.password,
roles, siteUrl: req.ncSiteUrl,
}; });
await Project.update(project.id, data);
data.url = `${req.ncSiteUrl}${config.dashboardPath}#/nc/base/${data.uuid}`; res.json(sharedBase);
delete data.password;
Tele.emit('evt', { evt_type: 'sharedBase:generated-link' });
res.json(data);
} }
async function updateSharedBaseLink(req, res): Promise<any> {
const project = await Project.get(req.params.projectId);
let roles = req.body?.roles;
if (!roles || (roles !== 'editor' && roles !== 'viewer')) {
roles = 'viewer';
}
if (!project) { async function updateSharedBaseLink(req, res): Promise<any> {
NcError.badRequest('Invalid project id'); const sharedBase = await sharedBaseService.updateSharedBaseLink({
} projectId: req.params.projectId,
const data: any = { roles: req.body?.roles,
uuid: project.uuid || uuidv4(),
password: req.body?.password, password: req.body?.password,
roles, siteUrl: req.ncSiteUrl,
}; });
await Project.update(project.id, data);
data.url = `${req.ncSiteUrl}${config.dashboardPath}#/nc/base/${data.uuid}`; res.json(sharedBase);
delete data.password;
Tele.emit('evt', { evt_type: 'sharedBase:generated-link' });
res.json(data);
} }
async function disableSharedBaseLink(req, res): Promise<any> { async function disableSharedBaseLink(req, res): Promise<any> {
const project = await Project.get(req.params.projectId); const sharedBase = await sharedBaseService.disableSharedBaseLink({
projectId: req.params.projectId,
});
if (!project) { res.json(sharedBase);
NcError.badRequest('Invalid project id');
}
const data: any = {
uuid: null,
};
await Project.update(project.id, data);
Tele.emit('evt', { evt_type: 'sharedBase:disable-link' });
res.json({ uuid: null });
} }
async function getSharedBaseLink(req, res): Promise<any> { async function getSharedBaseLink(req, res): Promise<any> {
const project = await Project.get(req.params.projectId); const sharedBase = await sharedBaseService.getSharedBaseLink({
projectId: req.params.projectId,
if (!project) { siteUrl: req.ncSiteUrl,
NcError.badRequest('Invalid project id'); });
}
const data: any = {
uuid: project.uuid,
roles: project.roles,
};
if (data.uuid)
data.url = `${req.ncSiteUrl}${config.dashboardPath}#/nc/base/${data.shared_base_id}`;
res.json(data); res.json(sharedBase);
} }
const router = Router({ mergeParams: true }); const router = Router({ mergeParams: true });
@ -97,12 +48,10 @@ router.get(
); );
router.post( router.post(
'/api/v1/db/meta/projects/:projectId/shared', '/api/v1/db/meta/projects/:projectId/shared',
getAjvValidatorMw('swagger.json#/components/schemas/SharedBaseReq'),
ncMetaAclMw(createSharedBaseLink, 'createSharedBaseLink') ncMetaAclMw(createSharedBaseLink, 'createSharedBaseLink')
); );
router.patch( router.patch(
'/api/v1/db/meta/projects/:projectId/shared', '/api/v1/db/meta/projects/:projectId/shared',
getAjvValidatorMw('swagger.json#/components/schemas/SharedBaseReq'),
ncMetaAclMw(updateSharedBaseLink, 'updateSharedBaseLink') ncMetaAclMw(updateSharedBaseLink, 'updateSharedBaseLink')
); );
router.delete( router.delete(

3
packages/nocodb/src/lib/controllers/sortController.ts

@ -1,5 +1,4 @@
import { Request, Response, Router } from 'express'; import { Request, Response, Router } from 'express';
import { getAjvValidatorMw } from '../meta/api/helpers';
import { PagedResponseImpl } from '../meta/helpers/PagedResponse'; import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
import { SortListType, SortReqType } from 'nocodb-sdk'; import { SortListType, SortReqType } from 'nocodb-sdk';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
@ -59,7 +58,6 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/views/:viewId/sorts/', '/api/v1/db/meta/views/:viewId/sorts/',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/SortReq'),
ncMetaAclMw(sortCreate, 'sortCreate') ncMetaAclMw(sortCreate, 'sortCreate')
); );
@ -72,7 +70,6 @@ router.get(
router.patch( router.patch(
'/api/v1/db/meta/sorts/:sortId', '/api/v1/db/meta/sorts/:sortId',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/SortReq'),
ncMetaAclMw(sortUpdate, 'sortUpdate') ncMetaAclMw(sortUpdate, 'sortUpdate')
); );
router.delete( router.delete(

117
packages/nocodb/src/lib/controllers/tableController.ts

@ -1,11 +1,17 @@
import { Request, Response, Router } from 'express'; import { Request, Response, Router } from 'express';
import DOMPurify from 'isomorphic-dompurify';
import { TableListType, TableReqType, TableType } from 'nocodb-sdk'; import { TableListType, TableReqType, TableType } from 'nocodb-sdk';
import { getAjvValidatorMw } from '../meta/api/helpers'; import ProjectMgrv2 from '../db/sql-mgr/v2/ProjectMgrv2';
import { metaApiMetrics } from '../meta/helpers/apiMetrics'; import { metaApiMetrics } from '../meta/helpers/apiMetrics';
import { NcError } from '../meta/helpers/catchError';
import getTableNameAlias from '../meta/helpers/getTableName';
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
import { PagedResponseImpl } from '../meta/helpers/PagedResponse'; import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
import Model from '../models/Model';
import Project from '../models/Project';
import { T } from 'nc-help';
import { tableService } from '../services'; import { tableService } from '../services';
import NcConnectionMgrv2 from '../utils/common/NcConnectionMgrv2';
export async function tableList(req: Request, res: Response<TableListType>) { export async function tableList(req: Request, res: Response<TableListType>) {
res.json( res.json(
@ -58,15 +64,108 @@ export async function tableReorder(req: Request, res: Response) {
); );
} }
// todo: move to table service
export async function tableUpdate(req: Request<any, any>, 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);
export async function tableUpdate(req: Request<any, any>, res) { if (model.project_id !== project.id) {
tableService.tableUpdate({ NcError.badRequest('Model does not belong to project');
tableId: req.params.tableId, }
table: req.body,
}) // 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,
});
T.emit('evt', { evt_type: 'table:updated' });
res.json({ msg: 'success' }) res.json({ msg: 'success' });
} }
const router = Router({ mergeParams: true }); const router = Router({ mergeParams: true });
@ -83,13 +182,11 @@ router.get(
router.post( router.post(
'/api/v1/db/meta/projects/:projectId/tables', '/api/v1/db/meta/projects/:projectId/tables',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/TableReq'),
ncMetaAclMw(tableCreate, 'tableCreate') ncMetaAclMw(tableCreate, 'tableCreate')
); );
router.post( router.post(
'/api/v1/db/meta/projects/:projectId/:baseId/tables', '/api/v1/db/meta/projects/:projectId/:baseId/tables',
metaApiMetrics, metaApiMetrics,
getAjvValidatorMw('swagger.json#/components/schemas/TableReq'),
ncMetaAclMw(tableCreate, 'tableCreate') ncMetaAclMw(tableCreate, 'tableCreate')
); );
router.get( router.get(

22
packages/nocodb/src/lib/controllers/userController/index.ts

@ -340,11 +340,7 @@ const mapRoutes = (router) => {
catchError(signin) catchError(signin)
); );
router.get('/auth/user/me', extractProjectIdAndAuthenticate, catchError(me)); router.get('/auth/user/me', extractProjectIdAndAuthenticate, catchError(me));
router.post( router.post('/auth/password/forgot', catchError(passwordForgot));
'/auth/password/forgot',
getAjvValidatorMw('swagger.json#/components/schemas/ForgotPasswordReq'),
catchError(passwordForgot)
);
router.post('/auth/token/validate/:tokenId', catchError(tokenValidate)); router.post('/auth/token/validate/:tokenId', catchError(tokenValidate));
router.post( router.post(
'/auth/password/reset/:tokenId', '/auth/password/reset/:tokenId',
@ -354,7 +350,6 @@ const mapRoutes = (router) => {
router.post('/auth/email/validate/:tokenId', catchError(emailVerification)); router.post('/auth/email/validate/:tokenId', catchError(emailVerification));
router.post( router.post(
'/user/password/change', '/user/password/change',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordChangeReq'),
ncMetaAclMw(passwordChange, 'passwordChange') ncMetaAclMw(passwordChange, 'passwordChange')
); );
router.post('/auth/token/refresh', catchError(refreshToken)); router.post('/auth/token/refresh', catchError(refreshToken));
@ -387,18 +382,13 @@ const mapRoutes = (router) => {
extractProjectIdAndAuthenticate, extractProjectIdAndAuthenticate,
catchError(me) catchError(me)
); );
router.post( router.post('/api/v1/db/auth/password/forgot', catchError(passwordForgot));
'/api/v1/db/auth/password/forgot',
getAjvValidatorMw('swagger.json#/components/schemas/ForgotPasswordReq'),
catchError(passwordForgot)
);
router.post( router.post(
'/api/v1/db/auth/token/validate/:tokenId', '/api/v1/db/auth/token/validate/:tokenId',
catchError(tokenValidate) catchError(tokenValidate)
); );
router.post( router.post(
'/api/v1/db/auth/password/reset/:tokenId', '/api/v1/db/auth/password/reset/:tokenId',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordResetReq'),
catchError(passwordReset) catchError(passwordReset)
); );
router.post( router.post(
@ -407,7 +397,6 @@ const mapRoutes = (router) => {
); );
router.post( router.post(
'/api/v1/db/auth/password/change', '/api/v1/db/auth/password/change',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordChangeReq'),
ncMetaAclMw(passwordChange, 'passwordChange') ncMetaAclMw(passwordChange, 'passwordChange')
); );
router.post('/api/v1/db/auth/token/refresh', catchError(refreshToken)); router.post('/api/v1/db/auth/token/refresh', catchError(refreshToken));
@ -432,11 +421,7 @@ const mapRoutes = (router) => {
extractProjectIdAndAuthenticate, extractProjectIdAndAuthenticate,
catchError(me) catchError(me)
); );
router.post( router.post('/api/v1/auth/password/forgot', catchError(passwordForgot));
'/api/v1/auth/password/forgot',
getAjvValidatorMw('swagger.json#/components/schemas/ForgotPasswordReq'),
catchError(passwordForgot)
);
router.post( router.post(
'/api/v1/auth/token/validate/:tokenId', '/api/v1/auth/token/validate/:tokenId',
catchError(tokenValidate) catchError(tokenValidate)
@ -451,7 +436,6 @@ const mapRoutes = (router) => {
); );
router.post( router.post(
'/api/v1/auth/password/change', '/api/v1/auth/password/change',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordChangeReq'),
ncMetaAclMw(passwordChange, 'passwordChange') ncMetaAclMw(passwordChange, 'passwordChange')
); );
router.post('/api/v1/auth/token/refresh', catchError(refreshToken)); router.post('/api/v1/auth/token/refresh', catchError(refreshToken));

6
packages/nocodb/src/lib/meta/api/helpers/apiHelpers.ts

@ -2,7 +2,7 @@ import { NextFunction, Request, Response } from 'express';
import Ajv, { ErrorObject } from 'ajv'; import Ajv, { ErrorObject } from 'ajv';
// @ts-ignore // @ts-ignore
import swagger from '../../../../schema/swagger.json'; import swagger from '../../../../schema/swagger.json';
import { NcError } from '../../helpers/catchError' import { NcError } from '../../helpers/catchError';
export function parseHrtimeToSeconds(hrtime) { export function parseHrtimeToSeconds(hrtime) {
const seconds = (hrtime[0] + hrtime[1] / 1e6).toFixed(3); const seconds = (hrtime[0] + hrtime[1] / 1e6).toFixed(3);
@ -38,7 +38,7 @@ export const getAjvValidatorMw = (schema) => {
}; };
// a function to validate the payload against the schema // a function to validate the payload against the schema
export const ajvValidator = (schema, payload) => { export const validatePayload = (schema, payload) => {
// Validate the request body against the schema // Validate the request body against the schema
const valid = ajv.validate( const valid = ajv.validate(
typeof schema === 'string' ? { $ref: schema } : schema, typeof schema === 'string' ? { $ref: schema } : schema,
@ -55,4 +55,4 @@ export const ajvValidator = (schema, payload) => {
errors, errors,
}); });
} }
} };

164
packages/nocodb/src/lib/models/Hook.ts

@ -1,42 +1,42 @@
import { BoolType, HookReqType, HookType } from 'nocodb-sdk' import { BoolType, HookReqType, HookType } from 'nocodb-sdk';
import { import {
CacheDelDirection, CacheDelDirection,
CacheGetType, CacheGetType,
CacheScope, CacheScope,
MetaTable, MetaTable,
} from '../utils/globals' } from '../utils/globals';
import Noco from '../Noco' import Noco from '../Noco';
import Model from './Model' import Model from './Model';
import NocoCache from '../cache/NocoCache' import NocoCache from '../cache/NocoCache';
import Filter from './Filter' import Filter from './Filter';
import HookFilter from './HookFilter' import HookFilter from './HookFilter';
import { extractProps } from '../meta/helpers/extractProps' import { extractProps } from '../meta/helpers/extractProps';
export default class Hook implements HookType { export default class Hook implements HookType {
id?: string id?: string;
fk_model_id?: string fk_model_id?: string;
title?: string title?: string;
description?: string description?: string;
env?: string env?: string;
type?: string type?: string;
event?: 'after' | 'before' event?: 'after' | 'before';
operation?: 'insert' | 'delete' | 'update' operation?: 'insert' | 'delete' | 'update';
async?: BoolType async?: BoolType;
payload?: string payload?: string;
url?: string url?: string;
headers?: string headers?: string;
condition?: BoolType condition?: BoolType;
notification?: string notification?: string;
retries?: number retries?: number;
retry_interval?: number retry_interval?: number;
timeout?: number timeout?: number;
active?: BoolType active?: BoolType;
project_id?: string project_id?: string;
base_id?: string base_id?: string;
constructor(hook: Partial<Hook | HookReqType>) { constructor(hook: Partial<Hook | HookReqType>) {
Object.assign(this, hook) Object.assign(this, hook);
} }
public static async get(hookId: string, ncMeta = Noco.ncMeta) { public static async get(hookId: string, ncMeta = Noco.ncMeta) {
@ -44,17 +44,17 @@ export default class Hook implements HookType {
hookId && hookId &&
(await NocoCache.get( (await NocoCache.get(
`${CacheScope.HOOK}:${hookId}`, `${CacheScope.HOOK}:${hookId}`,
CacheGetType.TYPE_OBJECT, CacheGetType.TYPE_OBJECT
)) ));
if (!hook) { if (!hook) {
hook = await ncMeta.metaGet2(null, null, MetaTable.HOOKS, hookId) hook = await ncMeta.metaGet2(null, null, MetaTable.HOOKS, hookId);
await NocoCache.set(`${CacheScope.HOOK}:${hookId}`, hook) await NocoCache.set(`${CacheScope.HOOK}:${hookId}`, hook);
} }
return hook && new Hook(hook) return hook && new Hook(hook);
} }
public async getFilters(ncMeta = Noco.ncMeta) { public async getFilters(ncMeta = Noco.ncMeta) {
return await Filter.rootFilterListByHook({ hookId: this.id }, ncMeta) return await Filter.rootFilterListByHook({ hookId: this.id }, ncMeta);
} }
// public static async insert(hook: Partial<Hook>) { // public static async insert(hook: Partial<Hook>) {
@ -81,9 +81,9 @@ export default class Hook implements HookType {
event?: 'after' | 'before'; event?: 'after' | 'before';
operation?: 'insert' | 'delete' | 'update'; operation?: 'insert' | 'delete' | 'update';
}, },
ncMeta = Noco.ncMeta, ncMeta = Noco.ncMeta
) { ) {
let hooks = await NocoCache.getList(CacheScope.HOOK, [param.fk_model_id]) let hooks = await NocoCache.getList(CacheScope.HOOK, [param.fk_model_id]);
if (!hooks.length) { if (!hooks.length) {
hooks = await ncMeta.metaList(null, null, MetaTable.HOOKS, { hooks = await ncMeta.metaList(null, null, MetaTable.HOOKS, {
condition: { condition: {
@ -96,29 +96,31 @@ export default class Hook implements HookType {
orderBy: { orderBy: {
created_at: 'asc', created_at: 'asc',
}, },
}) });
await NocoCache.setList(CacheScope.HOOK, [param.fk_model_id], hooks) await NocoCache.setList(CacheScope.HOOK, [param.fk_model_id], hooks);
} }
// filter event & operation // filter event & operation
if (param.event) { if (param.event) {
hooks = hooks.filter( hooks = hooks.filter(
(h) => h.event?.toLowerCase() === param.event?.toLowerCase(), (h) => h.event?.toLowerCase() === param.event?.toLowerCase()
) );
} }
if (param.operation) { if (param.operation) {
hooks = hooks.filter( hooks = hooks.filter(
(h) => h.operation?.toLowerCase() === param.operation?.toLowerCase(), (h) => h.operation?.toLowerCase() === param.operation?.toLowerCase()
) );
} }
return hooks?.map((h) => new Hook(h)) return hooks?.map((h) => new Hook(h));
} }
public static async insert( public static async insert(
hook: Partial<Hook & { hook: Partial<
created_at?; Hook & {
updated_at?; created_at?;
}>, updated_at?;
ncMeta = Noco.ncMeta, }
>,
ncMeta = Noco.ncMeta
) { ) {
const insertObj = extractProps(hook, [ const insertObj = extractProps(hook, [
'fk_model_id', 'fk_model_id',
@ -140,49 +142,49 @@ export default class Hook implements HookType {
'base_id', 'base_id',
'created_at', 'created_at',
'updated_at', 'updated_at',
]) ]);
if (insertObj.event) { if (insertObj.event) {
insertObj.event = insertObj.event.toLowerCase() as 'after' | 'before' insertObj.event = insertObj.event.toLowerCase() as 'after' | 'before';
} }
if (insertObj.operation) { if (insertObj.operation) {
insertObj.operation = insertObj.operation.toLowerCase() as insertObj.operation = insertObj.operation.toLowerCase() as
| 'insert' | 'insert'
| 'delete' | 'delete'
| 'update' | 'update';
} }
if (insertObj.notification && typeof insertObj.notification === 'object') { if (insertObj.notification && typeof insertObj.notification === 'object') {
insertObj.notification = JSON.stringify(insertObj.notification) insertObj.notification = JSON.stringify(insertObj.notification);
} }
if (!(hook.project_id && hook.base_id)) { if (!(hook.project_id && hook.base_id)) {
const model = await Model.getByIdOrName({ id: hook.fk_model_id }, ncMeta) const model = await Model.getByIdOrName({ id: hook.fk_model_id }, ncMeta);
insertObj.project_id = model.project_id insertObj.project_id = model.project_id;
insertObj.base_id = model.base_id insertObj.base_id = model.base_id;
} }
const { id } = await ncMeta.metaInsert2( const { id } = await ncMeta.metaInsert2(
null, null,
null, null,
MetaTable.HOOKS, MetaTable.HOOKS,
insertObj, insertObj
) );
await NocoCache.appendToList( await NocoCache.appendToList(
CacheScope.HOOK, CacheScope.HOOK,
[hook.fk_model_id], [hook.fk_model_id],
`${CacheScope.HOOK}:${id}`, `${CacheScope.HOOK}:${id}`
) );
return this.get(id, ncMeta) return this.get(id, ncMeta);
} }
public static async update( public static async update(
hookId: string, hookId: string,
hook: Partial<Hook>, hook: Partial<Hook>,
ncMeta = Noco.ncMeta, ncMeta = Noco.ncMeta
) { ) {
const updateObj = extractProps(hook, [ const updateObj = extractProps(hook, [
'title', 'title',
@ -201,38 +203,38 @@ export default class Hook implements HookType {
'retry_interval', 'retry_interval',
'timeout', 'timeout',
'active', 'active',
]) ]);
if (updateObj.event) { if (updateObj.event) {
updateObj.event = updateObj.event.toLowerCase() as 'after' | 'before' updateObj.event = updateObj.event.toLowerCase() as 'after' | 'before';
} }
if (updateObj.operation) { if (updateObj.operation) {
updateObj.operation = updateObj.operation.toLowerCase() as updateObj.operation = updateObj.operation.toLowerCase() as
| 'insert' | 'insert'
| 'delete' | 'delete'
| 'update' | 'update';
} }
if (updateObj.notification && typeof updateObj.notification === 'object') { if (updateObj.notification && typeof updateObj.notification === 'object') {
updateObj.notification = JSON.stringify(updateObj.notification) updateObj.notification = JSON.stringify(updateObj.notification);
} }
// get existing cache // get existing cache
const key = `${CacheScope.HOOK}:${hookId}` const key = `${CacheScope.HOOK}:${hookId}`;
let o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT) let o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT);
if (o) { if (o) {
// update data // update data
o = { ...o, ...updateObj } o = { ...o, ...updateObj };
// replace notification // replace notification
o.notification = updateObj.notification o.notification = updateObj.notification;
// set cache // set cache
await NocoCache.set(key, o) await NocoCache.set(key, o);
} }
// set meta // set meta
await ncMeta.metaUpdate(null, null, MetaTable.HOOKS, updateObj, hookId) await ncMeta.metaUpdate(null, null, MetaTable.HOOKS, updateObj, hookId);
return this.get(hookId, ncMeta) return this.get(hookId, ncMeta);
} }
static async delete(hookId: any, ncMeta = Noco.ncMeta) { static async delete(hookId: any, ncMeta = Noco.ncMeta) {
@ -243,22 +245,22 @@ export default class Hook implements HookType {
MetaTable.FILTER_EXP, MetaTable.FILTER_EXP,
{ {
condition: { fk_hook_id: hookId }, condition: { fk_hook_id: hookId },
}, }
) );
for (const filter of filterList) { for (const filter of filterList) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.FILTER_EXP, CacheScope.FILTER_EXP,
`${CacheScope.FILTER_EXP}:${filter.id}`, `${CacheScope.FILTER_EXP}:${filter.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT
) );
await HookFilter.delete(filter.id) await HookFilter.delete(filter.id);
} }
// Delete Hook // Delete Hook
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.HOOK, CacheScope.HOOK,
`${CacheScope.HOOK}:${hookId}`, `${CacheScope.HOOK}:${hookId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT
) );
return await ncMeta.metaDelete(null, null, MetaTable.HOOKS, hookId) return await ncMeta.metaDelete(null, null, MetaTable.HOOKS, hookId);
} }
} }

6
packages/nocodb/src/lib/services/apiTokenService.ts

@ -1,5 +1,6 @@
import { ApiTokenReqType, OrgUserRoles } from 'nocodb-sdk'; import { ApiTokenReqType, OrgUserRoles } from 'nocodb-sdk';
import { T } from 'nc-help'; import { T } from 'nc-help';
import { validatePayload } from '../meta/api/helpers';
import { NcError } from '../meta/helpers/catchError'; import { NcError } from '../meta/helpers/catchError';
import ApiToken from '../models/ApiToken'; import ApiToken from '../models/ApiToken';
import User from '../models/User'; import User from '../models/User';
@ -11,6 +12,11 @@ export async function apiTokenCreate(param: {
userId: string; userId: string;
tokenBody: ApiTokenReqType; tokenBody: ApiTokenReqType;
}) { }) {
await validatePayload(
'swagger.json#/components/schemas/ApiTokenReq',
param.tokenBody
);
T.emit('evt', { evt_type: 'apiToken:created' }); T.emit('evt', { evt_type: 'apiToken:created' });
return ApiToken.insert({ ...param.tokenBody, fk_user_id: param.userId }); return ApiToken.insert({ ...param.tokenBody, fk_user_id: param.userId });
} }

11
packages/nocodb/src/lib/services/auditService.ts

@ -2,6 +2,7 @@ import {
AuditRowUpdatePayloadType, AuditRowUpdatePayloadType,
CommentRowPayloadType, CommentRowPayloadType,
} from 'nocodb-sdk/build/main/lib/CustomAPI'; } from 'nocodb-sdk/build/main/lib/CustomAPI';
import { validatePayload } from '../meta/api/helpers';
import Audit from '../models/Audit'; import Audit from '../models/Audit';
import { AuditOperationSubTypes, AuditOperationTypes } from 'nocodb-sdk'; import { AuditOperationSubTypes, AuditOperationTypes } from 'nocodb-sdk';
import Model from '../models/Model'; import Model from '../models/Model';
@ -14,6 +15,11 @@ export async function commentRow(param: {
body: CommentRowPayloadType; body: CommentRowPayloadType;
user: any; user: any;
}) { }) {
await validatePayload(
'swagger.json#/components/schemas/CommentReq',
param.body
);
return await Audit.insert({ return await Audit.insert({
...param.body, ...param.body,
user: param.user?.email, user: param.user?.email,
@ -25,6 +31,11 @@ export async function auditRowUpdate(param: {
rowId: string; rowId: string;
body: AuditRowUpdatePayloadType; body: AuditRowUpdatePayloadType;
}) { }) {
await validatePayload(
'swagger.json#/components/schemas/AuditRowUpdateReq',
param.body
);
const model = await Model.getByIdOrName({ id: param.body.fk_model_id }); const model = await Model.getByIdOrName({ id: param.body.fk_model_id });
return await Audit.insert({ return await Audit.insert({
fk_model_id: param.body.fk_model_id, fk_model_id: param.body.fk_model_id,

6
packages/nocodb/src/lib/services/baseService.ts

@ -3,7 +3,7 @@ import { BaseReqType } from 'nocodb-sdk';
import { syncBaseMigration } from '../meta/helpers/syncMigration'; import { syncBaseMigration } from '../meta/helpers/syncMigration';
import Base from '../models/Base'; import Base from '../models/Base';
import { T } from 'nc-help'; import { T } from 'nc-help';
import { populateMeta } from '../meta/api/helpers'; import { populateMeta, validatePayload } from '../meta/api/helpers';
export async function baseGetWithConfig(param: { baseId: any }) { export async function baseGetWithConfig(param: { baseId: any }) {
const base = await Base.get(param.baseId); const base = await Base.get(param.baseId);
@ -18,6 +18,8 @@ export async function baseUpdate(param: {
base: BaseReqType; base: BaseReqType;
projectId: string; projectId: string;
}) { }) {
validatePayload('swagger.json#/components/schemas/BaseReq', param.base);
const baseBody = param.base; const baseBody = param.base;
const project = await Project.getWithInfo(param.projectId); const project = await Project.getWithInfo(param.projectId);
const base = await Base.updateBase(param.baseId, { const base = await Base.updateBase(param.baseId, {
@ -53,6 +55,8 @@ export async function baseCreate(param: {
projectId: string; projectId: string;
base: BaseReqType; base: BaseReqType;
}) { }) {
validatePayload('swagger.json#/components/schemas/BaseReq', param.base);
// type | base | projectId // type | base | projectId
const baseBody = param.base; const baseBody = param.base;
const project = await Project.getWithInfo(param.projectId); const project = await Project.getWithInfo(param.projectId);

3
packages/nocodb/src/lib/services/columnService.ts

@ -18,6 +18,7 @@ import {
generateFkName, generateFkName,
randomID, randomID,
validateLookupPayload, validateLookupPayload,
validatePayload,
validateRequiredField, validateRequiredField,
validateRollupPayload, validateRollupPayload,
} from '../meta/api/helpers'; } from '../meta/api/helpers';
@ -835,6 +836,8 @@ export async function columnAdd(param: {
tableId: string; tableId: string;
column: ColumnReqType; column: ColumnReqType;
}) { }) {
validatePayload('swagger.json#/components/schemas/ColumnReq', param.column);
const table = await Model.getWithInfo({ const table = await Model.getWithInfo({
id: param.tableId, id: param.tableId,
}); });

2
packages/nocodb/src/lib/services/dataService/index.ts

@ -795,4 +795,4 @@ export async function relationDataAdd(param: {
return true; return true;
} }
export * from './helpers' export * from './helpers';

7
packages/nocodb/src/lib/services/filterService.ts

@ -1,4 +1,5 @@
import { FilterReqType } from 'nocodb-sdk'; import { FilterReqType } from 'nocodb-sdk';
import { validatePayload } from '../meta/api/helpers';
import Filter from '../models/Filter'; import Filter from '../models/Filter';
import { T } from 'nc-help'; import { T } from 'nc-help';
@ -6,6 +7,8 @@ export async function hookFilterCreate(param: {
filter: FilterReqType; filter: FilterReqType;
hookId: any; hookId: any;
}) { }) {
validatePayload('swagger.json#/components/schemas/FilterReq', param.filter);
const filter = await Filter.insert({ const filter = await Filter.insert({
...param.filter, ...param.filter,
fk_hook_id: param.hookId, fk_hook_id: param.hookId,
@ -29,6 +32,8 @@ export async function filterCreate(param: {
filter: FilterReqType; filter: FilterReqType;
viewId: string; viewId: string;
}) { }) {
validatePayload('swagger.json#/components/schemas/FilterReq', param.filter);
const filter = await Filter.insert({ const filter = await Filter.insert({
...param.filter, ...param.filter,
fk_view_id: param.viewId, fk_view_id: param.viewId,
@ -42,6 +47,8 @@ export async function filterUpdate(param: {
filter: FilterReqType; filter: FilterReqType;
filterId: string; filterId: string;
}) { }) {
validatePayload('swagger.json#/components/schemas/FilterReq', param.filter);
// todo: type correction // todo: type correction
const filter = await Filter.update(param.filterId, param.filter as Filter); const filter = await Filter.update(param.filterId, param.filter as Filter);

6
packages/nocodb/src/lib/services/formViewColumnService.ts

@ -1,3 +1,4 @@
import { validatePayload } from '../meta/api/helpers';
import { FormViewColumn } from '../models'; import { FormViewColumn } from '../models';
import { T } from 'nc-help'; import { T } from 'nc-help';
export async function columnUpdate(param: { export async function columnUpdate(param: {
@ -5,6 +6,11 @@ export async function columnUpdate(param: {
// todo: replace with FormColumnReq // todo: replace with FormColumnReq
formViewColumn: FormViewColumn; formViewColumn: FormViewColumn;
}) { }) {
validatePayload(
'swagger.json#/components/schemas/FormColumnReq',
param.formViewColumn
);
T.emit('evt', { evt_type: 'formViewColumn:updated' }); T.emit('evt', { evt_type: 'formViewColumn:updated' });
return await FormViewColumn.update( return await FormViewColumn.update(
param.formViewColumnId, param.formViewColumnId,

5
packages/nocodb/src/lib/services/formViewService.ts

@ -1,5 +1,6 @@
import { T } from 'nc-help'; import { T } from 'nc-help';
import { FormReqType, ViewTypes } from 'nocodb-sdk'; import { FormReqType, ViewTypes } from 'nocodb-sdk';
import { validatePayload } from '../meta/api/helpers';
import { FormView, View } from '../models'; import { FormView, View } from '../models';
export async function formViewGet(param: { formViewId: string }) { export async function formViewGet(param: { formViewId: string }) {
@ -11,6 +12,8 @@ export async function formViewCreate(param: {
tableId: string; tableId: string;
body: FormReqType; body: FormReqType;
}) { }) {
validatePayload('swagger.json#/components/schemas/FormCreateReq', param.body);
T.emit('evt', { evt_type: 'vtable:created', show_as: 'form' }); T.emit('evt', { evt_type: 'vtable:created', show_as: 'form' });
const view = await View.insert({ const view = await View.insert({
...param.body, ...param.body,
@ -26,6 +29,8 @@ export async function formViewUpdate(param: {
formViewId: string; formViewId: string;
body: FormReqType; body: FormReqType;
}) { }) {
validatePayload('swagger.json#/components/schemas/FormReq', param.body);
T.emit('evt', { evt_type: 'view:updated', type: 'grid' }); T.emit('evt', { evt_type: 'view:updated', type: 'grid' });
await FormView.update(param.formViewId, param.body); await FormView.update(param.formViewId, param.body);
} }

5
packages/nocodb/src/lib/services/galleryViewService.ts

@ -1,5 +1,6 @@
import { GalleryReqType, ViewTypes } from 'nocodb-sdk'; import { GalleryReqType, ViewTypes } from 'nocodb-sdk';
import { T } from 'nc-help'; import { T } from 'nc-help';
import { validatePayload } from '../meta/api/helpers';
import { GalleryView, View } from '../models'; import { GalleryView, View } from '../models';
export async function galleryViewGet(param: { galleryViewId: string }) { export async function galleryViewGet(param: { galleryViewId: string }) {
@ -10,6 +11,8 @@ export async function galleryViewCreate(param: {
tableId: string; tableId: string;
gallery: GalleryReqType; gallery: GalleryReqType;
}) { }) {
validatePayload('swagger.json#/components/schemas/GalleryReq', param.gallery);
T.emit('evt', { evt_type: 'vtable:created', show_as: 'gallery' }); T.emit('evt', { evt_type: 'vtable:created', show_as: 'gallery' });
const view = await View.insert({ const view = await View.insert({
...param.gallery, ...param.gallery,
@ -24,6 +27,8 @@ export async function galleryViewUpdate(param: {
galleryViewId: string; galleryViewId: string;
gallery: GalleryReqType; gallery: GalleryReqType;
}) { }) {
validatePayload('swagger.json#/components/schemas/GalleryReq', param.gallery);
T.emit('evt', { evt_type: 'view:updated', type: 'gallery' }); T.emit('evt', { evt_type: 'view:updated', type: 'gallery' });
await GalleryView.update(param.galleryViewId, param.gallery); await GalleryView.update(param.galleryViewId, param.gallery);
} }

3
packages/nocodb/src/lib/services/gridViewColumnService.ts

@ -1,4 +1,5 @@
import { GridColumnReqType } from 'nocodb-sdk'; import { GridColumnReqType } from 'nocodb-sdk';
import { validatePayload } from '../meta/api/helpers';
import GridViewColumn from '../models/GridViewColumn'; import GridViewColumn from '../models/GridViewColumn';
import { T } from 'nc-help'; import { T } from 'nc-help';
@ -10,6 +11,8 @@ export async function gridColumnUpdate(param: {
gridViewColumnId: string; gridViewColumnId: string;
grid: GridColumnReqType; grid: GridColumnReqType;
}) { }) {
validatePayload('swagger.json#/components/schemas/GridColumnReq', param.grid);
T.emit('evt', { evt_type: 'gridViewColumn:updated' }); T.emit('evt', { evt_type: 'gridViewColumn:updated' });
return await GridViewColumn.update(param.gridViewColumnId, param.grid); return await GridViewColumn.update(param.gridViewColumnId, param.grid);
} }

4
packages/nocodb/src/lib/services/gridViewService.ts

@ -1,5 +1,6 @@
import { T } from 'nc-help'; import { T } from 'nc-help';
import { GridReqType, ViewTypes } from 'nocodb-sdk'; import { GridReqType, ViewTypes } from 'nocodb-sdk';
import { validatePayload } from '../meta/api/helpers';
import { View } from '../models'; import { View } from '../models';
import { GridView } from '../models'; import { GridView } from '../models';
@ -7,6 +8,8 @@ export async function gridViewCreate(param: {
tableId: string; tableId: string;
grid: GridReqType; grid: GridReqType;
}) { }) {
validatePayload('swagger.json#/components/schemas/GridReq', param.grid);
const view = await View.insert({ const view = await View.insert({
...param.grid, ...param.grid,
// todo: sanitize // todo: sanitize
@ -17,6 +20,7 @@ export async function gridViewCreate(param: {
return view; return view;
} }
// todo: json schema validation
export async function gridViewUpdate(param: { export async function gridViewUpdate(param: {
viewId: string; viewId: string;
grid: GridReqType; grid: GridReqType;

5
packages/nocodb/src/lib/services/hookFilterService.ts

@ -1,5 +1,6 @@
import { T } from 'nc-help'; import { T } from 'nc-help';
import { FilterReqType } from 'nocodb-sdk'; import { FilterReqType } from 'nocodb-sdk';
import { validatePayload } from '../meta/api/helpers';
import Filter from '../models/Filter'; import Filter from '../models/Filter';
export async function filterGet(param: { hookId: string }) { export async function filterGet(param: { hookId: string }) {
@ -32,6 +33,8 @@ export async function filterCreate(param: {
hookId: string; hookId: string;
filter: FilterReqType; filter: FilterReqType;
}) { }) {
validatePayload('swagger.json#/components/schemas/FilterReq', param.filter);
const filter = await Filter.insert({ const filter = await Filter.insert({
...param.filter, ...param.filter,
fk_hook_id: param.hookId, fk_hook_id: param.hookId,
@ -46,6 +49,8 @@ export async function filterUpdate(param: {
filterId: string; filterId: string;
filter: FilterReqType; filter: FilterReqType;
}) { }) {
validatePayload('swagger.json#/components/schemas/FilterReq', param.filter);
const filter = await Filter.update(param.filterId, { const filter = await Filter.update(param.filterId, {
...param.filter, ...param.filter,
fk_hook_id: param.hookId, fk_hook_id: param.hookId,

10
packages/nocodb/src/lib/services/hookService.ts

@ -1,4 +1,5 @@
import { T } from 'nc-help'; import { T } from 'nc-help';
import { validatePayload } from '../meta/api/helpers';
import { Hook, Model } from '../models'; import { Hook, Model } from '../models';
import { HookReqType, HookTestReqType } from 'nocodb-sdk'; import { HookReqType, HookTestReqType } from 'nocodb-sdk';
@ -14,6 +15,8 @@ export async function hookCreate(param: {
tableId: string; tableId: string;
hook: HookReqType; hook: HookReqType;
}) { }) {
validatePayload('swagger.json#/components/schemas/HookReq', param.hook);
T.emit('evt', { evt_type: 'webhooks:created' }); T.emit('evt', { evt_type: 'webhooks:created' });
// todo: type correction // todo: type correction
const hook = await Hook.insert({ const hook = await Hook.insert({
@ -30,6 +33,8 @@ export async function hookDelete(param: { hookId: string }) {
} }
export async function hookUpdate(param: { hookId: string; hook: HookReqType }) { export async function hookUpdate(param: { hookId: string; hook: HookReqType }) {
validatePayload('swagger.json#/components/schemas/HookReq', param.hook);
T.emit('evt', { evt_type: 'webhooks:updated' }); T.emit('evt', { evt_type: 'webhooks:updated' });
// todo: correction in swagger // todo: correction in swagger
@ -40,6 +45,11 @@ export async function hookTest(param: {
tableId: string; tableId: string;
hookTest: HookTestReqType; hookTest: HookTestReqType;
}) { }) {
validatePayload(
'swagger.json#/components/schemas/HookTestReq',
param.hookTest
);
const model = await Model.getByIdOrName({ id: param.tableId }); const model = await Model.getByIdOrName({ id: param.tableId });
const { const {

10
packages/nocodb/src/lib/services/kanbanViewService.ts

@ -1,4 +1,5 @@
import { KanbanReqType, ViewTypes } from 'nocodb-sdk'; import { KanbanReqType, ViewTypes } from 'nocodb-sdk';
import { validatePayload } from '../meta/api/helpers';
import { KanbanView, View } from '../models'; import { KanbanView, View } from '../models';
import { T } from 'nc-help'; import { T } from 'nc-help';
@ -10,7 +11,8 @@ export async function kanbanViewCreate(param: {
tableId: string; tableId: string;
kanban: KanbanReqType; kanban: KanbanReqType;
}) { }) {
T.emit('evt', { evt_type: 'vtable:created', show_as: 'kanban' }); validatePayload('swagger.json#/components/schemas/KanbanReq', param.kanban),
T.emit('evt', { evt_type: 'vtable:created', show_as: 'kanban' });
const view = await View.insert({ const view = await View.insert({
...param.kanban, ...param.kanban,
// todo: sanitize // todo: sanitize
@ -24,6 +26,10 @@ export async function kanbanViewUpdate(param: {
kanbanViewId: string; kanbanViewId: string;
kanban: KanbanReqType; kanban: KanbanReqType;
}) { }) {
T.emit('evt', { evt_type: 'view:updated', type: 'kanban' }); validatePayload(
'swagger.json#/components/schemas/KanbanUpdateReq',
param.kanban
),
T.emit('evt', { evt_type: 'view:updated', type: 'kanban' });
return await KanbanView.update(param.kanbanViewId, param.kanban); return await KanbanView.update(param.kanbanViewId, param.kanban);
} }

7
packages/nocodb/src/lib/services/modelVisibilityService.ts

@ -1,4 +1,5 @@
import { VisibilityRuleReqType } from 'nocodb-sdk'; import { VisibilityRuleReqType } from 'nocodb-sdk';
import { validatePayload } from '../meta/api/helpers';
import { NcError } from '../meta/helpers/catchError'; import { NcError } from '../meta/helpers/catchError';
import ModelRoleVisibility from '../models/ModelRoleVisibility'; import ModelRoleVisibility from '../models/ModelRoleVisibility';
import { T } from 'nc-help'; import { T } from 'nc-help';
@ -8,7 +9,11 @@ export async function xcVisibilityMetaSetAll(param: {
visibilityRule: VisibilityRuleReqType; visibilityRule: VisibilityRuleReqType;
projectId: string; projectId: string;
}) { }) {
T.emit('evt', { evt_type: 'uiAcl:updated' }); validatePayload(
'swagger.json#/components/schemas/VisibilityRuleReq',
param.visibilityRule
),
T.emit('evt', { evt_type: 'uiAcl:updated' });
for (const d of param.visibilityRule) { for (const d of param.visibilityRule) {
for (const role of Object.keys(d.disabled)) { for (const role of Object.keys(d.disabled)) {
const view = await View.get(d.id); const view = await View.get(d.id);

3
packages/nocodb/src/lib/services/orgLicenseService.ts

@ -1,4 +1,5 @@
import { NC_LICENSE_KEY } from '../constants'; import { NC_LICENSE_KEY } from '../constants';
import { validatePayload } from '../meta/api/helpers';
import Store from '../models/Store'; import Store from '../models/Store';
import Noco from '../Noco'; import Noco from '../Noco';
@ -9,6 +10,8 @@ export async function licenseGet() {
} }
export async function licenseSet(param: { key: string }) { export async function licenseSet(param: { key: string }) {
validatePayload('swagger.json#/components/schemas/LicenseReq', param);
await Store.saveOrUpdate({ value: param.key, key: NC_LICENSE_KEY }); await Store.saveOrUpdate({ value: param.key, key: NC_LICENSE_KEY });
await Noco.loadEEState(); await Noco.loadEEState();
return true; return true;

6
packages/nocodb/src/lib/services/orgTokenService.ts

@ -1,4 +1,5 @@
import { ApiTokenReqType, OrgUserRoles } from 'nocodb-sdk'; import { ApiTokenReqType, OrgUserRoles } from 'nocodb-sdk';
import { validatePayload } from '../meta/api/helpers';
import { User } from '../models'; import { User } from '../models';
import ApiToken from '../models/ApiToken'; import ApiToken from '../models/ApiToken';
import { T } from 'nc-help'; import { T } from 'nc-help';
@ -32,6 +33,11 @@ export async function apiTokenCreate(param: {
user: User; user: User;
apiToken: ApiTokenReqType; apiToken: ApiTokenReqType;
}) { }) {
validatePayload(
'swagger.json#/components/schemas/ApiTokenReq',
param.apiToken
);
T.emit('evt', { evt_type: 'org:apiToken:created' }); T.emit('evt', { evt_type: 'org:apiToken:created' });
return await ApiToken.insert({ return await ApiToken.insert({
...param.apiToken, ...param.apiToken,

5
packages/nocodb/src/lib/services/orgUserService.ts

@ -8,6 +8,7 @@ import { v4 as uuidv4 } from 'uuid';
import validator from 'validator'; import validator from 'validator';
import { OrgUserRoles } from 'nocodb-sdk'; import { OrgUserRoles } from 'nocodb-sdk';
import { NC_APP_SETTINGS } from '../constants'; import { NC_APP_SETTINGS } from '../constants';
import { validatePayload } from '../meta/api/helpers';
import { Audit, ProjectUser, Store, SyncSource, User } from '../models'; import { Audit, ProjectUser, Store, SyncSource, User } from '../models';
import Noco from '../Noco'; import Noco from '../Noco';
import { MetaTable } from '../utils/globals'; import { MetaTable } from '../utils/globals';
@ -35,6 +36,8 @@ export async function userUpdate(param: {
user: Partial<UserType>; user: Partial<UserType>;
userId: string; userId: string;
}) { }) {
validatePayload('swagger.json#/components/schemas/OrgUserReq', param.user);
const updateBody = extractProps(param.user, ['roles']); const updateBody = extractProps(param.user, ['roles']);
const user = await User.get(param.userId); const user = await User.get(param.userId);
@ -95,6 +98,8 @@ export async function userAdd(param: {
// todo: refactor // todo: refactor
req: any; req: any;
}) { }) {
validatePayload('swagger.json#/components/schemas/OrgUserReq', param.user);
// allow only viewer or creator role // allow only viewer or creator role
if ( if (
param.user.roles && param.user.roles &&

5
packages/nocodb/src/lib/services/pluginService.ts

@ -1,4 +1,5 @@
import { T } from 'nc-help'; import { T } from 'nc-help';
import { validatePayload } from '../meta/api/helpers';
import { Plugin } from '../models'; import { Plugin } from '../models';
import { PluginTestReqType, PluginType } from 'nocodb-sdk'; import { PluginTestReqType, PluginType } from 'nocodb-sdk';
import NcPluginMgrv2 from '../meta/helpers/NcPluginMgrv2'; import NcPluginMgrv2 from '../meta/helpers/NcPluginMgrv2';
@ -8,6 +9,8 @@ export async function pluginList() {
} }
export async function pluginTest(param: { body: PluginTestReqType }) { export async function pluginTest(param: { body: PluginTestReqType }) {
validatePayload('swagger.json#/components/schemas/PluginTestReq', param.body);
T.emit('evt', { evt_type: 'plugin:tested' }); T.emit('evt', { evt_type: 'plugin:tested' });
return await NcPluginMgrv2.test(param.body); return await NcPluginMgrv2.test(param.body);
} }
@ -19,6 +22,8 @@ export async function pluginUpdate(param: {
pluginId: string; pluginId: string;
plugin: PluginType; plugin: PluginType;
}) { }) {
validatePayload('swagger.json#/components/schemas/PluginReq', param.plugin);
const plugin = await Plugin.update(param.pluginId, param.plugin); const plugin = await Plugin.update(param.pluginId, param.plugin);
T.emit('evt', { T.emit('evt', {
evt_type: plugin.active ? 'plugin:installed' : 'plugin:uninstalled', evt_type: plugin.active ? 'plugin:installed' : 'plugin:uninstalled',

4
packages/nocodb/src/lib/services/projectService.ts

@ -1,7 +1,7 @@
import DOMPurify from 'isomorphic-dompurify'; import DOMPurify from 'isomorphic-dompurify';
import { OrgUserRoles, ProjectReqType } from 'nocodb-sdk'; import { OrgUserRoles, ProjectReqType } from 'nocodb-sdk';
import { promisify } from 'util'; import { promisify } from 'util';
import { populateMeta } from '../meta/api/helpers'; import { populateMeta, validatePayload } from '../meta/api/helpers';
import { extractPropsAndSanitize } from '../meta/helpers/extractProps'; import { extractPropsAndSanitize } from '../meta/helpers/extractProps';
import syncMigration from '../meta/helpers/syncMigration'; import syncMigration from '../meta/helpers/syncMigration';
import Project from '../models/Project'; import Project from '../models/Project';
@ -15,6 +15,8 @@ export async function projectCreate(param: {
project: ProjectReqType; project: ProjectReqType;
user: any; user: any;
}) { }) {
validatePayload('swagger.json#/components/schemas/ProjectReq', param.project);
const projectBody: ProjectReqType & Record<string, any> = param.project; const projectBody: ProjectReqType & Record<string, any> = param.project;
if (!projectBody.external) { if (!projectBody.external) {
const ranId = nanoid(); const ranId = nanoid();

11
packages/nocodb/src/lib/services/projectUserService.ts

@ -1,5 +1,6 @@
import { OrgUserRoles, ProjectUserReqType } from 'nocodb-sdk'; import { OrgUserRoles, ProjectUserReqType } from 'nocodb-sdk';
import { T } from 'nc-help'; import { T } from 'nc-help';
import { validatePayload } from '../meta/api/helpers';
import { PagedResponseImpl } from '../meta/helpers/PagedResponse'; import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
import ProjectUser from '../models/ProjectUser'; import ProjectUser from '../models/ProjectUser';
import validator from 'validator'; import validator from 'validator';
@ -33,6 +34,11 @@ export async function userInvite(param: {
projectUser: ProjectUserReqType; projectUser: ProjectUserReqType;
req: any; req: any;
}): Promise<any> { }): Promise<any> {
validatePayload(
'swagger.json#/components/schemas/ProjectUserReq',
param.projectUser
);
const emails = (param.projectUser.email || '') const emails = (param.projectUser.email || '')
.toLowerCase() .toLowerCase()
.split(/\s*,\s*/) .split(/\s*,\s*/)
@ -157,6 +163,11 @@ export async function projectUserUpdate(param: {
req: any; req: any;
projectId: string; projectId: string;
}): Promise<any> { }): Promise<any> {
validatePayload(
'swagger.json#/components/schemas/ProjectUserReq',
param.projectUser
);
// todo: use param.projectId // todo: use param.projectId
if (!param.projectUser?.project_id) { if (!param.projectUser?.project_id) {
NcError.badRequest('Missing project id in request body.'); NcError.badRequest('Missing project id in request body.');

5
packages/nocodb/src/lib/services/sharedBaseService.ts

@ -1,5 +1,6 @@
import { T } from 'nc-help'; import { T } from 'nc-help';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { validatePayload } from '../meta/api/helpers';
import Project from '../models/Project'; import Project from '../models/Project';
import { NcError } from '../meta/helpers/catchError'; import { NcError } from '../meta/helpers/catchError';
// todo: load from config // todo: load from config
@ -13,6 +14,8 @@ export async function createSharedBaseLink(param: {
password: string; password: string;
siteUrl: string; siteUrl: string;
}): Promise<any> { }): Promise<any> {
validatePayload('swagger.json#/components/schemas/SharedBaseReq', param);
const project = await Project.get(param.projectId); const project = await Project.get(param.projectId);
let roles = param?.roles; let roles = param?.roles;
@ -44,6 +47,8 @@ export async function updateSharedBaseLink(param: {
password: string; password: string;
siteUrl: string; siteUrl: string;
}): Promise<any> { }): Promise<any> {
validatePayload('swagger.json#/components/schemas/SharedBaseReq', param);
const project = await Project.get(param.projectId); const project = await Project.get(param.projectId);
let roles = param.roles; let roles = param.roles;

5
packages/nocodb/src/lib/services/sortService.ts

@ -1,4 +1,5 @@
import { SortReqType } from 'nocodb-sdk'; import { SortReqType } from 'nocodb-sdk';
import { validatePayload } from '../meta/api/helpers';
import Sort from '../models/Sort'; import Sort from '../models/Sort';
import { T } from 'nc-help'; import { T } from 'nc-help';
@ -13,12 +14,16 @@ export async function sortDelete(param: { sortId: string }) {
} }
export async function sortUpdate(param: { sortId: any; sort: SortReqType }) { export async function sortUpdate(param: { sortId: any; sort: SortReqType }) {
validatePayload('swagger.json#/components/schemas/SortReq', param.sort);
const sort = await Sort.update(param.sortId, param.sort); const sort = await Sort.update(param.sortId, param.sort);
T.emit('evt', { evt_type: 'sort:updated' }); T.emit('evt', { evt_type: 'sort:updated' });
return sort; return sort;
} }
export async function sortCreate(param: { viewId: any; sort: SortReqType }) { export async function sortCreate(param: { viewId: any; sort: SortReqType }) {
validatePayload('swagger.json#/components/schemas/SortReq', param.sort);
const sort = await Sort.insert({ const sort = await Sort.insert({
...param.sort, ...param.sort,
fk_view_id: param.viewId, fk_view_id: param.viewId,

21
packages/nocodb/src/lib/services/tableService.ts

@ -9,6 +9,7 @@ import {
UITypes, UITypes,
} from 'nocodb-sdk'; } from 'nocodb-sdk';
import ProjectMgrv2 from '../db/sql-mgr/v2/ProjectMgrv2'; import ProjectMgrv2 from '../db/sql-mgr/v2/ProjectMgrv2';
import { validatePayload } from '../meta/api/helpers';
import { NcError } from '../meta/helpers/catchError'; import { NcError } from '../meta/helpers/catchError';
import getColumnPropsFromUIDT from '../meta/helpers/getColumnPropsFromUIDT'; import getColumnPropsFromUIDT from '../meta/helpers/getColumnPropsFromUIDT';
import getColumnUiType from '../meta/helpers/getColumnUiType'; import getColumnUiType from '../meta/helpers/getColumnUiType';
@ -16,14 +17,16 @@ import getTableNameAlias, {
getColumnNameAlias, getColumnNameAlias,
} from '../meta/helpers/getTableName'; } from '../meta/helpers/getTableName';
import mapDefaultDisplayValue from '../meta/helpers/mapDefaultDisplayValue'; import mapDefaultDisplayValue from '../meta/helpers/mapDefaultDisplayValue';
import Audit from '../models/Audit'; import {
import Column from '../models/Column'; Audit,
import LinkToAnotherRecordColumn from '../models/LinkToAnotherRecordColumn'; Column,
import Model from '../models/Model'; LinkToAnotherRecordColumn,
import ModelRoleVisibility from '../models/ModelRoleVisibility'; Model,
import Project from '../models/Project'; ModelRoleVisibility,
import User from '../models/User'; Project,
import View from '../models/View'; User,
View,
} from '../models';
import NcConnectionMgrv2 from '../utils/common/NcConnectionMgrv2'; import NcConnectionMgrv2 from '../utils/common/NcConnectionMgrv2';
import { T } from 'nc-help'; import { T } from 'nc-help';
@ -304,6 +307,8 @@ export async function tableCreate(args: {
table: TableReqType; table: TableReqType;
user: User; user: User;
}) { }) {
validatePayload('swagger.json#/components/schemas/TableReq', args.table);
const project = await Project.getWithInfo(args.projectId); const project = await Project.getWithInfo(args.projectId);
let base = project.bases[0]; let base = project.bases[0];

16
packages/nocodb/src/lib/services/userService/index.ts

@ -13,6 +13,7 @@ import * as ejs from 'ejs';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { promisify } from 'util'; import { promisify } from 'util';
import { NC_APP_SETTINGS } from '../../constants'; import { NC_APP_SETTINGS } from '../../constants';
import { validatePayload } from '../../meta/api/helpers';
import { NcError } from '../../meta/helpers/catchError'; import { NcError } from '../../meta/helpers/catchError';
import NcPluginMgrv2 from '../../meta/helpers/NcPluginMgrv2'; import NcPluginMgrv2 from '../../meta/helpers/NcPluginMgrv2';
import { Audit, Store, User } from '../../models'; import { Audit, Store, User } from '../../models';
@ -79,6 +80,11 @@ export async function passwordChange(param: {
user: UserType; user: UserType;
req: any; req: any;
}): Promise<any> { }): Promise<any> {
validatePayload(
'swagger.json#/components/schemas/PasswordChangeReq',
param.body
);
const { currentPassword, newPassword } = param.body; const { currentPassword, newPassword } = param.body;
if (!currentPassword || !newPassword) { if (!currentPassword || !newPassword) {
@ -129,6 +135,11 @@ export async function passwordForgot(param: {
siteUrl: string; siteUrl: string;
req: any; req: any;
}): Promise<any> { }): Promise<any> {
validatePayload(
'swagger.json#/components/schemas/ForgotPasswordReq',
param.body
);
const _email = param.body.email; const _email = param.body.email;
if (!_email) { if (!_email) {
@ -203,6 +214,11 @@ export async function passwordReset(param: {
// todo: exclude // todo: exclude
req: any; req: any;
}): Promise<any> { }): Promise<any> {
validatePayload(
'swagger.json#/components/schemas/PasswordResetReq',
param.body
);
const { token, body, req } = param; const { token, body, req } = param;
const user = await Noco.ncMeta.metaGet(null, null, MetaTable.USERS, { const user = await Noco.ncMeta.metaGet(null, null, MetaTable.USERS, {

Loading…
Cancel
Save