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;