mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
202 lines
4.9 KiB
TypeScript
202 lines
4.9 KiB
TypeScript
import type { StaticDecode } from "@sinclair/typebox";
|
|
import { type SQL, and, eq, sql } from "drizzle-orm";
|
|
import { db } from "~/db";
|
|
import { showTranslations, shows } from "~/db/schema";
|
|
import { getColumns, sqlarr } from "~/db/utils";
|
|
import type { MovieStatus } from "~/models/movie";
|
|
import { SerieStatus } from "~/models/serie";
|
|
import {
|
|
type FilterDef,
|
|
Genre,
|
|
type Image,
|
|
Sort,
|
|
isUuid,
|
|
keysetPaginate,
|
|
sortToSql,
|
|
} from "~/models/utils";
|
|
|
|
export const showFilters: FilterDef = {
|
|
genres: {
|
|
column: shows.genres,
|
|
type: "enum",
|
|
values: Genre.enum,
|
|
isArray: true,
|
|
},
|
|
rating: { column: shows.rating, type: "int" },
|
|
status: { column: shows.status, type: "enum", values: SerieStatus.enum },
|
|
runtime: { column: shows.runtime, type: "float" },
|
|
airDate: { column: shows.startAir, type: "date" },
|
|
startAir: { column: shows.startAir, type: "date" },
|
|
endAir: { column: shows.startAir, type: "date" },
|
|
originalLanguage: { column: shows.originalLanguage, type: "string" },
|
|
tags: {
|
|
column: sql.raw(`t.${showTranslations.tags.name}`),
|
|
type: "string",
|
|
isArray: true,
|
|
},
|
|
};
|
|
export const showSort = Sort(
|
|
[
|
|
"slug",
|
|
"rating",
|
|
"airDate",
|
|
"startAir",
|
|
"endAir",
|
|
"createdAt",
|
|
"nextRefresh",
|
|
],
|
|
{
|
|
remap: { airDate: "startAir" },
|
|
default: ["slug"],
|
|
},
|
|
);
|
|
|
|
export async function getShows({
|
|
after,
|
|
limit,
|
|
query,
|
|
sort,
|
|
filter,
|
|
languages,
|
|
preferOriginal,
|
|
}: {
|
|
after: string | undefined;
|
|
limit: number;
|
|
query: string | undefined;
|
|
sort: StaticDecode<typeof showSort>;
|
|
filter: SQL | undefined;
|
|
languages: string[];
|
|
preferOriginal: boolean | undefined;
|
|
}) {
|
|
const transQ = db
|
|
.selectDistinctOn([showTranslations.pk])
|
|
.from(showTranslations)
|
|
.orderBy(
|
|
showTranslations.pk,
|
|
sql`array_position(${sqlarr(languages)}, ${showTranslations.language})`,
|
|
)
|
|
.as("t");
|
|
const { pk, poster, thumbnail, banner, logo, ...transCol } =
|
|
getColumns(transQ);
|
|
|
|
return await db
|
|
.select({
|
|
...getColumns(shows),
|
|
...transCol,
|
|
// movie columns (status is only a typescript hint)
|
|
status: sql<MovieStatus>`${shows.status}`,
|
|
airDate: shows.startAir,
|
|
|
|
poster: sql<Image>`coalesce(${showTranslations.poster}, ${poster})`,
|
|
thumbnail: sql<Image>`coalesce(${showTranslations.thumbnail}, ${thumbnail})`,
|
|
banner: sql<Image>`coalesce(${showTranslations.banner}, ${banner})`,
|
|
logo: sql<Image>`coalesce(${showTranslations.logo}, ${logo})`,
|
|
})
|
|
.from(shows)
|
|
.innerJoin(transQ, eq(shows.pk, transQ.pk))
|
|
.leftJoin(
|
|
showTranslations,
|
|
and(
|
|
eq(shows.pk, showTranslations.pk),
|
|
eq(showTranslations.language, shows.originalLanguage),
|
|
// TODO: check user's settings before fallbacking to false.
|
|
sql`coalesce(${preferOriginal ?? null}::boolean, false)`,
|
|
),
|
|
)
|
|
.where(
|
|
and(
|
|
filter,
|
|
query ? sql`${transQ.name} %> ${query}::text` : undefined,
|
|
keysetPaginate({ table: shows, after, sort }),
|
|
),
|
|
)
|
|
.orderBy(
|
|
...(query
|
|
? [sql`word_similarity(${query}::text, ${transQ.name})`]
|
|
: sortToSql(sort, shows)),
|
|
shows.pk,
|
|
)
|
|
.limit(limit);
|
|
}
|
|
|
|
export async function getShow(
|
|
id: string,
|
|
{
|
|
languages,
|
|
preferOriginal,
|
|
relations,
|
|
filters,
|
|
}: {
|
|
languages: string[];
|
|
preferOriginal: boolean | undefined;
|
|
relations: ("translations" | "videos")[];
|
|
filters: SQL | undefined;
|
|
},
|
|
) {
|
|
const ret = await db.query.shows.findFirst({
|
|
extras: {
|
|
airDate: sql<string>`${shows.startAir}`.as("airDate"),
|
|
status: sql<MovieStatus>`${shows.status}`.as("status"),
|
|
},
|
|
where: and(isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id), filters),
|
|
with: {
|
|
selectedTranslation: {
|
|
columns: {
|
|
pk: false,
|
|
},
|
|
where: !languages.includes("*")
|
|
? eq(showTranslations.language, sql`any(${sqlarr(languages)})`)
|
|
: undefined,
|
|
orderBy: [
|
|
sql`array_position(${sqlarr(languages)}, ${showTranslations.language})`,
|
|
],
|
|
limit: 1,
|
|
},
|
|
originalTranslation: {
|
|
columns: {
|
|
poster: true,
|
|
thumbnail: true,
|
|
banner: true,
|
|
logo: true,
|
|
},
|
|
extras: {
|
|
// TODO: also fallback on user settings (that's why i made a select here)
|
|
preferOriginal:
|
|
sql<boolean>`(select coalesce(${preferOriginal ?? null}::boolean, false))`.as(
|
|
"preferOriginal",
|
|
),
|
|
},
|
|
},
|
|
...(relations.includes("translations") && {
|
|
translations: {
|
|
columns: {
|
|
pk: false,
|
|
},
|
|
},
|
|
}),
|
|
},
|
|
});
|
|
if (!ret) return null;
|
|
const translation = ret.selectedTranslation[0];
|
|
if (!translation) return { show: null, language: null };
|
|
const ot = ret.originalTranslation;
|
|
const show = {
|
|
...ret,
|
|
...translation,
|
|
...(ot?.preferOriginal && {
|
|
...(ot.poster && { poster: ot.poster }),
|
|
...(ot.thumbnail && { thumbnail: ot.thumbnail }),
|
|
...(ot.banner && { banner: ot.banner }),
|
|
...(ot.logo && { logo: ot.logo }),
|
|
}),
|
|
...(ret.translations && {
|
|
translations: Object.fromEntries(
|
|
ret.translations.map(
|
|
({ language, ...translation }) => [language, translation] as const,
|
|
),
|
|
),
|
|
}),
|
|
};
|
|
return { show, language: translation.language };
|
|
}
|