Fix shelf catalog pagination so browse shows all titles.
Shelf endpoints now return a real total and page by skip/take instead of capping around 60. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
38a779c5f8
commit
7e624dd452
2 changed files with 93 additions and 75 deletions
|
|
@ -79,7 +79,12 @@ fun BrowseScreen(
|
||||||
val page = api.browse(catalogId, skip = skip, take = 60)
|
val page = api.browse(catalogId, skip = skip, take = 60)
|
||||||
title = page.title.ifBlank { titleHint ?: catalogId }
|
title = page.title.ifBlank { titleHint ?: catalogId }
|
||||||
total = page.total
|
total = page.total
|
||||||
items = if (reset) page.items else items + page.items.filter { n -> items.none { it.id == n.id } }
|
items = if (reset) {
|
||||||
|
page.items
|
||||||
|
} else {
|
||||||
|
val existing = items.map { "${it.type}:${it.id}" }.toSet()
|
||||||
|
items + page.items.filter { "${it.type}:${it.id}" !in existing }
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
error = e.message
|
error = e.message
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
||||||
|
|
@ -328,7 +328,16 @@ export class ViewerService {
|
||||||
title: string;
|
title: string;
|
||||||
kind: "MOVIE" | "SERIES";
|
kind: "MOVIE" | "SERIES";
|
||||||
slug: string;
|
slug: string;
|
||||||
items: Awaited<ReturnType<ViewerService["shelfItems"]>>;
|
items: Array<{
|
||||||
|
type: "movie" | "series";
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
year: number | null;
|
||||||
|
posterUrl: string | null;
|
||||||
|
backdropUrl: string | null;
|
||||||
|
mediaFileId: string | null;
|
||||||
|
badge?: string;
|
||||||
|
}>;
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
if (recentMovies.length) {
|
if (recentMovies.length) {
|
||||||
|
|
@ -567,80 +576,79 @@ export class ViewerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async shelfItems(shelfId: string, kind: "MOVIE" | "SERIES", take: number) {
|
async shelfItems(shelfId: string, kind: "MOVIE" | "SERIES", take: number) {
|
||||||
// Sorteer op laatst toegevoegd mediabestand (niet op metadata-aanmaakdatum)
|
const page = await this.shelfItemsPage(shelfId, kind, 0, take);
|
||||||
|
return page.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Volledige plank met echte total + skip/take (voor browse/Alles). */
|
||||||
|
async shelfItemsPage(
|
||||||
|
shelfId: string,
|
||||||
|
kind: "MOVIE" | "SERIES",
|
||||||
|
skip: number,
|
||||||
|
take: number
|
||||||
|
) {
|
||||||
if (kind === "MOVIE") {
|
if (kind === "MOVIE") {
|
||||||
const files = await prisma.mediaFile.findMany({
|
const where = {
|
||||||
where: { shelfId, available: true, movieId: { not: null } },
|
mediaFiles: { some: { shelfId, available: true } },
|
||||||
orderBy: { createdAt: "desc" },
|
};
|
||||||
take: Math.max(take * 4, take),
|
const [total, movies] = await Promise.all([
|
||||||
include: { movie: true },
|
prisma.movie.count({ where }),
|
||||||
});
|
prisma.movie.findMany({
|
||||||
const seen = new Set<string>();
|
where,
|
||||||
const out: Array<{
|
orderBy: [{ title: "asc" }, { year: "asc" }],
|
||||||
type: "movie";
|
skip,
|
||||||
id: string;
|
take,
|
||||||
title: string;
|
include: {
|
||||||
year: number | null;
|
mediaFiles: {
|
||||||
posterUrl: string | null;
|
where: { shelfId, available: true },
|
||||||
backdropUrl: string | null;
|
orderBy: { createdAt: "desc" },
|
||||||
mediaFileId: string | null;
|
take: 1,
|
||||||
}> = [];
|
},
|
||||||
for (const f of files) {
|
},
|
||||||
if (!f.movieId || !f.movie || seen.has(f.movieId)) continue;
|
}),
|
||||||
seen.add(f.movieId);
|
]);
|
||||||
out.push({
|
return {
|
||||||
type: "movie",
|
total,
|
||||||
id: f.movie.id,
|
items: movies.map((m) => ({
|
||||||
title: f.movie.title,
|
type: "movie" as const,
|
||||||
year: f.movie.year,
|
id: m.id,
|
||||||
posterUrl: f.movie.posterUrl,
|
title: m.title,
|
||||||
backdropUrl: f.movie.backdropUrl,
|
year: m.year,
|
||||||
mediaFileId: f.id,
|
posterUrl: m.posterUrl,
|
||||||
});
|
backdropUrl: m.backdropUrl,
|
||||||
if (out.length >= take) break;
|
mediaFileId: m.mediaFiles[0]?.id ?? null,
|
||||||
}
|
})),
|
||||||
return out;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const files = await prisma.mediaFile.findMany({
|
const where = {
|
||||||
where: {
|
episodes: {
|
||||||
shelfId,
|
some: {
|
||||||
available: true,
|
mediaFiles: { some: { shelfId, available: true } },
|
||||||
episodeId: { not: null },
|
},
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: "desc" },
|
};
|
||||||
take: Math.max(take * 6, take),
|
const [total, series] = await Promise.all([
|
||||||
include: {
|
prisma.series.count({ where }),
|
||||||
episode: { include: { series: true } },
|
prisma.series.findMany({
|
||||||
},
|
where,
|
||||||
});
|
orderBy: [{ title: "asc" }, { year: "asc" }],
|
||||||
const seen = new Set<string>();
|
skip,
|
||||||
const out: Array<{
|
take,
|
||||||
type: "series";
|
}),
|
||||||
id: string;
|
]);
|
||||||
title: string;
|
return {
|
||||||
year: number | null;
|
total,
|
||||||
posterUrl: string | null;
|
items: series.map((s) => ({
|
||||||
backdropUrl: string | null;
|
type: "series" as const,
|
||||||
mediaFileId: string | null;
|
id: s.id,
|
||||||
}> = [];
|
title: s.title,
|
||||||
for (const f of files) {
|
year: s.year,
|
||||||
const series = f.episode?.series;
|
posterUrl: s.posterUrl,
|
||||||
const seriesId = f.episode?.seriesId;
|
backdropUrl: s.backdropUrl,
|
||||||
if (!series || !seriesId || seen.has(seriesId)) continue;
|
mediaFileId: null as string | null,
|
||||||
seen.add(seriesId);
|
})),
|
||||||
out.push({
|
};
|
||||||
type: "series",
|
|
||||||
id: series.id,
|
|
||||||
title: series.title,
|
|
||||||
year: series.year,
|
|
||||||
posterUrl: series.posterUrl,
|
|
||||||
backdropUrl: series.backdropUrl,
|
|
||||||
mediaFileId: null,
|
|
||||||
});
|
|
||||||
if (out.length >= take) break;
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async continueWatching(viewerId: string) {
|
async continueWatching(viewerId: string) {
|
||||||
|
|
@ -990,13 +998,18 @@ export class ViewerService {
|
||||||
})) || null;
|
})) || null;
|
||||||
if (!shelf) throw new AppError("NOT_FOUND", "Catalogus niet gevonden", 404);
|
if (!shelf) throw new AppError("NOT_FOUND", "Catalogus niet gevonden", 404);
|
||||||
|
|
||||||
const items = await this.shelfItems(shelf.id, shelf.kind, skip + take);
|
const page = await this.shelfItemsPage(
|
||||||
|
shelf.id,
|
||||||
|
shelf.kind as "MOVIE" | "SERIES",
|
||||||
|
skip,
|
||||||
|
take
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
id: shelf.slug,
|
id: shelf.slug,
|
||||||
title: shelf.name,
|
title: shelf.name,
|
||||||
kind: shelf.kind,
|
kind: shelf.kind,
|
||||||
total: items.length + skip,
|
total: page.total,
|
||||||
items: items.slice(skip, skip + take),
|
items: page.items,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue