diff --git a/src/controllers/adminController.js b/src/controllers/adminController.js index d4c1737..62e6ad1 100644 --- a/src/controllers/adminController.js +++ b/src/controllers/adminController.js @@ -1,5 +1,14 @@ +import fs from 'fs'; import Track from '../models/Track.js'; +const removeFile = (file) => { + if (!file?.path) { + return; + } + + fs.unlink(file.path, () => {}); +}; + export const adminController = { async addTrack(req, res, next) { try { @@ -11,7 +20,27 @@ export const adminController = { .json({ success: false, message: 'All fields are required' }); } - const coverUrl = req.file ? `/uploads/covers/${req.file.filename}` : ''; + const coverFile = req.files?.cover?.[0]; + const audioFile = req.files?.audio?.[0]; + + if (!coverFile || !audioFile) { + removeFile(coverFile); + removeFile(audioFile); + return res + .status(400) + .json({ success: false, message: 'Cover and audio files are required' }); + } + + if (coverFile.size > 5 * 1024 * 1024) { + removeFile(coverFile); + removeFile(audioFile); + return res + .status(400) + .json({ success: false, message: 'Cover image must be 5MB or smaller' }); + } + + const coverUrl = `/uploads/covers/${coverFile.filename}`; + const audioUrl = `/uploads/audio/${audioFile.filename}`; const track = await Track.create({ title, @@ -19,6 +48,7 @@ export const adminController = { album, genre, coverUrl, + audioUrl, createdBy: req.userId, }); diff --git a/src/models/Track.js b/src/models/Track.js index 121bcc8..d317c6d 100644 --- a/src/models/Track.js +++ b/src/models/Track.js @@ -29,6 +29,12 @@ const trackSchema = new mongoose.Schema( coverUrl: { type: String, trim: true, + required: true, + }, + audioUrl: { + type: String, + trim: true, + required: true, }, createdBy: { type: mongoose.Schema.Types.ObjectId, diff --git a/src/routes/admins.js b/src/routes/admins.js index a146055..3f52bb5 100644 --- a/src/routes/admins.js +++ b/src/routes/admins.js @@ -2,7 +2,7 @@ import { Router } from 'express'; import { adminController } from '../controllers/adminController.js'; import { auth } from '../middlewares/auth.js'; import { requireAdmin } from '../middlewares/roles.js'; -import { uploadCover } from '../utils/multer.js'; +import { uploadTrackAssets } from '../utils/multer.js'; const router = Router(); @@ -10,7 +10,10 @@ router.post( '/tracks', auth, requireAdmin, - uploadCover.single('cover'), + uploadTrackAssets.fields([ + { name: 'cover', maxCount: 1 }, + { name: 'audio', maxCount: 1 }, + ]), adminController.addTrack ); diff --git a/src/utils/multer.js b/src/utils/multer.js index 2cb2ded..c368958 100644 --- a/src/utils/multer.js +++ b/src/utils/multer.js @@ -3,14 +3,27 @@ import path from 'path'; import multer from 'multer'; const coversDir = path.join(process.cwd(), 'uploads', 'covers'); +const audioDir = path.join(process.cwd(), 'uploads', 'audio'); if (!fs.existsSync(coversDir)) { fs.mkdirSync(coversDir, { recursive: true }); } +if (!fs.existsSync(audioDir)) { + fs.mkdirSync(audioDir, { recursive: true }); +} + const storage = multer.diskStorage({ destination: (req, file, cb) => { - cb(null, coversDir); + if (file.fieldname === 'cover') { + return cb(null, coversDir); + } + + if (file.fieldname === 'audio') { + return cb(null, audioDir); + } + + return cb(new Error('Invalid upload field')); }, filename: (req, file, cb) => { const safeName = file.originalname.replace(/\s+/g, '-'); @@ -19,16 +32,36 @@ const storage = multer.diskStorage({ }); const imageFilter = (req, file, cb) => { - const allowed = ['image/jpeg', 'image/png', 'image/webp']; - if (!allowed.includes(file.mimetype)) { - return cb(new Error('Only JPG, PNG, or WEBP images are allowed')); + if (file.fieldname === 'cover') { + const allowed = ['image/jpeg', 'image/png', 'image/webp']; + if (!allowed.includes(file.mimetype)) { + return cb(new Error('Only JPG, PNG, or WEBP images are allowed')); + } + + return cb(null, true); } - return cb(null, true); + if (file.fieldname === 'audio') { + const allowed = [ + 'audio/mpeg', + 'audio/mp3', + 'audio/wav', + 'audio/x-wav', + 'audio/flac', + 'audio/x-flac', + ]; + if (!allowed.includes(file.mimetype)) { + return cb(new Error('Only MP3, WAV, or FLAC audio is allowed')); + } + + return cb(null, true); + } + + return cb(new Error('Invalid upload field')); }; -export const uploadCover = multer({ +export const uploadTrackAssets = multer({ storage, fileFilter: imageFilter, - limits: { fileSize: 5 * 1024 * 1024 }, + limits: { fileSize: 50 * 1024 * 1024 }, });