Sort 4K shelf by newest 4K file discovery, not title first-seen.

This commit is contained in:
Jos Vooges | STH 2026-09-12 02:37:03 +02:00
parent 06a022089b
commit 4c15a37783
2 changed files with 47 additions and 23 deletions

View file

@ -508,9 +508,8 @@ async function getMovieCatalog(
} else if (catalogId === "4k-movies") { } else if (catalogId === "4k-movies") {
if (!search) { if (!search) {
const idRows = await prisma.$queryRaw<Array<{ movie_id: string }>>` const idRows = await prisma.$queryRaw<Array<{ movie_id: string }>>`
SELECT m.id AS movie_id SELECT mf.movie_id
FROM media_files mf FROM media_files mf
INNER JOIN movies m ON m.id = mf.movie_id
WHERE mf.available = true WHERE mf.available = true
AND mf.movie_id IS NOT NULL AND mf.movie_id IS NOT NULL
AND ( AND (
@ -518,9 +517,8 @@ async function getMovieCatalog(
OR mf.resolution ILIKE '%4K%' OR mf.resolution ILIKE '%4K%'
OR mf.resolution ILIKE '%UHD%' OR mf.resolution ILIKE '%UHD%'
) )
GROUP BY m.id, m.library_added_at GROUP BY mf.movie_id
ORDER BY COALESCE(m.library_added_at, MAX(mf.discovered_at)) DESC NULLS LAST, ORDER BY MAX(mf.discovered_at) DESC, MIN(mf.release_name) ASC
MAX(mf.discovered_at) DESC
OFFSET ${skip} OFFSET ${skip}
LIMIT ${CATALOG_PAGE} LIMIT ${CATALOG_PAGE}
`; `;

View file

@ -985,40 +985,65 @@ export class ViewerService {
/** /**
* Sorteer op library_added_at (cluster first-seen). Fallback discovered_at. * Sorteer op library_added_at (cluster first-seen). Fallback discovered_at.
* Tweede node bump’t niet; mtime bij eerste index voorkomt scan-flood. * Tweede node bump’t niet; mtime bij eerste index voorkomt scan-flood.
* Optie byFileDiscoveredAt: voor 4K — nieuwste 4K-file (mtime/discovered_at), niet titel-first-seen.
*/ */
private async moviesByRecency( private async moviesByRecency(
skip: number, skip: number,
take: number, take: number,
extraWhereSql: Prisma.Sql = Prisma.empty, extraWhereSql: Prisma.Sql = Prisma.empty,
opts: { cap?: number; kidsMode?: boolean } = {} opts: { cap?: number; kidsMode?: boolean; byFileDiscoveredAt?: boolean } = {}
) { ) {
const cap = opts.cap; const cap = opts.cap;
const kidsSql = opts.kidsMode ? kidsMovieSql() : Prisma.empty; const kidsSql = opts.kidsMode ? kidsMovieSql() : Prisma.empty;
const idRows = await prisma.$queryRaw<Array<{ movie_id: string }>>` const idRows = opts.byFileDiscoveredAt
SELECT m.id AS movie_id ? await prisma.$queryRaw<Array<{ movie_id: string }>>`
FROM media_files mf SELECT mf.movie_id
INNER JOIN movies m ON m.id = mf.movie_id FROM media_files mf
WHERE mf.available = true WHERE mf.available = true
AND mf.movie_id IS NOT NULL AND mf.movie_id IS NOT NULL
${extraWhereSql} ${extraWhereSql}
${kidsSql} ${kidsSql}
GROUP BY m.id, m.library_added_at GROUP BY mf.movie_id
ORDER BY COALESCE(m.library_added_at, MAX(mf.discovered_at)) DESC NULLS LAST, ORDER BY MAX(mf.discovered_at) DESC, MIN(mf.release_name) ASC
MAX(mf.discovered_at) DESC, ${cap != null ? Prisma.sql`LIMIT ${cap}` : Prisma.empty}
MIN(mf.release_name) ASC `
${cap != null ? Prisma.sql`LIMIT ${cap}` : Prisma.empty} : await prisma.$queryRaw<Array<{ movie_id: string }>>`
`; SELECT m.id AS movie_id
FROM media_files mf
INNER JOIN movies m ON m.id = mf.movie_id
WHERE mf.available = true
AND mf.movie_id IS NOT NULL
${extraWhereSql}
${kidsSql}
GROUP BY m.id, m.library_added_at
ORDER BY COALESCE(m.library_added_at, MAX(mf.discovered_at)) DESC NULLS LAST,
MAX(mf.discovered_at) DESC,
MIN(mf.release_name) ASC
${cap != null ? Prisma.sql`LIMIT ${cap}` : Prisma.empty}
`;
const allIds = idRows.map((r) => r.movie_id); const allIds = idRows.map((r) => r.movie_id);
const total = allIds.length; const total = allIds.length;
const ids = allIds.slice(skip, skip + take); const ids = allIds.slice(skip, skip + take);
if (!ids.length) return { total, items: [], offset: skip }; if (!ids.length) return { total, items: [], offset: skip };
const fourKOnly = opts.byFileDiscoveredAt === true;
const movies = await prisma.movie.findMany({ const movies = await prisma.movie.findMany({
where: { id: { in: ids } }, where: { id: { in: ids } },
include: { include: {
mediaFiles: { mediaFiles: {
where: { available: true }, where: fourKOnly
orderBy: [{ lastSeen: "desc" }, { createdAt: "desc" }], ? {
available: true,
OR: [
{ resolution: { contains: "2160" } },
{ resolution: { contains: "4K", mode: "insensitive" } },
{ resolution: { contains: "UHD", mode: "insensitive" } },
],
}
: { available: true },
orderBy: fourKOnly
? [{ discoveredAt: "desc" }, { lastSeen: "desc" }]
: [{ lastSeen: "desc" }, { createdAt: "desc" }],
take: 1, take: 1,
}, },
}, },
@ -1332,7 +1357,7 @@ export class ViewerService {
OR mf.resolution ILIKE '%4K%' OR mf.resolution ILIKE '%4K%'
OR mf.resolution ILIKE '%UHD%' OR mf.resolution ILIKE '%UHD%'
)`, )`,
{ cap: RECENT_CATALOG_CAP, kidsMode } { cap: RECENT_CATALOG_CAP, kidsMode, byFileDiscoveredAt: true }
); );
return page.items; return page.items;
} }
@ -1912,6 +1937,7 @@ export class ViewerService {
: await this.moviesByRecency(skip, take, fourKSql, { : await this.moviesByRecency(skip, take, fourKSql, {
cap: RECENT_CATALOG_CAP, cap: RECENT_CATALOG_CAP,
kidsMode, kidsMode,
byFileDiscoveredAt: true,
}); });
return { return {
id, id,