feat(admin): accept audio uploads
This commit is contained in:
@@ -1,5 +1,14 @@
|
|||||||
|
import fs from 'fs';
|
||||||
import Track from '../models/Track.js';
|
import Track from '../models/Track.js';
|
||||||
|
|
||||||
|
const removeFile = (file) => {
|
||||||
|
if (!file?.path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.unlink(file.path, () => {});
|
||||||
|
};
|
||||||
|
|
||||||
export const adminController = {
|
export const adminController = {
|
||||||
async addTrack(req, res, next) {
|
async addTrack(req, res, next) {
|
||||||
try {
|
try {
|
||||||
@@ -11,7 +20,27 @@ export const adminController = {
|
|||||||
.json({ success: false, message: 'All fields are required' });
|
.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({
|
const track = await Track.create({
|
||||||
title,
|
title,
|
||||||
@@ -19,6 +48,7 @@ export const adminController = {
|
|||||||
album,
|
album,
|
||||||
genre,
|
genre,
|
||||||
coverUrl,
|
coverUrl,
|
||||||
|
audioUrl,
|
||||||
createdBy: req.userId,
|
createdBy: req.userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ const trackSchema = new mongoose.Schema(
|
|||||||
coverUrl: {
|
coverUrl: {
|
||||||
type: String,
|
type: String,
|
||||||
trim: true,
|
trim: true,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
audioUrl: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
createdBy: {
|
createdBy: {
|
||||||
type: mongoose.Schema.Types.ObjectId,
|
type: mongoose.Schema.Types.ObjectId,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Router } from 'express';
|
|||||||
import { adminController } from '../controllers/adminController.js';
|
import { adminController } from '../controllers/adminController.js';
|
||||||
import { auth } from '../middlewares/auth.js';
|
import { auth } from '../middlewares/auth.js';
|
||||||
import { requireAdmin } from '../middlewares/roles.js';
|
import { requireAdmin } from '../middlewares/roles.js';
|
||||||
import { uploadCover } from '../utils/multer.js';
|
import { uploadTrackAssets } from '../utils/multer.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -10,7 +10,10 @@ router.post(
|
|||||||
'/tracks',
|
'/tracks',
|
||||||
auth,
|
auth,
|
||||||
requireAdmin,
|
requireAdmin,
|
||||||
uploadCover.single('cover'),
|
uploadTrackAssets.fields([
|
||||||
|
{ name: 'cover', maxCount: 1 },
|
||||||
|
{ name: 'audio', maxCount: 1 },
|
||||||
|
]),
|
||||||
adminController.addTrack
|
adminController.addTrack
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+40
-7
@@ -3,14 +3,27 @@ import path from 'path';
|
|||||||
import multer from 'multer';
|
import multer from 'multer';
|
||||||
|
|
||||||
const coversDir = path.join(process.cwd(), 'uploads', 'covers');
|
const coversDir = path.join(process.cwd(), 'uploads', 'covers');
|
||||||
|
const audioDir = path.join(process.cwd(), 'uploads', 'audio');
|
||||||
|
|
||||||
if (!fs.existsSync(coversDir)) {
|
if (!fs.existsSync(coversDir)) {
|
||||||
fs.mkdirSync(coversDir, { recursive: true });
|
fs.mkdirSync(coversDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(audioDir)) {
|
||||||
|
fs.mkdirSync(audioDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
const storage = multer.diskStorage({
|
const storage = multer.diskStorage({
|
||||||
destination: (req, file, cb) => {
|
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) => {
|
filename: (req, file, cb) => {
|
||||||
const safeName = file.originalname.replace(/\s+/g, '-');
|
const safeName = file.originalname.replace(/\s+/g, '-');
|
||||||
@@ -19,16 +32,36 @@ const storage = multer.diskStorage({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const imageFilter = (req, file, cb) => {
|
const imageFilter = (req, file, cb) => {
|
||||||
const allowed = ['image/jpeg', 'image/png', 'image/webp'];
|
if (file.fieldname === 'cover') {
|
||||||
if (!allowed.includes(file.mimetype)) {
|
const allowed = ['image/jpeg', 'image/png', 'image/webp'];
|
||||||
return cb(new Error('Only JPG, PNG, or WEBP images are allowed'));
|
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,
|
storage,
|
||||||
fileFilter: imageFilter,
|
fileFilter: imageFilter,
|
||||||
limits: { fileSize: 5 * 1024 * 1024 },
|
limits: { fileSize: 50 * 1024 * 1024 },
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user