From 4e3f1e0c1555d942e96a9d480411172de4e33502 Mon Sep 17 00:00:00 2001 From: flfeders Date: Tue, 14 Jul 2026 17:33:05 +0200 Subject: [PATCH] Include letterhead files in tenant archive --- backend/src/utils/tenantFullExport.ts | 60 +++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/backend/src/utils/tenantFullExport.ts b/backend/src/utils/tenantFullExport.ts index 59475e5..f2e4579 100644 --- a/backend/src/utils/tenantFullExport.ts +++ b/backend/src/utils/tenantFullExport.ts @@ -265,6 +265,22 @@ const archiveEntryPathForFile = (path: string) => { return `files/${normalizedPath || "unnamed-file"}` } +const filenameFromPath = (path: string) => { + const filename = path + .replace(/\\/g, "/") + .split("/") + .filter(Boolean) + .pop() + + if (!filename) return null + + try { + return decodeURIComponent(filename) + } catch { + return filename + } +} + const jsonBuffer = (value: unknown) => Buffer.from(`${JSON.stringify(value, null, 2)}\n`, "utf8") export const buildTenantFullExport = async ( @@ -329,11 +345,17 @@ export const buildTenantFullExport = async ( tables.entitybankaccounts = decryptEntityBankAccountsForExport(tables.entitybankaccounts) } - const fileRows = tables.files || [] const files = [] - - for (const file of fileRows) { - if (!file.path) continue + const filePaths = new Set() + const addExportFile = async (file: { + id: string + path: string + name?: string | null + mimeType?: string | null + size?: number | null + }) => { + if (!file.path || filePaths.has(file.path)) return + filePaths.add(file.path) const object = includeFileContents ? await loadObjectAsBase64(file.path) @@ -350,7 +372,7 @@ export const buildTenantFullExport = async ( files.push({ id: file.id, path: file.path, - name: file.name || null, + name: file.name || filenameFromPath(file.path), mimeType: file.mimeType || null, size: file.size || null, contentBase64: object.contentBase64, @@ -359,6 +381,30 @@ export const buildTenantFullExport = async ( }) } + for (const file of tables.files || []) { + if (!file.path) continue + + await addExportFile({ + id: file.id, + path: file.path, + name: file.name || null, + mimeType: file.mimeType || null, + size: file.size || null, + }) + } + + for (const letterhead of tables.letterheads || []) { + if (!letterhead.path) continue + + await addExportFile({ + id: `letterhead:${letterhead.id}`, + path: letterhead.path, + name: filenameFromPath(letterhead.path), + mimeType: "application/pdf", + size: null, + }) + } + return { format: "fedeo.tenant-full-export", version: 1, @@ -384,7 +430,7 @@ export const createTenantFullExportArchive = async ( .toLowerCase() const filename = options.filename || `fedeo-tenant-${safeTenantName || tenantId}-${new Date().toISOString().slice(0, 10)}.fedeo-export.zip` const storagePath = options.storagePath || `tenant-exports/${tenantId}/${Date.now()}-${filename}` - const fileRows = exportData.tables.files || [] + const fileRows = exportData.files || [] const archive = archiver("zip", { zlib: { level: 6 } }) const tempPath = path.join(os.tmpdir(), `fedeo-tenant-export-${tenantId}-${Date.now()}-${filename}`) const output = createWriteStream(tempPath) @@ -523,7 +569,7 @@ const restoreArchiveFiles = async ( let skipped = 0 const filesByPath = new Map((manifest.files || []).map((file) => [file.path, file])) - for (const fileRow of exportData.tables.files || []) { + for (const fileRow of exportData.files || []) { const originalPath = fileRow.path if (!originalPath) { skipped += 1