diff --git a/api/src/controllers/profiles/history.ts b/api/src/controllers/profiles/history.ts index d61cf26f..77b7a361 100644 --- a/api/src/controllers/profiles/history.ts +++ b/api/src/controllers/profiles/history.ts @@ -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`, diff --git a/api/src/controllers/profiles/watchlist.ts b/api/src/controllers/profiles/watchlist.ts index ee3ea827..8107eaf9 100644 --- a/api/src/controllers/profiles/watchlist.ts +++ b/api/src/controllers/profiles/watchlist.ts @@ -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; 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, }, }); }, diff --git a/api/tests/helpers/series-helper.ts b/api/tests/helpers/series-helper.ts index f367542b..836fce43 100644 --- a/api/tests/helpers/series-helper.ts +++ b/api/tests/helpers/series-helper.ts @@ -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, +) => { const resp = await app.handle( new Request(buildUrl(`series/${id}/watchstatus`), { method: "POST",