diff --git a/api/src/controllers/watchlist.ts b/api/src/controllers/watchlist.ts index 622f5460..cc6acfac 100644 --- a/api/src/controllers/watchlist.ts +++ b/api/src/controllers/watchlist.ts @@ -4,7 +4,7 @@ import { auth, getUserInfo } from "~/auth"; import { db } from "~/db"; import { profiles, shows } from "~/db/schema"; import { watchlist } from "~/db/schema/watchlist"; -import { conflictUpdateAllExcept } from "~/db/utils"; +import { conflictUpdateAllExcept, getColumns } from "~/db/utils"; import { KError } from "~/models/error"; import { bubble, madeInAbyss } from "~/models/examples"; import { Show } from "~/models/show"; @@ -30,22 +30,34 @@ async function setWatchStatus({ status: SerieWatchStatus; userId: string; }) { - const profileQ = db + let [profile] = await db .select({ pk: profiles.pk }) .from(profiles) .where(eq(profiles.id, userId)) - .as("profileQ"); + .limit(1); + if (!profile) { + [profile] = await db + .insert(profiles) + .values({ id: userId }) + .onConflictDoUpdate({ + // we can't do `onConflictDoNothing` because on race conditions + // we still want the profile to be returned. + target: [profiles.id], + set: { id: sql`excluded.id` }, + }) + .returning({ pk: profiles.pk }); + } + const showQ = db .select({ pk: shows.pk }) .from(shows) - .where(and(showFilter.id, eq(shows.kind, showFilter.kind))) - .as("showQ"); + .where(and(showFilter.id, eq(shows.kind, showFilter.kind))); - return await db + const [ret] = await db .insert(watchlist) .values({ ...status, - profilePk: sql`${profileQ}`, + profilePk: profile.pk, showPk: sql`${showQ}`, }) .onConflictDoUpdate({ @@ -63,7 +75,11 @@ async function setWatchStatus({ : {}), }, }) - .returning(); + .returning({ + ...getColumns(watchlist), + percent: sql`${watchlist.seenCount}`.as("percent"), + }); + return ret; } export const watchlistH = new Elysia({ tags: ["profiles"] }) @@ -87,10 +103,6 @@ export const watchlistH = new Elysia({ tags: ["profiles"] }) }), ), }), - response: { - 200: Page(Show), - 422: KError, - }, }, (app) => app @@ -127,6 +139,10 @@ export const watchlistH = new Elysia({ tags: ["profiles"] }) }, { additionalProperties: true }, ), + response: { + 200: Page(Show), + 422: KError, + }, }, ) .get( @@ -136,11 +152,11 @@ export const watchlistH = new Elysia({ tags: ["profiles"] }) query: { limit, after, query, sort, filter, preferOriginal }, headers: { "accept-language": languages, authorization }, request: { url }, + error, }) => { - if (!isUuid(id)) { - const uInfo = await getUserInfo(id, { authorization }); - id = uInfo.id; - } + const uInfo = await getUserInfo(id, { authorization }); + + if ("status" in uInfo) return error(uInfo.status as 404, uInfo); const langs = processLanguages(languages); const items = await getShows({ @@ -155,7 +171,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] }) ), languages: langs, preferOriginal, - userId: id, + userId: uInfo.id, }); return createPage(items, { url, sort, limit }); }, @@ -209,7 +225,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] }) }), body: SerieWatchStatus, response: { - 201: t.Union([SerieWatchStatus, DbMetadata]), + 200: t.Union([SerieWatchStatus, DbMetadata]), }, permissions: ["core.read"], }, @@ -241,7 +257,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] }) }), body: t.Omit(MovieWatchStatus, ["percent"]), response: { - 201: t.Union([MovieWatchStatus, DbMetadata]), + 200: t.Union([MovieWatchStatus, DbMetadata]), }, permissions: ["core.read"], }, diff --git a/api/tests/helpers/jwt.ts b/api/tests/helpers/jwt.ts index 80efc31b..668dfb7f 100644 --- a/api/tests/helpers/jwt.ts +++ b/api/tests/helpers/jwt.ts @@ -5,7 +5,7 @@ export async function getJwtHeaders() { sub: "39158be0-3f59-4c45-b00d-d25b3bc2b884", sid: "04ac7ecc-255b-481d-b0c8-537c1578e3d5", username: "test-username", - permissions: ["core.read", "core.write"], + permissions: ["core.read", "core.write", "users.read"], }) .setProtectedHeader({ alg: "HS256" }) .setIssuedAt() diff --git a/api/tests/helpers/series-helper.ts b/api/tests/helpers/series-helper.ts index 843ff375..d585ef7a 100644 --- a/api/tests/helpers/series-helper.ts +++ b/api/tests/helpers/series-helper.ts @@ -1,8 +1,8 @@ import { buildUrl } from "tests/utils"; import { app } from "~/base"; import type { SeedSerie } from "~/models/serie"; +import type { SerieWatchStatus } from "~/models/watchlist"; import { getJwtHeaders } from "./jwt"; -import { SerieWatchStatus } from "~/models/watchlist"; export const createSerie = async (serie: SeedSerie) => { const resp = await app.handle( @@ -164,10 +164,7 @@ export const getNews = async ({ return [resp, body] as const; }; -export const setSerieStatus = async ( - id: string, - status: SerieWatchStatus -) => { +export const setSerieStatus = async (id: string, status: SerieWatchStatus) => { const resp = await app.handle( new Request(buildUrl(`movies/${id}/watchstatus`), { method: "POST", diff --git a/api/tests/movies/watchstatus.test.ts b/api/tests/movies/watchstatus.test.ts index 98caa014..96025ab3 100644 --- a/api/tests/movies/watchstatus.test.ts +++ b/api/tests/movies/watchstatus.test.ts @@ -28,7 +28,7 @@ describe("Set & get watch status", () => { completedAt: "2024-12-21", score: 85, }); - expectStatus(r, b).toBe(201); + expectStatus(r, b).toBe(200); [resp, body] = await getWatchlist("me", {}); expectStatus(resp, body).toBe(200); @@ -36,7 +36,7 @@ describe("Set & get watch status", () => { expect(body.items[0].slug).toBe(bubble.slug); expect(body.items[0].watchStatus).toMatchObject({ status: "completed", - completedAt: "2024-12-21", + completedAt: "2024-12-21 00:00:00+00", score: 85, percent: 100, }); @@ -53,15 +53,15 @@ describe("Set & get watch status", () => { completedAt: "2024-12-21", score: 85, }); - expectStatus(r, b).toBe(201); + expectStatus(r, b).toBe(200); [resp, body] = await getWatchlist("me", {}); expectStatus(resp, body).toBe(200); expect(body.items).toBeArrayOfSize(1); expect(body.items[0].slug).toBe(bubble.slug); expect(body.items[0].watchStatus).toMatchObject({ - status: "completed", - completedAt: "2024-12-21", + status: "rewatching", + completedAt: "2024-12-21 00:00:00+00", score: 85, percent: 0, }); @@ -73,8 +73,8 @@ describe("Set & get watch status", () => { expect(body.items).toBeArrayOfSize(1); expect(body.items[0].slug).toBe(bubble.slug); expect(body.items[0].watchStatus).toMatchObject({ - status: "completed", - completedAt: "2024-12-21", + status: "rewatching", + completedAt: "2024-12-21 00:00:00+00", score: 85, percent: 0, }); @@ -85,8 +85,8 @@ describe("Set & get watch status", () => { expectStatus(resp, body).toBe(200); expect(body.slug).toBe(bubble.slug); expect(body.watchStatus).toMatchObject({ - status: "completed", - completedAt: "2024-12-21", + status: "rewatching", + completedAt: "2024-12-21 00:00:00+00", score: 85, percent: 0, });