feat(playlists): track custom order in separate 'order' array; reading API honors 'order' while preserving 'tracks'

This commit is contained in:
Geert Rademakes 2025-08-08 14:25:50 +02:00
parent 5a396d774e
commit 61d4ca16de
3 changed files with 13 additions and 9 deletions

View File

@ -9,6 +9,8 @@ const playlistSchema = new mongoose.Schema({
required: true required: true
}, },
tracks: [String], tracks: [String],
// Optional custom order of track IDs (display-only, not exported)
order: [String],
children: [{ children: [{
type: mongoose.Schema.Types.Mixed type: mongoose.Schema.Types.Mixed
}] }]

View File

@ -80,7 +80,7 @@ router.post('/batch', async (req, res) => {
} }
}); });
// Reorder tracks inside a playlist (by name) // Reorder tracks inside a playlist (by name). Stores order separately in `order` to avoid interfering with original `tracks`.
router.post('/reorder', async (req: Request, res: Response) => { router.post('/reorder', async (req: Request, res: Response) => {
try { try {
const { playlistName, orderedIds } = req.body as { playlistName: string; orderedIds: string[] }; const { playlistName, orderedIds } = req.body as { playlistName: string; orderedIds: string[] };
@ -94,13 +94,9 @@ router.post('/reorder', async (req: Request, res: Response) => {
const reorderNode = (node: any): any => { const reorderNode = (node: any): any => {
if (!node) return node; if (!node) return node;
if (node.type === 'playlist' && node.name === playlistName) { if (node.type === 'playlist' && node.name === playlistName) {
// Ensure unique order and only include known IDs // Store order separately; do not mutate tracks
const unique = Array.from(new Set(orderedIds)); const unique = Array.from(new Set(orderedIds));
// If tracks exist, keep only ids present in unique, and append any tracks not included to preserve membership node.order = unique;
const current: string[] = Array.isArray(node.tracks) ? node.tracks : [];
const orderedKnown = unique.filter(id => current.includes(id));
const missing = current.filter(id => !unique.includes(id));
node.tracks = [...orderedKnown, ...missing];
updated = true; updated = true;
return node; return node;
} }

View File

@ -110,8 +110,14 @@ router.get('/playlist/*', async (req: Request, res: Response) => {
// Get all track IDs from the playlist (including nested playlists) // Get all track IDs from the playlist (including nested playlists)
const getAllTrackIds = (node: any): string[] => { const getAllTrackIds = (node: any): string[] => {
if (node.type === 'playlist' && node.tracks) { if (node.type === 'playlist') {
return node.tracks; const base = Array.isArray(node.tracks) ? node.tracks : [];
const order = Array.isArray(node.order) ? node.order : [];
if (order.length === 0) return base;
const setBase = new Set(base);
const orderedKnown = order.filter((id: string) => setBase.has(id));
const missing = base.filter((id: string) => !order.includes(id));
return [...orderedKnown, ...missing];
} }
if (node.type === 'folder' && node.children) { if (node.type === 'folder' && node.children) {
return node.children.flatMap((child: any) => getAllTrackIds(child)); return node.children.flatMap((child: any) => getAllTrackIds(child));