Replace Upload class with PutObjectCommand for RustFS compatibility

The @aws-sdk/lib-storage Upload class internally uses
CreateMultipartUploadCommand for files over the part size threshold,
which returns non-standard XML from RustFS causing UnknownError.
PutObjectCommand does a simple single PUT request that RustFS handles
correctly. Fixed in both /api/upload and /api/link/:id/upload endpoints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Zac Gaetano 2026-04-07 00:52:54 -04:00
parent d5192e8847
commit 44c22bd95e

View file

@ -535,14 +535,14 @@ app.post("/api/upload", requireAuth, upload.array("files", 50), async (req, res)
const key = prefix ? `${prefix.replace(/[-\/]+$/, "")}--${file.originalname}` : file.originalname; const key = prefix ? `${prefix.replace(/[-\/]+$/, "")}--${file.originalname}` : file.originalname;
const contentType = getMimeType(file.originalname, file.mimetype); const contentType = getMimeType(file.originalname, file.mimetype);
console.log(`Uploading: ${key} (${file.size} bytes, ${contentType})`); console.log(`Uploading: ${key} (${file.size} bytes, ${contentType})`);
const uploadPromise = new Upload({ // Use PutObjectCommand (single PUT) — compatible with RustFS/MinIO/generic S3.
client: s3Client, // The @aws-sdk/lib-storage Upload class uses CreateMultipartUpload internally
params: { Bucket: bucket, Key: key, Body: fs.createReadStream(file.path), ContentType: contentType }, // which returns non-standard XML from RustFS and causes UnknownError.
queueSize: 4, const fileBuffer = fs.readFileSync(file.path);
partSize: 10 * 1024 * 1024, const putPromise = s3Client.send(new PutObjectCommand({
leavePartsOnError: false, Bucket: bucket, Key: key, Body: fileBuffer, ContentType: contentType,
}).done(); }));
const result = await withTimeout(uploadPromise, UPLOAD_TIMEOUT_MS, key); const result = await withTimeout(putPromise, UPLOAD_TIMEOUT_MS, key);
if (result?.assumed) console.log(`Assumed success (timeout): ${key}`); if (result?.assumed) console.log(`Assumed success (timeout): ${key}`);
else console.log(`Confirmed success: ${key}`); else console.log(`Confirmed success: ${key}`);
try { fs.unlinkSync(file.path); } catch (_) {} try { fs.unlinkSync(file.path); } catch (_) {}
@ -841,12 +841,11 @@ app.post("/api/sharelinks/:token/upload", upload.array("files", 50), async (req,
for (const file of req.files) { for (const file of req.files) {
const key = prefix ? `${prefix.replace(/[-\/]+$/, "")}--${file.originalname}` : file.originalname; const key = prefix ? `${prefix.replace(/[-\/]+$/, "")}--${file.originalname}` : file.originalname;
const contentType = getMimeType(file.originalname, file.mimetype); const contentType = getMimeType(file.originalname, file.mimetype);
const uploadPromise = new Upload({ const fileBuffer = fs.readFileSync(file.path);
client: s3Client, const putPromise = s3Client.send(new PutObjectCommand({
params: { Bucket: bucket, Key: key, Body: fs.createReadStream(file.path), ContentType: contentType }, Bucket: bucket, Key: key, Body: fileBuffer, ContentType: contentType,
queueSize: 4, partSize: 10 * 1024 * 1024, leavePartsOnError: false, }));
}).done(); await withTimeout(putPromise, UPLOAD_TIMEOUT_MS, key);
await withTimeout(uploadPromise, UPLOAD_TIMEOUT_MS, key);
try { fs.unlinkSync(file.path); } catch (_) {} try { fs.unlinkSync(file.path); } catch (_) {}
results.push({ originalName: file.originalname, key, size: file.size }); results.push({ originalName: file.originalname, key, size: file.size });
} }