Update watchlist when inserting into history

This commit is contained in:
Zoe Roux 2025-04-07 22:53:07 +02:00
parent c0e00c0fd4
commit 69006478cb
No known key found for this signature in database
2 changed files with 155 additions and 27 deletions

View File

@ -1,9 +1,22 @@
import { and, eq, isNotNull, ne, not, or, sql } from "drizzle-orm"; import {
and,
count,
eq,
exists,
gt,
isNotNull,
ne,
not,
or,
sql,
} from "drizzle-orm";
import { alias } from "drizzle-orm/pg-core";
import Elysia, { t } from "elysia"; import Elysia, { t } from "elysia";
import { auth, getUserInfo } from "~/auth"; import { auth, getUserInfo } from "~/auth";
import { db } from "~/db"; import { db } from "~/db";
import { entries, history, profiles, videos } from "~/db/schema"; import { entries, history, profiles, shows, videos } from "~/db/schema";
import { values } from "~/db/utils"; import { watchlist } from "~/db/schema/watchlist";
import { coalesce, values } from "~/db/utils";
import { Entry } from "~/models/entry"; import { Entry } from "~/models/entry";
import { KError } from "~/models/error"; import { KError } from "~/models/error";
import { SeedHistory } from "~/models/history"; import { SeedHistory } from "~/models/history";
@ -165,6 +178,10 @@ export const historyH = new Elysia({ tags: ["profiles"] })
async ({ body, jwt: { sub }, error }) => { async ({ body, jwt: { sub }, error }) => {
const profilePk = await getOrCreateProfile(sub); const profilePk = await getOrCreateProfile(sub);
const vals = values(
body.map((x) => ({ ...x, entryUseId: isUuid(x.entry) })),
).as("hist");
const rows = await db const rows = await db
.insert(history) .insert(history)
.select( .select(
@ -177,11 +194,7 @@ export const historyH = new Elysia({ tags: ["profiles"] })
time: sql`hist.time::integer`, time: sql`hist.time::integer`,
playedDate: sql`hist.playedDate::timestamptz`, playedDate: sql`hist.playedDate::timestamptz`,
}) })
.from( .from(vals)
values(
body.map((x) => ({ ...x, entryUseId: isUuid(x.entry) })),
).as("hist"),
)
.innerJoin( .innerJoin(
entries, entries,
or( or(
@ -198,6 +211,121 @@ export const historyH = new Elysia({ tags: ["profiles"] })
.leftJoin(videos, eq(videos.id, sql`hist.videoId::uuid`)), .leftJoin(videos, eq(videos.id, sql`hist.videoId::uuid`)),
) )
.returning({ pk: history.pk }); .returning({ pk: history.pk });
// automatically update watchlist with this new info
const nextEntry = alias(entries, "next_entry");
const nextEntryQ = db
.select({
pk: nextEntry.pk,
})
.from(nextEntry)
.where(
and(
eq(nextEntry.showPk, entries.showPk),
gt(nextEntry.order, entries.order),
),
)
.orderBy(nextEntry.showPk, entries.order)
.as("nextEntryQ");
const seenCountQ = db
.select({ c: count() })
.from(entries)
.where(
and(
eq(entries.showPk, sql`excluded.show_pk`),
exists(
db
.select()
.from(history)
.where(
and(
eq(history.profilePk, profilePk),
eq(history.entryPk, entries.pk),
),
),
),
),
)
.as("seenCountQ");
await db
.insert(watchlist)
.select(
db
.select({
profilePk: sql`${profilePk}`,
showPk: entries.showPk,
status: sql`
case
when
hist.progress >= 95
and ${nextEntryQ.pk} is null
then 'completed'::watchstatus
else 'watching'::watchstatus
end
`,
seenCount: sql`
case
when ${eq(entries.kind, "movie")} then hist.progress::number
when hist.progress >= 95 then 1
else 0
end
`,
nextEntry: nextEntryQ.pk,
score: sql`null`,
startedAt: sql`hist.playedDate::timestamptz`,
completedAt: sql`
case
when ${nextEntryQ.pk} is null then hist.playedDate::timestamptz
else null
end
`,
})
.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`),
),
),
)
.leftLateralJoin(nextEntryQ, sql`true`),
)
.onConflictDoUpdate({
target: [watchlist.profilePk, watchlist.showPk],
set: {
status: sql`
case
when ${eq(sql`excluded.status`, "completed")} then excluded.status
when ${and(
ne(watchlist.status, "completed"),
ne(watchlist.status, "rewatching"),
)} then excluded.status
else ${watchlist.status}
end
`,
seenCount: sql`${seenCountQ.c}`,
nextEntry: sql`
case
when ${eq(watchlist.status, "completed")} then null
else excluded.nextEntry
end
`,
completedAt: coalesce(
watchlist.completedAt,
sql`excluded.completed_at`,
),
},
});
return error(201, { status: 201, inserted: rows.length }); return error(201, { status: 201, inserted: rows.length });
}, },
{ {

View File

@ -6,6 +6,7 @@ import {
getEntries, getEntries,
getHistory, getHistory,
getNews, getNews,
getWatchlist,
} from "tests/helpers"; } from "tests/helpers";
import { expectStatus } from "tests/utils"; import { expectStatus } from "tests/utils";
import { db } from "~/db"; import { db } from "~/db";
@ -127,23 +128,22 @@ describe("Set & get history", () => {
// extras, unknowns // extras, unknowns
// it("Update watchlist", async () => { it("Update watchlist", async () => {
// const [r, b] = await setMovieStatus(bubble.slug, { const [resp, body] = await getWatchlist("me", {});
// status: "rewatching", expectStatus(resp, body).toBe(200);
// // we still need to specify all values expect(body.items).toBeArrayOfSize(2);
// completedAt: "2024-12-21", // watching items before completed ones
// score: 85, expect(body.items[0].slug).toBe(madeInAbyss.slug);
// }); expect(body.items[0].watchStatus).toMatchObject({
// expectStatus(r, b).toBe(200); status: "watching",
// seenCount: 1,
// const [resp, body] = await getMovie(bubble.slug, {}); startedAt: "2025-02-01 00:00:00+00",
// expectStatus(resp, body).toBe(200); });
// expect(body.slug).toBe(bubble.slug); expect(body.items[1].slug).toBe(bubble.slug);
// expect(body.progress).toMatchObject({ expect(body.items[1].watchStatus).toMatchObject({
// status: "rewatching", status: "completed",
// completedAt: "2024-12-21 00:00:00+00", percent: 100,
// score: 85, startedAt: "2025-02-02 00:00:00+00",
// percent: 0, });
// }); });
// });
}); });