Add ?with=studios in movies & series

This commit is contained in:
Zoe Roux 2025-03-03 16:15:56 +01:00
parent 6cf8947c80
commit 750434465d
No known key found for this signature in database
17 changed files with 1461 additions and 140 deletions

View File

@ -0,0 +1,20 @@
ALTER TABLE "kyoo"."show_studio_join" RENAME COLUMN "show" TO "show_pk";--> statement-breakpoint
ALTER TABLE "kyoo"."show_studio_join" RENAME COLUMN "studio" TO "studio_pk";--> statement-breakpoint
ALTER TABLE "kyoo"."entry_video_join" RENAME COLUMN "entry" TO "entry_pk";--> statement-breakpoint
ALTER TABLE "kyoo"."entry_video_join" RENAME COLUMN "video" TO "video_pk";--> statement-breakpoint
ALTER TABLE "kyoo"."show_studio_join" DROP CONSTRAINT "show_studio_join_show_shows_pk_fk";
--> statement-breakpoint
ALTER TABLE "kyoo"."show_studio_join" DROP CONSTRAINT "show_studio_join_studio_studios_pk_fk";
--> statement-breakpoint
ALTER TABLE "kyoo"."entry_video_join" DROP CONSTRAINT "entry_video_join_entry_entries_pk_fk";
--> statement-breakpoint
ALTER TABLE "kyoo"."entry_video_join" DROP CONSTRAINT "entry_video_join_video_videos_pk_fk";
--> statement-breakpoint
ALTER TABLE "kyoo"."show_studio_join" DROP CONSTRAINT "show_studio_join_show_studio_pk";--> statement-breakpoint
ALTER TABLE "kyoo"."entry_video_join" DROP CONSTRAINT "entry_video_join_entry_video_pk";--> statement-breakpoint
ALTER TABLE "kyoo"."show_studio_join" ADD CONSTRAINT "show_studio_join_show_pk_studio_pk_pk" PRIMARY KEY("show_pk","studio_pk");--> statement-breakpoint
ALTER TABLE "kyoo"."entry_video_join" ADD CONSTRAINT "entry_video_join_entry_pk_video_pk_pk" PRIMARY KEY("entry_pk","video_pk");--> statement-breakpoint
ALTER TABLE "kyoo"."show_studio_join" ADD CONSTRAINT "show_studio_join_show_pk_shows_pk_fk" FOREIGN KEY ("show_pk") REFERENCES "kyoo"."shows"("pk") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "kyoo"."show_studio_join" ADD CONSTRAINT "show_studio_join_studio_pk_studios_pk_fk" FOREIGN KEY ("studio_pk") REFERENCES "kyoo"."studios"("pk") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "kyoo"."entry_video_join" ADD CONSTRAINT "entry_video_join_entry_pk_entries_pk_fk" FOREIGN KEY ("entry_pk") REFERENCES "kyoo"."entries"("pk") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "kyoo"."entry_video_join" ADD CONSTRAINT "entry_video_join_video_pk_videos_pk_fk" FOREIGN KEY ("video_pk") REFERENCES "kyoo"."videos"("pk") ON DELETE cascade ON UPDATE no action;

File diff suppressed because it is too large Load Diff

View File

@ -78,6 +78,13 @@
"when": 1740950531468, "when": 1740950531468,
"tag": "0010_studios", "tag": "0010_studios",
"breakpoints": true "breakpoints": true
},
{
"idx": 11,
"version": "7",
"when": 1741014917375,
"tag": "0011_join_rename",
"breakpoints": true
} }
] ]
} }

View File

