Fix nextEntry & lastPlayedAt calculations on history/watchlist

This commit is contained in:
Zoe Roux 2025-04-08 16:31:09 +02:00
parent 67d7643261
commit 09dd78b272
No known key found for this signature in database
3 changed files with 74 additions and 39 deletions

View File

@ -181,6 +181,12 @@ export const historyH = new Elysia({ tags: ["profiles"] })
const vals = values(
body.map((x) => ({ ...x, entryUseId: isUuid(x.entry) })),
).as("hist");
const valEqEntries = sql`
case
when hist.entryUseId::boolean then ${entries.id} = hist.entry::uuid
else ${entries.slug} = hist.entry
end
`;
const rows = await db
.insert(history)
@ -195,19 +201,7 @@ export const historyH = new Elysia({ tags: ["profiles"] })
playedDate: sql`hist.playedDate::timestamptz`,
})
.from(vals)
.innerJoin(
entries,
or(
and(
sql`hist.entryUseId::boolean`,
eq(entries.id, sql`hist.entry::uuid`),
),
and(
not(sql`hist.entryUseId::boolean`),
eq(entries.slug, sql`hist.entry`),
),
),
)
.innerJoin(entries, valEqEntries)
.leftJoin(videos, eq(videos.id, sql`hist.videoId::uuid`)),
)
.returning({ pk: history.pk });
@ -273,9 +267,15 @@ export const historyH = new Elysia({ tags: ["profiles"] })
else 0
end
`,
nextEntry: nextEntryQ.pk,
nextEntry: sql`
case
when hist.percent::integer >= 95 then ${nextEntryQ.pk}
else ${entries.pk}
end
`,
score: sql`null`,
startedAt: sql`hist.playedDate::timestamptz`,
lastPlayedAt: sql`hist.playedDate::timestamptz`,
completedAt: sql`
case
when ${nextEntryQ.pk} is null then hist.playedDate::timestamptz
@ -286,19 +286,7 @@ export const historyH = new Elysia({ tags: ["profiles"] })
updatedAt: sql`now()`,
})
.from(vals)
.leftJoin(
entries,
or(
and(
sql`hist.entryUseId::boolean`,
eq(entries.id, sql`hist.entry::uuid`),
),
and(
not(sql`hist.entryUseId::boolean`),
eq(entries.slug, sql`hist.entry`),
),
),
)
.leftJoin(entries, valEqEntries)
.leftJoinLateral(nextEntryQ, sql`true`),
)
.onConflictDoUpdate({
@ -321,6 +309,7 @@ export const historyH = new Elysia({ tags: ["profiles"] })
else excluded.next_entry
end
`,
lastPlayedAt: sql`excluded.last_played_at`,
completedAt: coalesce(
watchlist.completedAt,
sql`excluded.completed_at`,

View File

@ -8,7 +8,7 @@ import {
watchStatusQ,
} from "~/controllers/shows/logic";
import { db } from "~/db";
import { shows } from "~/db/schema";
import { entries, shows } from "~/db/schema";
import { watchlist } from "~/db/schema/watchlist";
import { conflictUpdateAllExcept, getColumns } from "~/db/utils";
import { KError } from "~/models/error";
@ -32,18 +32,39 @@ async function setWatchStatus({
status,
userId,
}: {
show: { pk: number; kind: "movie" | "serie" };
status: SerieWatchStatus;
show:
| { pk: number; kind: "movie" }
| { pk: number; kind: "serie"; entriesCount: number };
status: Omit<SerieWatchStatus, "seenCount">;
userId: string;
}) {
const profilePk = await getOrCreateProfile(userId);
const firstEntryQ = db
.select({ pk: entries.pk })
.from(entries)
.where(eq(entries.showPk, show.pk))
.orderBy(entries.order)
.limit(1);
const [ret] = await db
.insert(watchlist)
.values({
...status,
profilePk: profilePk,
seenCount:
status.status === "completed"
? show.kind === "movie"
? 100
: show.entriesCount
: 0,
showPk: show.pk,
nextEntry:
show.kind === "movie" &&
(status.status === "watching" || status.status === "rewatching")
? sql`${firstEntryQ}`
: sql`null`,
lastPlayedAt: status.startedAt,
})
.onConflictDoUpdate({
target: [watchlist.profilePk, watchlist.showPk],
@ -53,10 +74,32 @@ async function setWatchStatus({
"showPk",
"createdAt",
"seenCount",
"nextEntry",
"lastPlayedAt",
]),
// do not reset movie's progress during drop
...(show.kind === "movie" && status.status !== "dropped"
? { seenCount: sql`excluded.seen_count` }
...(status.status === "completed"
? {
seenCount: sql`excluded.seen_count`,
nextEntry: sql`null`,
}
: {}),
// only set seenCount & nextEntry when marking as "rewatching"
// if it's already rewatching, the history updates are more up-dated.
...(status.status === "rewatching"
? {
seenCount: sql`
case when ${watchlist.status} != 'rewatching'
then excluded.seen_count
else
${watchlist.seenCount}
end`,
nextEntry: sql`
case when ${watchlist.status} != 'rewatching'
then excluded.next_entry
else
${watchlist.nextEntry}
end`,
}
: {}),
},
})
@ -115,6 +158,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
),
languages: langs,
preferOriginal: preferOriginal ?? settings.preferOriginal,
relations: ["nextEntry"],
userId: sub,
});
return createPage(items, { url, sort, limit });
@ -159,6 +203,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
),
languages: langs,
preferOriginal: preferOriginal ?? settings.preferOriginal,
relations: ["nextEntry"],
userId: uInfo.id,
});
return createPage(items, { url, sort, limit });
@ -195,7 +240,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
"/series/:id/watchstatus",
async ({ params: { id }, body, jwt: { sub }, error }) => {
const [show] = await db
.select({ pk: shows.pk })
.select({ pk: shows.pk, entriesCount: shows.entriesCount })
.from(shows)
.where(
and(
@ -211,7 +256,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
});
}
return await setWatchStatus({
show: { pk: show.pk, kind: "serie" },
show: { pk: show.pk, kind: "serie", entriesCount: show.entriesCount },
userId: sub,
status: body,
});
@ -224,7 +269,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
example: madeInAbyss.slug,
}),
}),
body: SerieWatchStatus,
body: t.Omit(SerieWatchStatus, ["seenCount"]),
response: {
200: t.Intersect([SerieWatchStatus, DbMetadata]),
404: KError,
@ -258,8 +303,6 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
status: {
...body,
startedAt: body.completedAt,
// for movies, watch-percent is stored in `seenCount`.
seenCount: body.status === "completed" ? 100 : 0,
},
});
},

View File

@ -184,7 +184,10 @@ export const getNews = async ({
return [resp, body] as const;
};
export const setSerieStatus = async (id: string, status: SerieWatchStatus) => {
export const setSerieStatus = async (
id: string,
status: Omit<SerieWatchStatus, "seenCount">,
) => {
const resp = await app.handle(
new Request(buildUrl(`series/${id}/watchstatus`), {
method: "POST",