|
|
|
@ -1,4 +1,5 @@
|
|
|
|
|
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'; |
|
|
|
@ -12,8 +13,38 @@ import LookupColumn from '../../../models/LookupColumn';
|
|
|
|
|
import catchError, { NcError } from '../../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) 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 = XLSX.utils.json_to_sheet(dbRows); |
|
|
|
|
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) NcError.notFound('Not found'); |
|
|
|
@ -25,6 +56,40 @@ async function exportCsv(req: Request, res: Response) {
|
|
|
|
|
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( |
|
|
|
@ -35,7 +100,6 @@ async function exportCsv(req: Request, res: Response) {
|
|
|
|
|
|
|
|
|
|
if (!model) NcError.notFound('Table not found'); |
|
|
|
|
|
|
|
|
|
const fields = req.query.fields; |
|
|
|
|
const listArgs: any = { ...req.query }; |
|
|
|
|
try { |
|
|
|
|
listArgs.filterArr = JSON.parse(listArgs.filterArrJson); |
|
|
|
@ -62,7 +126,7 @@ async function exportCsv(req: Request, res: Response) {
|
|
|
|
|
const limit = 100; |
|
|
|
|
// const size = +process.env.NC_EXPORT_MAX_SIZE || 1024;
|
|
|
|
|
const timeout = +process.env.NC_EXPORT_MAX_TIMEOUT || 5000; |
|
|
|
|
const csvRows = []; |
|
|
|
|
const dbRows = []; |
|
|
|
|
const startTime = process.hrtime(); |
|
|
|
|
let elapsed, temp; |
|
|
|
|
|
|
|
|
@ -86,47 +150,18 @@ async function exportCsv(req: Request, res: Response) {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (const row of rows) { |
|
|
|
|
const csvRow = { ...row }; |
|
|
|
|
const dbRow = { ...row }; |
|
|
|
|
|
|
|
|
|
for (const column of view.model.columns) { |
|
|
|
|
csvRow[column.title] = await serializeCellValue({ |
|
|
|
|
dbRow[column.title] = await serializeCellValue({ |
|
|
|
|
value: row[column.title], |
|
|
|
|
column, |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
csvRows.push(csvRow); |
|
|
|
|
dbRows.push(dbRow); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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: csvRows, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
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); |
|
|
|
|
return { offset, dbRows, elapsed }; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async function serializeCellValue({ |
|
|
|
@ -198,4 +233,8 @@ 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; |
|
|
|
|