Fix tests & misc errors

This commit is contained in:
Zoe Roux 2025-04-07 15:38:08 +02:00
parent db0b244286
commit 080da9bc27
No known key found for this signature in database
11 changed files with 136 additions and 53 deletions

View File

@ -23,9 +23,12 @@ const validator = TypeCompiler.Compile(Jwt);
export const auth = new Elysia({ name: "auth" }) export const auth = new Elysia({ name: "auth" })
.guard({ .guard({
headers: t.Object({ headers: t.Object(
{
authorization: t.TemplateLiteral("Bearer ${string}"), authorization: t.TemplateLiteral("Bearer ${string}"),
}), },
{ additionalProperties: true },
),
}) })
.resolve(async ({ headers: { authorization }, error }) => { .resolve(async ({ headers: { authorization }, error }) => {
const bearer = authorization?.slice(7); const bearer = authorization?.slice(7);

View File

@ -1,5 +1,6 @@
import { and, eq, sql } from "drizzle-orm"; import { and, eq, sql } from "drizzle-orm";
import { Elysia, t } from "elysia"; import { Elysia, t } from "elysia";
import { auth } from "~/auth";
import { prefix } from "~/base"; import { prefix } from "~/base";
import { db } from "~/db"; import { db } from "~/db";
import { shows } from "~/db/schema"; import { shows } from "~/db/schema";
@ -32,12 +33,14 @@ export const collections = new Elysia({
collection: Collection, collection: Collection,
"collection-translation": CollectionTranslation, "collection-translation": CollectionTranslation,
}) })
.use(auth)
.get( .get(
"/:id", "/:id",
async ({ async ({
params: { id }, params: { id },
headers: { "accept-language": languages }, headers: { "accept-language": languages },
query: { preferOriginal, with: relations }, query: { preferOriginal, with: relations },
jwt: { sub },
error, error,
set, set,
}) => { }) => {
@ -52,6 +55,7 @@ export const collections = new Elysia({
fallbackLanguage: langs.includes("*"), fallbackLanguage: langs.includes("*"),
preferOriginal, preferOriginal,
relations, relations,
userId: sub,
}); });
if (!ret) { if (!ret) {
return error(404, { return error(404, {
@ -140,6 +144,7 @@ export const collections = new Elysia({
async ({ async ({
query: { limit, after, query, sort, filter, preferOriginal }, query: { limit, after, query, sort, filter, preferOriginal },
headers: { "accept-language": languages }, headers: { "accept-language": languages },
jwt: { sub },
request: { url }, request: { url },
}) => { }) => {
const langs = processLanguages(languages); const langs = processLanguages(languages);
@ -151,6 +156,7 @@ export const collections = new Elysia({
filter: and(eq(shows.kind, "collection"), filter), filter: and(eq(shows.kind, "collection"), filter),
languages: langs, languages: langs,
preferOriginal, preferOriginal,
userId: sub,
}); });
return createPage(items, { url, sort, limit }); return createPage(items, { url, sort, limit });
}, },
@ -222,6 +228,7 @@ export const collections = new Elysia({
params: { id }, params: { id },
query: { limit, after, query, sort, filter, preferOriginal }, query: { limit, after, query, sort, filter, preferOriginal },
headers: { "accept-language": languages }, headers: { "accept-language": languages },
jwt: { sub },
request: { url }, request: { url },
error, error,
}) => { }) => {
@ -256,6 +263,7 @@ export const collections = new Elysia({
), ),
languages: langs, languages: langs,
preferOriginal, preferOriginal,
userId: sub,
}); });
return createPage(items, { url, sort, limit }); return createPage(items, { url, sort, limit });
}, },
@ -277,6 +285,7 @@ export const collections = new Elysia({
params: { id }, params: { id },
query: { limit, after, query, sort, filter, preferOriginal }, query: { limit, after, query, sort, filter, preferOriginal },
headers: { "accept-language": languages }, headers: { "accept-language": languages },
jwt: { sub },
request: { url }, request: { url },
error, error,
}) => { }) => {
@ -311,6 +320,7 @@ export const collections = new Elysia({
), ),
languages: langs, languages: langs,
preferOriginal, preferOriginal,
userId: sub,
}); });
return createPage(items, { url, sort, limit }); return createPage(items, { url, sort, limit });
}, },
@ -332,6 +342,7 @@ export const collections = new Elysia({
params: { id }, params: { id },
query: { limit, after, query, sort, filter, preferOriginal }, query: { limit, after, query, sort, filter, preferOriginal },
headers: { "accept-language": languages }, headers: { "accept-language": languages },
jwt: { sub },
request: { url }, request: { url },
error, error,
}) => { }) => {
@ -362,6 +373,7 @@ export const collections = new Elysia({
filter: and(eq(shows.collectionPk, collection.pk), filter), filter: and(eq(shows.collectionPk, collection.pk), filter),
languages: langs, languages: langs,
preferOriginal, preferOriginal,
userId: sub,
}); });
return createPage(items, { url, sort, limit }); return createPage(items, { url, sort, limit });
}, },

