mirror of https://github.com/nocodb/nocodb
Pranav C
1 year ago
committed by
GitHub
20 changed files with 4352 additions and 92 deletions
@ -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>(DataTableController); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should be defined', () => { |
||||||
|
expect(controller).toBeDefined(); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,189 @@ |
|||||||
|
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'; |
||||||
|
|
||||||
|
@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/tables/:modelId/rows') |
||||||
|
@Acl('dataList') |
||||||
|
async dataList( |
||||||
|
@Request() req, |
||||||
|
@Response() res, |
||||||
|
@Param('modelId') modelId: string, |
||||||
|
@Query('viewId') viewId: string, |
||||||
|
) { |
||||||
|
const startTime = process.hrtime(); |
||||||
|
const responseData = await this.dataTableService.dataList({ |
||||||
|
query: req.query, |
||||||
|
modelId: modelId, |
||||||
|
viewId: viewId, |
||||||
|
}); |
||||||
|
const elapsedSeconds = parseHrtimeToSeconds(process.hrtime(startTime)); |
||||||
|
res.setHeader('xc-db-response', elapsedSeconds); |
||||||
|
res.json(responseData); |
||||||
|
} |
||||||
|
|
||||||
|
@Get(['/api/v1/tables/:modelId/rows/count']) |
||||||
|
@Acl('dataCount') |
||||||
|
async dataCount( |
||||||
|
@Request() req, |
||||||
|
@Response() res, |
||||||
|
@Param('modelId') modelId: string, |
||||||
|
@Query('viewId') viewId: string, |
||||||
|
) { |
||||||
|
const countResult = await this.dataTableService.dataCount({ |
||||||
|
query: req.query, |
||||||
|
modelId, |
||||||
|
viewId, |
||||||
|
}); |
||||||
|
|
||||||
|
res.json(countResult); |
||||||
|
} |
||||||
|
|
||||||
|
@Post(['/api/v1/tables/:modelId/rows']) |
||||||
|
@HttpCode(200) |
||||||
|
@Acl('dataInsert') |
||||||
|
async dataInsert( |
||||||
|
@Request() req, |
||||||
|
@Param('modelId') modelId: string, |
||||||
|
@Query('viewId') viewId: string, |
||||||
|
@Body() body: any, |
||||||
|
) { |
||||||
|
return await this.dataTableService.dataInsert({ |
||||||
|
modelId: modelId, |
||||||
|
body: body, |
||||||
|
viewId, |
||||||
|
cookie: req, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Patch(['/api/v1/tables/:modelId/rows']) |
||||||
|
@Acl('dataUpdate') |
||||||
|
async dataUpdate( |
||||||
|
@Request() req, |
||||||
|
@Param('modelId') modelId: string, |
||||||
|
@Query('viewId') viewId: string, |
||||||
|
@Param('rowId') rowId: string, |
||||||
|
) { |
||||||
|
return await this.dataTableService.dataUpdate({ |
||||||
|
modelId: modelId, |
||||||
|
body: req.body, |
||||||
|
cookie: req, |
||||||
|
viewId, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Delete(['/api/v1/tables/:modelId/rows']) |
||||||
|
@Acl('dataDelete') |
||||||
|
async dataDelete( |
||||||
|
@Request() req, |
||||||
|
@Param('modelId') modelId: string, |
||||||
|
@Query('viewId') viewId: string, |
||||||
|
@Param('rowId') rowId: string, |
||||||
|
) { |
||||||
|
return await this.dataTableService.dataDelete({ |
||||||
|
modelId: modelId, |
||||||
|
cookie: req, |
||||||
|
viewId, |
||||||
|
body: req.body, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Get(['/api/v1/tables/:modelId/rows/:rowId']) |
||||||
|
@Acl('dataRead') |
||||||
|
async dataRead( |
||||||
|
@Request() req, |
||||||
|
@Param('modelId') modelId: string, |
||||||
|
@Query('viewId') viewId: string, |
||||||
|
@Param('rowId') rowId: string, |
||||||
|
) { |
||||||
|
return await this.dataTableService.dataRead({ |
||||||
|
modelId, |
||||||
|
rowId: rowId, |
||||||
|
query: req.query, |
||||||
|
viewId, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Get(['/api/v1/tables/:modelId/links/:columnId/rows/:rowId']) |
||||||
|
@Acl('nestedDataList') |
||||||
|
async nestedDataList( |
||||||
|
@Request() req, |
||||||
|
@Param('modelId') modelId: string, |
||||||
|
@Query('viewId') viewId: string, |
||||||
|
@Param('columnId') columnId: string, |
||||||
|
@Param('rowId') rowId: string, |
||||||
|
) { |
||||||
|
return await this.dataTableService.nestedDataList({ |
||||||
|
modelId, |
||||||
|
rowId: rowId, |
||||||
|
query: req.query, |
||||||
|
viewId, |
||||||
|
columnId, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Post(['/api/v1/tables/:modelId/links/:columnId/rows/:rowId']) |
||||||
|
@Acl('nestedDataLink') |
||||||
|
async nestedLink( |
||||||
|
@Request() req, |
||||||
|
@Param('modelId') modelId: string, |
||||||
|
@Query('viewId') viewId: string, |
||||||
|
@Param('columnId') columnId: string, |
||||||
|
@Param('rowId') rowId: string, |
||||||
|
@Body() refRowIds: string | string[] | number | number[], |
||||||
|
) { |
||||||
|
return await this.dataTableService.nestedLink({ |
||||||
|
modelId, |
||||||
|
rowId: rowId, |
||||||
|
query: req.query, |
||||||
|
viewId, |
||||||
|
columnId, |
||||||
|
refRowIds, |
||||||
|
cookie: req, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Delete(['/api/v1/tables/:modelId/links/:columnId/rows/:rowId']) |
||||||
|
@Acl('nestedDataUnlink') |
||||||
|
async nestedUnlink( |
||||||
|
@Request() req, |
||||||
|
@Param('modelId') modelId: string, |
||||||
|
@Query('viewId') viewId: string, |
||||||
|
@Param('columnId') columnId: string, |
||||||
|
@Param('rowId') rowId: string, |
||||||
|
@Body() refRowIds: string | string[] | number | number[], |
||||||
|
) { |
||||||
|
return await this.dataTableService.nestedUnlink({ |
||||||
|
modelId, |
||||||
|
rowId: rowId, |
||||||
|
query: req.query, |
||||||
|
viewId, |
||||||
|
columnId, |
||||||
|
refRowIds, |
||||||
|
cookie: req, |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
const config = { |
||||||
|
limitDefault: Math.max(+process.env.DB_QUERY_LIMIT_DEFAULT || 25, 1), |
||||||
|
limitMin: Math.max(+process.env.DB_QUERY_LIMIT_MIN || 1, 1), |
||||||
|
limitMax: Math.max(+process.env.DB_QUERY_LIMIT_MAX || 1000, 1), |
||||||
|
}; |
||||||
|
|
||||||
|
export function extractLimitAndOffset( |
||||||
|
args: { |
||||||
|
limit?: number | string; |
||||||
|
offset?: number | string; |
||||||
|
l?: number | string; |
||||||
|
o?: number | string; |
||||||
|
} = {}, |
||||||
|
) { |
||||||
|
const obj: { |
||||||
|
limit?: number; |
||||||
|
offset?: number; |
||||||
|
} = {}; |
||||||
|
|
||||||
|
// use default value if invalid limit
|
||||||
|
// for example, if limit is not a number, it will be ignored
|
||||||
|
// if limit is less than 1, it will be ignored
|
||||||
|
const limit = +(args.limit || args.l); |
||||||
|
obj.limit = Math.max( |
||||||
|
Math.min( |
||||||
|
limit && limit > 0 && Number.isInteger(limit) |
||||||
|
? limit |
||||||
|
: config.limitDefault, |
||||||
|
config.limitMax, |
||||||
|
), |
||||||
|
config.limitMin, |
||||||
|
); |
||||||
|
|
||||||
|
// skip any invalid offset, ignore negative and non-integer values
|
||||||
|
const offset = +(args.offset || args.o) || 0; |
||||||
|
obj.offset = Math.max(Number.isInteger(offset) ? offset : 0, 0); |
||||||
|
|
||||||
|
return obj; |
||||||
|
} |
@ -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>(DataTableService); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should be defined', () => { |
||||||
|
expect(service).toBeDefined(); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,434 @@ |
|||||||
|
import { Injectable } from '@nestjs/common'; |
||||||
|
import { RelationTypes, UITypes } from 'nocodb-sdk'; |
||||||
|
import { nocoExecute } from 'nc-help'; |
||||||
|
import { NcError } from '../helpers/catchError'; |
||||||
|
import getAst from '../helpers/getAst'; |
||||||
|
import { PagedResponseImpl } from '../helpers/PagedResponse'; |
||||||
|
import { Base, Column, Model, View } from '../models'; |
||||||
|
import NcConnectionMgrv2 from '../utils/common/NcConnectionMgrv2'; |
||||||
|
import { DatasService } from './datas.service'; |
||||||
|
import type { LinkToAnotherRecordColumn } from '../models'; |
||||||
|
|
||||||
|
@Injectable() |
||||||
|
export class DataTableService { |
||||||
|
constructor(private datasService: DatasService) {} |
||||||
|
|
||||||
|
async dataList(param: { |
||||||
|
projectId?: string; |
||||||
|
modelId: string; |
||||||
|
query: any; |
||||||
|
viewId?: string; |
||||||
|
}) { |
||||||
|
const { model, view } = await this.getModelAndView(param); |
||||||
|
|
||||||
|
return await this.datasService.getDataList({ |
||||||
|
model, |
||||||
|
view, |
||||||
|
query: param.query, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
async dataRead(param: { |
||||||
|
projectId?: string; |
||||||
|
modelId: string; |
||||||
|
rowId: string; |
||||||
|
viewId?: string; |
||||||
|
query: any; |
||||||
|
}) { |
||||||
|
const { model, view } = await this.getModelAndView(param); |
||||||
|
|
||||||
|
const base = await Base.get(model.base_id); |
||||||
|
|
||||||
|
const baseModel = await Model.getBaseModelSQL({ |
||||||
|
id: model.id, |
||||||
|
viewId: view?.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; |
||||||
|
viewId?: string; |
||||||
|
modelId: string; |
||||||
|
body: any; |
||||||
|
cookie: any; |
||||||
|
}) { |
||||||
|
const { model, view } = await this.getModelAndView(param); |
||||||
|
const base = await Base.get(model.base_id); |
||||||
|
|
||||||
|
const baseModel = await Model.getBaseModelSQL({ |
||||||
|
id: model.id, |
||||||
|
viewId: view?.id, |
||||||
|
dbDriver: await NcConnectionMgrv2.get(base), |
||||||
|
}); |
||||||
|
|
||||||
|
// if array then do bulk insert
|
||||||
|
const result = await baseModel.bulkInsert( |
||||||
|
Array.isArray(param.body) ? param.body : [param.body], |
||||||
|
{ cookie: param.cookie, insertOneByOneAsFallback: true }, |
||||||
|
); |
||||||
|
|
||||||
|
return Array.isArray(param.body) ? result : result[0]; |
||||||
|
} |
||||||
|
|
||||||
|
async dataUpdate(param: { |
||||||
|
projectId?: string; |
||||||
|
modelId: string; |
||||||
|
viewId?: string; |
||||||
|
// rowId: string;
|
||||||
|
body: any; |
||||||
|
cookie: any; |
||||||
|
}) { |
||||||
|
const { model, view } = await this.getModelAndView(param); |
||||||
|
|
||||||
|
await this.checkForDuplicateRow({ rows: param.body, model }); |
||||||
|
|
||||||
|
const base = await Base.get(model.base_id); |
||||||
|
|
||||||
|
const baseModel = await Model.getBaseModelSQL({ |
||||||
|
id: model.id, |
||||||
|
viewId: view?.id, |
||||||
|
dbDriver: await NcConnectionMgrv2.get(base), |
||||||
|
}); |
||||||
|
|
||||||
|
const res = await baseModel.bulkUpdate( |
||||||
|
Array.isArray(param.body) ? param.body : [param.body], |
||||||
|
{ cookie: param.cookie, throwExceptionIfNotExist: true }, |
||||||
|
); |
||||||
|
|
||||||
|
return this.extractIdObj({ body: param.body, model }); |
||||||
|
} |
||||||
|
|
||||||
|
async dataDelete(param: { |
||||||
|
projectId?: string; |
||||||
|
modelId: string; |
||||||
|
viewId?: string; |
||||||
|
// rowId: string;
|
||||||
|
cookie: any; |
||||||
|
body: any; |
||||||
|
}) { |
||||||
|
const { model, view } = await this.getModelAndView(param); |
||||||
|
|
||||||
|
await this.checkForDuplicateRow({ rows: param.body, model }); |
||||||
|
|
||||||
|
const base = await Base.get(model.base_id); |
||||||
|
const baseModel = await Model.getBaseModelSQL({ |
||||||
|
id: model.id, |
||||||
|
viewId: view?.id, |
||||||
|
dbDriver: await NcConnectionMgrv2.get(base), |
||||||
|
}); |
||||||
|
|
||||||
|
await baseModel.bulkDelete( |
||||||
|
Array.isArray(param.body) ? param.body : [param.body], |
||||||
|
{ cookie: param.cookie, throwExceptionIfNotExist: true }, |
||||||
|
); |
||||||
|
|
||||||
|
return this.extractIdObj({ body: param.body, model }); |
||||||
|
} |
||||||
|
|
||||||
|
async dataCount(param: { |
||||||
|
projectId?: string; |
||||||
|
viewId?: string; |
||||||
|
modelId: string; |
||||||
|
query: any; |
||||||
|
}) { |
||||||
|
const { model, view } = await this.getModelAndView(param); |
||||||
|
|
||||||
|
const base = await Base.get(model.base_id); |
||||||
|
|
||||||
|
const baseModel = await Model.getBaseModelSQL({ |
||||||
|
id: model.id, |
||||||
|
viewId: view?.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 }; |
||||||
|
} |
||||||
|
|
||||||
|
private async getModelAndView(param: { |
||||||
|
projectId?: string; |
||||||
|
viewId?: string; |
||||||
|
modelId: string; |
||||||
|
}) { |
||||||
|
const model = await Model.get(param.modelId); |
||||||
|
|
||||||
|
if (!model) { |
||||||
|
NcError.notFound(`Table with id '${param.modelId}' not found`); |
||||||
|
} |
||||||
|
|
||||||
|
if (param.projectId && model.project_id !== param.projectId) { |
||||||
|
throw new Error('Table not belong to project'); |
||||||
|
} |
||||||
|
|
||||||
|
let view: View; |
||||||
|
|
||||||
|
if (param.viewId) { |
||||||
|
view = await View.get(param.viewId); |
||||||
|
if (!view || (view.fk_model_id && view.fk_model_id !== param.modelId)) { |
||||||
|
NcError.unprocessableEntity(`View with id '${param.viewId}' not found`); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return { model, view }; |
||||||
|
} |
||||||
|
|
||||||
|
private async extractIdObj({ |
||||||
|
model, |
||||||
|
body, |
||||||
|
}: { |
||||||
|
body: Record<string, any> | Record<string, any>[]; |
||||||
|
model: Model; |
||||||
|
}) { |
||||||
|
const pkColumns = await model |
||||||
|
.getColumns() |
||||||
|
.then((cols) => cols.filter((col) => col.pk)); |
||||||
|
|
||||||
|
const result = (Array.isArray(body) ? body : [body]).map((row) => { |
||||||
|
return pkColumns.reduce((acc, col) => { |
||||||
|
acc[col.title] = row[col.title]; |
||||||
|
return acc; |
||||||
|
}, {}); |
||||||
|
}); |
||||||
|
|
||||||
|
return Array.isArray(body) ? result : result[0]; |
||||||
|
} |
||||||
|
|
||||||
|
private async checkForDuplicateRow({ |
||||||
|
rows, |
||||||
|
model, |
||||||
|
}: { |
||||||
|
rows: any[] | any; |
||||||
|
model: Model; |
||||||
|
}) { |
||||||
|
if (!rows || !Array.isArray(rows) || rows.length === 1) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
await model.getColumns(); |
||||||
|
|
||||||
|
const keys = new Set(); |
||||||
|
|
||||||
|
for (const row of rows) { |
||||||
|
let pk; |
||||||
|
// if only one primary key then extract the value
|
||||||
|
if (model.primaryKeys.length === 1) pk = row[model.primaryKey.title]; |
||||||
|
// if composite primary key then join the values with ___
|
||||||
|
else pk = model.primaryKeys.map((pk) => row[pk.title]).join('___'); |
||||||
|
// if duplicate then throw error
|
||||||
|
if (keys.has(pk)) { |
||||||
|
NcError.unprocessableEntity('Duplicate row with id ' + pk); |
||||||
|
} |
||||||
|
keys.add(pk); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async nestedDataList(param: { |
||||||
|
viewId: string; |
||||||
|
modelId: string; |
||||||
|
query: any; |
||||||
|
rowId: string | string[] | number | number[]; |
||||||
|
columnId: string; |
||||||
|
}) { |
||||||
|
const { model, view } = await this.getModelAndView(param); |
||||||
|
const base = await Base.get(model.base_id); |
||||||
|
|
||||||
|
const baseModel = await Model.getBaseModelSQL({ |
||||||
|
id: model.id, |
||||||
|
viewId: view?.id, |
||||||
|
dbDriver: await NcConnectionMgrv2.get(base), |
||||||
|
}); |
||||||
|
|
||||||
|
if (!(await baseModel.exist(param.rowId))) { |
||||||
|
NcError.notFound(`Row with id '${param.rowId}' not found`); |
||||||
|
} |
||||||
|
|
||||||
|
const column = await this.getColumn(param); |
||||||
|
|
||||||
|
const colOptions = await column.getColOptions<LinkToAnotherRecordColumn>(); |
||||||
|
|
||||||
|
const relatedModel = await colOptions.getRelatedTable(); |
||||||
|
|
||||||
|
const { ast, dependencyFields } = await getAst({ |
||||||
|
model: relatedModel, |
||||||
|
query: param.query, |
||||||
|
extractOnlyPrimaries: !(param.query?.f || param.query?.fields), |
||||||
|
}); |
||||||
|
|
||||||
|
const listArgs: any = dependencyFields; |
||||||
|
try { |
||||||
|
listArgs.filterArr = JSON.parse(listArgs.filterArrJson); |
||||||
|
} catch (e) {} |
||||||
|
try { |
||||||
|
listArgs.sortArr = JSON.parse(listArgs.sortArrJson); |
||||||
|
} catch (e) {} |
||||||
|
|
||||||
|
let data: any[]; |
||||||
|
let count: number; |
||||||
|
if (colOptions.type === RelationTypes.MANY_TO_MANY) { |
||||||
|
data = await baseModel.mmList( |
||||||
|
{ |
||||||
|
colId: column.id, |
||||||
|
parentId: param.rowId, |
||||||
|
}, |
||||||
|
listArgs as any, |
||||||
|
); |
||||||
|
count = (await baseModel.mmListCount({ |
||||||
|
colId: column.id, |
||||||
|
parentId: param.rowId, |
||||||
|
})) as number; |
||||||
|
} else if (colOptions.type === RelationTypes.HAS_MANY) { |
||||||
|
data = await baseModel.hmList( |
||||||
|
{ |
||||||
|
colId: column.id, |
||||||
|
id: param.rowId, |
||||||
|
}, |
||||||
|
listArgs as any, |
||||||
|
); |
||||||
|
count = (await baseModel.hmListCount({ |
||||||
|
colId: column.id, |
||||||
|
id: param.rowId, |
||||||
|
})) as number; |
||||||
|
} else { |
||||||
|
data = await baseModel.btRead( |
||||||
|
{ |
||||||
|
colId: column.id, |
||||||
|
id: param.rowId, |
||||||
|
}, |
||||||
|
param.query as any, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
data = await nocoExecute(ast, data, {}, listArgs); |
||||||
|
|
||||||
|
if (colOptions.type === RelationTypes.BELONGS_TO) return data; |
||||||
|
|
||||||
|
return new PagedResponseImpl(data, { |
||||||
|
count, |
||||||
|
...param.query, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private async getColumn(param: { modelId: string; columnId: string }) { |
||||||
|
const column = await Column.get({ colId: param.columnId }); |
||||||
|
|
||||||
|
if (!column) |
||||||
|
NcError.notFound(`Column with id '${param.columnId}' not found`); |
||||||
|
|
||||||
|
if (column.fk_model_id !== param.modelId) |
||||||
|
NcError.badRequest('Column not belong to model'); |
||||||
|
|
||||||
|
if (column.uidt !== UITypes.LinkToAnotherRecord) |
||||||
|
NcError.badRequest('Column is not LTAR'); |
||||||
|
return column; |
||||||
|
} |
||||||
|
|
||||||
|
async nestedLink(param: { |
||||||
|
cookie: any; |
||||||
|
viewId: string; |
||||||
|
modelId: string; |
||||||
|
columnId: string; |
||||||
|
query: any; |
||||||
|
refRowIds: string | string[] | number | number[]; |
||||||
|
rowId: string; |
||||||
|
}) { |
||||||
|
this.validateIds(param.refRowIds); |
||||||
|
|
||||||
|
const { model, view } = await this.getModelAndView(param); |
||||||
|
|
||||||
|
const base = await Base.get(model.base_id); |
||||||
|
|
||||||
|
const baseModel = await Model.getBaseModelSQL({ |
||||||
|
id: model.id, |
||||||
|
viewId: view?.id, |
||||||
|
dbDriver: await NcConnectionMgrv2.get(base), |
||||||
|
}); |
||||||
|
|
||||||
|
const column = await this.getColumn(param); |
||||||
|
|
||||||
|
await baseModel.addLinks({ |
||||||
|
colId: column.id, |
||||||
|
childIds: Array.isArray(param.refRowIds) |
||||||
|
? param.refRowIds |
||||||
|
: [param.refRowIds], |
||||||
|
rowId: param.rowId, |
||||||
|
cookie: param.cookie, |
||||||
|
}); |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
async nestedUnlink(param: { |
||||||
|
cookie: any; |
||||||
|
viewId: string; |
||||||
|
modelId: string; |
||||||
|
columnId: string; |
||||||
|
query: any; |
||||||
|
refRowIds: string | string[] | number | number[]; |
||||||
|
rowId: string; |
||||||
|
}) { |
||||||
|
this.validateIds(param.refRowIds); |
||||||
|
|
||||||
|
const { model, view } = await this.getModelAndView(param); |
||||||
|
if (!model) |
||||||
|
NcError.notFound('Table with id ' + param.modelId + ' not found'); |
||||||
|
|
||||||
|
const base = await Base.get(model.base_id); |
||||||
|
|
||||||
|
const baseModel = await Model.getBaseModelSQL({ |
||||||
|
id: model.id, |
||||||
|
viewId: view?.id, |
||||||
|
dbDriver: await NcConnectionMgrv2.get(base), |
||||||
|
}); |
||||||
|
|
||||||
|
const column = await this.getColumn(param); |
||||||
|
|
||||||
|
await baseModel.removeLinks({ |
||||||
|
colId: column.id, |
||||||
|
childIds: Array.isArray(param.refRowIds) |
||||||
|
? param.refRowIds |
||||||
|
: [param.refRowIds], |
||||||
|
rowId: param.rowId, |
||||||
|
cookie: param.cookie, |
||||||
|
}); |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private validateIds(rowIds: any[] | any) { |
||||||
|
if (Array.isArray(rowIds)) { |
||||||
|
const map = new Map<string, boolean>(); |
||||||
|
const set = new Set<string>(); |
||||||
|
for (const rowId of rowIds) { |
||||||
|
if (rowId === undefined || rowId === null) |
||||||
|
NcError.unprocessableEntity('Invalid row id ' + rowId); |
||||||
|
if (map.has(rowId)) { |
||||||
|
set.add(rowId); |
||||||
|
} else { |
||||||
|
map.set(rowId, true); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (set.size > 0) |
||||||
|
NcError.unprocessableEntity( |
||||||
|
'Child record with id [' + [...set].join(', ') + '] are duplicated', |
||||||
|
); |
||||||
|
} else if (rowIds === undefined || rowIds === null) { |
||||||
|
NcError.unprocessableEntity('Invalid row id ' + rowIds); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue