Fix watchlist apis & add delete watchlist api

This commit is contained in:
Zoe Roux
2025-12-21 20:18:36 +01:00
parent 04fef7bd20
commit 6d21eeab07
7 changed files with 190 additions and 77 deletions
+119 -35
View File
@@ -10,7 +10,7 @@ import {
import { db } from "~/db";
import { entries, shows } from "~/db/schema";
import { watchlist } from "~/db/schema/watchlist";
import { conflictUpdateAllExcept, getColumns } from "~/db/utils";
import { coalesce, getColumns, rowToModel } from "~/db/utils";
import { Entry } from "~/models/entry";
import { KError } from "~/models/error";
import { bubble, madeInAbyss } from "~/models/examples";
@@ -26,9 +26,16 @@ import {
processLanguages,
} from "~/models/utils";
import { desc } from "~/models/utils/descriptions";
import { MovieWatchStatus, SerieWatchStatus } from "~/models/watchlist";
import {
MovieWatchStatus,
SeedMovieWatchStatus,
SeedSerieWatchStatus,
SerieWatchStatus,
} from "~/models/watchlist";
import { getOrCreateProfile } from "./profile";
console.log();
async function setWatchStatus({
show,
status,
@@ -37,7 +44,7 @@ async function setWatchStatus({
show:
| { pk: number; kind: "movie" }
| { pk: number; kind: "serie"; entriesCount: number };
status: Omit<SerieWatchStatus, "seenCount">;
status: SeedSerieWatchStatus;
userId: string;
}) {
const profilePk = await getOrCreateProfile(userId);
@@ -53,6 +60,7 @@ async function setWatchStatus({
.insert(watchlist)
.values({
...status,
startedAt: coalesce(sql`${status.startedAt ?? null}`, sql`now()`),
profilePk: profilePk,
seenCount:
status.status === "completed"
@@ -70,38 +78,33 @@ async function setWatchStatus({
.onConflictDoUpdate({
target: [watchlist.profilePk, watchlist.showPk],
set: {
...conflictUpdateAllExcept(watchlist, [
"profilePk",
"showPk",
"createdAt",
"seenCount",
"nextEntry",
"lastPlayedAt",
]),
...(status.status === "completed"
? {
seenCount: sql`excluded.seen_count`,
nextEntry: sql`null`,
}
: {}),
status: sql`excluded.status`,
startedAt: coalesce(
sql`${status.startedAt ?? null}`,
watchlist.startedAt,
),
completedAt: sql`
case
when excluded.status = 'completed' then coalesce(excluded.completed_at, now())
else coalesce(excluded.completed_at, ${watchlist.completedAt})
end
`,
// 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`,
}
: {}),
seenCount: sql`
case
when excluded.status = 'completed' then excluded.seen_count
when excluded.status = 'rewatching' and ${watchlist.status} != 'rewatching' then excluded.seen_count
else ${watchlist.seenCount}
end
`,
nextEntry: sql`
case
when excluded.status = 'completed' then null
when excluded.status = 'rewatching' and ${watchlist.status} != 'rewatching' then excluded.next_entry
else ${watchlist.nextEntry}
end
`,
},
})
.returning({
@@ -296,7 +299,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
example: madeInAbyss.slug,
}),
}),
body: t.Omit(SerieWatchStatus, ["seenCount"]),
body: SeedSerieWatchStatus,
response: {
200: t.Intersect([SerieWatchStatus, DbMetadata]),
404: KError,
@@ -341,7 +344,88 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
example: bubble.slug,
}),
}),
body: t.Omit(MovieWatchStatus, ["percent"]),
body: SeedMovieWatchStatus,
response: {
200: t.Intersect([MovieWatchStatus, DbMetadata]),
404: KError,
},
permissions: ["core.read"],
},
)
.delete(
"/series/:id/watchstatus",
async ({ params: { id }, jwt: { sub }, status }) => {
const profilePk = await getOrCreateProfile(sub);
const rows = await db.execute(sql`
delete from ${watchlist} using ${shows}
where ${and(
eq(watchlist.profilePk, profilePk),
eq(watchlist.showPk, shows.pk),
eq(shows.kind, "serie"),
isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id),
)}
returning ${watchlist}.*
`);
if (rows.rowCount === 0) {
return status(404, {
status: 404,
message: `No serie found in your watchlist the id/slug: '${id}'.`,
});
}
return rowToModel(rows.rows[0], watchlist);
},
{
detail: { description: "Set watchstatus of a serie." },
params: t.Object({
id: t.String({
description: "The id or slug of the serie.",
example: madeInAbyss.slug,
}),
}),
response: {
200: t.Intersect([SerieWatchStatus, DbMetadata]),
404: KError,
},
permissions: ["core.read"],
},
)
.delete(
"/movies/:id/watchstatus",
async ({ params: { id }, jwt: { sub }, status }) => {
const profilePk = await getOrCreateProfile(sub);
const rows = await db.execute(sql`
delete from ${watchlist} using ${shows}
where ${and(
eq(watchlist.profilePk, profilePk),
eq(watchlist.showPk, shows.pk),
eq(shows.kind, "movie"),
isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id),
)}
returning ${watchlist}.*
`);
if (rows.rowCount === 0) {
return status(404, {
status: 404,
message: `No movie found in your watchlist the id/slug: '${id}'.`,
});
}
const ret = rowToModel(rows.rows[0], watchlist);
return { ...ret, percent: ret.seenCount };
},
{
detail: { description: "Set watchstatus of a movie." },
params: t.Object({
id: t.String({
description: "The id or slug of the movie.",
example: bubble.slug,
}),
}),
response: {
200: t.Intersect([MovieWatchStatus, DbMetadata]),
404: KError,