From e75556c30d59b5fb5b012da0602016412402d611 Mon Sep 17 00:00:00 2001 From: mertmit Date: Wed, 20 Nov 2024 17:11:14 +0000 Subject: [PATCH] fix: handle data url using stream --- .../nocodb/src/plugins/GenericS3/GenericS3.ts | 5 +- packages/nocodb/src/plugins/storage/Local.ts | 18 ++++++- .../src/services/attachments.service.ts | 49 +++++++++++++++---- .../types/nc-plugin/lib/IStorageAdapterV2.ts | 8 ++- 4 files changed, 66 insertions(+), 14 deletions(-) diff --git a/packages/nocodb/src/plugins/GenericS3/GenericS3.ts b/packages/nocodb/src/plugins/GenericS3/GenericS3.ts index 104f2d012f..42b0a914cc 100644 --- a/packages/nocodb/src/plugins/GenericS3/GenericS3.ts +++ b/packages/nocodb/src/plugins/GenericS3/GenericS3.ts @@ -104,7 +104,10 @@ export default class GenericS3 implements IStorageAdapterV2 { options?: { mimetype?: string; }, - ): Promise { + ): Promise<{ + url: string | null; + data: any; + }> { try { const streamError = new Promise((_, reject) => { stream.on('error', (err) => { diff --git a/packages/nocodb/src/plugins/storage/Local.ts b/packages/nocodb/src/plugins/storage/Local.ts index 8a874a2f08..b241fca390 100644 --- a/packages/nocodb/src/plugins/storage/Local.ts +++ b/packages/nocodb/src/plugins/storage/Local.ts @@ -70,13 +70,27 @@ export default class Local implements IStorageAdapterV2 { public async fileCreateByStream( key: string, stream: Readable, - ): Promise { + ): Promise<{ + url: string | null; + data: any; + }> { return new Promise((resolve, reject) => { const destPath = validateAndNormaliseLocalPath(key); try { mkdirp(path.dirname(destPath)).then(() => { const writableStream = fs.createWriteStream(destPath); - writableStream.on('finish', () => resolve()); + writableStream.on('finish', () => { + this.fileRead(destPath) + .then((data) => { + resolve({ + url: null, + data, + }); + }) + .catch((e) => { + reject(e); + }); + }); writableStream.on('error', (err) => reject(err)); stream.pipe(writableStream); }); diff --git a/packages/nocodb/src/services/attachments.service.ts b/packages/nocodb/src/services/attachments.service.ts index 0d38d915a1..69048969a4 100644 --- a/packages/nocodb/src/services/attachments.service.ts +++ b/packages/nocodb/src/services/attachments.service.ts @@ -1,5 +1,6 @@ import path from 'path'; import Url from 'url'; +import { Readable } from 'stream'; import { AppEvents, PublicAttachmentScope } from 'nocodb-sdk'; import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common'; import { nanoid } from 'nanoid'; @@ -280,11 +281,23 @@ export class AttachmentsService { size, finalUrl = url; + let base64TempStream: Readable; + if (!url.startsWith('data:')) { response = await axios.head(url, { maxRedirects: 5 }); mimeType = response.headers['content-type']?.split(';')[0]; size = response.headers['content-length']; finalUrl = response.request.res.responseUrl; + } else { + const matches = url.match(/^data:(.+);base64,(.+)$/); + if (!matches) throw new Error('Invalid data URL format.'); + + // Extract MIME type and base64 data + const [, _mimeType, base64Data] = matches; + + mimeType = _mimeType; + size = Buffer.byteLength(base64Data, 'base64'); + base64TempStream = Readable.from(Buffer.from(base64Data, 'base64')); } const parsedUrl = Url.parse(finalUrl, true); @@ -303,17 +316,33 @@ export class AttachmentsService { mimeType = mime.getType(path.extname(fileNameWithExt).slice(1)); } - const { url: attachmentUrl, data: file } = - await storageAdapter.fileCreateByUrl( - slash(path.join(fileDestPath, fileName)), - finalUrl, - { - fetchOptions: { - // The sharp requires image to be passed as buffer.); - buffer: mimeType.includes('image'), + let attachmentUrl, file; + + if (!base64TempStream) { + const { url: _attachmentUrl, data: _file } = + await storageAdapter.fileCreateByUrl( + slash(path.join(fileDestPath, fileName)), + finalUrl, + { + fetchOptions: { + // The sharp requires image to be passed as buffer.); + buffer: mimeType.includes('image'), + }, }, - }, - ); + ); + + attachmentUrl = _attachmentUrl; + file = _file; + } else { + const { url: _attachmentUrl, data: _file } = + (await storageAdapter.fileCreateByStream( + slash(path.join(fileDestPath, fileName)), + base64TempStream, + )) as any; + + attachmentUrl = _attachmentUrl; + file = _file; + } const tempMetadata: { width?: number; diff --git a/packages/nocodb/src/types/nc-plugin/lib/IStorageAdapterV2.ts b/packages/nocodb/src/types/nc-plugin/lib/IStorageAdapterV2.ts index eeb1f30392..74da138392 100644 --- a/packages/nocodb/src/types/nc-plugin/lib/IStorageAdapterV2.ts +++ b/packages/nocodb/src/types/nc-plugin/lib/IStorageAdapterV2.ts @@ -30,7 +30,13 @@ export default interface IStorageAdapterV2< url: string, options?: FileCreateByUrlOptions, ): Promise; - fileCreateByStream(destPath: string, readStream: Readable): Promise; + fileCreateByStream( + destPath: string, + readStream: Readable, + ): Promise<{ + url: string | null; + data: any; + }>; fileReadByStream( key: string, options?: { encoding?: string },