- Remove /api/songs/debug/playlist/:name endpoint - Debug endpoint was temporary and no longer needed - Playlist loading issue resolved by database reload - Clean up code after successful troubleshooting
248 lines
7.8 KiB
TypeScript
248 lines
7.8 KiB
TypeScript
import express, { Request, Response } from 'express';
|
|
import { Song } from '../models/Song.js';
|
|
import { Playlist } from '../models/Playlist.js';
|
|
|
|
const router = express.Router();
|
|
|
|
|
|
|
|
// Get songs with pagination and search
|
|
router.get('/', async (req: Request, res: Response) => {
|
|
try {
|
|
const page = parseInt(req.query.page as string) || 1;
|
|
const limit = parseInt(req.query.limit as string) || 50;
|
|
const search = req.query.search as string || '';
|
|
const skip = (page - 1) * limit;
|
|
|
|
console.log(`Fetching songs from database... Page: ${page}, Limit: ${limit}, Search: "${search}"`);
|
|
|
|
// Build query for search
|
|
let query = {};
|
|
if (search) {
|
|
query = {
|
|
$or: [
|
|
{ title: { $regex: search, $options: 'i' } },
|
|
{ artist: { $regex: search, $options: 'i' } },
|
|
{ album: { $regex: search, $options: 'i' } },
|
|
{ genre: { $regex: search, $options: 'i' } }
|
|
]
|
|
};
|
|
}
|
|
|
|
// Get total count for pagination
|
|
const totalSongs = await Song.countDocuments(query);
|
|
const totalPages = Math.ceil(totalSongs / limit);
|
|
|
|
// Get songs with pagination
|
|
const songs = await Song.find(query)
|
|
.sort({ title: 1 })
|
|
.skip(skip)
|
|
.limit(limit)
|
|
.populate('s3File.musicFileId')
|
|
.lean();
|
|
|
|
console.log(`Found ${songs.length} songs (${totalSongs} total), ${songs.filter((s: any) => s.s3File?.hasS3File).length} with S3 files`);
|
|
|
|
res.json({
|
|
songs,
|
|
pagination: {
|
|
page,
|
|
limit,
|
|
totalSongs,
|
|
totalPages,
|
|
hasNextPage: page < totalPages,
|
|
hasPrevPage: page > 1
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching songs:', error);
|
|
res.status(500).json({ message: 'Error fetching songs', error });
|
|
}
|
|
});
|
|
|
|
// Get songs by playlist with pagination
|
|
router.get('/playlist/*', async (req: Request, res: Response) => {
|
|
try {
|
|
const playlistName = decodeURIComponent(req.params[0]);
|
|
const page = parseInt(req.query.page as string) || 1;
|
|
const limit = parseInt(req.query.limit as string) || 50;
|
|
const search = req.query.search as string || '';
|
|
const skip = (page - 1) * limit;
|
|
|
|
console.log(`Fetching songs for playlist "${playlistName}"... Page: ${page}, Limit: ${limit}, Search: "${search}"`);
|
|
|
|
// Find the playlist recursively in the playlist structure
|
|
const findPlaylistRecursively = (nodes: any[], targetName: string): any => {
|
|
for (const node of nodes) {
|
|
if (node.name === targetName) {
|
|
return node;
|
|
}
|
|
if (node.children && node.children.length > 0) {
|
|
const found = findPlaylistRecursively(node.children, targetName);
|
|
if (found) return found;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
// Get all playlists and search recursively
|
|
const allPlaylists = await Playlist.find({});
|
|
let playlist = null;
|
|
|
|
for (const playlistDoc of allPlaylists) {
|
|
playlist = findPlaylistRecursively([playlistDoc], playlistName);
|
|
if (playlist) break;
|
|
}
|
|
|
|
if (!playlist) {
|
|
console.log(`Playlist "${playlistName}" not found. Available playlists:`);
|
|
const allPlaylistNames = await Playlist.find({}, 'name');
|
|
console.log(allPlaylistNames.map(p => p.name));
|
|
return res.status(404).json({ message: `Playlist "${playlistName}" not found` });
|
|
}
|
|
|
|
// Get all track IDs from the playlist (including nested playlists)
|
|
const getAllTrackIds = (node: any): string[] => {
|
|
if (node.type === 'playlist' && node.tracks) {
|
|
return node.tracks;
|
|
}
|
|
if (node.type === 'folder' && node.children) {
|
|
return node.children.flatMap((child: any) => getAllTrackIds(child));
|
|
}
|
|
return [];
|
|
};
|
|
|
|
const trackIds = getAllTrackIds(playlist);
|
|
console.log(`Found ${trackIds.length} tracks in playlist "${playlistName}"`);
|
|
|
|
if (trackIds.length === 0) {
|
|
return res.json({
|
|
songs: [],
|
|
pagination: {
|
|
page,
|
|
limit,
|
|
totalSongs: 0,
|
|
totalPages: 0,
|
|
hasNextPage: false,
|
|
hasPrevPage: false
|
|
}
|
|
});
|
|
}
|
|
|
|
// Build query for songs in playlist
|
|
let query: any = { id: { $in: trackIds } };
|
|
if (search) {
|
|
query = {
|
|
$and: [
|
|
{ id: { $in: trackIds } },
|
|
{
|
|
$or: [
|
|
{ title: { $regex: search, $options: 'i' } },
|
|
{ artist: { $regex: search, $options: 'i' } },
|
|
{ album: { $regex: search, $options: 'i' } },
|
|
{ genre: { $regex: search, $options: 'i' } }
|
|
]
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
// Get total count for pagination
|
|
const totalSongs = await Song.countDocuments(query);
|
|
const totalPages = Math.ceil(totalSongs / limit);
|
|
|
|
// Calculate total duration for the entire playlist
|
|
const allPlaylistSongs = await Song.find({ id: { $in: trackIds } }).lean();
|
|
const totalDuration = allPlaylistSongs.reduce((total, song: any) => {
|
|
if (!song.totalTime) return total;
|
|
const totalTimeStr = String(song.totalTime);
|
|
const seconds = Math.floor(Number(totalTimeStr) / (totalTimeStr.length > 4 ? 1000 : 1));
|
|
return total + seconds;
|
|
}, 0);
|
|
|
|
// Get songs with pagination
|
|
const songs = await Song.find(query)
|
|
.sort({ title: 1 })
|
|
.skip(skip)
|
|
.limit(limit)
|
|
.populate('s3File.musicFileId')
|
|
.lean();
|
|
|
|
console.log(`Found ${songs.length} songs for playlist "${playlistName}" (${totalSongs} total), ${songs.filter((s: any) => s.s3File?.hasS3File).length} with S3 files`);
|
|
|
|
res.json({
|
|
songs,
|
|
pagination: {
|
|
page,
|
|
limit,
|
|
totalSongs,
|
|
totalPages,
|
|
hasNextPage: page < totalPages,
|
|
hasPrevPage: page > 1
|
|
},
|
|
totalDuration: totalDuration
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching playlist songs:', error);
|
|
res.status(500).json({ message: 'Error fetching playlist songs', error });
|
|
}
|
|
});
|
|
|
|
// Get total song count
|
|
router.get('/count', async (req: Request, res: Response) => {
|
|
try {
|
|
const count = await Song.countDocuments();
|
|
res.json({ count });
|
|
} catch (error) {
|
|
console.error('Error fetching song count:', error);
|
|
res.status(500).json({ message: 'Error fetching song count', error });
|
|
}
|
|
});
|
|
|
|
// Export library to XML format with streaming
|
|
router.get('/export', async (req: Request, res: Response) => {
|
|
try {
|
|
console.log('Starting streaming XML export...');
|
|
|
|
// Set response headers for file download
|
|
res.setHeader('Content-Type', 'application/xml');
|
|
res.setHeader('Content-Disposition', `attachment; filename="rekordbox-library-${new Date().toISOString().split('T')[0]}.xml"`);
|
|
res.setHeader('Transfer-Encoding', 'chunked');
|
|
|
|
// Import the streaming XML generation function
|
|
const { streamToXml } = await import('../services/xmlService.js');
|
|
|
|
// Stream XML generation to response
|
|
await streamToXml(res);
|
|
|
|
console.log('Streaming XML export completed successfully');
|
|
} catch (error) {
|
|
console.error('Error exporting library:', error);
|
|
if (!res.headersSent) {
|
|
res.status(500).json({ message: 'Error exporting library', error });
|
|
}
|
|
}
|
|
});
|
|
|
|
// Create multiple songs
|
|
router.post('/batch', async (req: Request, res: Response) => {
|
|
try {
|
|
console.log('Received batch upload request');
|
|
const songs = req.body;
|
|
console.log(`Attempting to save ${songs.length} songs`);
|
|
|
|
// Delete all existing songs first
|
|
await Song.deleteMany({});
|
|
console.log('Cleared existing songs');
|
|
|
|
// Insert new songs
|
|
const result = await Song.insertMany(songs);
|
|
console.log(`Successfully saved ${result.length} songs`);
|
|
res.status(201).json(result);
|
|
} catch (error) {
|
|
console.error('Error creating songs:', error);
|
|
res.status(500).json({ message: 'Error creating songs', error });
|
|
}
|
|
});
|
|
|
|
export const songsRouter = router;
|