@ -141,8 +141,8 @@ export const insertEntries = async (
.select( .select(
db db
.select({ .select({
entry: sql<number>`vids.entryPk::integer`.as("entry"), entryPk: sql<number>`vids.entryPk::integer`.as("entry"),
video: sql`${videos.pk}`.as("video"), videoPk: sql`${videos.pk}`.as("video"),
slug: computeVideoSlug( slug: computeVideoSlug(
sql`${show.slug}::text`, sql`${show.slug}::text`,
sql`vids.needRendering::boolean`, sql`vids.needRendering::boolean`,
@ -154,7 +154,7 @@ export const insertEntries = async (
.onConflictDoNothing() .onConflictDoNothing()
.returning({ .returning({
slug: entryVideoJoin.slug, slug: entryVideoJoin.slug,
entryPk: entryVideoJoin.entry, entryPk: entryVideoJoin.entryPk,
}); });
return retEntries.map((entry) => ({ return retEntries.map((entry) => ({

View File

@ -48,7 +48,7 @@ export const insertStudios = async (seed: SeedStudio[], showPk: number) => {
await tx await tx
.insert(showStudioJoin) .insert(showStudioJoin)
.values(ret.map((studio) => ({ show: showPk, studio: studio.pk }))) .values(ret.map((studio) => ({ showPk: showPk, studioPk: studio.pk })))
.onConflictDoNothing(); .onConflictDoNothing();
return ret; return ret;
}); });

View File

@ -1,7 +1,7 @@
import type { StaticDecode } from "@sinclair/typebox"; import type { StaticDecode } from "@sinclair/typebox";
import { type SQL, and, eq, sql } from "drizzle-orm"; import { type SQL, and, eq, sql } from "drizzle-orm";
import { db } from "~/db"; import { db } from "~/db";
import { showTranslations, shows } from "~/db/schema"; import { showTranslations, shows, studioTranslations } from "~/db/schema";
import { getColumns, sqlarr } from "~/db/utils"; import { getColumns, sqlarr } from "~/db/utils";
import type { MovieStatus } from "~/models/movie"; import type { MovieStatus } from "~/models/movie";
import { SerieStatus } from "~/models/serie"; import { SerieStatus } from "~/models/serie";
@ -12,6 +12,7 @@ import {
Sort, Sort,
isUuid, isUuid,
keysetPaginate, keysetPaginate,
selectTranslationQuery,
sortToSql, sortToSql,
} from "~/models/utils"; } from "~/models/utils";
@ -130,7 +131,7 @@ export async function getShow(
}: { }: {
languages: string[]; languages: string[];
preferOriginal: boolean | undefined; preferOriginal: boolean | undefined;
relations: ("translations" | "videos")[]; relations: ("translations" | "studios" | "videos")[];
filters: SQL | undefined; filters: SQL | undefined;
}, },
) { ) {
@ -141,18 +142,7 @@ export async function getShow(
}, },
where: and(isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id), filters), where: and(isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id), filters),
with: { with: {
selectedTranslation: { selectedTranslation: selectTranslationQuery(showTranslations, languages),
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: { originalTranslation: {
columns: { columns: {
poster: true, poster: true,
@ -175,6 +165,23 @@ export async function getShow(
}, },
}, },
}), }),
...(relations.includes("studios") && {
studios: {
with: {
studio: {
columns: {
pk: false,
},
with: {
selectedTranslation: selectTranslationQuery(
studioTranslations,
languages,
),
},
},
},
},
}),
}, },
}); });
if (!ret) return null; if (!ret) return null;
@ -198,6 +205,12 @@ export async function getShow(
), ),
), ),
}), }),
...(ret.studios && {
studios: ret.studios.map((x: any) => ({
...x.studio,
...x.studio.selectedTranslation[0],
})),
}),
}; };
return { show, language: translation.language }; return { show, language: translation.language };
} }

View File

@ -65,7 +65,7 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
preferOriginal: t.Optional( preferOriginal: t.Optional(
t.Boolean({ description: desc.preferOriginal }), t.Boolean({ description: desc.preferOriginal }),
), ),
with: t.Array(t.UnionEnum(["translations", "videos"]), { with: t.Array(t.UnionEnum(["translations", "studios", "videos"]), {
default: [], default: [],
description: "Include related resources in the response.", description: "Include related resources in the response.",
}), }),

View File

@ -65,7 +65,7 @@ export const series = new Elysia({ prefix: "/series", tags: ["series"] })
preferOriginal: t.Optional( preferOriginal: t.Optional(
t.Boolean({ description: desc.preferOriginal }), t.Boolean({ description: desc.preferOriginal }),
), ),
with: t.Array(t.UnionEnum(["translations"]), { with: t.Array(t.UnionEnum(["translations", "studios"]), {
default: [], default: [],
description: "Include related resources in the response.", description: "Include related resources in the response.",
}), }),

View File

