From 587dc4f97099a0445153732c989d0fa89e5461ff Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 19 Dec 2024 16:21:12 +0100 Subject: [PATCH] Add get /movies & sort api --- api/src/controllers/movies.ts | 103 +++++++++++++++++++++++++++++++++- api/src/models/utils/page.ts | 13 +++++ api/src/utils.ts | 5 ++ 3 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 api/src/models/utils/page.ts diff --git a/api/src/controllers/movies.ts b/api/src/controllers/movies.ts index f5b6fbbe..8a5d4220 100644 --- a/api/src/controllers/movies.ts +++ b/api/src/controllers/movies.ts @@ -1,13 +1,14 @@ -import { and, eq, sql } from "drizzle-orm"; +import { and, desc, eq, sql } from "drizzle-orm"; import { Elysia, t } from "elysia"; import { KError } from "~/models/error"; import { isUuid, processLanguages } from "~/models/utils"; -import { comment } from "~/utils"; +import { comment, RemovePrefix } from "~/utils"; import { db } from "../db"; import { shows, showTranslations } from "../db/schema/shows"; import { getColumns } from "../db/schema/utils"; import { bubble } from "../models/examples"; import { Movie, type MovieStatus, MovieTranslation } from "../models/movie"; +import { Page } from "~/models/utils/page"; // drizzle is bugged and doesn't allow js arrays to be used in raw sql. export function sqlarr(array: unknown[]) { @@ -130,4 +131,102 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) }, }, }, + ) + .get( + "", + async ({ + query: { limit, after, sort }, + headers: { "accept-language": languages }, + }) => { + const langs = processLanguages(languages); + const [transQ, transCol] = getTranslationQuery(langs); + const order = sort.map((x) => { + const desc = x[0] === "-"; + const key = (desc ? x.substring(1) : x) as RemovePrefix; + if (key === "airDate") return { key: "startAir" as const, desc }; + return { key, desc }; + }); + + const items = await db + .select({ + ...moviesCol, + ...transCol, + status: sql`${moviesCol.status}`, + airDate: startAir, + }) + .from(shows) + .innerJoin(transQ, eq(shows.pk, transQ.pk)) + .orderBy( + ...order.map((x) => (x.desc ? desc(shows[x.key]) : shows[x.key])), + shows.pk, + ) + .limit(limit); + + return { items, next: "", prev: "", this: "" }; + }, + { + detail: { description: "Get all movies" }, + query: t.Object({ + sort: t.Array( + t.UnionEnum([ + "slug", + "-slug", + "rating", + "-rating", + "airDate", + "-airDate", + "createdAt", + "-createdAt", + "nextRefresh", + "-nextRefresh", + ]), + // TODO: support explode: true (allow sort=slug,-createdAt). needs a pr to elysia + { explode: false, default: ["slug"] }, + ), + limit: t.Integer({ + minimum: 1, + maximum: 250, + default: 50, + description: "Max page size.", + }), + after: t.Optional( + t.String({ + format: "uuid", + description: comment` + Id of the cursor in the pagination. + You can ignore this and only use the prev/next field in the response. + `, + }), + ), + }), + headers: t.Object({ + "accept-language": t.String({ + default: "*", + example: "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). + + In this request, * is always implied (if no language could satisfy the request, kyoo will use any language available). + `, + }), + }), + // response: { + // 200: Page(Movie, { + // description: "Paginated list of movies that match filters.", + // }), + // 422: { + // ...KError, + // description: "Invalid query parameters.", + // examples: [ + // { + // status: 422, + // message: "Accept-Language header could not be satisfied.", + // details: undefined, + // }, + // ], + // }, + // }, + }, ); diff --git a/api/src/models/utils/page.ts b/api/src/models/utils/page.ts new file mode 100644 index 00000000..a41b8504 --- /dev/null +++ b/api/src/models/utils/page.ts @@ -0,0 +1,13 @@ +import type { ObjectOptions } from "@sinclair/typebox"; +import { t, type TSchema } from "elysia"; + +export const Page = (schema: T, options?: ObjectOptions) => + t.Object( + { + items: t.Array(schema), + this: t.String({ format: "uri" }), + prev: t.String({ format: "uri" }), + next: t.String({ format: "uri" }), + }, + options, + ); diff --git a/api/src/utils.ts b/api/src/utils.ts index eefe2e39..1a7ab6e5 100644 --- a/api/src/utils.ts +++ b/api/src/utils.ts @@ -1,3 +1,8 @@ // remove indent in multi-line comments export const comment = (str: TemplateStringsArray, ...values: any[]) => str.reduce((acc, str, i) => `${acc}${str}${values[i]}`).replace(/^\s+/gm, ""); + +export type RemovePrefix< + T extends string, + Prefix extends string, +> = T extends `${Prefix}${infer Ret}` ? Ret : T;