From a5360ad34163f291f2637ff1db5d995a92219303 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Sat, 28 Mar 2026 10:23:29 +0000 Subject: [PATCH] feat: use confluence real file names --- .../services/import-attachment.service.ts | 24 ++++++++++++++++--- .../integrations/import/utils/import.utils.ts | 13 ++++++++++ 2 files changed, 34 insertions(+), 3 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 a4da955d..b044e2e2 100644 --- a/apps/server/src/integrations/import/services/import-attachment.service.ts +++ b/apps/server/src/integrations/import/services/import-attachment.service.ts @@ -190,13 +190,32 @@ export class ImportAttachmentService { } } + // Build a map from resolved archive path → real filename from Confluence + // metadata. Confluence Server archives often store files under numeric IDs + // (e.g. "attachments/65601/65602") instead of the original filename. + const pageDir = path.dirname(pageRelativePath); + const attachmentNameByRelPath = new Map(); + for (const attachment of pageAttachments) { + const relPath = resolveRelativeAttachmentPath( + attachment.href, + pageDir, + attachmentCandidates, + ); + if (relPath && attachment.fileName) { + attachmentNameByRelPath.set(relPath, attachment.fileName); + } + } + const uploadOnce = (relPath: string) => { const abs = attachmentCandidates.get(relPath)!; const attachmentId = v7(); - const ext = path.extname(abs); + + const realName = attachmentNameByRelPath.get(relPath); + const baseName = realName || path.basename(abs); + const ext = path.extname(baseName); const fileNameWithExt = - sanitizeFileName(path.basename(abs, ext)) + ext.toLowerCase(); + sanitizeFileName(path.basename(baseName, ext)) + ext.toLowerCase(); const storageFilePath = `${getAttachmentFolderPath( AttachmentType.File, @@ -240,7 +259,6 @@ export class ImportAttachmentService { return fresh; }; - const pageDir = path.dirname(pageRelativePath); const $ = load(html); // image diff --git a/apps/server/src/integrations/import/utils/import.utils.ts b/apps/server/src/integrations/import/utils/import.utils.ts index cebe89ea..45f4ed12 100644 --- a/apps/server/src/integrations/import/utils/import.utils.ts +++ b/apps/server/src/integrations/import/utils/import.utils.ts @@ -41,6 +41,15 @@ export function resolveRelativeAttachmentPath( 'ImportUtils', ); } + + // Confluence Server uses "/download/attachments/..." in HTML but the ZIP + // stores files under "attachments/...". Strip the "download/" prefix so + // the path can match candidates from the archive. + const confluenceStripped = mainRel.replace( + /^download\/attachments\//, + 'attachments/', + ); + const fallback = path .normalize(path.join(pageDir, mainRel)) .split(path.sep) @@ -49,9 +58,13 @@ export function resolveRelativeAttachmentPath( if (attachmentCandidates.has(mainRel)) { return mainRel; } + if (confluenceStripped !== mainRel && attachmentCandidates.has(confluenceStripped)) { + return confluenceStripped; + } if (attachmentCandidates.has(fallback)) { return fallback; } + return null; }