fix: stream FilePond uploads to S3 and proxy private file previews

Disk-based multer plus an authenticated content endpoint match the working Node.js uploader and avoid browser signed-URL failures on SeaweedFS.
This commit is contained in:
2026-08-15 18:46:26 +03:30
parent 7ebf9409f0
commit 1ef54ad414
9 changed files with 173 additions and 30 deletions
+31 -7
View File
@@ -5,18 +5,28 @@ const fileService = require('./fileService');
const { successResponse } = require('../../utils/apiResponse');
const AppError = require('../../utils/AppError');
const pickUploadedFile = (req) => {
if (req.file) return req.file;
if (Array.isArray(req.files) && req.files.length) return req.files[0];
if (req.files && typeof req.files === 'object') {
return req.files.file?.[0]
|| req.files['filepond-image']?.[0]
|| Object.values(req.files).flat().find(Boolean)
|| null;
}
return null;
};
exports.uploadTemp = catchAsync(async (req, res, next) => {
if (!req.file) {
const uploaded = pickUploadedFile(req);
if (!uploaded) {
return next(new AppError('FILE_REQUIRED'));
}
const result = await fileService.uploadTempFile(
req.file.buffer,
req.file.originalname,
req.file.mimetype
);
const result = await fileService.uploadTempFile(uploaded);
return successResponse(res, 201, 'File uploaded to temp bucket successfully', result);
// 200 matches the working FilePond Node.js uploader (2xx is required)
return successResponse(res, 200, 'File uploaded to temp bucket successfully', result);
});
exports.getSignedUrl = catchAsync(async (req, res, next) => {
@@ -27,6 +37,20 @@ exports.getSignedUrl = catchAsync(async (req, res, next) => {
return successResponse(res, 200, 'Temporary presigned URL generated successfully', result);
});
exports.streamFile = catchAsync(async (req, res, next) => {
const key = req.query.key || req.params.filename;
const bucket = req.query.bucket;
if (!key) {
return next(new AppError('VALIDATION_FAILED', null, 'Filename is required'));
}
const { buffer, contentType } = await fileService.getFileContent(key, bucket);
res.set('Content-Type', contentType);
res.set('Cache-Control', 'private, max-age=60');
return res.status(200).send(buffer);
});
exports.deleteFile = catchAsync(async (req, res, next) => {
const { filename } = req.params;
const { bucket } = req.query;