View File

@ -1,5 +1,6 @@
import { and, eq, sql } from "drizzle-orm"; import { and, eq, sql } from "drizzle-orm";
import { Elysia, t } from "elysia"; import { Elysia, t } from "elysia";
import { auth } from "~/auth";
import { prefix } from "~/base"; import { prefix } from "~/base";
import { db } from "~/db"; import { db } from "~/db";
import { shows } from "~/db/schema"; import { shows } from "~/db/schema";
@ -22,12 +23,14 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
movie: Movie, movie: Movie,
"movie-translation": MovieTranslation, "movie-translation": MovieTranslation,
}) })
.use(auth)
.get( .get(
"/:id", "/:id",
async ({ async ({
params: { id }, params: { id },
headers: { "accept-language": languages }, headers: { "accept-language": languages },
query: { preferOriginal, with: relations }, query: { preferOriginal, with: relations },
jwt: { sub },
error, error,
set, set,
}) => { }) => {
@ -42,6 +45,7 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
fallbackLanguage: langs.includes("*"), fallbackLanguage: langs.includes("*"),
preferOriginal, preferOriginal,
relations, relations,
userId: sub,
}); });
if (!ret) { if (!ret) {
return error(404, { return error(404, {
@ -131,6 +135,7 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
query: { limit, after, query, sort, filter, preferOriginal }, query: { limit, after, query, sort, filter, preferOriginal },
headers: { "accept-language": languages }, headers: { "accept-language": languages },
request: { url }, request: { url },
jwt: { sub },
}) => { }) => {
const langs = processLanguages(languages); const langs = processLanguages(languages);
const items = await getShows({ const items = await getShows({
@ -141,6 +146,7 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
filter: and(eq(shows.kind, "movie"), filter), filter: and(eq(shows.kind, "movie"), filter),
languages: langs, languages: langs,
preferOriginal, preferOriginal,
userId: sub,
}); });
return createPage(items, { url, sort, limit }); return createPage(items, { url, sort, limit });
}, },

View File

@ -25,7 +25,7 @@ import {
sortToSql, sortToSql,
} from "~/models/utils"; } from "~/models/utils";
import { desc } from "~/models/utils/descriptions"; import { desc } from "~/models/utils/descriptions";
import type { WatchStatus } from "~/models/watchlist"; import type { MovieWatchStatus, SerieWatchStatus } from "~/models/watchlist";
import { showFilters, showSort } from "./shows/logic"; import { showFilters, showSort } from "./shows/logic";
const staffSort = Sort( const staffSort = Sort(
@ -219,7 +219,7 @@ export const staffH = new Elysia({ tags: ["staff"] })
const watchStatusQ = db const watchStatusQ = db
.select({ .select({
watchStatus: jsonbBuildObject<WatchStatus>({ watchStatus: jsonbBuildObject<MovieWatchStatus & SerieWatchStatus>({
...getColumns(watchlist), ...getColumns(watchlist),
percent: watchlist.seenCount, percent: watchlist.seenCount,
}).as("watchStatus"), }).as("watchStatus"),

View File

@ -1,5 +1,6 @@
import { type SQL, and, eq, exists, sql } from "drizzle-orm"; import { type SQL, and, eq, exists, sql } from "drizzle-orm";
import Elysia, { t } from "elysia"; import Elysia, { t } from "elysia";
import { auth } from "~/auth";
import { prefix } from "~/base"; import { prefix } from "~/base";
import { db } from "~/db"; import { db } from "~/db";
import { import {
@ -127,6 +128,7 @@ export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
studio: Studio, studio: Studio,
"studio-translation": StudioTranslation, "studio-translation": StudioTranslation,
}) })
.use(auth)
.get( .get(
"/:id", "/:id",
async ({ async ({
@ -301,6 +303,7 @@ export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
params: { id }, params: { id },
query: { limit, after, query, sort, filter, preferOriginal }, query: { limit, after, query, sort, filter, preferOriginal },
headers: { "accept-language": languages }, headers: { "accept-language": languages },
jwt: { sub },
request: { url }, request: { url },
error, error,
}) => { }) => {
@ -339,6 +342,7 @@ export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
), ),
languages: langs, languages: langs,
preferOriginal, preferOriginal,
userId: sub,
}); });
return createPage(items, { url, sort, limit }); return createPage(items, { url, sort, limit });
}, },
@ -360,6 +364,7 @@ export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
params: { id }, params: { id },
query: { limit, after, query, sort, filter, preferOriginal }, query: { limit, after, query, sort, filter, preferOriginal },
headers: { "accept-language": languages }, headers: { "accept-language": languages },
jwt: { sub },
request: { url }, request: { url },
error, error,
}) => { }) => {
@ -399,6 +404,7 @@ export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
), ),
languages: langs, languages: langs,
preferOriginal, preferOriginal,
userId: sub,
}); });
return createPage(items, { url, sort, limit }); return createPage(items, { url, sort, limit });
}, },
@ -420,6 +426,7 @@ export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
params: { id }, params: { id },
query: { limit, after, query, sort, filter, preferOriginal }, query: { limit, after, query, sort, filter, preferOriginal },
headers: { "accept-language": languages }, headers: { "accept-language": languages },
jwt: { sub },
request: { url }, request: { url },
error, error,
}) => { }) => {
@ -459,6 +466,7 @@ export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
), ),
languages: langs, languages: langs,
preferOriginal, preferOriginal,
userId: sub,
}); });
return createPage(items, { url, sort, limit }); return createPage(items, { url, sort, limit });
}, },

