diff --git a/api/src/controllers/movies.ts b/api/src/controllers/movies.ts index ce61824f..35647ab7 100644 --- a/api/src/controllers/movies.ts +++ b/api/src/controllers/movies.ts @@ -2,21 +2,24 @@ import { Elysia, t } from "elysia"; import { Movie, MovieTranslation } from "../models/movie"; import { db } from "../db"; import { shows, showTranslations } from "../db/schema/shows"; -import { eq, and, sql, or, inArray } from "drizzle-orm"; +import { eq, and, sql, or } from "drizzle-orm"; import { getColumns } from "../db/schema/utils"; import { bubble } from "../models/examples"; +import { comment } from "~/utils"; +import { processLanguages } from "~/models/utils"; const translations = db - .selectDistinctOn([showTranslations.language]) + .selectDistinctOn([showTranslations.pk]) .from(showTranslations) - .where( - or( - inArray(showTranslations.language, sql.placeholder("langs")), - eq(showTranslations.language, shows.originalLanguage), - ), - ) + // .where( + // or( + // eq(showTranslations.language, sql`any(${sql.placeholder("langs")})`), + // eq(showTranslations.language, shows.originalLanguage), + // ), + // ) .orderBy( - sql`array_position(${showTranslations.language}, ${sql.placeholder("langs")})`, + showTranslations.pk, + sql`array_position(${sql.placeholder("langs")}, ${showTranslations.language})`, ) .as("t"); @@ -26,21 +29,21 @@ const { pk, language, ...translationsCol } = getColumns(translations); const findMovie = db .select({ ...moviesCol, + ...translationsCol, airDate: startAir, - translations: translationsCol, }) .from(shows) .innerJoin(translations, eq(shows.pk, translations.pk)) .where( and( eq(shows.kind, "movie"), - or( - eq(shows.id, sql.placeholder("id")), + // or( + // eq(shows.id, sql.placeholder("id")), eq(shows.slug, sql.placeholder("id")), - ), + // ), ), ) - .orderBy() + // .orderBy() .limit(1) .prepare("findMovie"); @@ -57,12 +60,31 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) examples: [bubble.slug], }), }), + headers: t.Object({ + "Accept-Language": t.String({ + default: "*", + examples: "en-us, ja;q=0.5", + description: comment` + List of languages you want the data in. + This follows the Accept-Language offical specification + (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language) + `, + }), + }), response: { 200: "movie", 404: "error" }, }) .get( "/:id", - async ({ params: { id }, error }) => { - const ret = await findMovie.execute({ id }); + async ({ + params: { id }, + headers: { "Accept-Language": languages }, + error, + }) => { + const langs = processLanguages(languages); + console.log(langs); + console.log(findMovie.getQuery()); + const ret = await findMovie.execute({ id, langs }); + console.log(ret); if (ret.length !== 1) return error(404, {}); return ret[0]; }, @@ -71,4 +93,4 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) description: "Get a movie by id or slug", }, }, - ); + ) diff --git a/api/src/models/utils/language.ts b/api/src/models/utils/language.ts index b843df0b..4706cd40 100644 --- a/api/src/models/utils/language.ts +++ b/api/src/models/utils/language.ts @@ -48,3 +48,18 @@ export const Language = (props?: StringProps) => error: "Expected a valid (and NORMALIZED) bcp-47 language code.", ...props, }); + +export const processLanguages = (languages: string) => { + return languages + .split(",") + .map((x) => { + const [lang, q] = x.trim().split(";q="); + return [lang, q ? Number.parseFloat(q) : 1] as const; + }) + .sort(([_, q1], [__, q2]) => q1 - q2) + .flatMap(([lang]) => { + const [base, spec] = lang.split("-"); + if (spec) return [lang, base]; + return [lang]; + }); +}; diff --git a/api/tests/get-movies.test.ts b/api/tests/get-movies.test.ts new file mode 100644 index 00000000..e6cefacb --- /dev/null +++ b/api/tests/get-movies.test.ts @@ -0,0 +1,52 @@ +import { afterAll, beforeAll, describe, expect, it } from "bun:test"; +import { eq } from "drizzle-orm"; +import Elysia from "elysia"; +import { base } from "~/base"; +import { movies } from "~/controllers/movies"; +import { seedMovie } from "~/controllers/seed/movies"; +import { db } from "~/db"; +import { shows } from "~/db/schema"; +import { bubble } from "~/models/examples"; + +const app = new Elysia().use(base).use(movies); +const getMovie = async (id: string, langs: string) => { + const resp = await app.handle( + new Request(`http://localhost/movies/${id}`, { + method: "GET", + headers: { + "Accept-Language": langs, + }, + }), + ); + const body = await resp.json(); + return [resp, body] as const; +}; + +function expectStatus(resp: Response, body: object) { + const matcher = expect({ ...body, status: resp.status }); + return { + toBe: (status: number) => { + matcher.toMatchObject({ status: status }); + }, + }; +} + +describe("Get movie", () => { + it("Retrive by slug", async () => { + const [resp, body] = await getMovie(bubble.slug, "en"); + + expectStatus(resp, body).toBe(200); + expect(body).toMatchObject({ + slug: bubble.slug, + name: bubble.translations.en.name, + }); + }); +}); + +beforeAll(async () => { + const ret = await seedMovie(bubble); + console.log("seed bubble", ret); +}); +afterAll(async () => { + // await db.delete(shows).where(eq(shows.slug, bubble.slug)); +});