Handle profiles creation in kyoo db schema

This commit is contained in:
Zoe Roux 2025-04-07 12:39:43 +02:00
parent 59533e5f0c
commit db0b244286
No known key found for this signature in database
4 changed files with 47 additions and 34 deletions

View File

@ -4,7 +4,7 @@ import { auth, getUserInfo } from "~/auth";
import { db } from "~/db"; import { db } from "~/db";
import { profiles, shows } from "~/db/schema"; import { profiles, shows } from "~/db/schema";
import { watchlist } from "~/db/schema/watchlist"; import { watchlist } from "~/db/schema/watchlist";
import { conflictUpdateAllExcept } from "~/db/utils"; import { conflictUpdateAllExcept, getColumns } from "~/db/utils";
import { KError } from "~/models/error"; import { KError } from "~/models/error";
import { bubble, madeInAbyss } from "~/models/examples"; import { bubble, madeInAbyss } from "~/models/examples";
import { Show } from "~/models/show"; import { Show } from "~/models/show";
@ -30,22 +30,34 @@ async function setWatchStatus({
status: SerieWatchStatus; status: SerieWatchStatus;
userId: string; userId: string;
}) { }) {
const profileQ = db let [profile] = await db
.select({ pk: profiles.pk }) .select({ pk: profiles.pk })
.from(profiles) .from(profiles)
.where(eq(profiles.id, userId)) .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 const showQ = db
.select({ pk: shows.pk }) .select({ pk: shows.pk })
.from(shows) .from(shows)
.where(and(showFilter.id, eq(shows.kind, showFilter.kind))) .where(and(showFilter.id, eq(shows.kind, showFilter.kind)));
.as("showQ");
return await db const [ret] = await db
.insert(watchlist) .insert(watchlist)
.values({ .values({
...status, ...status,
profilePk: sql`${profileQ}`, profilePk: profile.pk,
showPk: sql`${showQ}`, showPk: sql`${showQ}`,
}) })
.onConflictDoUpdate({ .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"] }) 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) =>
app app
@ -127,6 +139,10 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
}, },
{ additionalProperties: true }, { additionalProperties: true },
), ),
response: {
200: Page(Show),
422: KError,
},
}, },
) )
.get( .get(
@ -136,11 +152,11 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
query: { limit, after, query, sort, filter, preferOriginal }, query: { limit, after, query, sort, filter, preferOriginal },
headers: { "accept-language": languages, authorization }, headers: { "accept-language": languages, authorization },
request: { url }, request: { url },
error,
}) => { }) => {
if (!isUuid(id)) { const uInfo = await getUserInfo(id, { authorization });
const uInfo = await getUserInfo(id, { authorization });
id = uInfo.id; if ("status" in uInfo) return error(uInfo.status as 404, uInfo);
}
const langs = processLanguages(languages); const langs = processLanguages(languages);
const items = await getShows({ const items = await getShows({
@ -155,7 +171,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
), ),
languages: langs, languages: langs,
preferOriginal, preferOriginal,
userId: id, userId: uInfo.id,
}); });
return createPage(items, { url, sort, limit }); return createPage(items, { url, sort, limit });
}, },
@ -209,7 +225,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
}), }),
body: SerieWatchStatus, body: SerieWatchStatus,
response: { response: {
201: t.Union([SerieWatchStatus, DbMetadata]), 200: t.Union([SerieWatchStatus, DbMetadata]),
}, },
permissions: ["core.read"], permissions: ["core.read"],
}, },
@ -241,7 +257,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
}), }),
body: t.Omit(MovieWatchStatus, ["percent"]), body: t.Omit(MovieWatchStatus, ["percent"]),
response: { response: {
201: t.Union([MovieWatchStatus, DbMetadata]), 200: t.Union([MovieWatchStatus, DbMetadata]),
}, },
permissions: ["core.read"], permissions: ["core.read"],
}, },

View File

@ -5,7 +5,7 @@ export async function getJwtHeaders() {
sub: "39158be0-3f59-4c45-b00d-d25b3bc2b884", sub: "39158be0-3f59-4c45-b00d-d25b3bc2b884",
sid: "04ac7ecc-255b-481d-b0c8-537c1578e3d5", sid: "04ac7ecc-255b-481d-b0c8-537c1578e3d5",
username: "test-username", username: "test-username",
permissions: ["core.read", "core.write"], permissions: ["core.read", "core.write", "users.read"],
}) })
.setProtectedHeader({ alg: "HS256" }) .setProtectedHeader({ alg: "HS256" })
.setIssuedAt() .setIssuedAt()

View File

@ -1,8 +1,8 @@
import { buildUrl } from "tests/utils"; import { buildUrl } from "tests/utils";
import { app } from "~/base"; import { app } from "~/base";
import type { SeedSerie } from "~/models/serie"; import type { SeedSerie } from "~/models/serie";
import type { SerieWatchStatus } from "~/models/watchlist";
import { getJwtHeaders } from "./jwt"; import { getJwtHeaders } from "./jwt";
import { SerieWatchStatus } from "~/models/watchlist";
export const createSerie = async (serie: SeedSerie) => { export const createSerie = async (serie: SeedSerie) => {
const resp = await app.handle( const resp = await app.handle(
@ -164,10 +164,7 @@ export const getNews = async ({
return [resp, body] as const; return [resp, body] as const;
}; };
export const setSerieStatus = async ( export const setSerieStatus = async (id: string, status: SerieWatchStatus) => {
id: string,
status: SerieWatchStatus
) => {
const resp = await app.handle( const resp = await app.handle(
new Request(buildUrl(`movies/${id}/watchstatus`), { new Request(buildUrl(`movies/${id}/watchstatus`), {
method: "POST", method: "POST",

View File

@ -28,7 +28,7 @@ describe("Set & get watch status", () => {
completedAt: "2024-12-21", completedAt: "2024-12-21",
score: 85, score: 85,
}); });
expectStatus(r, b).toBe(201); expectStatus(r, b).toBe(200);
[resp, body] = await getWatchlist("me", {}); [resp, body] = await getWatchlist("me", {});
expectStatus(resp, body).toBe(200); 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].slug).toBe(bubble.slug);
expect(body.items[0].watchStatus).toMatchObject({ expect(body.items[0].watchStatus).toMatchObject({
status: "completed", status: "completed",
completedAt: "2024-12-21", completedAt: "2024-12-21 00:00:00+00",
score: 85, score: 85,
percent: 100, percent: 100,
}); });
@ -53,15 +53,15 @@ describe("Set & get watch status", () => {
completedAt: "2024-12-21", completedAt: "2024-12-21",
score: 85, score: 85,
}); });
expectStatus(r, b).toBe(201); expectStatus(r, b).toBe(200);
[resp, body] = await getWatchlist("me", {}); [resp, body] = await getWatchlist("me", {});
expectStatus(resp, body).toBe(200); expectStatus(resp, body).toBe(200);
expect(body.items).toBeArrayOfSize(1); expect(body.items).toBeArrayOfSize(1);
expect(body.items[0].slug).toBe(bubble.slug); expect(body.items[0].slug).toBe(bubble.slug);
expect(body.items[0].watchStatus).toMatchObject({ expect(body.items[0].watchStatus).toMatchObject({
status: "completed", status: "rewatching",
completedAt: "2024-12-21", completedAt: "2024-12-21 00:00:00+00",
score: 85, score: 85,
percent: 0, percent: 0,
}); });
@ -73,8 +73,8 @@ describe("Set & get watch status", () => {
expect(body.items).toBeArrayOfSize(1); expect(body.items).toBeArrayOfSize(1);
expect(body.items[0].slug).toBe(bubble.slug); expect(body.items[0].slug).toBe(bubble.slug);
expect(body.items[0].watchStatus).toMatchObject({ expect(body.items[0].watchStatus).toMatchObject({
status: "completed", status: "rewatching",
completedAt: "2024-12-21", completedAt: "2024-12-21 00:00:00+00",
score: 85, score: 85,
percent: 0, percent: 0,
}); });
@ -85,8 +85,8 @@ describe("Set & get watch status", () => {
expectStatus(resp, body).toBe(200); expectStatus(resp, body).toBe(200);
expect(body.slug).toBe(bubble.slug); expect(body.slug).toBe(bubble.slug);
expect(body.watchStatus).toMatchObject({ expect(body.watchStatus).toMatchObject({
status: "completed", status: "rewatching",
completedAt: "2024-12-21", completedAt: "2024-12-21 00:00:00+00",
score: 85, score: 85,
percent: 0, percent: 0,
}); });