diff --git a/server.js b/server.js index e8940c6..6a66087 100644 --- a/server.js +++ b/server.js @@ -918,3 +918,38 @@ server.timeout = 0; server.keepAliveTimeout = 0; server.headersTimeout = 0; server.requestTimeout = 0; + +const desktopSessions = new Map(); +app.post("/api/desktop/multipart/init", requireAuth, async (req, res) => { + const { filename, size, prefix, totalParts } = req.body; + if (!filename || !size || !totalParts) return res.status(400).json({ error: "Missing fields" }); + if (!s3Client) return res.status(503).json({ error: "S3 not configured" }); + const s3cfg = db.s3Config || {}; + const key = prefix ? prefix.replace(/\/+$/, "") + "/" + filename : filename; + try { + const cr = await s3Client.send(new CreateMultipartUploadCommand({ Bucket: s3cfg.bucket, Key: key })); + const uid = cr.UploadId; + const pp = await Promise.all(Array.from({ length: totalParts }, (_, i) => + getSignedUrl(s3Client, new UploadPartCommand({ Bucket: s3cfg.bucket, Key: key, UploadId: uid, PartNumber: i+1 }), { expiresIn: 3600 }) + )); + desktopSessions.set(uid, { key, bucket: s3cfg.bucket }); + console.log("[desktop] Init:", key, totalParts, "parts"); + res.json({ uploadId: uid, key, bucket: s3cfg.bucket, presignedParts: pp }); + } catch (err) { console.error("[desktop] Init error:", err.message); res.status(500).json({ error: err.message }); } +}); +app.post("/api/desktop/multipart/complete", requireAuth, async (req, res) => { + const { uploadId, key, bucket, parts } = req.body; + if (!uploadId || !parts) return res.status(400).json({ error: "Missing fields" }); + try { + await s3Client.send(new CompleteMultipartUploadCommand({ Bucket: bucket, Key: key, UploadId: uploadId, MultipartUpload: { Parts: parts.sort((a,b)=>a.PartNumber-b.PartNumber) } })); + desktopSessions.delete(uploadId); + console.log("[desktop] Complete:", key); + res.json({ success: true }); + } catch (err) { console.error("[desktop] Complete error:", err.message); res.status(500).json({ error: err.message }); } +}); +app.post("/api/desktop/multipart/abort", requireAuth, async (req, res) => { + const { uploadId, key, bucket } = req.body; + try { if (uploadId && key && bucket && s3Client) await s3Client.send(new AbortMultipartUploadCommand({ Bucket: bucket, Key: key, UploadId: uploadId })); } catch (_) {} + desktopSessions.delete(uploadId); + res.json({ success: true }); +});