2025-05-09 00:13:29 +00:00
|
|
|
import multer from 'multer';
|
|
|
|
|
import path from 'path';
|
|
|
|
|
import fs from 'fs';
|
|
|
|
|
import crypto from 'crypto';
|
|
|
|
|
import config from '../config/index.js';
|
|
|
|
|
|
|
|
|
|
const uploadDir = path.resolve(process.cwd(), config.uploadDir);
|
|
|
|
|
if (!fs.existsSync(uploadDir)) {
|
|
|
|
|
fs.mkdirSync(uploadDir, { recursive: true });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const storage = multer.diskStorage({
|
|
|
|
|
destination: (req, file, cb) => {
|
2025-05-14 18:17:08 +00:00
|
|
|
const userPath = req.body?.path || ''; //optional path from request body
|
2025-05-09 00:13:29 +00:00
|
|
|
|
2025-05-14 18:17:08 +00:00
|
|
|
const normalizedUserPath = path.normalize(userPath).replace(/^(\.\.[\/\\])+/, '');
|
2025-05-09 00:13:29 +00:00
|
|
|
|
2025-05-14 18:17:08 +00:00
|
|
|
const targetPath = path.join(uploadDir, normalizedUserPath);
|
2025-05-09 00:13:29 +00:00
|
|
|
|
|
|
|
|
if (!targetPath.startsWith(uploadDir)) {
|
|
|
|
|
return cb(new Error('Invalid path.'));
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-14 18:17:08 +00:00
|
|
|
// Crea el directorio si no existe
|
2025-05-09 00:13:29 +00:00
|
|
|
if (!fs.existsSync(targetPath)) {
|
|
|
|
|
fs.mkdirSync(targetPath, { recursive: true });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cb(null, targetPath);
|
2025-05-14 18:17:08 +00:00
|
|
|
},
|
2025-05-09 00:13:29 +00:00
|
|
|
|
|
|
|
|
filename: (req, file, cb) => {
|
|
|
|
|
const randomName = crypto.randomBytes(16).toString('hex');
|
|
|
|
|
const ext = path.extname(file.originalname);
|
|
|
|
|
cb(null, randomName + ext);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const fileFilter = (req, file, cb) => {
|
|
|
|
|
const allowedExtensions = /jpeg|jpg|png|gif/;
|
|
|
|
|
const allowedMimeTypes = /image\/jpeg|image\/jpg|image\/png|image\/gif/;
|
|
|
|
|
|
|
|
|
|
const extname = allowedExtensions.test(path.extname(file.originalname).toLowerCase());
|
|
|
|
|
const mimetype = allowedMimeTypes.test(file.mimetype);
|
|
|
|
|
|
|
|
|
|
if (extname && mimetype) {
|
|
|
|
|
cb(null, true);
|
|
|
|
|
} else {
|
|
|
|
|
cb(new multer.MulterError('LIMIT_UNEXPECTED_FILE', file.fieldname));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const multerUpload = multer({
|
|
|
|
|
storage,
|
|
|
|
|
fileFilter,
|
|
|
|
|
limits: { fileSize: 1024 * 1024 * 1024 } // 1GB
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const uploadMiddleware = (fieldName, multiple = false) => (req, res, next) => {
|
|
|
|
|
const uploader = multiple ? multerUpload.array(fieldName) : multerUpload.single(fieldName);
|
|
|
|
|
|
|
|
|
|
uploader(req, res, (err) => {
|
|
|
|
|
if (err instanceof multer.MulterError) {
|
2025-05-14 18:17:08 +00:00
|
|
|
res.status(400).json({ error: err.message });
|
2025-05-09 00:13:29 +00:00
|
|
|
} else if (err) {
|
2025-05-14 18:17:08 +00:00
|
|
|
res.status(500).json({ error: 'Internal server error during file upload.' });
|
2025-05-09 00:13:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!multiple && !req.file) {
|
2025-05-14 18:17:08 +00:00
|
|
|
res.status(400).json({ error: `No file uploaded under field "${fieldName}"` });
|
2025-05-09 00:13:29 +00:00
|
|
|
}
|
|
|
|
|
const result = req.file
|
|
|
|
|
|
|
|
|
|
next(result);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export {
|
|
|
|
|
fileFilter,
|
|
|
|
|
uploadMiddleware
|
|
|
|
|
};
|