From b722d039bbdf0b9a27d445d20f47bffbd813d415 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Fri, 26 May 2023 19:20:10 +0530 Subject: [PATCH] feat: add new data apis Signed-off-by: Pranav C --- packages/nocodb/src/app.module.ts | 6 +- .../data-alias-nested.controller.ts | 1 - .../controllers/data-table.controller.spec.ts | 18 +++ .../src/controllers/data-table.controller.ts | 137 +++++++++++++++++ .../src/services/data-table.service.spec.ts | 18 +++ .../nocodb/src/services/data-table.service.ts | 140 ++++++++++++++++++ packages/nocodb/src/services/datas.service.ts | 2 +- 7 files changed, 319 insertions(+), 3 deletions(-) create mode 100644 packages/nocodb/src/controllers/data-table.controller.spec.ts create mode 100644 packages/nocodb/src/controllers/data-table.controller.ts create mode 100644 packages/nocodb/src/services/data-table.service.spec.ts create mode 100644 packages/nocodb/src/services/data-table.service.ts diff --git a/packages/nocodb/src/app.module.ts b/packages/nocodb/src/app.module.ts index f5a4e8f1b7..ad8a162866 100644 --- a/packages/nocodb/src/app.module.ts +++ b/packages/nocodb/src/app.module.ts @@ -19,6 +19,9 @@ import { MetasModule } from './modules/metas/metas.module'; import { JobsModule } from './modules/jobs/jobs.module'; import { AppInitService } from './services/app-init.service'; import type { MiddlewareConsumer } from '@nestjs/common'; +import { DataTableController } from './controllers/data-table.controller'; +import { DataTableController } from './servicess/data-table.controller'; +import { DataTableService } from './services/data-table.service'; @Module({ imports: [ @@ -38,7 +41,7 @@ import type { MiddlewareConsumer } from '@nestjs/common'; ] : []), ], - controllers: [], + controllers: [DataTableController], providers: [ AuthService, { @@ -50,6 +53,7 @@ import type { MiddlewareConsumer } from '@nestjs/common'; BaseViewStrategy, HookHandlerService, AppInitService, + DataTableService, ], }) export class AppModule { diff --git a/packages/nocodb/src/controllers/data-alias-nested.controller.ts b/packages/nocodb/src/controllers/data-alias-nested.controller.ts index 53c11f5f42..c10bdb8088 100644 --- a/packages/nocodb/src/controllers/data-alias-nested.controller.ts +++ b/packages/nocodb/src/controllers/data-alias-nested.controller.ts @@ -8,7 +8,6 @@ import { Request, UseGuards, } from '@nestjs/common'; -import { AuthGuard } from '@nestjs/passport'; import { GlobalGuard } from '../guards/global/global.guard'; import { Acl, diff --git a/packages/nocodb/src/controllers/data-table.controller.spec.ts b/packages/nocodb/src/controllers/data-table.controller.spec.ts new file mode 100644 index 0000000000..cb22816371 --- /dev/null +++ b/packages/nocodb/src/controllers/data-table.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { DataTableController } from './data-table.controller'; + +describe('DataTableController', () => { + let controller: DataTableController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [DataTableController], + }).compile(); + + controller = module.get(DataTableController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/packages/nocodb/src/controllers/data-table.controller.ts b/packages/nocodb/src/controllers/data-table.controller.ts new file mode 100644 index 0000000000..da18ac5b30 --- /dev/null +++ b/packages/nocodb/src/controllers/data-table.controller.ts @@ -0,0 +1,137 @@ +import { + Body, + Controller, + Delete, + Get, + HttpCode, + Param, + Patch, + Post, + Query, + Request, + Response, + UseGuards, +} from '@nestjs/common'; +import { GlobalGuard } from '../guards/global/global.guard'; +import { parseHrtimeToSeconds } from '../helpers'; +import { + Acl, + ExtractProjectIdMiddleware, +} from '../middlewares/extract-project-id/extract-project-id.middleware'; +import { DataTableService } from '../services/data-table.service'; +import { DatasService } from '../services/datas.service'; + +@Controller() +@UseGuards(ExtractProjectIdMiddleware, GlobalGuard) +export class DataTableController { + constructor(private readonly dataTableService: DataTableService) {} + + // todo: Handle the error case where view doesnt belong to model + @Get('/api/v1/db/:projectId/tables/:modelId') + @Acl('dataList') + async dataList( + @Request() req, + @Response() res, + @Param('projectId') projectId: string, + @Param('modelId') modelId: string, + @Query('viewId') viewId: string, + ) { + const startTime = process.hrtime(); + const responseData = await this.dataTableService.dataList({ + query: req.query, + projectId: projectId, + modelId: modelId, + }); + const elapsedSeconds = parseHrtimeToSeconds(process.hrtime(startTime)); + res.setHeader('xc-db-response', elapsedSeconds); + res.json(responseData); + } + + @Get(['/api/v1/db/:projectId/tables/:modelId/count']) + @Acl('dataCount') + async dataCount( + @Request() req, + @Response() res, + @Param('projectId') projectId: string, + @Param('modelId') modelId: string, + @Query('viewId') viewId: string, + ) { + const countResult = await this.dataTableService.dataCount({ + query: req.query, + modelId, + projectId, + }); + + res.json(countResult); + } + + @Post(['/api/v1/db/:projectId/tables/:modelId']) + @HttpCode(200) + @Acl('dataInsert') + async dataInsert( + @Request() req, + @Param('projectId') projectId: string, + @Param('modelId') modelId: string, + @Query('viewId') viewId: string, + @Body() body: any, + ) { + return await this.dataTableService.dataInsert({ + projectId: projectId, + modelId: modelId, + body: body, + cookie: req, + }); + } + + @Patch(['/api/v1/db/:projectId/tables/:modelId/:rowId']) + @Acl('dataUpdate') + async dataUpdate( + @Request() req, + @Param('projectId') projectId: string, + @Param('modelId') modelId: string, + @Query('viewId') viewId: string, + @Param('rowId') rowId: string, + ) { + return await this.dataTableService.dataUpdate({ + projectId: projectId, + modelId: modelId, + body: req.body, + cookie: req, + rowId: rowId, + }); + } + + @Delete(['/api/v1/db/:projectId/tables/:modelId/:rowId']) + @Acl('dataDelete') + async dataDelete( + @Request() req, + @Param('projectId') projectId: string, + @Param('modelId') modelId: string, + @Query('viewId') viewId: string, + @Param('rowId') rowId: string, + ) { + return await this.dataTableService.dataDelete({ + projectId: projectId, + modelId: modelId, + cookie: req, + rowId: rowId, + }); + } + + @Get(['/api/v1/db/:projectId/tables/:modelId/:rowId']) + @Acl('dataRead') + async dataRead( + @Request() req, + @Param('projectId') projectId: string, + @Param('modelId') modelId: string, + @Query('viewId') viewId: string, + @Param('rowId') rowId: string, + ) { + return await this.dataTableService.dataRead({ + modelId, + projectId, + rowId: rowId, + query: req.query, + }); + } +} diff --git a/packages/nocodb/src/services/data-table.service.spec.ts b/packages/nocodb/src/services/data-table.service.spec.ts new file mode 100644 index 0000000000..cf61df154f --- /dev/null +++ b/packages/nocodb/src/services/data-table.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { DataTableService } from './data-table.service'; + +describe('DataTableService', () => { + let service: DataTableService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [DataTableService], + }).compile(); + + service = module.get(DataTableService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/packages/nocodb/src/services/data-table.service.ts b/packages/nocodb/src/services/data-table.service.ts new file mode 100644 index 0000000000..5fad03a26d --- /dev/null +++ b/packages/nocodb/src/services/data-table.service.ts @@ -0,0 +1,140 @@ +import { Injectable } from '@nestjs/common'; +import { NcError } from '../helpers/catchError'; +import { Base, Model } from '../models'; +import NcConnectionMgrv2 from '../utils/common/NcConnectionMgrv2'; +import { DatasService } from './datas.service'; + +@Injectable() +export class DataTableService { + constructor(private datasService: DatasService) {} + + async dataList(param: { projectId?: string; modelId: string; query: any }) { + const model = await this.getModelAndValidate(param); + + return await this.datasService.getDataList({ + model, + query: param.query, + }); + } + + async dataRead(param: { + projectId?: string; + modelId: string; + rowId: string; + query: any; + }) { + const model = await this.getModelAndValidate(param); + + const base = await Base.get(model.base_id); + + const baseModel = await Model.getBaseModelSQL({ + id: model.id, + dbDriver: await NcConnectionMgrv2.get(base), + }); + + const row = await baseModel.readByPk(param.rowId, false, param.query); + + if (!row) { + NcError.notFound('Row not found'); + } + + return row; + } + + async dataInsert(param: { + projectId?: string; + modelId: string; + body: any; + cookie: any; + }) { + const model = await this.getModelAndValidate(param); + const base = await Base.get(model.base_id); + + const baseModel = await Model.getBaseModelSQL({ + id: model.id, + dbDriver: await NcConnectionMgrv2.get(base), + }); + + return await baseModel.insert(param.body, null, param.cookie); + } + + async dataUpdate(param: { + projectId?: string; + modelId: string; + rowId: string; + body: any; + cookie: any; + }) { + const model = await this.getModelAndValidate(param); + + const base = await Base.get(model.base_id); + + const baseModel = await Model.getBaseModelSQL({ + id: model.id, + dbDriver: await NcConnectionMgrv2.get(base), + }); + + return await baseModel.updateByPk( + param.rowId, + param.body, + null, + param.cookie, + ); + } + + async dataDelete(param: { + projectId?: string; + modelId: string; + rowId: string; + cookie: any; + }) { + const model = await this.getModelAndValidate(param); + const base = await Base.get(model.base_id); + const baseModel = await Model.getBaseModelSQL({ + id: model.id, + dbDriver: await NcConnectionMgrv2.get(base), + }); + + // todo: Should have error http status code + const message = await baseModel.hasLTARData(param.rowId, model); + if (message.length) { + return { message }; + } + return await baseModel.delByPk(param.rowId, null, param.cookie); + } + + private async getModelAndValidate(param: { + projectId?: string; + modelId: string; + query: any; + }) { + const model = await Model.get(param.modelId); + + if (model.project_id && model.project_id !== param.projectId) { + throw new Error('Model not found in project'); + } + return model; + } + + async dataCount(param: { projectId?: string; modelId: string; query: any }) { + + const model = await this.getModelAndValidate(param); + + const base = await Base.get(model.base_id); + + const baseModel = await Model.getBaseModelSQL({ + id: model.id, + dbDriver: await NcConnectionMgrv2.get(base), + }); + + const countArgs: any = { ...param.query }; + try { + countArgs.filterArr = JSON.parse(countArgs.filterArrJson); + } catch (e) {} + + const count: number = await baseModel.count(countArgs); + + return { count }; + + } +} diff --git a/packages/nocodb/src/services/datas.service.ts b/packages/nocodb/src/services/datas.service.ts index c41609f6c0..68e45dfc49 100644 --- a/packages/nocodb/src/services/datas.service.ts +++ b/packages/nocodb/src/services/datas.service.ts @@ -113,7 +113,7 @@ export class DatasService { async getDataList(param: { model: Model; - view: View; + view?: View; query: any; baseModel?: BaseModelSqlv2; }) {