Browse Source

feat: encoding for export stream

nc-feat/export-csv-delimiters
mertmit 2 days ago
parent
commit
020c8bdbdc
  1. 13
      packages/nocodb/src/controllers/attachments.controller.ts
  2. 10
      packages/nocodb/src/models/PresignedUrl.ts
  3. 14
      packages/nocodb/src/modules/jobs/jobs/data-export/data-export.processor.ts

13
packages/nocodb/src/controllers/attachments.controller.ts

@ -168,6 +168,7 @@ export class AttachmentsController {
let queryResponseContentType = null; let queryResponseContentType = null;
let queryResponseContentDisposition = null; let queryResponseContentDisposition = null;
let queryResponseContentEncoding = null;
if (queryHelper.length > 1) { if (queryHelper.length > 1) {
const query = new URLSearchParams(queryHelper[1]); const query = new URLSearchParams(queryHelper[1]);
@ -175,6 +176,7 @@ export class AttachmentsController {
queryResponseContentDisposition = query.get( queryResponseContentDisposition = query.get(
'ResponseContentDisposition', 'ResponseContentDisposition',
); );
queryResponseContentEncoding = query.get('ResponseContentEncoding');
} }
const targetParam = param.split('/')[2]; const targetParam = param.split('/')[2];
@ -191,12 +193,23 @@ export class AttachmentsController {
if (queryResponseContentType) { if (queryResponseContentType) {
res.setHeader('Content-Type', queryResponseContentType); res.setHeader('Content-Type', queryResponseContentType);
if (queryResponseContentEncoding) {
res.setHeader(
'Content-Type',
`${queryResponseContentType}; charset=${queryResponseContentEncoding}`,
);
}
} }
if (queryResponseContentDisposition) { if (queryResponseContentDisposition) {
res.setHeader('Content-Disposition', queryResponseContentDisposition); res.setHeader('Content-Disposition', queryResponseContentDisposition);
} }
if (queryResponseContentEncoding) {
res.setHeader('Content-Encoding', queryResponseContentEncoding);
}
res.sendFile(file.path); res.sendFile(file.path);
} catch (e) { } catch (e) {
res.status(404).send('Not found'); res.status(404).send('Not found');

10
packages/nocodb/src/models/PresignedUrl.ts

@ -96,6 +96,7 @@ export default class PresignedUrl {
filename?: string; filename?: string;
preview?: boolean; preview?: boolean;
mimetype?: string; mimetype?: string;
encoding?: string;
}, },
ncMeta = Noco.ncMeta, ncMeta = Noco.ncMeta,
) { ) {
@ -109,6 +110,7 @@ export default class PresignedUrl {
expireSeconds = DEFAULT_EXPIRE_SECONDS, expireSeconds = DEFAULT_EXPIRE_SECONDS,
filename, filename,
mimetype, mimetype,
encoding,
} = param; } = param;
const preview = param.preview const preview = param.preview
@ -152,6 +154,14 @@ export default class PresignedUrl {
if (mimetype) { if (mimetype) {
pathParameters.ResponseContentType = mimetype; pathParameters.ResponseContentType = mimetype;
if (encoding) {
pathParameters.ResponseContentType = `${mimetype}; charset=${encoding}`;
}
}
if (encoding) {
pathParameters.ResponseContentEncoding = encoding;
} }
// append query params to the cache path // append query params to the cache path

14
packages/nocodb/src/modules/jobs/jobs/data-export/data-export.processor.ts

@ -1,5 +1,6 @@
import { Readable } from 'stream'; import { Readable } from 'stream';
import path from 'path'; import path from 'path';
import iconv from 'iconv-lite';
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import moment from 'moment'; import moment from 'moment';
import type { Job } from 'bull'; import type { Job } from 'bull';
@ -61,10 +62,19 @@ export class DataExportProcessor {
dataStream.setEncoding('utf8'); dataStream.setEncoding('utf8');
const encodedStream =
options?.encoding &&
options.encoding !== 'utf-8' &&
iconv.encodingExists(options.encoding)
? dataStream
.pipe(iconv.decodeStream('utf-8'))
.pipe(iconv.encodeStream(options?.encoding || 'utf-8'))
: dataStream;
let error = null; let error = null;
const uploadFilePromise = (storageAdapter as any) const uploadFilePromise = (storageAdapter as any)
.fileCreateByStream(destPath, dataStream) .fileCreateByStream(destPath, encodedStream)
.catch((e) => { .catch((e) => {
this.logger.error(e); this.logger.error(e);
error = e; error = e;
@ -97,6 +107,7 @@ export class DataExportProcessor {
expireSeconds: 3 * 60 * 60, // 3 hours expireSeconds: 3 * 60 * 60, // 3 hours
preview: false, preview: false,
mimetype: 'text/csv', mimetype: 'text/csv',
encoding: options?.encoding || 'utf-8',
}); });
} else { } else {
url = await PresignedUrl.getSignedUrl({ url = await PresignedUrl.getSignedUrl({
@ -105,6 +116,7 @@ export class DataExportProcessor {
expireSeconds: 3 * 60 * 60, // 3 hours expireSeconds: 3 * 60 * 60, // 3 hours
preview: false, preview: false,
mimetype: 'text/csv', mimetype: 'text/csv',
encoding: options?.encoding || 'utf-8',
}); });
} }

Loading…
Cancel
Save