From b5bf4282cf66e580b7ad44a83a84384c96231858 Mon Sep 17 00:00:00 2001 From: Salihu Date: Wed, 30 Sep 2026 01:00:26 +0100 Subject: [PATCH] merge attachment processing methods --- .../services/import-attachment.service.ts | 700 +++++++++++------- .../import/services/import.service.ts | 29 +- 2 files changed, 469 insertions(+), 260 deletions(-) diff --git a/apps/server/src/integrations/import/services/import-attachment.service.ts b/apps/server/src/integrations/import/services/import-attachment.service.ts index 9100149be..917038dd2 100644 --- a/apps/server/src/integrations/import/services/import-attachment.service.ts +++ b/apps/server/src/integrations/import/services/import-attachment.service.ts @@ -15,11 +15,15 @@ import { AttachmentType } from '../../../core/attachment/attachment.constants'; import { unwrapFromParagraph } from '../utils/import-formatter'; import { resolveRelativeAttachmentPath } from '../utils/import.utils'; import { imageDimensionsFromData } from 'image-dimensions'; -import { load } from 'cheerio'; +import { CheerioAPI, load } from 'cheerio'; import pLimit from 'p-limit'; import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; import { QueueJob, QueueName } from '../../queue/constants'; +import { isBase64 } from 'class-validator'; +import { EnvironmentService } from '../../environment/environment.service'; +import * as bytes from 'bytes'; +import * as mimeTypes from 'mime-types'; interface AttachmentInfo { href: string; @@ -33,6 +37,48 @@ interface DrawioPair { baseName: string; } +interface UploadStats { + total: number; + completed: number; + failed: number; + failedFiles: string[]; +} + +interface ResolvedFile { + attachmentId: string; + apiFilePath: string; + fileName: string; + mimeType: string; + size?: number; + /** Absolute path on disk. Only present for relative (archive) sources. */ + abs?: string; + /** Decoded payload. Only present for embedded (`data:` URI) sources. */ + buffer?: Buffer; +} + +const AUDIO_EXTENSIONS = new Set([ + '.mp3', + '.wav', + '.ogg', + '.m4a', + '.webm', + '.flac', + '.aac', +]); + +const MIME_EXTENSION_OVERRIDES: Record = { + 'image/jpeg': '.jpg', + 'audio/mpeg': '.mp3', +}; + +function resolveExtensionForMimeType(mimeType: string): string | null { + const override = MIME_EXTENSION_OVERRIDES[mimeType]; + if (override) return override; + + const ext = mimeTypes.extension(mimeType); + return ext ? `.${ext}` : null; +} + @Injectable() export class ImportAttachmentService { private readonly logger = new Logger(ImportAttachmentService.name); @@ -42,17 +88,119 @@ export class ImportAttachmentService { constructor( private readonly storageService: StorageService, + private readonly environmentService: EnvironmentService, @InjectKysely() private readonly db: KyselyDB, @InjectQueue(QueueName.ATTACHMENT_QUEUE) private attachmentQueue: Queue, ) {} + + private buildAttachmentNode( + $: CheerioAPI, + file: ResolvedFile, + name?: string, + ) { + const $div = $('
') + .attr('data-type', 'attachment') + .attr('data-attachment-url', file.apiFilePath) + .attr('data-attachment-name', name || file.fileName) + .attr('data-attachment-mime', file.mimeType) + .attr('data-attachment-id', file.attachmentId); + + if (file.size != null) { + $div.attr('data-attachment-size', String(file.size)); + } + + return $div; + } + + private buildPdfNode( + $: CheerioAPI, + file: ResolvedFile, + opts: { name?: string; width?: string; height?: string } = {}, + ) { + const $pdf = $('
') + .attr('data-type', 'pdf') + .attr('src', file.apiFilePath) + .attr('data-attachment-id', file.attachmentId) + .attr('data-name', opts.name || file.fileName) + .attr('width', opts.width || '800') + .attr('height', opts.height || '600'); + + if (file.size != null) { + $pdf.attr('data-size', String(file.size)); + } + + return $pdf; + } + + private buildVideoNode( + $: CheerioAPI, + file: ResolvedFile, + opts: { width?: string; align?: string } = {}, + ) { + return $('