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,