fix: address Copilot review feedback on pagination PR

- Add MAX_LIMIT=10000 constant to prevent DoS attacks while supporting
  large libraries through pagination
- Apply MAX_LIMIT to /library/artists, /library/albums, /library/tracks
- Add trackCount field to artists endpoint (fixes "Most Tracks" sort)
- Add /library/tracks/shuffle endpoint for server-side shuffle
- Update frontend to use server-side shuffle instead of fetching 10k tracks

The shuffle endpoint uses Fisher-Yates algorithm and samples random tracks
for large libraries to avoid loading the entire database into memory.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Daniel Thiberge
2025-12-27 14:23:53 -05:00
parent 567f38e1ea
commit e3fe3c6f48
3 changed files with 122 additions and 12 deletions
+5 -9
View File
@@ -112,21 +112,17 @@ export default function LibraryPage() {
playTracks(formattedTracks, startIndex);
};
// Shuffle entire library - fetches all tracks for true shuffle
// Shuffle entire library - uses server-side shuffle for large libraries
const handleShuffleLibrary = async () => {
try {
// Fetch a large batch of tracks for shuffling
const { tracks: allTracks } = await api.getTracks({
limit: 10000,
});
// Use server-side shuffle endpoint for better performance with large libraries
const { tracks: shuffledTracks } = await api.getShuffledTracks(500);
if (allTracks.length === 0) {
if (shuffledTracks.length === 0) {
return;
}
// Shuffle the tracks
const shuffled = [...allTracks].sort(() => Math.random() - 0.5);
const formattedTracks = formatTracksForAudio(shuffled);
const formattedTracks = formatTracksForAudio(shuffledTracks);
playTracks(formattedTracks, 0);
} catch (error) {
console.error("Failed to shuffle library:", error);
+8
View File
@@ -402,6 +402,14 @@ class ApiClient {
}>(`/library/tracks?${new URLSearchParams(params as any).toString()}`);
}
async getShuffledTracks(limit?: number) {
const params = limit ? `?limit=${limit}` : "";
return this.request<{
tracks: any[];
total: number;
}>(`/library/tracks/shuffle${params}`);
}
async deleteTrack(trackId: string) {
return this.request<{ message: string }>(`/library/tracks/${trackId}`, {
method: "DELETE",