From 717392726a44e52cc6cf45fe41e6914f76fc9736 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Sun, 9 Apr 2023 11:14:21 +0530 Subject: [PATCH] feat: model visibility apis Signed-off-by: Pranav C --- .../model-visibilities.controller.spec.ts | 20 ++++ .../model-visibilities.controller.ts | 49 ++++++++ .../model-visibilities.module.ts | 9 ++ .../model-visibilities.service.spec.ts | 18 +++ .../model-visibilities.service.ts | 112 ++++++++++++++++++ 5 files changed, 208 insertions(+) create mode 100644 packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.controller.spec.ts create mode 100644 packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.controller.ts create mode 100644 packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.module.ts create mode 100644 packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.service.spec.ts create mode 100644 packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.service.ts diff --git a/packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.controller.spec.ts b/packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.controller.spec.ts new file mode 100644 index 0000000000..007695f337 --- /dev/null +++ b/packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ModelVisibilitiesController } from './model-visibilities.controller'; +import { ModelVisibilitiesService } from './model-visibilities.service'; + +describe('ModelVisibilitiesController', () => { + let controller: ModelVisibilitiesController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [ModelVisibilitiesController], + providers: [ModelVisibilitiesService], + }).compile(); + + controller = module.get(ModelVisibilitiesController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.controller.ts b/packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.controller.ts new file mode 100644 index 0000000000..95b6f67b78 --- /dev/null +++ b/packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.controller.ts @@ -0,0 +1,49 @@ +import { + Body, + Controller, + Get, + Param, + Post, + Query, + UseGuards, +} from '@nestjs/common'; +import { + Acl, + ExtractProjectIdMiddleware, +} from '../../middlewares/extract-project-id/extract-project-id.middleware'; +import { ModelVisibilitiesService } from './model-visibilities.service'; +import { AuthGuard } from '@nestjs/passport'; + +@Controller('model-visibilities') +@UseGuards(ExtractProjectIdMiddleware, AuthGuard('jwt')) +export class ModelVisibilitiesController { + constructor( + private readonly modelVisibilitiesService: ModelVisibilitiesService, + ) {} + + @Post('/api/v1/db/meta/projects/:projectId/visibility-rules') + @Acl('modelVisibilitySet') + async xcVisibilityMetaSetAll( + @Param('projectId') projectId: string, + @Body() body: any, + ) { + await this.modelVisibilitiesService.xcVisibilityMetaSetAll({ + visibilityRule: body, + projectId, + }); + + return { msg: 'UI ACL has been created successfully' }; + } + + @Get('/api/v1/db/meta/projects/:projectId/visibility-rules') + @Acl('modelVisibilityList') + async modelVisibilityList( + @Param('projectId') projectId: string, + @Query('includeM2M') includeM2M: boolean | string, + ) { + return await this.modelVisibilitiesService.xcVisibilityMetaGet({ + projectId, + includeM2M: includeM2M === true || includeM2M === 'true', + }); + } +} diff --git a/packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.module.ts b/packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.module.ts new file mode 100644 index 0000000000..5b001e8d85 --- /dev/null +++ b/packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ModelVisibilitiesService } from './model-visibilities.service'; +import { ModelVisibilitiesController } from './model-visibilities.controller'; + +@Module({ + controllers: [ModelVisibilitiesController], + providers: [ModelVisibilitiesService] +}) +export class ModelVisibilitiesModule {} diff --git a/packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.service.spec.ts b/packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.service.spec.ts new file mode 100644 index 0000000000..9ea100d0ac --- /dev/null +++ b/packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ModelVisibilitiesService } from './model-visibilities.service'; + +describe('ModelVisibilitiesService', () => { + let service: ModelVisibilitiesService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ModelVisibilitiesService], + }).compile(); + + service = module.get(ModelVisibilitiesService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.service.ts b/packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.service.ts new file mode 100644 index 0000000000..3c0863cdb9 --- /dev/null +++ b/packages/nocodb-nest/src/modules/model-visibilities/model-visibilities.service.ts @@ -0,0 +1,112 @@ +import { Injectable } from '@nestjs/common'; +import { VisibilityRuleReqType } from 'nocodb-sdk'; +import { validatePayload } from '../../helpers'; +import { NcError } from '../../helpers/catchError'; +import { Model, ModelRoleVisibility, View } from '../../models'; +import { T } from 'nc-help'; + +@Injectable() +export class ModelVisibilitiesService { + async xcVisibilityMetaSetAll(param: { + visibilityRule: VisibilityRuleReqType; + projectId: string; + }) { + validatePayload( + 'swagger.json#/components/schemas/VisibilityRuleReq', + param.visibilityRule, + ); + T.emit('evt', { evt_type: 'uiAcl:updated' }); + for (const d of param.visibilityRule) { + for (const role of Object.keys(d.disabled)) { + const view = await View.get(d.id); + + if (view.project_id !== param.projectId) { + NcError.badRequest('View does not belong to the project'); + } + + const dataInDb = await ModelRoleVisibility.get({ + role, + fk_view_id: d.id, + }); + if (dataInDb) { + if (d.disabled[role]) { + if (!dataInDb.disabled) { + await ModelRoleVisibility.update(d.id, role, { + disabled: d.disabled[role], + }); + } + } else { + await dataInDb.delete(); + } + } else if (d.disabled[role]) { + await ModelRoleVisibility.insert({ + fk_view_id: d.id, + disabled: d.disabled[role], + role, + }); + } + } + } + T.emit('evt', { evt_type: 'uiAcl:updated' }); + + return true; + } + + async xcVisibilityMetaGet(param: { + projectId: string; + includeM2M?: boolean; + models?: Model[]; + }) { + const { includeM2M = true, projectId, models: _models } = param ?? {}; + + // todo: move to + const roles = [ + 'owner', + 'creator', + 'viewer', + 'editor', + 'commenter', + 'guest', + ]; + + const defaultDisabled = roles.reduce((o, r) => ({ ...o, [r]: false }), {}); + + let models = + _models || + (await Model.list({ + project_id: projectId, + base_id: undefined, + })); + + models = includeM2M ? models : (models.filter((t) => !t.mm) as Model[]); + + const result = await models.reduce(async (_obj, model) => { + const obj = await _obj; + + const views = await model.getViews(); + for (const view of views) { + obj[view.id] = { + ptn: model.table_name, + _ptn: model.title, + ptype: model.type, + tn: view.title, + _tn: view.title, + table_meta: model.meta, + ...view, + disabled: { ...defaultDisabled }, + }; + } + + return obj; + }, Promise.resolve({})); + + const disabledList = await ModelRoleVisibility.list(projectId); + + for (const d of disabledList) { + if (result[d.fk_view_id]) + result[d.fk_view_id].disabled[d.role] = !!d.disabled; + } + + return Object.values(result); + } +}