View File

@ -22,11 +22,11 @@ import { MovieWatchStatus, SerieWatchStatus } from "~/models/watchlist";
import { getShows, showFilters, showSort, watchStatusQ } from "./shows/logic"; import { getShows, showFilters, showSort, watchStatusQ } from "./shows/logic";
async function setWatchStatus({ async function setWatchStatus({
showFilter, show,
status, status,
userId, userId,
}: { }: {
showFilter: { id: SQL; kind: "movie" | "serie" }; show: { pk: number; kind: "movie" | "serie" };
status: SerieWatchStatus; status: SerieWatchStatus;
userId: string; userId: string;
}) { }) {
@ -48,17 +48,12 @@ async function setWatchStatus({
.returning({ pk: profiles.pk }); .returning({ pk: profiles.pk });
} }
const showQ = db
.select({ pk: shows.pk })
.from(shows)
.where(and(showFilter.id, eq(shows.kind, showFilter.kind)));
const [ret] = await db const [ret] = await db
.insert(watchlist) .insert(watchlist)
.values({ .values({
...status, ...status,
profilePk: profile.pk, profilePk: profile.pk,
showPk: sql`${showQ}`, showPk: show.pk,
}) })
.onConflictDoUpdate({ .onConflictDoUpdate({
target: [watchlist.profilePk, watchlist.showPk], target: [watchlist.profilePk, watchlist.showPk],
@ -70,7 +65,7 @@ async function setWatchStatus({
"seenCount", "seenCount",
]), ]),
// do not reset movie's progress during drop // do not reset movie's progress during drop
...(showFilter.kind === "movie" && status.status !== "dropped" ...(show.kind === "movie" && status.status !== "dropped"
? { seenCount: sql`excluded.seen_count` } ? { seenCount: sql`excluded.seen_count` }
: {}), : {}),
}, },
@ -205,12 +200,25 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
) )
.post( .post(
"/series/:id/watchstatus", "/series/:id/watchstatus",
async ({ params: { id }, body, jwt: { sub } }) => { async ({ params: { id }, body, jwt: { sub }, error }) => {
const [show] = await db
.select({ pk: shows.pk })
.from(shows)
.where(
and(
eq(shows.kind, "serie"),
isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id),
),
);
if (!show) {
return error(404, {
status: 404,
message: `No serie found for the id/slug: '${id}'.`,
});
}
return await setWatchStatus({ return await setWatchStatus({
showFilter: { show: { pk: show.pk, kind: "serie" },
kind: "serie",
id: isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id),
},
userId: sub, userId: sub,
status: body, status: body,
}); });
@ -226,18 +234,33 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
body: SerieWatchStatus, body: SerieWatchStatus,
response: { response: {
200: t.Union([SerieWatchStatus, DbMetadata]), 200: t.Union([SerieWatchStatus, DbMetadata]),
404: KError,
}, },
permissions: ["core.read"], permissions: ["core.read"],
}, },
) )
.post( .post(
"/movies/:id/watchstatus", "/movies/:id/watchstatus",
async ({ params: { id }, body, jwt: { sub } }) => { async ({ params: { id }, body, jwt: { sub }, error }) => {
const [show] = await db
.select({ pk: shows.pk })
.from(shows)
.where(
and(
eq(shows.kind, "movie"),
isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id),
),
);
if (!show) {
return error(404, {
status: 404,
message: `No movie found for the id/slug: '${id}'.`,
});
}
return await setWatchStatus({ return await setWatchStatus({
showFilter: { show: { pk: show.pk, kind: "movie" },
kind: "movie",
id: isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id),
},
userId: sub, userId: sub,
status: { status: {
...body, ...body,
@ -258,6 +281,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
body: t.Omit(MovieWatchStatus, ["percent"]), body: t.Omit(MovieWatchStatus, ["percent"]),
response: { response: {
200: t.Union([MovieWatchStatus, DbMetadata]), 200: t.Union([MovieWatchStatus, DbMetadata]),
404: KError,
}, },
permissions: ["core.read"], permissions: ["core.read"],
}, },

View File

@ -84,7 +84,7 @@ export const FullSerie = t.Intersect([
firstEntry: t.Optional(Entry), firstEntry: t.Optional(Entry),
}), }),
]); ]);
export type FullMovie = Prettify<typeof FullSerie.static>; export type FullSerie = Prettify<typeof FullSerie.static>;
export const SeedSerie = t.Intersect([ export const SeedSerie = t.Intersect([
t.Omit(BaseSerie, ["kind", "nextRefresh"]), t.Omit(BaseSerie, ["kind", "nextRefresh"]),

View File

@ -41,6 +41,25 @@ export const getSerie = async (
return [resp, body] as const; return [resp, body] as const;
}; };
export const getSeries = async ({
langs,
...query
}: { langs?: string; preferOriginal?: boolean; with?: string[] }) => {
const resp = await app.handle(
new Request(buildUrl("series", query), {
method: "GET",
headers: langs
? {
"Accept-Language": langs,
...(await getJwtHeaders()),
}
: await getJwtHeaders(),
}),
);
const body = await resp.json();
return [resp, body] as const;
};
export const getSeasons = async ( export const getSeasons = async (
serie: string, serie: string,
{ {
@ -166,7 +185,7 @@ export const getNews = async ({
export const setSerieStatus = async (id: string, status: SerieWatchStatus) => { export const setSerieStatus = async (id: string, status: SerieWatchStatus) => {
const resp = await app.handle( const resp = await app.handle(
new Request(buildUrl(`movies/${id}/watchstatus`), { new Request(buildUrl(`series/${id}/watchstatus`), {
method: "POST", method: "POST",
body: JSON.stringify(status), body: JSON.stringify(status),
headers: { headers: {

View File

@ -1,24 +1,29 @@
import { processImages } from "~/controllers/seed/images";
import { db, migrate } from "~/db"; import { db, migrate } from "~/db";
import { mqueue, shows, videos } from "~/db/schema"; import { profiles, shows } from "~/db/schema";
import { madeInAbyss, madeInAbyssVideo } from "~/models/examples"; import { madeInAbyss } from "~/models/examples";
import { createSerie, createVideo, getSerie } from "./helpers"; import { createSerie, getSerie, setSerieStatus } from "./helpers";
import { getJwtHeaders } from "./helpers/jwt";
// test file used to run manually using `bun tests/manual.ts` // test file used to run manually using `bun tests/manual.ts`
await migrate(); await migrate();
await db.delete(shows); await db.delete(shows);
await db.delete(videos); await db.delete(profiles);
await db.delete(mqueue);
const [_, vid] = await createVideo(madeInAbyssVideo); console.log(await getJwtHeaders());
console.log(vid);
const [__, ser] = await createSerie(madeInAbyss); const [_, ser] = await createSerie(madeInAbyss);
console.log(ser); console.log(ser);
const [__, ret] = await setSerieStatus(madeInAbyss.slug, {
status: "watching",
startedAt: "2024-12-21",
completedAt: "2024-12-21",
seenCount: 2,
score: 85,
});
console.log(ret);
await processImages(); const [___, got] = await getSerie(madeInAbyss.slug, {});
console.log(JSON.stringify(got, undefined, 4));
const [___, got] = await getSerie(madeInAbyss.slug, { with: ["translations"] });
console.log(got);
process.exit(0); process.exit(0);

View File

@ -1,4 +1,4 @@
import { beforeAll, describe, expect, it } from "bun:test"; import { describe, expect, it } from "bun:test";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { defaultBlurhash, processImages } from "~/controllers/seed/images"; import { defaultBlurhash, processImages } from "~/controllers/seed/images";
import { db } from "~/db"; import { db } from "~/db";
@ -6,7 +6,8 @@ import { mqueue, shows, staff, studios, videos } from "~/db/schema";
import { madeInAbyss } from "~/models/examples"; import { madeInAbyss } from "~/models/examples";
import { createSerie } from "../helpers"; import { createSerie } from "../helpers";
beforeAll(async () => { describe("images", () => {
it("Create a serie download images", async () => {
await db.delete(shows); await db.delete(shows);
await db.delete(studios); await db.delete(studios);
await db.delete(staff); await db.delete(staff);
@ -17,10 +18,7 @@ beforeAll(async () => {
const release = await processImages(); const release = await processImages();
// remove notifications to prevent other images to be downloaded (do not curl 20000 images for nothing) // remove notifications to prevent other images to be downloaded (do not curl 20000 images for nothing)
release(); release();
});
describe("images", () => {
it("Create a serie download images", async () => {
const ret = await db.query.shows.findFirst({ const ret = await db.query.shows.findFirst({
where: eq(shows.slug, madeInAbyss.slug), where: eq(shows.slug, madeInAbyss.slug),
}); });

View File

@ -81,6 +81,14 @@ describe("Set & get watch status", () => {
}); });
it("Return watchstatus in /movies/:id", async () => { it("Return watchstatus in /movies/:id", async () => {
const [r, b] = await setMovieStatus(bubble.slug, {
status: "rewatching",
// we still need to specify all values
completedAt: "2024-12-21",
score: 85,
});
expectStatus(r, b).toBe(200);
const [resp, body] = await getMovie(bubble.slug, {}); const [resp, body] = await getMovie(bubble.slug, {});
expectStatus(resp, body).toBe(200); expectStatus(resp, body).toBe(200);
expect(body.slug).toBe(bubble.slug); expect(body.slug).toBe(bubble.slug);