diff --git a/api/src/controllers/movies.ts b/api/src/controllers/movies.ts index 2cad2f1f..3243b34c 100644 --- a/api/src/controllers/movies.ts +++ b/api/src/controllers/movies.ts @@ -176,7 +176,7 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) params: t.Object({ id: t.String({ description: "The id or slug of the movie to retrieve.", - example: bubble.slug, + examples: [bubble.slug], }), }), query: t.Object({ diff --git a/api/src/controllers/seasons.ts b/api/src/controllers/seasons.ts index f8ef929b..d85e4566 100644 --- a/api/src/controllers/seasons.ts +++ b/api/src/controllers/seasons.ts @@ -1,11 +1,123 @@ import { Elysia, t } from "elysia"; -import { Season } from "../models/season"; +import { Season, SeasonTranslation } from "../models/season"; +import { + AcceptLanguage, + createPage, + Filter, + FilterDef, + isUuid, + keysetPaginate, + Page, + processLanguages, + Sort, + sortToSql, +} from "~/models/utils"; +import { KError } from "~/models/error"; +import { and, eq, sql } from "drizzle-orm"; +import { desc } from "~/models/utils/descriptions"; +import { madeInAbyss } from "~/models/examples"; +import { seasons, seasonTranslations, shows } from "~/db/schema"; +import { db } from "~/db"; +import { getColumns, sqlarr } from "~/db/utils"; -export const seasons = new Elysia({ prefix: "/seasons" }) +const seasonFilters: FilterDef = { + seasonNumber: { column: seasons.seasonNumber, type: "int" }, + startAir: { column: seasons.startAir, type: "date" }, + endAir: { column: seasons.endAir, type: "date" }, +}; + +export const seasonsH = new Elysia({ tags: ["series"] }) .model({ season: Season, - error: t.Object({}), + "season-translation": SeasonTranslation, }) - .get("/:id", () => "hello" as unknown as Season, { - response: { 200: "season" }, - }); + .get( + "/series/:id/seasons", + async ({ + params: { id }, + query: { limit, after, query, sort, filter }, + headers: { "accept-language": languages }, + request: { url }, + }) => { + const langs = processLanguages(languages); + + const show = db.$with("serie").as( + db + .select({ pk: shows.pk }) + .from(shows) + .where( + and( + eq(shows.kind, "serie"), + isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id), + ), + ) + .limit(1), + ); + + const transQ = db + .selectDistinctOn([seasonTranslations.pk]) + .from(seasonTranslations) + .orderBy( + seasonTranslations.pk, + sql`array_position(${sqlarr(langs)}, ${seasonTranslations.language})`, + ) + .as("t"); + const { pk, ...transCol } = getColumns(transQ); + + const items = await db + .with(show) + .select({ + ...getColumns(seasons), + ...transCol, + }) + .from(seasons) + .innerJoin(transQ, eq(seasons.pk, transQ.pk)) + .where( + and( + eq(seasons.showPk, show.pk), + filter, + query ? sql`${transQ.name} %> ${query}::text` : undefined, + keysetPaginate({ table: seasons, after, sort }), + ), + ) + .orderBy( + ...(query + ? [sql`word_similarity(${query}::text, ${transQ.name})`] + : sortToSql(sort, seasons)), + seasons.pk, + ) + .limit(limit); + + return createPage(items, { url, sort, limit }); + }, + { + detail: { description: "Get seasons of a serie" }, + path: t.Object({ + id: t.String({ + description: "The id or slug of the serie.", + examples: [madeInAbyss.slug], + }), + }), + query: t.Object({ + sort: Sort(["seasonNumber", "startAir", "endAir", "nextRefresh"], { + default: ["seasonNumber"], + }), + filter: t.Optional(Filter({ def: seasonFilters })), + query: t.Optional(t.String({ description: desc.query })), + limit: t.Integer({ + minimum: 1, + maximum: 250, + default: 50, + description: "Max page size.", + }), + after: t.Optional(t.String({ description: desc.after })), + }), + headers: t.Object({ + "accept-language": AcceptLanguage({ autoFallback: true }), + }), + response: { + 200: Page(Season), + 422: KError, + }, + }, + ); diff --git a/api/src/index.ts b/api/src/index.ts index e61ad793..5950fd48 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -4,7 +4,7 @@ import { Elysia } from "elysia"; import { base } from "./base"; import { entries } from "./controllers/entries"; import { movies } from "./controllers/movies"; -import { seasons } from "./controllers/seasons"; +import { seasonsH } from "./controllers/seasons"; import { seed } from "./controllers/seed"; import { series } from "./controllers/series"; import { videos } from "./controllers/videos"; @@ -58,6 +58,7 @@ const app = new Elysia() ], tags: [ { name: "movies", description: "Routes about movies" }, + { name: "series", description: "Routes about series" }, { name: "videos", description: comment` @@ -73,7 +74,7 @@ const app = new Elysia() .use(movies) .use(series) .use(entries) - .use(seasons) + .use(seasonsH) .use(videos) .use(seed) .listen(3000);