feat: add album routes, playlist detail, liked tracks, fix search/artist routing
This commit is contained in:
@@ -0,0 +1,81 @@
|
|||||||
|
import mongoose from 'mongoose';
|
||||||
|
import Track from '../models/Track.js';
|
||||||
|
import Album from '../models/Album.js';
|
||||||
|
|
||||||
|
export const albumController = {
|
||||||
|
async list(req, res, next) {
|
||||||
|
try {
|
||||||
|
const albums = await Album.find()
|
||||||
|
.sort({ createdAt: -1 })
|
||||||
|
.populate('tracks')
|
||||||
|
.lean();
|
||||||
|
res.json({ success: true, data: albums });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async getById(req, res, next) {
|
||||||
|
try {
|
||||||
|
const { albumId } = req.params;
|
||||||
|
const decodedId = decodeURIComponent(albumId);
|
||||||
|
|
||||||
|
if (mongoose.Types.ObjectId.isValid(decodedId)) {
|
||||||
|
const album = await Album.findById(decodedId)
|
||||||
|
.populate('tracks')
|
||||||
|
.lean();
|
||||||
|
if (album) {
|
||||||
|
return res.json({ success: true, data: album });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tracks = await Track.find({ album: decodedId })
|
||||||
|
.sort({ createdAt: -1 })
|
||||||
|
.lean();
|
||||||
|
|
||||||
|
if (!tracks.length) {
|
||||||
|
return res.status(404).json({ success: false, message: 'Album not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
_id: decodedId,
|
||||||
|
title: decodedId,
|
||||||
|
artist: tracks[0].artist,
|
||||||
|
coverUrl: tracks.find(t => t.coverUrl)?.coverUrl || null,
|
||||||
|
releaseYear: null,
|
||||||
|
tracks,
|
||||||
|
};
|
||||||
|
|
||||||
|
return res.json({ success: true, data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async getByName(req, res, next) {
|
||||||
|
try {
|
||||||
|
const { albumTitle } = req.params;
|
||||||
|
const decoded = decodeURIComponent(albumTitle);
|
||||||
|
|
||||||
|
const tracks = await Track.find({ album: decoded })
|
||||||
|
.sort({ createdAt: -1 })
|
||||||
|
.lean();
|
||||||
|
|
||||||
|
if (!tracks.length) {
|
||||||
|
return res.status(404).json({ success: false, message: 'Album not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
title: decoded,
|
||||||
|
artist: tracks[0].artist,
|
||||||
|
coverUrl: tracks.find(t => t.coverUrl)?.coverUrl || null,
|
||||||
|
tracks,
|
||||||
|
trackCount: tracks.length,
|
||||||
|
};
|
||||||
|
|
||||||
|
return res.json({ success: true, data });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -13,6 +13,28 @@ export const playlistController = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async getById(req, res, next) {
|
||||||
|
try {
|
||||||
|
const { playlistId } = req.params;
|
||||||
|
const playlist = await Playlist.findOne({
|
||||||
|
_id: playlistId,
|
||||||
|
user: req.userId,
|
||||||
|
})
|
||||||
|
.populate('tracks')
|
||||||
|
.lean();
|
||||||
|
|
||||||
|
if (!playlist) {
|
||||||
|
return res
|
||||||
|
.status(404)
|
||||||
|
.json({ success: false, message: 'Playlist not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json({ success: true, data: playlist });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async create(req, res, next) {
|
async create(req, res, next) {
|
||||||
try {
|
try {
|
||||||
const { name, description } = req.body;
|
const { name, description } = req.body;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import mongoose from 'mongoose';
|
||||||
import Track from '../models/Track.js';
|
import Track from '../models/Track.js';
|
||||||
|
|
||||||
export const searchController = {
|
export const searchController = {
|
||||||
@@ -26,11 +27,42 @@ export const searchController = {
|
|||||||
.limit(50)
|
.limit(50)
|
||||||
.lean();
|
.lean();
|
||||||
|
|
||||||
const artistNames = [...new Set(tracks.map(t => t.artist))];
|
const artistMap = new Map();
|
||||||
const artists = artistNames.map(name => ({ name }));
|
tracks.forEach(t => {
|
||||||
|
if (!artistMap.has(t.artist)) {
|
||||||
|
artistMap.set(t.artist, {
|
||||||
|
_id: new mongoose.Types.ObjectId(),
|
||||||
|
name: t.artist,
|
||||||
|
imageUrl: t.coverUrl || '',
|
||||||
|
trackCount: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const entry = artistMap.get(t.artist);
|
||||||
|
entry.trackCount++;
|
||||||
|
if (t.coverUrl && !entry.imageUrl) {
|
||||||
|
entry.imageUrl = t.coverUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const artists = [...artistMap.values()];
|
||||||
|
|
||||||
const albumNames = [...new Set(tracks.map(t => t.album))];
|
const albumMap = new Map();
|
||||||
const albums = albumNames.map(name => ({ name }));
|
tracks.forEach(t => {
|
||||||
|
if (!albumMap.has(t.album)) {
|
||||||
|
albumMap.set(t.album, {
|
||||||
|
_id: new mongoose.Types.ObjectId(),
|
||||||
|
title: t.album,
|
||||||
|
artist: t.artist,
|
||||||
|
coverUrl: t.coverUrl || '',
|
||||||
|
trackCount: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const entry = albumMap.get(t.album);
|
||||||
|
entry.trackCount++;
|
||||||
|
if (t.coverUrl && !entry.coverUrl) {
|
||||||
|
entry.coverUrl = t.coverUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const albums = [...albumMap.values()];
|
||||||
|
|
||||||
const data = { tracks, artists, albums };
|
const data = { tracks, artists, albums };
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,25 @@ export const trackController = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async listLiked(req, res, next) {
|
||||||
|
try {
|
||||||
|
const user = await User.findById(req.userId)
|
||||||
|
.select('likedTracks')
|
||||||
|
.populate('likedTracks')
|
||||||
|
.lean();
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res
|
||||||
|
.status(404)
|
||||||
|
.json({ success: false, message: 'User not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json({ success: true, data: user.likedTracks || [] });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async like(req, res, next) {
|
async like(req, res, next) {
|
||||||
try {
|
try {
|
||||||
const { trackId } = req.params;
|
const { trackId } = req.params;
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { Router } from 'express';
|
||||||
|
import { albumController } from '../controllers/albumController.js';
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.get('/', albumController.list);
|
||||||
|
router.get('/:albumId', albumController.getById);
|
||||||
|
router.get('/title/:albumTitle', albumController.getByName);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -4,6 +4,7 @@ import adminRoutes from './admins.js';
|
|||||||
import trackRoutes from './tracks.js';
|
import trackRoutes from './tracks.js';
|
||||||
import playlistRoutes from './playlists.js';
|
import playlistRoutes from './playlists.js';
|
||||||
import artistRoutes from './artists.js';
|
import artistRoutes from './artists.js';
|
||||||
|
import albumRoutes from './albums.js';
|
||||||
import searchRoutes from './search.js';
|
import searchRoutes from './search.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
@@ -23,6 +24,7 @@ router.use('/admin', adminRoutes);
|
|||||||
router.use('/tracks', trackRoutes);
|
router.use('/tracks', trackRoutes);
|
||||||
router.use('/playlists', playlistRoutes);
|
router.use('/playlists', playlistRoutes);
|
||||||
router.use('/artists', artistRoutes);
|
router.use('/artists', artistRoutes);
|
||||||
|
router.use('/albums', albumRoutes);
|
||||||
router.use('/search', searchRoutes);
|
router.use('/search', searchRoutes);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { uploadPlaylistCover } from '../utils/multer.js';
|
|||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get('/', auth, playlistController.list);
|
router.get('/', auth, playlistController.list);
|
||||||
|
router.get('/:playlistId', auth, playlistController.getById);
|
||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
auth,
|
auth,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { auth } from '../middlewares/auth.js';
|
|||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get('/', trackController.list);
|
router.get('/', trackController.list);
|
||||||
|
router.get('/liked', auth, trackController.listLiked);
|
||||||
router.post('/:trackId/like', auth, trackController.like);
|
router.post('/:trackId/like', auth, trackController.like);
|
||||||
router.delete('/:trackId/like', auth, trackController.unlike);
|
router.delete('/:trackId/like', auth, trackController.unlike);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user