const express = require('express'); const router = express.Router(); const multer = require('multer'); const path = require('path'); const fs = require('fs'); const sharp = require('sharp'); const adminAuth = require('../middleware/adminAuth'); // ======================= // Config (LOCAL + PROD) // ======================= const UPLOAD_DIR = process.env.UPLOAD_DIR ? path.resolve(process.env.UPLOAD_DIR) : path.resolve(__dirname, '..', 'uploads'); // local fallback const PUBLIC_BASE = process.env.UPLOAD_PUBLIC_URL || '/uploads'; // Ensure uploads directory exists if (!fs.existsSync(UPLOAD_DIR)) { fs.mkdirSync(UPLOAD_DIR, { recursive: true }); } // Configure multer for file upload const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, UPLOAD_DIR); }, filename: (req, file, cb) => { const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9); const ext = path.extname(file.originalname).toLowerCase() || '.jpg'; const name = path .basename(file.originalname, ext) .replace(/[^a-z0-9]/gi, '-') .toLowerCase() .replace(/-+/g, '-') .replace(/(^-|-$)/g, ''); cb(null, `${name || 'image'}-${uniqueSuffix}${ext}`); }, }); const fileFilter = (req, file, cb) => { // Accept images only if (file.mimetype && file.mimetype.startsWith('image/')) { cb(null, true); } else { cb(new Error('Only image files are allowed'), false); } }; const upload = multer({ storage, fileFilter, limits: { fileSize: 10 * 1024 * 1024, // 10MB limit }, }); const optimizeImageInPlace = async (filePath, ext) => { const lowerExt = (ext || '').toLowerCase(); // GIF: skip optimization (sharp may drop animation) if (lowerExt === '.gif') return; const tmpPath = filePath + '.tmp'; const pipeline = sharp(filePath) .rotate() .resize(1920, 1080, { fit: 'inside', withoutEnlargement: true, }); if (lowerExt === '.png') { await pipeline.png({ compressionLevel: 9 }).toFile(tmpPath); } else if (lowerExt === '.webp') { await pipeline.webp({ quality: 85 }).toFile(tmpPath); } else { // jpg/jpeg + anything else await pipeline.jpeg({ quality: 85 }).toFile(tmpPath); } fs.renameSync(tmpPath, filePath); }; // @route POST /api/upload // @desc Upload single or multiple images // @access Private (Admin) router.post('/', adminAuth, upload.array('images', 10), async (req, res) => { try { if (!req.files || req.files.length === 0) { return res.status(400).json({ success: false, message: 'No files uploaded', }); } const uploadedFiles = await Promise.all( req.files.map(async (file) => { try { const ext = path.extname(file.filename); await optimizeImageInPlace(file.path, ext); return { filename: file.filename, originalName: file.originalname, url: `${PUBLIC_BASE}/${file.filename}`, size: fs.statSync(file.path).size, mimeType: file.mimetype, uploadedAt: new Date(), }; } catch (error) { console.error('Error processing image:', error); return { filename: file.filename, originalName: file.originalname, url: `${PUBLIC_BASE}/${file.filename}`, size: file.size, mimeType: file.mimetype, uploadedAt: new Date(), error: 'Optimization failed', }; } }) ); res.json({ success: true, message: 'Files uploaded successfully', data: { files: uploadedFiles }, }); } catch (error) { console.error('Upload error:', error); res.status(500).json({ success: false, message: error.message || 'Error uploading files', }); } }); // @route GET /api/upload/list // @desc Get list of uploaded files // @access Private (Admin) router.get('/list', adminAuth, async (req, res) => { try { const files = fs.readdirSync(UPLOAD_DIR); const fileList = files .filter((file) => !file.startsWith('.')) .map((filename) => { const filePath = path.join(UPLOAD_DIR, filename); const stats = fs.statSync(filePath); return { filename, url: `${PUBLIC_BASE}/${filename}`, size: stats.size, uploadedAt: stats.mtime, }; }) .sort((a, b) => b.uploadedAt - a.uploadedAt); res.json({ success: true, data: { files: fileList, total: fileList.length }, }); } catch (error) { console.error('List files error:', error); res.status(500).json({ success: false, message: 'Error listing files', }); } }); // @route DELETE /api/upload/:filename // @desc Delete an uploaded file // @access Private (Admin) router.delete('/:filename', adminAuth, async (req, res) => { try { const { filename } = req.params; // Security check if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) { return res.status(400).json({ success: false, message: 'Invalid filename', }); } const filePath = path.join(UPLOAD_DIR, filename); if (!fs.existsSync(filePath)) { return res.status(404).json({ success: false, message: 'File not found', }); } fs.unlinkSync(filePath); res.json({ success: true, message: 'File deleted successfully', }); } catch (error) { console.error('Delete file error:', error); res.status(500).json({ success: false, message: 'Error deleting file', }); } }); module.exports = router;