fix: remove 500/1000 item cap on library pagination (Issue #12)

- Remove Math.min(..., 1000) hard cap on /library/artists endpoint
- Remove Math.min(..., 1000) hard cap on /library/albums endpoint
- Add offset parameter and total count to /library/tracks endpoint
- Rewrite useLibraryData hook for true server-side pagination
- Update library page to fetch pages on demand instead of client-side slicing
- Disable pagination buttons during loading to prevent race conditions

This allows libraries of any size to be fully browsable. Previously,
users with 10,000+ songs were capped at seeing only 500 items.

Fixes #12

🤖 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 02:44:12 -05:00
parent 9e59f92fa7
commit 567f38e1ea
3 changed files with 166 additions and 149 deletions
+24 -19
View File
@@ -693,7 +693,7 @@ router.get("/artists", async (req, res) => {
offset: offsetParam = "0",
filter = "owned", // owned (default), discovery, all
} = req.query;
const limit = Math.min(parseInt(limitParam as string, 10) || 500, 1000); // Max 1000
const limit = parseInt(limitParam as string, 10) || 500; // No max cap - support unlimited pagination
const offset = parseInt(offsetParam as string, 10) || 0;
// Build where clause based on filter
@@ -1577,7 +1577,7 @@ router.get("/albums", async (req, res) => {
offset: offsetParam = "0",
filter = "owned", // owned (default), discovery, all
} = req.query;
const limit = Math.min(parseInt(limitParam as string, 10) || 500, 1000); // Max 1000
const limit = parseInt(limitParam as string, 10) || 500; // No max cap - support unlimited pagination
const offset = parseInt(offsetParam as string, 10) || 0;
let where: any = {
@@ -1721,34 +1721,39 @@ router.get("/albums/:id", async (req, res) => {
}
});
// GET /library/tracks?albumId=&limit=100
// GET /library/tracks?albumId=&limit=100&offset=0
router.get("/tracks", async (req, res) => {
try {
const { albumId, limit = "100" } = req.query;
const limitNum = parseInt(limit as string, 10);
const { albumId, limit: limitParam = "100", offset: offsetParam = "0" } = req.query;
const limit = parseInt(limitParam as string, 10) || 100;
const offset = parseInt(offsetParam as string, 10) || 0;
const where: any = {};
if (albumId) {
where.albumId = albumId as string;
}
const tracksData = await prisma.track.findMany({
where,
take: limitNum,
orderBy: albumId ? { trackNo: "asc" } : { id: "desc" },
include: {
album: {
include: {
artist: {
select: {
id: true,
name: true,
const [tracksData, total] = await Promise.all([
prisma.track.findMany({
where,
skip: offset,
take: limit,
orderBy: albumId ? { trackNo: "asc" } : { id: "desc" },
include: {
album: {
include: {
artist: {
select: {
id: true,
name: true,
},
},
},
},
},
},
});
}),
prisma.track.count({ where }),
]);
// Add coverArt field to albums
const tracks = tracksData.map((track) => ({
@@ -1759,7 +1764,7 @@ router.get("/tracks", async (req, res) => {
},
}));
res.json({ tracks });
res.json({ tracks, total, offset, limit });
} catch (error) {
console.error("Get tracks error:", error);
res.status(500).json({ error: "Failed to fetch tracks" });