mirror of https://github.com/nocodb/nocodb
Pranav C
2 years ago
1 changed files with 0 additions and 264 deletions
@ -1,264 +0,0 @@
|
||||
import { Request, Response, Router } from 'express'; |
||||
import * as XLSX from 'xlsx'; |
||||
import View from '../../models/View'; |
||||
import Model from '../../models/Model'; |
||||
import Base from '../../models/Base'; |
||||
import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2'; |
||||
import { nocoExecute } from 'nc-help'; |
||||
import papaparse from 'papaparse'; |
||||
import { ErrorMessages, isSystemColumn, UITypes, ViewTypes } from 'nocodb-sdk'; |
||||
import Column from '../../models/Column'; |
||||
import LinkToAnotherRecordColumn from '../../models/LinkToAnotherRecordColumn'; |
||||
import LookupColumn from '../../models/LookupColumn'; |
||||
import catchError, { NcError } from '../../meta/helpers/catchError'; |
||||
import getAst from '../../db/sql-data-mapper/lib/sql/helpers/getAst'; |
||||
|
||||
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; |
Loading…
Reference in new issue