diff --git a/api/src/controllers/movies.ts b/api/src/controllers/movies.ts index aa0469356..093ff70c4 100644 --- a/api/src/controllers/movies.ts +++ b/api/src/controllers/movies.ts @@ -7,13 +7,14 @@ import { isUuid, processLanguages, } from "~/models/utils"; -import { comment, type RemovePrefix } from "~/utils"; +import { comment } 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, MovieStatus, MovieTranslation } from "../models/movie"; import { Filter, type Page } from "~/models/utils"; +import { Sort } from "~/models/utils/sort"; // drizzle is bugged and doesn't allow js arrays to be used in raw sql. export function sqlarr(array: unknown[]) { @@ -158,15 +159,8 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) }) => { const langs = processLanguages(languages); const [transQ, transCol] = getTranslationQuery(langs); - // TODO: move this to typebox transform - 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 }; - }); - // TODO: Add sql indexes on order keys + // TODO: Add sql indexes on sort keys const items = await db .select({ @@ -179,7 +173,7 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) .innerJoin(transQ, eq(shows.pk, transQ.pk)) .where(filter) .orderBy( - ...order.map((x) => (x.desc ? desc(shows[x.key]) : shows[x.key])), + ...sort.map((x) => (x.desc ? desc(shows[x.key]) : shows[x.key])), shows.pk, ) .limit(limit); @@ -189,23 +183,17 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) { detail: { description: "Get all movies" }, query: t.Object({ - sort: t.Array( - // TODO: Add random - t.UnionEnum([ + sort: Sort( + [ "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, + // TODO: Add random + remap: { airDate: "startAir" }, default: ["slug"], description: "How to sort the query", }, diff --git a/api/src/models/utils/sort.ts b/api/src/models/utils/sort.ts new file mode 100644 index 000000000..5dcf2c964 --- /dev/null +++ b/api/src/models/utils/sort.ts @@ -0,0 +1,53 @@ +import { t } from "elysia"; + +type Sort< + T extends string[], + Remap extends Partial>, +> = { + key: Exclude | Remap[keyof Remap]; + desc: boolean; +}[]; + +type NonEmptyArray = [T, ...T[]]; + +export const Sort = < + const T extends NonEmptyArray, + const Remap extends Partial>, +>( + values: T, + { + description = "How to sort the query", + default: def, + remap, + }: { + default?: T[number][]; + description: string; + remap: Remap; + }, +) => + t + .Transform( + t.Array( + t.UnionEnum([ + ...values, + ...values.map((x: T[number]) => `-${x}` as const), + ]), + { + // TODO: support explode: true (allow sort=slug,-createdAt). needs a pr to elysia + explode: false, + default: def, + description: description, + }, + ), + ) + .Decode((sort): Sort => { + return sort.map((x) => { + const desc = x[0] === "-"; + const key = (desc ? x.substring(1) : x) as T[number]; + if (key in remap) return { key: remap[key], desc }; + return { key: key as Exclude, desc }; + }); + }) + .Encode(() => { + throw new Error("Encode not supported for sort"); + }); diff --git a/api/src/utils.ts b/api/src/utils.ts index e24a0ad61..00c2f5ab7 100644 --- a/api/src/utils.ts +++ b/api/src/utils.ts @@ -6,8 +6,3 @@ export const comment = (str: TemplateStringsArray, ...values: any[]) => .replace(/^[ \t]+/gm, "") // leading spaces .replace(/([^\n])\n([^\n])/g, "$1 $2") // two lines to space separated line .replace(/\n{2}/g, "\n"); // keep newline if there's an empty line - -export type RemovePrefix< - T extends string, - Prefix extends string, -> = T extends `${Prefix}${infer Ret}` ? Ret : T;