mirror of https://github.com/nocodb/nocodb
Pranav C
2 years ago
9 changed files with 1137 additions and 0 deletions
@ -0,0 +1,5 @@
|
||||
import publicDataApis from './publicDataApis'; |
||||
import publicDataExportApis from './publicDataExportApis'; |
||||
import publicMetaApis from './publicMetaApis'; |
||||
|
||||
export { publicDataApis, publicDataExportApis, publicMetaApis }; |
@ -0,0 +1,104 @@
|
||||
import { Request, Response, Router } from 'express'; |
||||
import multer from 'multer'; |
||||
import { NC_ATTACHMENT_FIELD_SIZE } from '../../constants'; |
||||
import catchError from '../../meta/helpers/catchError'; |
||||
import { publicDataService } from '../../services'; |
||||
|
||||
export async function dataList(req: Request, res: Response) { |
||||
const pagedResponse = publicDataService.dataList({ |
||||
query: req.query, |
||||
password: req.headers?.['xc-password'] as string, |
||||
sharedViewUuid: req.params.sharedViewUuid, |
||||
}); |
||||
res.json(pagedResponse); |
||||
} |
||||
|
||||
// todo: Handle the error case where view doesnt belong to model
|
||||
async function groupedDataList(req: Request, res: Response) { |
||||
const groupedData = await publicDataService.groupedDataList({ |
||||
query: req.query, |
||||
password: req.headers?.['xc-password'] as string, |
||||
sharedViewUuid: req.params.sharedViewUuid, |
||||
groupColumnId: req.params.columnId, |
||||
}); |
||||
res.json(groupedData); |
||||
} |
||||
async function dataInsert(req: Request & { files: any[] }, res: Response) { |
||||
const insertResult = await publicDataService.dataInsert({ |
||||
sharedViewUuid: req.params.sharedViewUuid, |
||||
password: req.headers?.['xc-password'] as string, |
||||
body: req.body?.data, |
||||
siteUrl: (req as any).ncSiteUrl, |
||||
files: req.files, |
||||
}); |
||||
|
||||
res.json(insertResult); |
||||
} |
||||
|
||||
async function relDataList(req, res) { |
||||
const pagedResponse = publicDataService.relDataList({ |
||||
query: req.query, |
||||
password: req.headers?.['xc-password'] as string, |
||||
sharedViewUuid: req.params.sharedViewUuid, |
||||
columnId: req.params.columnId, |
||||
}); |
||||
|
||||
res.json(pagedResponse); |
||||
} |
||||
|
||||
export async function publicMmList(req: Request, res: Response) { |
||||
const paginatedResponse = await publicDataService.publicMmList({ |
||||
query: req.query, |
||||
password: req.headers?.['xc-password'] as string, |
||||
sharedViewUuid: req.params.sharedViewUuid, |
||||
columnId: req.params.columnId, |
||||
rowId: req.params.rowId, |
||||
}); |
||||
res.json(paginatedResponse); |
||||
} |
||||
|
||||
export async function publicHmList(req: Request, res: Response) { |
||||
const paginatedResponse = await publicDataService.publicHmList({ |
||||
query: req.query, |
||||
password: req.headers?.['xc-password'] as string, |
||||
sharedViewUuid: req.params.sharedViewUuid, |
||||
columnId: req.params.columnId, |
||||
rowId: req.params.rowId, |
||||
}); |
||||
res.json(paginatedResponse); |
||||
} |
||||
|
||||
const router = Router({ mergeParams: true }); |
||||
router.get( |
||||
'/api/v1/db/public/shared-view/:sharedViewUuid/rows', |
||||
catchError(dataList) |
||||
); |
||||
router.get( |
||||
'/api/v1/db/public/shared-view/:sharedViewUuid/group/:columnId', |
||||
catchError(groupedDataList) |
||||
); |
||||
router.get( |
||||
'/api/v1/db/public/shared-view/:sharedViewUuid/nested/:columnId', |
||||
catchError(relDataList) |
||||
); |
||||
router.post( |
||||
'/api/v1/db/public/shared-view/:sharedViewUuid/rows', |
||||
multer({ |
||||
storage: multer.diskStorage({}), |
||||
limits: { |
||||
fieldSize: NC_ATTACHMENT_FIELD_SIZE, |
||||
}, |
||||
}).any(), |
||||
catchError(dataInsert) |
||||
); |
||||
|
||||
router.get( |
||||
'/api/v1/db/public/shared-view/:sharedViewUuid/rows/:rowId/mm/:colId', |
||||
catchError(publicMmList) |
||||
); |
||||
router.get( |
||||
'/api/v1/db/public/shared-view/:sharedViewUuid/rows/:rowId/hm/:colId', |
||||
catchError(publicHmList) |
||||
); |
||||
|
||||
export default router; |
@ -0,0 +1,266 @@
|
||||
import { Request, Response, Router } from 'express'; |
||||
import * as XLSX from 'xlsx'; |
||||
import { nocoExecute } from 'nc-help'; |
||||
import papaparse from 'papaparse'; |
||||
import { ErrorMessages, isSystemColumn, UITypes, ViewTypes } from 'nocodb-sdk'; |
||||
import getAst from '../../db/sql-data-mapper/lib/sql/helpers/getAst'; |
||||
import catchError, { NcError } from '../../meta/helpers/catchError'; |
||||
import { |
||||
Base, |
||||
Column, |
||||
LinkToAnotherRecordColumn, |
||||
LookupColumn, |
||||
Model, |
||||
View, |
||||
} from '../../models'; |
||||
import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2'; |
||||
|
||||
async function exportExcel(req: Request, res: Response) { |
||||
const view = await View.getByUUID(req.params.publicDataUuid); |
||||
if (!view) NcError.notFound('Not found'); |
||||
if ( |
||||
view.type !== ViewTypes.GRID && |
||||
view.type !== ViewTypes.KANBAN && |
||||
view.type !== ViewTypes.GALLERY && |
||||
view.type !== ViewTypes.MAP |
||||
) |
||||
NcError.notFound('Not found'); |
||||
|
||||
if (view.password && view.password !== req.headers?.['xc-password']) { |
||||
NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD); |
||||
} |
||||
|
||||
const model = await view.getModelWithInfo(); |
||||
|
||||
await view.getColumns(); |
||||
|
||||
const { offset, dbRows, elapsed } = await getDbRows(model, view, req); |
||||
|
||||
const fields = req.query.fields as string[]; |
||||
|
||||
const data = XLSX.utils.json_to_sheet( |
||||
dbRows.map((o: Record<string, any>) => |
||||
Object.fromEntries(fields.map((f) => [f, o[f]])) |
||||
), |
||||
{ header: fields } |
||||
); |
||||
|
||||
const wb = XLSX.utils.book_new(); |
||||
|
||||
XLSX.utils.book_append_sheet(wb, data, view.title); |
||||
|
||||
const buf = XLSX.write(wb, { type: 'base64', bookType: 'xlsx' }); |
||||
|
||||
res.set({ |
||||
'Access-Control-Expose-Headers': 'nc-export-offset', |
||||
'nc-export-offset': offset, |
||||
'nc-export-elapsed-time': elapsed, |
||||
'Content-Disposition': `attachment; filename="${encodeURI( |
||||
view.title |
||||
)}-export.xlsx"`,
|
||||
}); |
||||
res.end(buf); |
||||
} |
||||
|
||||
async function exportCsv(req: Request, res: Response) { |
||||
const view = await View.getByUUID(req.params.publicDataUuid); |
||||
const fields = req.query.fields; |
||||
|
||||
if (!view) NcError.notFound('Not found'); |
||||
if ( |
||||
view.type !== ViewTypes.GRID && |
||||
view.type !== ViewTypes.KANBAN && |
||||
view.type !== ViewTypes.GALLERY && |
||||
view.type !== ViewTypes.MAP |
||||
) |
||||
NcError.notFound('Not found'); |
||||
|
||||
if (view.password && view.password !== req.headers?.['xc-password']) { |
||||
NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD); |
||||
} |
||||
|
||||
const model = await view.getModelWithInfo(); |
||||
await view.getColumns(); |
||||
|
||||
const { offset, dbRows, elapsed } = await getDbRows(model, view, req); |
||||
|
||||
const data = papaparse.unparse( |
||||
{ |
||||
fields: model.columns |
||||
.sort((c1, c2) => |
||||
Array.isArray(fields) |
||||
? fields.indexOf(c1.title as any) - fields.indexOf(c2.title as any) |
||||
: 0 |
||||
) |
||||
.filter( |
||||
(c) => |
||||
!fields || !Array.isArray(fields) || fields.includes(c.title as any) |
||||
) |
||||
.map((c) => c.title), |
||||
data: dbRows, |
||||
}, |
||||
{ |
||||
escapeFormulae: true, |
||||
} |
||||
); |
||||
|
||||
res.set({ |
||||
'Access-Control-Expose-Headers': 'nc-export-offset', |
||||
'nc-export-offset': offset, |
||||
'nc-export-elapsed-time': elapsed, |
||||
'Content-Disposition': `attachment; filename="${encodeURI( |
||||
view.title |
||||
)}-export.csv"`,
|
||||
}); |
||||
res.send(data); |
||||
} |
||||
|
||||
async function getDbRows(model, view: View, req: Request) { |
||||
view.model.columns = view.columns |
||||
.filter((c) => c.show) |
||||
.map( |
||||
(c) => |
||||
new Column({ ...c, ...view.model.columnsById[c.fk_column_id] } as any) |
||||
) |
||||
.filter((column) => !isSystemColumn(column) || view.show_system_fields); |
||||
|
||||
if (!model) NcError.notFound('Table not found'); |
||||
|
||||
const listArgs: any = { ...req.query }; |
||||
try { |
||||
listArgs.filterArr = JSON.parse(listArgs.filterArrJson); |
||||
} catch (e) {} |
||||
try { |
||||
listArgs.sortArr = JSON.parse(listArgs.sortArrJson); |
||||
} catch (e) {} |
||||
|
||||
const base = await Base.get(model.base_id); |
||||
const baseModel = await Model.getBaseModelSQL({ |
||||
id: model.id, |
||||
viewId: view?.id, |
||||
dbDriver: NcConnectionMgrv2.get(base), |
||||
}); |
||||
|
||||
const requestObj = await getAst({ |
||||
query: req.query, |
||||
model, |
||||
view, |
||||
includePkByDefault: false, |
||||
}); |
||||
|
||||
let offset = +req.query.offset || 0; |
||||
const limit = 100; |
||||
// const size = +process.env.NC_EXPORT_MAX_SIZE || 1024;
|
||||
const timeout = +process.env.NC_EXPORT_MAX_TIMEOUT || 5000; |
||||
const dbRows = []; |
||||
const startTime = process.hrtime(); |
||||
let elapsed, temp; |
||||
|
||||
for ( |
||||
elapsed = 0; |
||||
elapsed < timeout; |
||||
offset += limit, |
||||
temp = process.hrtime(startTime), |
||||
elapsed = temp[0] * 1000 + temp[1] / 1000000 |
||||
) { |
||||
const rows = await nocoExecute( |
||||
requestObj, |
||||
await baseModel.list({ ...listArgs, offset, limit }), |
||||
{}, |
||||
listArgs |
||||
); |
||||
|
||||
if (!rows?.length) { |
||||
offset = -1; |
||||
break; |
||||
} |
||||
|
||||
for (const row of rows) { |
||||
const dbRow = { ...row }; |
||||
|
||||
for (const column of view.model.columns) { |
||||
dbRow[column.title] = await serializeCellValue({ |
||||
value: row[column.title], |
||||
column, |
||||
}); |
||||
} |
||||
dbRows.push(dbRow); |
||||
} |
||||
} |
||||
return { offset, dbRows, elapsed }; |
||||
} |
||||
|
||||
async function serializeCellValue({ |
||||
value, |
||||
column, |
||||
}: { |
||||
column?: Column; |
||||
value: any; |
||||
}) { |
||||
if (!column) { |
||||
return value; |
||||
} |
||||
|
||||
if (!value) return value; |
||||
|
||||
switch (column?.uidt) { |
||||
case UITypes.Attachment: { |
||||
let data = value; |
||||
try { |
||||
if (typeof value === 'string') { |
||||
data = JSON.parse(value); |
||||
} |
||||
} catch {} |
||||
|
||||
return (data || []).map( |
||||
(attachment) => |
||||
`${encodeURI(attachment.title)}(${encodeURI(attachment.url)})` |
||||
); |
||||
} |
||||
case UITypes.Lookup: |
||||
{ |
||||
const colOptions = await column.getColOptions<LookupColumn>(); |
||||
const lookupColumn = await colOptions.getLookupColumn(); |
||||
return ( |
||||
await Promise.all( |
||||
[...(Array.isArray(value) ? value : [value])].map(async (v) => |
||||
serializeCellValue({ |
||||
value: v, |
||||
column: lookupColumn, |
||||
}) |
||||
) |
||||
) |
||||
).join(', '); |
||||
} |
||||
break; |
||||
case UITypes.LinkToAnotherRecord: |
||||
{ |
||||
const colOptions = |
||||
await column.getColOptions<LinkToAnotherRecordColumn>(); |
||||
const relatedModel = await colOptions.getRelatedTable(); |
||||
await relatedModel.getColumns(); |
||||
return [...(Array.isArray(value) ? value : [value])] |
||||
.map((v) => { |
||||
return v[relatedModel.displayValue?.title]; |
||||
}) |
||||
.join(', '); |
||||
} |
||||
break; |
||||
default: |
||||
if (value && typeof value === 'object') { |
||||
return JSON.stringify(value); |
||||
} |
||||
return value; |
||||
} |
||||
} |
||||
|
||||
const router = Router({ mergeParams: true }); |
||||
router.get( |
||||
'/api/v1/db/public/shared-view/:publicDataUuid/rows/export/csv', |
||||
catchError(exportCsv) |
||||
); |
||||
router.get( |
||||
'/api/v1/db/public/shared-view/:publicDataUuid/rows/export/excel', |
||||
catchError(exportExcel) |
||||
); |
||||
export default router; |
@ -0,0 +1,31 @@
|
||||
import { Request, Response, Router } from 'express'; |
||||
import catchError from '../../meta/helpers/catchError'; |
||||
import { publicMetaService } from '../../services'; |
||||
|
||||
export async function viewMetaGet(req: Request, res: Response) { |
||||
res.json( |
||||
await publicMetaService.viewMetaGet({ |
||||
password: req.headers?.['xc-password'] as string, |
||||
sharedViewUuid: req.params.sharedViewUuid, |
||||
}) |
||||
); |
||||
} |
||||
async function publicSharedBaseGet(req, res): Promise<any> { |
||||
res.json( |
||||
await publicMetaService.publicSharedBaseGet({ |
||||
sharedBaseUuid: req.params.sharedViewUuid, |
||||
}) |
||||
); |
||||
} |
||||
|
||||
const router = Router({ mergeParams: true }); |
||||
router.get( |
||||
'/api/v1/db/public/shared-view/:sharedViewUuid/meta', |
||||
catchError(viewMetaGet) |
||||
); |
||||
|
||||
router.get( |
||||
'/api/v1/db/public/shared-base/:sharedBaseUuid/meta', |
||||
catchError(publicSharedBaseGet) |
||||
); |
||||
export default router; |
@ -0,0 +1,5 @@
|
||||
import * as publicDataService from './publicDataService'; |
||||
import * as publicDataExportApis from './publicDataExportService'; |
||||
import * as publicMetaService from './publicMetaservice'; |
||||
|
||||
export { publicDataService, publicDataExportApis, publicMetaService }; |
@ -0,0 +1,162 @@
|
||||
import { nocoExecute } from 'nc-help'; |
||||
import { isSystemColumn, UITypes } from 'nocodb-sdk'; |
||||
import getAst from '../../db/sql-data-mapper/lib/sql/helpers/getAst'; |
||||
import { NcError } from '../../meta/helpers/catchError'; |
||||
import { |
||||
Base, |
||||
Column, |
||||
LinkToAnotherRecordColumn, |
||||
LookupColumn, |
||||
Model, |
||||
View, |
||||
} from '../../models'; |
||||
import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2'; |
||||
|
||||
export async function getDbRows(param: { |
||||
model; |
||||
view: View; |
||||
query: any; |
||||
offset?: number; |
||||
}) { |
||||
param.view.model.columns = param.view.columns |
||||
.filter((c) => c.show) |
||||
.map( |
||||
(c) => |
||||
new Column({ |
||||
...c, |
||||
...param.view.model.columnsById[c.fk_column_id], |
||||
} as any) |
||||
) |
||||
.filter( |
||||
(column) => !isSystemColumn(column) || param.view.show_system_fields |
||||
); |
||||
|
||||
if (!param.model) NcError.notFound('Table not found'); |
||||
|
||||
const listArgs: any = { ...param.query }; |
||||
try { |
||||
listArgs.filterArr = JSON.parse(listArgs.filterArrJson); |
||||
} catch (e) {} |
||||
try { |
||||
listArgs.sortArr = JSON.parse(listArgs.sortArrJson); |
||||
} catch (e) {} |
||||
|
||||
const base = await Base.get(param.model.base_id); |
||||
const baseModel = await Model.getBaseModelSQL({ |
||||
id: param.model.id, |
||||
viewId: param.view?.id, |
||||
dbDriver: NcConnectionMgrv2.get(base), |
||||
}); |
||||
|
||||
const requestObj = await getAst({ |
||||
query: param.query, |
||||
model: param.model, |
||||
view: param.view, |
||||
includePkByDefault: false, |
||||
}); |
||||
|
||||
let offset = +param.offset || 0; |
||||
const limit = 100; |
||||
// const size = +process.env.NC_EXPORT_MAX_SIZE || 1024;
|
||||
const timeout = +process.env.NC_EXPORT_MAX_TIMEOUT || 5000; |
||||
const dbRows = []; |
||||
const startTime = process.hrtime(); |
||||
let elapsed, temp; |
||||
|
||||
for ( |
||||
elapsed = 0; |
||||
elapsed < timeout; |
||||
offset += limit, |
||||
temp = process.hrtime(startTime), |
||||
elapsed = temp[0] * 1000 + temp[1] / 1000000 |
||||
) { |
||||
const rows = await nocoExecute( |
||||
requestObj, |
||||
await baseModel.list({ ...listArgs, offset, limit }), |
||||
{}, |
||||
listArgs |
||||
); |
||||
|
||||
if (!rows?.length) { |
||||
offset = -1; |
||||
break; |
||||
} |
||||
|
||||
for (const row of rows) { |
||||
const dbRow = { ...row }; |
||||
|
||||
for (const column of param.view.model.columns) { |
||||
dbRow[column.title] = await serializeCellValue({ |
||||
value: row[column.title], |
||||
column, |
||||
}); |
||||
} |
||||
dbRows.push(dbRow); |
||||
} |
||||
} |
||||
return { offset, dbRows, elapsed }; |
||||
} |
||||
|
||||
export async function serializeCellValue({ |
||||
value, |
||||
column, |
||||
}: { |
||||
column?: Column; |
||||
value: any; |
||||
}) { |
||||
if (!column) { |
||||
return value; |
||||
} |
||||
|
||||
if (!value) return value; |
||||
|
||||
switch (column?.uidt) { |
||||
case UITypes.Attachment: { |
||||
let data = value; |
||||
try { |
||||
if (typeof value === 'string') { |
||||
data = JSON.parse(value); |
||||
} |
||||
} catch {} |
||||
|
||||
return (data || []).map( |
||||
(attachment) => |
||||
`${encodeURI(attachment.title)}(${encodeURI(attachment.url)})` |
||||
); |
||||
} |
||||
case UITypes.Lookup: |
||||
{ |
||||
const colOptions = await column.getColOptions<LookupColumn>(); |
||||
const lookupColumn = await colOptions.getLookupColumn(); |
||||
return ( |
||||
await Promise.all( |
||||
[...(Array.isArray(value) ? value : [value])].map(async (v) => |
||||
serializeCellValue({ |
||||
value: v, |
||||
column: lookupColumn, |
||||
}) |
||||
) |
||||
) |
||||
).join(', '); |
||||
} |
||||
break; |
||||
case UITypes.LinkToAnotherRecord: |
||||
{ |
||||
const colOptions = |
||||
await column.getColOptions<LinkToAnotherRecordColumn>(); |
||||
const relatedModel = await colOptions.getRelatedTable(); |
||||
await relatedModel.getColumns(); |
||||
return [...(Array.isArray(value) ? value : [value])] |
||||
.map((v) => { |
||||
return v[relatedModel.displayValue?.title]; |
||||
}) |
||||
.join(', '); |
||||
} |
||||
break; |
||||
default: |
||||
if (value && typeof value === 'object') { |
||||
return JSON.stringify(value); |
||||
} |
||||
return value; |
||||
} |
||||
} |
@ -0,0 +1,463 @@
|
||||
import { nocoExecute } from 'nc-help'; |
||||
import { ErrorMessages, UITypes, ViewTypes } from 'nocodb-sdk'; |
||||
import path from 'path'; |
||||
import { nanoid } from 'nanoid'; |
||||
import slash from 'slash'; |
||||
import getAst from '../../db/sql-data-mapper/lib/sql/helpers/getAst'; |
||||
import { NcError } from '../../meta/helpers/catchError'; |
||||
import NcPluginMgrv2 from '../../meta/helpers/NcPluginMgrv2'; |
||||
import { PagedResponseImpl } from '../../meta/helpers/PagedResponse'; |
||||
import { |
||||
Base, |
||||
Column, |
||||
LinkToAnotherRecordColumn, |
||||
Model, |
||||
View, |
||||
} from '../../models'; |
||||
import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2'; |
||||
import { mimeIcons } from '../../utils/mimeTypes'; |
||||
import { sanitizeUrlPath } from '../attachmentService'; |
||||
import { getColumnByIdOrName } from '../dataService/helpers'; |
||||
|
||||
export async function dataList(param: { |
||||
sharedViewUuid: string; |
||||
password?: string; |
||||
query: any; |
||||
}) { |
||||
const view = await View.getByUUID(param.sharedViewUuid); |
||||
|
||||
if (!view) NcError.notFound('Not found'); |
||||
if ( |
||||
view.type !== ViewTypes.GRID && |
||||
view.type !== ViewTypes.KANBAN && |
||||
view.type !== ViewTypes.GALLERY && |
||||
view.type !== ViewTypes.MAP |
||||
) { |
||||
NcError.notFound('Not found'); |
||||
} |
||||
|
||||
if (view.password && view.password !== param.password) { |
||||
return NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD); |
||||
} |
||||
|
||||
const model = await Model.getByIdOrName({ |
||||
id: view?.fk_model_id, |
||||
}); |
||||
|
||||
const base = await Base.get(model.base_id); |
||||
|
||||
const baseModel = await Model.getBaseModelSQL({ |
||||
id: model.id, |
||||
viewId: view?.id, |
||||
dbDriver: NcConnectionMgrv2.get(base), |
||||
}); |
||||
|
||||
const listArgs: any = { ...param.query }; |
||||
try { |
||||
listArgs.filterArr = JSON.parse(listArgs.filterArrJson); |
||||
} catch (e) {} |
||||
try { |
||||
listArgs.sortArr = JSON.parse(listArgs.sortArrJson); |
||||
} catch (e) {} |
||||
|
||||
let data = []; |
||||
let count = 0; |
||||
|
||||
try { |
||||
data = await nocoExecute( |
||||
await getAst({ |
||||
query: param.query, |
||||
model, |
||||
view, |
||||
}), |
||||
await baseModel.list(listArgs), |
||||
{}, |
||||
listArgs |
||||
); |
||||
count = await baseModel.count(listArgs); |
||||
} catch (e) { |
||||
// show empty result instead of throwing error here
|
||||
// e.g. search some text in a numeric field
|
||||
} |
||||
|
||||
return new PagedResponseImpl(data, { ...param.query, count }); |
||||
} |
||||
|
||||
// todo: Handle the error case where view doesnt belong to model
|
||||
export async function groupedDataList(param: { |
||||
sharedViewUuid: string; |
||||
password?: string; |
||||
query: any; |
||||
groupColumnId: string; |
||||
}) { |
||||
const view = await View.getByUUID(param.sharedViewUuid); |
||||
|
||||
if (!view) NcError.notFound('Not found'); |
||||
|
||||
if ( |
||||
view.type !== ViewTypes.GRID && |
||||
view.type !== ViewTypes.KANBAN && |
||||
view.type !== ViewTypes.GALLERY |
||||
) { |
||||
NcError.notFound('Not found'); |
||||
} |
||||
|
||||
if (view.password && view.password !== param.password) { |
||||
return NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD); |
||||
} |
||||
|
||||
const model = await Model.getByIdOrName({ |
||||
id: view?.fk_model_id, |
||||
}); |
||||
|
||||
return await getGroupedDataList({ |
||||
model, |
||||
view, |
||||
query: param.query, |
||||
groupColumnId: param.groupColumnId, |
||||
}); |
||||
} |
||||
|
||||
async function getGroupedDataList(param: { |
||||
model: Model; |
||||
view: View; |
||||
query: any; |
||||
groupColumnId: string; |
||||
}) { |
||||
const { model, view, query = {}, groupColumnId } = param; |
||||
const base = await Base.get(param.model.base_id); |
||||
|
||||
const baseModel = await Model.getBaseModelSQL({ |
||||
id: model.id, |
||||
viewId: view?.id, |
||||
dbDriver: NcConnectionMgrv2.get(base), |
||||
}); |
||||
|
||||
const requestObj = await getAst({ model, query: param.query, view }); |
||||
|
||||
const listArgs: any = { ...query }; |
||||
try { |
||||
listArgs.filterArr = JSON.parse(listArgs.filterArrJson); |
||||
} catch (e) {} |
||||
try { |
||||
listArgs.sortArr = JSON.parse(listArgs.sortArrJson); |
||||
} catch (e) {} |
||||
try { |
||||
listArgs.options = JSON.parse(listArgs.optionsArrJson); |
||||
} catch (e) {} |
||||
|
||||
let data = []; |
||||
|
||||
try { |
||||
const groupedData = await baseModel.groupedList({ |
||||
...listArgs, |
||||
groupColumnId, |
||||
}); |
||||
data = await nocoExecute( |
||||
{ key: 1, value: requestObj }, |
||||
groupedData, |
||||
{}, |
||||
listArgs |
||||
); |
||||
const countArr = await baseModel.groupedListCount({ |
||||
...listArgs, |
||||
groupColumnId, |
||||
}); |
||||
data = data.map((item) => { |
||||
// todo: use map to avoid loop
|
||||
const count = |
||||
countArr.find((countItem: any) => countItem.key === item.key)?.count ?? |
||||
0; |
||||
|
||||
item.value = new PagedResponseImpl(item.value, { |
||||
...query, |
||||
count: count, |
||||
}); |
||||
return item; |
||||
}); |
||||
} catch (e) { |
||||
console.log(e); |
||||
NcError.internalServerError('Internal Server Error'); |
||||
} |
||||
return data; |
||||
} |
||||
|
||||
export async function dataInsert(param: { |
||||
sharedViewUuid: string; |
||||
password?: string; |
||||
body: any; |
||||
files: any[]; |
||||
siteUrl: string; |
||||
}) { |
||||
const view = await View.getByUUID(param.sharedViewUuid); |
||||
|
||||
if (!view) NcError.notFound(); |
||||
if (view.type !== ViewTypes.FORM) NcError.notFound(); |
||||
|
||||
if (view.password && view.password !== param.password) { |
||||
return NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD); |
||||
} |
||||
|
||||
const model = await Model.getByIdOrName({ |
||||
id: view?.fk_model_id, |
||||
}); |
||||
|
||||
const base = await Base.get(model.base_id); |
||||
const project = await base.getProject(); |
||||
|
||||
const baseModel = await Model.getBaseModelSQL({ |
||||
id: model.id, |
||||
viewId: view?.id, |
||||
dbDriver: NcConnectionMgrv2.get(base), |
||||
}); |
||||
|
||||
await view.getViewWithInfo(); |
||||
await view.getColumns(); |
||||
await view.getModelWithInfo(); |
||||
await view.model.getColumns(); |
||||
|
||||
const fields = (view.model.columns = view.columns |
||||
.filter((c) => c.show) |
||||
.reduce((o, c) => { |
||||
o[view.model.columnsById[c.fk_column_id].title] = new Column({ |
||||
...c, |
||||
...view.model.columnsById[c.fk_column_id], |
||||
} as any); |
||||
return o; |
||||
}, {}) as any); |
||||
|
||||
let body = param?.body; |
||||
|
||||
if (typeof body === 'string') body = JSON.parse(body); |
||||
|
||||
const insertObject = Object.entries(body).reduce((obj, [key, val]) => { |
||||
if (key in fields) { |
||||
obj[key] = val; |
||||
} |
||||
return obj; |
||||
}, {}); |
||||
|
||||
const attachments = {}; |
||||
const storageAdapter = await NcPluginMgrv2.storageAdapter(); |
||||
|
||||
for (const file of param.files || []) { |
||||
// remove `_` prefix and `[]` suffix
|
||||
const fieldName = file?.fieldname?.replace(/^_|\[\d*]$/g, ''); |
||||
|
||||
const filePath = sanitizeUrlPath([ |
||||
'v1', |
||||
project.title, |
||||
model.title, |
||||
fieldName, |
||||
]); |
||||
|
||||
if (fieldName in fields && fields[fieldName].uidt === UITypes.Attachment) { |
||||
attachments[fieldName] = attachments[fieldName] || []; |
||||
const fileName = `${nanoid(6)}_${file.originalname}`; |
||||
let url = await storageAdapter.fileCreate( |
||||
slash(path.join('nc', 'uploads', ...filePath, fileName)), |
||||
file |
||||
); |
||||
|
||||
if (!url) { |
||||
url = `${param.siteUrl}/download/${filePath.join('/')}/${fileName}`; |
||||
} |
||||
|
||||
attachments[fieldName].push({ |
||||
url, |
||||
title: file.originalname, |
||||
mimetype: file.mimetype, |
||||
size: file.size, |
||||
icon: mimeIcons[path.extname(file.originalname).slice(1)] || undefined, |
||||
}); |
||||
} |
||||
} |
||||
|
||||
for (const [column, data] of Object.entries(attachments)) { |
||||
insertObject[column] = JSON.stringify(data); |
||||
} |
||||
|
||||
return await baseModel.nestedInsert(insertObject, null); |
||||
} |
||||
|
||||
export async function relDataList(param: { |
||||
query: any; |
||||
sharedViewUuid: string; |
||||
password?: string; |
||||
columnId: string; |
||||
}) { |
||||
const view = await View.getByUUID(param.sharedViewUuid); |
||||
|
||||
if (!view) NcError.notFound('Not found'); |
||||
|
||||
if (view.type !== ViewTypes.FORM) NcError.notFound('Not found'); |
||||
|
||||
if (view.password && view.password !== param.password) { |
||||
NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD); |
||||
} |
||||
|
||||
const column = await Column.get({ colId: param.columnId }); |
||||
const colOptions = await column.getColOptions<LinkToAnotherRecordColumn>(); |
||||
|
||||
const model = await colOptions.getRelatedTable(); |
||||
|
||||
const base = await Base.get(model.base_id); |
||||
|
||||
const baseModel = await Model.getBaseModelSQL({ |
||||
id: model.id, |
||||
viewId: view?.id, |
||||
dbDriver: NcConnectionMgrv2.get(base), |
||||
}); |
||||
|
||||
const requestObj = await getAst({ |
||||
query: param.query, |
||||
model, |
||||
extractOnlyPrimaries: true, |
||||
}); |
||||
|
||||
let data = []; |
||||
let count = 0; |
||||
try { |
||||
data = data = await nocoExecute( |
||||
requestObj, |
||||
await baseModel.list(param.query), |
||||
{}, |
||||
param.query |
||||
); |
||||
count = await baseModel.count(param.query); |
||||
} catch (e) { |
||||
// show empty result instead of throwing error here
|
||||
// e.g. search some text in a numeric field
|
||||
} |
||||
|
||||
return new PagedResponseImpl(data, { ...param.query, count }); |
||||
} |
||||
|
||||
export async function publicMmList(param: { |
||||
query: any; |
||||
sharedViewUuid: string; |
||||
password?: string; |
||||
columnId: string; |
||||
rowId: string; |
||||
}) { |
||||
const view = await View.getByUUID(param.sharedViewUuid); |
||||
|
||||
if (!view) NcError.notFound('Not found'); |
||||
if (view.type !== ViewTypes.GRID) NcError.notFound('Not found'); |
||||
|
||||
if (view.password && view.password !== param.password) { |
||||
NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD); |
||||
} |
||||
|
||||
const column = await getColumnByIdOrName( |
||||
param.columnId, |
||||
await view.getModel() |
||||
); |
||||
|
||||
if (column.fk_model_id !== view.fk_model_id) |
||||
NcError.badRequest("Column doesn't belongs to the model"); |
||||
|
||||
const base = await Base.get(view.base_id); |
||||
|
||||
const baseModel = await Model.getBaseModelSQL({ |
||||
id: view.fk_model_id, |
||||
viewId: view?.id, |
||||
dbDriver: NcConnectionMgrv2.get(base), |
||||
}); |
||||
|
||||
const key = `List`; |
||||
const requestObj: any = { |
||||
[key]: 1, |
||||
}; |
||||
|
||||
const data = ( |
||||
await nocoExecute( |
||||
requestObj, |
||||
{ |
||||
[key]: async (args) => { |
||||
return await baseModel.mmList( |
||||
{ |
||||
colId: param.columnId, |
||||
parentId: param.rowId, |
||||
}, |
||||
args |
||||
); |
||||
}, |
||||
}, |
||||
{}, |
||||
|
||||
{ nested: { [key]: param.query } } |
||||
) |
||||
)?.[key]; |
||||
|
||||
const count: any = await baseModel.mmListCount({ |
||||
colId: param.columnId, |
||||
parentId: param.rowId, |
||||
}); |
||||
|
||||
return new PagedResponseImpl(data, { ...param.query, count }); |
||||
} |
||||
|
||||
export async function publicHmList(param: { |
||||
query: any; |
||||
rowId: string; |
||||
sharedViewUuid: string; |
||||
password?: string; |
||||
columnId: string; |
||||
}) { |
||||
const view = await View.getByUUID(param.sharedViewUuid); |
||||
|
||||
if (!view) NcError.notFound('Not found'); |
||||
if (view.type !== ViewTypes.GRID) NcError.notFound('Not found'); |
||||
|
||||
if (view.password && view.password !== param.password) { |
||||
NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD); |
||||
} |
||||
|
||||
const column = await getColumnByIdOrName( |
||||
param.columnId, |
||||
await view.getModel() |
||||
); |
||||
|
||||
if (column.fk_model_id !== view.fk_model_id) |
||||
NcError.badRequest("Column doesn't belongs to the model"); |
||||
|
||||
const base = await Base.get(view.base_id); |
||||
|
||||
const baseModel = await Model.getBaseModelSQL({ |
||||
id: view.fk_model_id, |
||||
viewId: view?.id, |
||||
dbDriver: NcConnectionMgrv2.get(base), |
||||
}); |
||||
|
||||
const key = `List`; |
||||
const requestObj: any = { |
||||
[key]: 1, |
||||
}; |
||||
|
||||
const data = ( |
||||
await nocoExecute( |
||||
requestObj, |
||||
{ |
||||
[key]: async (args) => { |
||||
return await baseModel.hmList( |
||||
{ |
||||
colId: param.columnId, |
||||
id: param.rowId, |
||||
}, |
||||
args |
||||
); |
||||
}, |
||||
}, |
||||
{}, |
||||
{ nested: { [key]: param.query } } |
||||
) |
||||
)?.[key]; |
||||
|
||||
const count = await baseModel.hmListCount({ |
||||
colId: param.columnId, |
||||
id: param.rowId, |
||||
}); |
||||
|
||||
return new PagedResponseImpl(data, { ...param.query, count }); |
||||
} |
@ -0,0 +1,100 @@
|
||||
import { |
||||
ErrorMessages, |
||||
LinkToAnotherRecordType, |
||||
RelationTypes, |
||||
UITypes, |
||||
} from 'nocodb-sdk'; |
||||
import { NcError } from '../../meta/helpers/catchError'; |
||||
import { |
||||
Base, |
||||
Column, |
||||
LinkToAnotherRecordColumn, |
||||
Model, |
||||
Project, |
||||
View, |
||||
} from '../../models'; |
||||
|
||||
export async function viewMetaGet(param: { |
||||
sharedViewUuid: string; |
||||
password: string; |
||||
}) { |
||||
const view: View & { |
||||
relatedMetas?: { [ket: string]: Model }; |
||||
client?: string; |
||||
} = await View.getByUUID(param.sharedViewUuid); |
||||
|
||||
if (!view) NcError.notFound('Not found'); |
||||
|
||||
if (view.password && view.password !== param.password) { |
||||
NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD); |
||||
} |
||||
|
||||
await view.getFilters(); |
||||
await view.getSorts(); |
||||
|
||||
await view.getViewWithInfo(); |
||||
await view.getColumns(); |
||||
await view.getModelWithInfo(); |
||||
await view.model.getColumns(); |
||||
|
||||
const base = await Base.get(view.model.base_id); |
||||
view.client = base.type; |
||||
|
||||
// todo: return only required props
|
||||
delete view['password']; |
||||
|
||||
view.model.columns = view.columns |
||||
.filter((c) => { |
||||
const column = view.model.columnsById[c.fk_column_id]; |
||||
return ( |
||||
c.show || |
||||
(column.rqd && !column.cdf && !column.ai) || |
||||
column.pk || |
||||
view.model.columns.some( |
||||
(c1) => |
||||
c1.uidt === UITypes.LinkToAnotherRecord && |
||||
(<LinkToAnotherRecordColumn>c1.colOptions).type === |
||||
RelationTypes.BELONGS_TO && |
||||
view.columns.some((vc) => vc.fk_column_id === c1.id && vc.show) && |
||||
(<LinkToAnotherRecordColumn>c1.colOptions).fk_child_column_id === |
||||
c.fk_column_id |
||||
) |
||||
); |
||||
}) |
||||
.map( |
||||
(c) => |
||||
new Column({ ...c, ...view.model.columnsById[c.fk_column_id] } as any) |
||||
) as any; |
||||
|
||||
const relatedMetas = {}; |
||||
|
||||
// load related table metas
|
||||
for (const col of view.model.columns) { |
||||
if (UITypes.LinkToAnotherRecord === col.uidt) { |
||||
const colOpt = await col.getColOptions<LinkToAnotherRecordType>(); |
||||
relatedMetas[colOpt.fk_related_model_id] = await Model.getWithInfo({ |
||||
id: colOpt.fk_related_model_id, |
||||
}); |
||||
if (colOpt.type === 'mm') { |
||||
relatedMetas[colOpt.fk_mm_model_id] = await Model.getWithInfo({ |
||||
id: colOpt.fk_mm_model_id, |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
|
||||
view.relatedMetas = relatedMetas; |
||||
|
||||
return view; |
||||
} |
||||
export async function publicSharedBaseGet(param: { |
||||
sharedBaseUuid: string; |
||||
}): Promise<any> { |
||||
const project = await Project.getByUuid(param.sharedBaseUuid); |
||||
|
||||
if (!project) { |
||||
NcError.notFound(); |
||||
} |
||||
|
||||
return { project_id: project.id }; |
||||
} |
Loading…
Reference in new issue