const multer = require('multer'); const path = require('path'); const fs = require('fs'); /** * Upload Controller - Quản lý upload file */ class UploadController { constructor() { // Danh sách các extension cho từng loại file this.imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.ico']; this.audioExtensions = ['.mp3', '.wav', '.ogg', '.m4a', '.aac', '.flac', '.wma']; this.videoExtensions = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv', '.webm']; // Tạo các thư mục nếu chưa tồn tại this.ensureDirectories(); // Cấu hình multer storage this.storage = multer.diskStorage({ destination: (req, file, cb) => { const fileExt = path.extname(file.originalname).toLowerCase(); let uploadPath = 'public/files'; if (this.imageExtensions.includes(fileExt)) { uploadPath = 'public/images'; } else if (this.audioExtensions.includes(fileExt) || this.videoExtensions.includes(fileExt)) { uploadPath = 'public/media'; } cb(null, uploadPath); }, filename: (req, file, cb) => { // Tạo tên file unique: timestamp-random-originalname const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); const ext = path.extname(file.originalname); const nameWithoutExt = path.basename(file.originalname, ext); const sanitizedName = nameWithoutExt.replace(/[^a-zA-Z0-9]/g, '_'); cb(null, `${sanitizedName}-${uniqueSuffix}${ext}`); } }); // Cấu hình multer với giới hạn kích thước this.upload = multer({ storage: this.storage, limits: { fileSize: 50 * 1024 * 1024, // 50MB max file size }, fileFilter: (req, file, cb) => { // Cho phép tất cả các loại file cb(null, true); } }); } /** * Đảm bảo các thư mục upload tồn tại */ ensureDirectories() { const directories = [ 'public/images', 'public/media', 'public/files' ]; directories.forEach(dir => { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } }); } /** * Upload single file */ uploadSingle(req, res, next) { const uploadHandler = this.upload.single('file'); uploadHandler(req, res, (err) => { if (err instanceof multer.MulterError) { return res.status(400).json({ success: false, message: 'Upload error', error: err.message, }); } else if (err) { return res.status(500).json({ success: false, message: 'Server error during upload', error: err.message, }); } if (!req.file) { return res.status(400).json({ success: false, message: 'No file uploaded', }); } // Xác định loại file const fileExt = path.extname(req.file.originalname).toLowerCase(); let fileType = 'file'; if (this.imageExtensions.includes(fileExt)) { fileType = 'image'; } else if (this.audioExtensions.includes(fileExt)) { fileType = 'audio'; } else if (this.videoExtensions.includes(fileExt)) { fileType = 'video'; } // Tạo URL truy cập file const fileUrl = `/${req.file.path.replace(/\\/g, '/')}`; res.status(201).json({ success: true, message: 'File uploaded successfully', data: { filename: req.file.filename, originalname: req.file.originalname, mimetype: req.file.mimetype, size: req.file.size, type: fileType, path: req.file.path, url: fileUrl, }, }); }); } /** * Upload multiple files */ uploadMultiple(req, res, next) { const uploadHandler = this.upload.array('files', 10); // Max 10 files uploadHandler(req, res, (err) => { if (err instanceof multer.MulterError) { return res.status(400).json({ success: false, message: 'Upload error', error: err.message, }); } else if (err) { return res.status(500).json({ success: false, message: 'Server error during upload', error: err.message, }); } if (!req.files || req.files.length === 0) { return res.status(400).json({ success: false, message: 'No files uploaded', }); } const uploadedFiles = req.files.map(file => { const fileExt = path.extname(file.originalname).toLowerCase(); let fileType = 'file'; if (this.imageExtensions.includes(fileExt)) { fileType = 'image'; } else if (this.audioExtensions.includes(fileExt)) { fileType = 'audio'; } else if (this.videoExtensions.includes(fileExt)) { fileType = 'video'; } const fileUrl = `/${file.path.replace(/\\/g, '/')}`; return { filename: file.filename, originalname: file.originalname, mimetype: file.mimetype, size: file.size, type: fileType, path: file.path, url: fileUrl, }; }); res.status(201).json({ success: true, message: `${uploadedFiles.length} files uploaded successfully`, data: uploadedFiles, }); }); } /** * Delete file */ async deleteFile(req, res, next) { try { const { filepath } = req.body; if (!filepath) { return res.status(400).json({ success: false, message: 'Filepath is required', }); } // Kiểm tra file có tồn tại không if (!fs.existsSync(filepath)) { return res.status(404).json({ success: false, message: 'File not found', }); } // Xóa file fs.unlinkSync(filepath); res.json({ success: true, message: 'File deleted successfully', }); } catch (error) { next(error); } } /** * Get file info */ async getFileInfo(req, res, next) { try { const { filepath } = req.query; if (!filepath) { return res.status(400).json({ success: false, message: 'Filepath is required', }); } // Kiểm tra file có tồn tại không if (!fs.existsSync(filepath)) { return res.status(404).json({ success: false, message: 'File not found', }); } const stats = fs.statSync(filepath); const fileExt = path.extname(filepath).toLowerCase(); let fileType = 'file'; if (this.imageExtensions.includes(fileExt)) { fileType = 'image'; } else if (this.audioExtensions.includes(fileExt)) { fileType = 'audio'; } else if (this.videoExtensions.includes(fileExt)) { fileType = 'video'; } res.json({ success: true, data: { path: filepath, size: stats.size, type: fileType, created: stats.birthtime, modified: stats.mtime, }, }); } catch (error) { next(error); } } } module.exports = new UploadController();