From b1785a35a9c760998fa5bf05ac4247739d3fbbed Mon Sep 17 00:00:00 2001 From: root Date: Mon, 31 Aug 2026 20:14:18 +0000 Subject: [PATCH] =?UTF-8?q?S3-Import=20mit=20bekannter=20Dateigr=C3=B6?= =?UTF-8?q?=C3=9Fe=20hochladen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/routes/admin.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/backend/src/routes/admin.ts b/backend/src/routes/admin.ts index d6699eb..0246993 100644 --- a/backend/src/routes/admin.ts +++ b/backend/src/routes/admin.ts @@ -3,6 +3,9 @@ import { and, eq, inArray, isNull, sql } from "drizzle-orm"; import multipart from "@fastify/multipart"; import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3"; import { randomUUID } from "node:crypto"; +import { createReadStream, createWriteStream } from "node:fs"; +import { stat, unlink } from "node:fs/promises"; +import { pipeline } from "node:stream/promises"; import { authTenantUsers, @@ -1446,6 +1449,7 @@ export default async function adminRoutes(server: FastifyInstance) { // ------------------------------------------------------------- server.post("/admin/tenant-imports", { bodyLimit: 1024 * 1024 * 1024 }, async (req, reply) => { let archiveStoragePath: string | null = null; + let temporaryArchivePath: string | null = null; try { const currentUser = await requireAdmin(req, reply); if (!currentUser) return; @@ -1469,13 +1473,19 @@ export default async function adminRoutes(server: FastifyInstance) { } archiveStoragePath = `tenant-imports/${randomUUID()}-${filename}`; + temporaryArchivePath = `${process.env.TMPDIR || "/tmp"}/fedeo-tenant-import-${randomUUID()}.zip`; + await pipeline(data.file, createWriteStream(temporaryArchivePath, { flags: "wx" })); + const temporaryArchive = await stat(temporaryArchivePath); await s3.send(new PutObjectCommand({ Bucket: secrets.S3_BUCKET, Key: archiveStoragePath, - Body: data.file, + Body: createReadStream(temporaryArchivePath), + ContentLength: temporaryArchive.size, ContentType: data.mimetype || "application/zip", })); - fileSize = data.file.bytesRead || null; + fileSize = temporaryArchive.size; + await unlink(temporaryArchivePath); + temporaryArchivePath = null; source = { archiveStoragePath }; } else { const body = req.body as TenantFullExport | { exportData?: TenantFullExport; targetTenantId?: number }; @@ -1520,6 +1530,11 @@ export default async function adminRoutes(server: FastifyInstance) { }); } catch (err: any) { console.error("ERROR /admin/tenant-imports:", err); + if (temporaryArchivePath) { + await unlink(temporaryArchivePath).catch((cleanupError) => { + console.error("ERROR cleanup temporary tenant import archive:", cleanupError); + }); + } if (archiveStoragePath) { await s3.send(new DeleteObjectCommand({ Bucket: secrets.S3_BUCKET,