const path = require('path'); const fs = require('fs'); const crypto = require('crypto'); const multer = require('multer'); // Builds a filename multer can never be tricked into escaping the upload // directory with — extension only, no attacker-controlled path segments. // (file.originalname is untrusted; joining it into a path allows `../` traversal.) function safeFilename(prefix, ext) { return `${prefix}-${Date.now()}-${crypto.randomBytes(8).toString('hex')}${ext}`; } // Setup multer storage const storage = multer.diskStorage({ destination: function (req, file, cb) { const uploadPath = path.join(__dirname, '..', '..','public', 'uploads', 'events'); // Check if the upload location exists try { if (!fs.existsSync(uploadPath)) { console.log(`Upload directory does not exist. Creating: ${uploadPath}`); fs.mkdirSync(uploadPath, { recursive: true }); } else { // Verify we have write permissions to the directory fs.accessSync(uploadPath, fs.constants.W_OK); console.log(`Upload directory exists and is writable: ${uploadPath}`); } cb(null, uploadPath); } catch (error) { console.error(`Error with upload directory: ${error.message}`); cb(new Error(`Cannot access upload directory: ${error.message}`)); } }, filename: function (req, file, cb) { cb(null, safeFilename('event', path.extname(file.originalname).toLowerCase())); } }); // Multer middleware const upload = multer({ storage, limits: { fileSize: 5 * 1024 * 1024 }, // 5MB max fileFilter: function (req, file, cb) { const ext = path.extname(file.originalname).toLowerCase(); if (!['.jpg', '.jpeg', '.png', '.webp', '.gif'].includes(ext)) { return cb(new Error('Only images are allowed'), false); } cb(null, true); } }); // Logo storage (separate subfolder) const logoStorage = multer.diskStorage({ destination: function (req, file, cb) { const uploadPath = path.join(__dirname, '..', '..', 'public', 'uploads', 'branding'); try { if (!fs.existsSync(uploadPath)) fs.mkdirSync(uploadPath, { recursive: true }); cb(null, uploadPath); } catch (error) { cb(new Error(`Cannot access upload directory: ${error.message}`)); } }, filename: function (req, file, cb) { cb(null, safeFilename('logo', path.extname(file.originalname).toLowerCase())); } }); const uploadLogo = multer({ storage: logoStorage, limits: { fileSize: 2 * 1024 * 1024 }, // 2 MB fileFilter: function (req, file, cb) { const ext = path.extname(file.originalname).toLowerCase(); if (!['.jpg', '.jpeg', '.png', '.webp', '.svg'].includes(ext)) { return cb(new Error('Only image files are allowed'), false); } cb(null, true); } }); // Favicon storage (same subfolder as the logo — both are "branding" assets) const faviconStorage = multer.diskStorage({ destination: function (req, file, cb) { const uploadPath = path.join(__dirname, '..', '..', 'public', 'uploads', 'branding'); try { if (!fs.existsSync(uploadPath)) fs.mkdirSync(uploadPath, { recursive: true }); cb(null, uploadPath); } catch (error) { cb(new Error(`Cannot access upload directory: ${error.message}`)); } }, filename: function (req, file, cb) { cb(null, safeFilename('favicon', path.extname(file.originalname).toLowerCase())); } }); const uploadFavicon = multer({ storage: faviconStorage, limits: { fileSize: 2 * 1024 * 1024 }, // 2 MB fileFilter: function (req, file, cb) { const ext = path.extname(file.originalname).toLowerCase(); if (!['.ico', '.png', '.svg'].includes(ext)) { return cb(new Error('Only .ico, .png, or .svg files are allowed for the favicon'), false); } cb(null, true); } }); // Controller function const uploadEventImage = (req, res) => { // Check for multer errors which would be passed in req.multerError if (req.multerError) { return res.status(500).json({ message: `Upload failed: ${req.multerError.message}` }); } if (!req.file) { return res.status(400).json({ message: 'No file uploaded' }); } const imageUrl = `/uploads/events/${req.file.filename}`; res.status(200).json({ url: imageUrl }); }; const uploadLogoImage = (req, res) => { if (req.multerError) { return res.status(500).json({ message: `Upload failed: ${req.multerError.message}` }); } if (!req.file) { return res.status(400).json({ message: 'No file uploaded' }); } res.status(200).json({ url: `/uploads/branding/${req.file.filename}` }); }; const uploadFaviconImage = (req, res) => { if (req.multerError) { return res.status(500).json({ message: `Upload failed: ${req.multerError.message}` }); } if (!req.file) { return res.status(400).json({ message: 'No file uploaded' }); } res.status(200).json({ url: `/uploads/branding/${req.file.filename}` }); }; module.exports = { upload, uploadEventImage, uploadLogo, uploadLogoImage, uploadFavicon, uploadFaviconImage, };