@ -22,6 +22,7 @@ import {
isUuid, isUuid,
keysetPaginate, keysetPaginate,
processLanguages, processLanguages,
selectTranslationQuery,
sortToSql, sortToSql,
} from "~/models/utils"; } from "~/models/utils";
import { desc } from "~/models/utils/descriptions"; import { desc } from "~/models/utils/descriptions";
@ -47,16 +48,10 @@ export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
const ret = await db.query.studios.findFirst({ const ret = await db.query.studios.findFirst({
where: isUuid(id) ? eq(studios.id, id) : eq(studios.slug, id), where: isUuid(id) ? eq(studios.id, id) : eq(studios.slug, id),
with: { with: {
selectedTranslation: { selectedTranslation: selectTranslationQuery(
columns: { pk: false }, studioTranslations,
where: !languages.includes("*") langs,
? eq(studioTranslations.language, sql`any(${sqlarr(langs)})`) ),
: undefined,
orderBy: [
sql`array_position(${sqlarr(langs)}, ${studioTranslations.language})`,
],
limit: 1,
},
...(relations.includes("translations") && { ...(relations.includes("translations") && {
translations: { translations: {
columns: { columns: {
@ -150,7 +145,7 @@ export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
.get( .get(
"", "",
async ({ async ({
query: { limit, after, query, sort, filter }, query: { limit, after, query, sort },
headers: { "accept-language": languages }, headers: { "accept-language": languages },
request: { url }, request: { url },
}) => { }) => {
@ -271,8 +266,8 @@ export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
.from(showStudioJoin) .from(showStudioJoin)
.where( .where(
and( and(
eq(showStudioJoin.studio, studio.pk), eq(showStudioJoin.studioPk, studio.pk),
eq(showStudioJoin.show, shows.pk), eq(showStudioJoin.showPk, shows.pk),
), ),
), ),
), ),
@ -331,8 +326,8 @@ export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
.from(showStudioJoin) .from(showStudioJoin)
.where( .where(
and( and(
eq(showStudioJoin.studio, studio.pk), eq(showStudioJoin.studioPk, studio.pk),
eq(showStudioJoin.show, shows.pk), eq(showStudioJoin.showPk, shows.pk),
), ),
), ),
), ),
@ -391,8 +386,8 @@ export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
.from(showStudioJoin) .from(showStudioJoin)
.where( .where(
and( and(
eq(showStudioJoin.studio, studio.pk), eq(showStudioJoin.studioPk, studio.pk),
eq(showStudioJoin.show, shows.pk), eq(showStudioJoin.showPk, shows.pk),
), ),
), ),
), ),

View File

@ -42,78 +42,77 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
return error(201, oldRet); return error(201, oldRet);
// TODO: this is a huge untested wip // TODO: this is a huge untested wip
// biome-ignore lint/correctness/noUnreachable: leave me alone // const vidsI = db.$with("vidsI").as(
const vidsI = db.$with("vidsI").as( // db.insert(videos).values(body).onConflictDoNothing().returning({
db.insert(videos).values(body).onConflictDoNothing().returning({ // pk: videos.pk,
pk: videos.pk, // id: videos.id,
id: videos.id, // path: videos.path,
path: videos.path, // guess: videos.guess,
guess: videos.guess, // }),
}), // );
); //
// const findEntriesQ = db
const findEntriesQ = db // .select({
.select({ // guess: videos.guess,
guess: videos.guess, // entryPk: entries.pk,
entryPk: entries.pk, // showSlug: shows.slug,
showSlug: shows.slug, // // TODO: handle extras here
// TODO: handle extras here // // guessit can't know if an episode is a special or not. treat specials like a normal episode.
// guessit can't know if an episode is a special or not. treat specials like a normal episode. // kind: sql`
kind: sql` // case when ${entries.kind} = 'movie' then 'movie' else 'episode' end
case when ${entries.kind} = 'movie' then 'movie' else 'episode' end // `.as("kind"),
`.as("kind"), // season: entries.seasonNumber,
season: entries.seasonNumber, // episode: entries.episodeNumber,
episode: entries.episodeNumber, // })
}) // .from(entries)
.from(entries) // .leftJoin(entryVideoJoin, eq(entryVideoJoin.entry, entries.pk))
.leftJoin(entryVideoJoin, eq(entryVideoJoin.entry, entries.pk)) // .leftJoin(videos, eq(videos.pk, entryVideoJoin.video))
.leftJoin(videos, eq(videos.pk, entryVideoJoin.video)) // .leftJoin(shows, eq(shows.pk, entries.showPk))
.leftJoin(shows, eq(shows.pk, entries.showPk)) // .as("find_entries");
.as("find_entries"); //
// const hasRenderingQ = db
const hasRenderingQ = db // .select()
.select() // .from(entryVideoJoin)
.from(entryVideoJoin) // .where(eq(entryVideoJoin.entry, findEntriesQ.entryPk));
.where(eq(entryVideoJoin.entry, findEntriesQ.entryPk)); //
// const ret = await db
const ret = await db // .with(vidsI)
.with(vidsI) // .insert(entryVideoJoin)
.insert(entryVideoJoin) // .select(
.select( // db
db // .select({
.select({ // entry: findEntriesQ.entryPk,
entry: findEntriesQ.entryPk, // video: vidsI.pk,
video: vidsI.pk, // slug: computeVideoSlug(
slug: computeVideoSlug( // findEntriesQ.showSlug,
findEntriesQ.showSlug, // sql`exists(${hasRenderingQ})`,
sql`exists(${hasRenderingQ})`, // ),
), // })
}) // .from(vidsI)
.from(vidsI) // .leftJoin(
.leftJoin( // findEntriesQ,
findEntriesQ, // and(
and( // eq(
eq( // sql`${findEntriesQ.guess}->'title'`,
sql`${findEntriesQ.guess}->'title'`, // sql`${vidsI.guess}->'title'`,
sql`${vidsI.guess}->'title'`, // ),
), // // TODO: find if @> with a jsonb created on the fly is
// TODO: find if @> with a jsonb created on the fly is // // better than multiples checks
// better than multiples checks // sql`${vidsI.guess} @> {"kind": }::jsonb`,
sql`${vidsI.guess} @> {"kind": }::jsonb`, // inArray(findEntriesQ.kind, sql`${vidsI.guess}->'type'`),
inArray(findEntriesQ.kind, sql`${vidsI.guess}->'type'`), // inArray(findEntriesQ.episode, sql`${vidsI.guess}->'episode'`),
inArray(findEntriesQ.episode, sql`${vidsI.guess}->'episode'`), // inArray(findEntriesQ.season, sql`${vidsI.guess}->'season'`),
inArray(findEntriesQ.season, sql`${vidsI.guess}->'season'`), // ),
), // ),
), // )
) // .onConflictDoNothing()
.onConflictDoNothing() // .returning({
.returning({ // slug: entryVideoJoin.slug,
slug: entryVideoJoin.slug, // entryPk: entryVideoJoin.entry,
entryPk: entryVideoJoin.entry, // id: vidsI.id,
id: vidsI.id, // path: vidsI.path,
path: vidsI.path, // });
}); // return error(201, ret as any);
return error(201, ret as any);
}, },
{ {
body: t.Array(SeedVideo), body: t.Array(SeedVideo),

View File

@ -5,7 +5,6 @@ import {
date, date,
index, index,
integer, integer,
jsonb,
primaryKey, primaryKey,
smallint, smallint,
text, text,
@ -15,7 +14,8 @@ import {
} from "drizzle-orm/pg-core"; } from "drizzle-orm/pg-core";
import { entries } from "./entries"; import { entries } from "./entries";
import { seasons } from "./seasons"; import { seasons } from "./seasons";
import { image, language, schema } from "./utils"; import { showStudioJoin } from "./studios";
import { externalid, image, language, schema } from "./utils";
export const showKind = schema.enum("show_kind", [ export const showKind = schema.enum("show_kind", [
"serie", "serie",
@ -54,20 +54,6 @@ export const genres = schema.enum("genres", [
"talk", "talk",
]); ]);
export const externalid = () =>
jsonb()
.$type<
Record<
string,
{
dataId: string;
link: string | null;
}
>
>()
.notNull()
.default({});
export const shows = schema.table( export const shows = schema.table(
"shows", "shows",
{ {
@ -144,6 +130,7 @@ export const showsRelations = relations(shows, ({ many, one }) => ({
}), }),
entries: many(entries, { relationName: "show_entries" }), entries: many(entries, { relationName: "show_entries" }),
seasons: many(seasons, { relationName: "show_seasons" }), seasons: many(seasons, { relationName: "show_seasons" }),
studios: many(showStudioJoin, { relationName: "ssj_show" }),
})); }));
export const showsTrRelations = relations(showTranslations, ({ one }) => ({ export const showsTrRelations = relations(showTranslations, ({ one }) => ({
show: one(shows, { show: one(shows, {

View File

@ -8,8 +8,8 @@ import {
uuid, uuid,
varchar, varchar,
} from "drizzle-orm/pg-core"; } from "drizzle-orm/pg-core";
import { externalid, shows } from "./shows"; import { shows } from "./shows";
import { image, language, schema } from "./utils"; import { externalid, image, language, schema } from "./utils";
export const studios = schema.table("studios", { export const studios = schema.table("studios", {
pk: integer().primaryKey().generatedAlwaysAsIdentity(), pk: integer().primaryKey().generatedAlwaysAsIdentity(),
@ -44,14 +44,14 @@ export const studioTranslations = schema.table(
export const showStudioJoin = schema.table( export const showStudioJoin = schema.table(
"show_studio_join", "show_studio_join",
{ {
show: integer() showPk: integer()
.notNull() .notNull()
.references(() => shows.pk, { onDelete: "cascade" }), .references(() => shows.pk, { onDelete: "cascade" }),
studio: integer() studioPk: integer()
.notNull() .notNull()
.references(() => studios.pk, { onDelete: "cascade" }), .references(() => studios.pk, { onDelete: "cascade" }),
}, },
(t) => [primaryKey({ columns: [t.show, t.studio] })], (t) => [primaryKey({ columns: [t.showPk, t.studioPk] })],
); );
export const studioRelations = relations(studios, ({ many }) => ({ export const studioRelations = relations(studios, ({ many }) => ({
@ -61,7 +61,7 @@ export const studioRelations = relations(studios, ({ many }) => ({
selectedTranslation: many(studioTranslations, { selectedTranslation: many(studioTranslations, {
relationName: "studio_selected_translation", relationName: "studio_selected_translation",
}), }),
showsJoin: many(showStudioJoin, { relationName: "show_studios" }), showsJoin: many(showStudioJoin, { relationName: "ssj_studio" }),
})); }));
export const studioTrRelations = relations(studioTranslations, ({ one }) => ({ export const studioTrRelations = relations(studioTranslations, ({ one }) => ({
studio: one(studios, { studio: one(studios, {
@ -78,12 +78,12 @@ export const studioTrRelations = relations(studioTranslations, ({ one }) => ({
export const ssjRelations = relations(showStudioJoin, ({ one }) => ({ export const ssjRelations = relations(showStudioJoin, ({ one }) => ({
show: one(shows, { show: one(shows, {
relationName: "ssj_show", relationName: "ssj_show",
fields: [showStudioJoin.show], fields: [showStudioJoin.showPk],
references: [shows.pk], references: [shows.pk],
}), }),
studio: one(studios, { studio: one(studios, {
relationName: "ssj_studio", relationName: "ssj_studio",
fields: [showStudioJoin.studio], fields: [showStudioJoin.studioPk],
references: [studios.pk], references: [studios.pk],
}), }),
})); }));

View File

@ -6,3 +6,18 @@ export const language = () => varchar({ length: 255 });
export const image = () => export const image = () =>
jsonb().$type<{ id: string; source: string; blurhash: string }>(); jsonb().$type<{ id: string; source: string; blurhash: string }>();
export const externalid = () =>
jsonb()
.$type<
Record<
string,
{
dataId: string;
link: string | null;
}
>
>()
.notNull()
.default({});

View File

@ -39,15 +39,15 @@ export const videos = schema.table(
export const entryVideoJoin = schema.table( export const entryVideoJoin = schema.table(
"entry_video_join", "entry_video_join",
{ {
entry: integer() entryPk: integer()
.notNull() .notNull()
.references(() => entries.pk, { onDelete: "cascade" }), .references(() => entries.pk, { onDelete: "cascade" }),
video: integer() videoPk: integer()
.notNull() .notNull()
.references(() => videos.pk, { onDelete: "cascade" }), .references(() => videos.pk, { onDelete: "cascade" }),
slug: varchar({ length: 255 }).notNull().unique(), slug: varchar({ length: 255 }).notNull().unique(),
}, },
(t) => [primaryKey({ columns: [t.entry, t.video] })], (t) => [primaryKey({ columns: [t.entryPk, t.videoPk] })],
); );
export const videosRelations = relations(videos, ({ many }) => ({ export const videosRelations = relations(videos, ({ many }) => ({
@ -59,12 +59,12 @@ export const videosRelations = relations(videos, ({ many }) => ({
export const evjRelations = relations(entryVideoJoin, ({ one }) => ({ export const evjRelations = relations(entryVideoJoin, ({ one }) => ({
video: one(videos, { video: one(videos, {
relationName: "evj_video", relationName: "evj_video",
fields: [entryVideoJoin.video], fields: [entryVideoJoin.videoPk],
references: [videos.pk], references: [videos.pk],
}), }),
entry: one(entries, { entry: one(entries, {
relationName: "evj_entry", relationName: "evj_entry",
fields: [entryVideoJoin.entry], fields: [entryVideoJoin.entryPk],
references: [entries.pk], references: [entries.pk],
}), }),
})); }));

View File

@ -2,7 +2,7 @@ import { t } from "elysia";
import type { Prettify } from "~/utils"; import type { Prettify } from "~/utils";
import { SeedCollection } from "./collections"; import { SeedCollection } from "./collections";
import { bubble, bubbleImages, registerExamples } from "./examples"; import { bubble, bubbleImages, registerExamples } from "./examples";
import { SeedStudio } from "./studio"; import { SeedStudio, Studio } from "./studio";
import { import {
DbMetadata, DbMetadata,
ExternalId, ExternalId,
@ -68,6 +68,7 @@ export const FullMovie = t.Intersect([
t.Object({ t.Object({
translations: t.Optional(TranslationRecord(MovieTranslation)), translations: t.Optional(TranslationRecord(MovieTranslation)),
videos: t.Optional(t.Array(Video)), videos: t.Optional(t.Array(Video)),
studios: t.Optional(t.Array(Studio)),
}), }),
]); ]);
export type FullMovie = Prettify<typeof FullMovie.static>; export type FullMovie = Prettify<typeof FullMovie.static>;

View File

@ -4,7 +4,7 @@ import { SeedCollection } from "./collections";
import { SeedEntry, SeedExtra } from "./entry"; import { SeedEntry, SeedExtra } from "./entry";
import { bubbleImages, madeInAbyss, registerExamples } from "./examples"; import { bubbleImages, madeInAbyss, registerExamples } from "./examples";
import { SeedSeason } from "./season"; import { SeedSeason } from "./season";
import { SeedStudio } from "./studio"; import { SeedStudio, Studio } from "./studio";
import { import {
DbMetadata, DbMetadata,
ExternalId, ExternalId,
@ -76,6 +76,7 @@ export const FullSerie = t.Intersect([
Serie, Serie,
t.Object({ t.Object({
translations: t.Optional(TranslationRecord(SerieTranslation)), translations: t.Optional(TranslationRecord(SerieTranslation)),
studios: t.Optional(t.Array(Studio)),
}), }),
]); ]);
export type FullMovie = Prettify<typeof FullSerie.static>; export type FullMovie = Prettify<typeof FullSerie.static>;

View File

@ -4,7 +4,9 @@ import {
type TSchema, type TSchema,
type TString, type TString,
} from "@sinclair/typebox"; } from "@sinclair/typebox";
import { type Column, type Table, eq, sql } from "drizzle-orm";
import { t } from "elysia"; import { t } from "elysia";
import { sqlarr } from "~/db/utils";
import { comment } from "../../utils"; import { comment } from "../../utils";
import { KErrorT } from "../error"; import { KErrorT } from "../error";
@ -106,3 +108,19 @@ export const AcceptLanguage = ({
` `
: ""), : ""),
}); });
export const selectTranslationQuery = (
translationTable: Table & { language: Column },
languages: string[],
) => ({
columns: {
pk: false,
} as const,
where: !languages.includes("*")
? eq(translationTable.language, sql`any(${sqlarr(languages)})`)
: undefined,
orderBy: [
sql`array_position(${sqlarr(languages)}, ${translationTable.language})`,
],
limit: 1,
});