Add with=translations in the sql builder logic

This commit is contained in:
Zoe Roux 2025-03-09 01:16:50 +01:00
parent 3f77a1bda5
commit c161d680e3
No known key found for this signature in database
7 changed files with 104 additions and 11 deletions

View File

@ -2,7 +2,12 @@ import type { StaticDecode } from "@sinclair/typebox";
import { type SQL, and, eq, sql } from "drizzle-orm";
import { db } from "~/db";
import { showTranslations, shows, studioTranslations } from "~/db/schema";
import { getColumns, sqlarr } from "~/db/utils";
import {
getColumns,
jsonbBuildObject,
jsonbObjectAgg,
sqlarr,
} from "~/db/utils";
import type { MovieStatus } from "~/models/movie";
import { SerieStatus } from "~/models/serie";
import {
@ -55,6 +60,16 @@ export const showSort = Sort(
},
);
const buildRelations = <R extends string>(
relations: R[],
toSql: (relation: R) => SQL,
) => {
return Object.fromEntries(relations.map((x) => [x, toSql(x)])) as Record<
R,
SQL
>;
};
export async function getShows({
after,
limit,
@ -64,15 +79,17 @@ export async function getShows({
languages,
fallbackLanguage = true,
preferOriginal = false,
relations = [],
}: {
after: string | undefined;
after?: string;
limit: number;
query: string | undefined;
sort: StaticDecode<typeof showSort>;
filter: SQL | undefined;
query?: string;
sort?: StaticDecode<typeof showSort>;
filter?: SQL;
languages: string[];
fallbackLanguage?: boolean;
preferOriginal?: boolean;
relations?: ("translations" | "studios" | "videos")[];
}) {
const transQ = db
.selectDistinctOn([showTranslations.pk])
@ -89,6 +106,22 @@ export async function getShows({
.as("t");
const { pk, ...transCol } = getColumns(transQ);
const relationsSql = buildRelations(relations, (x) => {
switch (x) {
case "studios":
case "videos":
case "translations": {
// we wrap that in a sql`` instead of using the builder because of this issue
// https://github.com/drizzle-team/drizzle-orm/pull/1674
const { pk, language, ...trans } = getColumns(showTranslations);
return sql`${db
.select({ json: jsonbObjectAgg(language, jsonbBuildObject(trans)) })
.from(showTranslations)
.where(eq(showTranslations.pk, shows.pk))}`;
}
}
});
return await db
.select({
...getColumns(shows),
@ -107,6 +140,8 @@ export async function getShows({
banner: sql<Image>`coalesce(nullif(${shows.original}->'banner', 'null'::jsonb), ${transQ.banner})`,
logo: sql<Image>`coalesce(nullif(${shows.original}->'logo', 'null'::jsonb), ${transQ.logo})`,
}),
...relationsSql,
})
.from(shows)
[fallbackLanguage ? "innerJoin" : "leftJoin"](

View File

@ -32,6 +32,7 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
}) => {
const langs = processLanguages(languages);
const [ret] = await getShows({
limit: 1,
filter: and(
isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id),
eq(shows.kind, "movie"),

View File

@ -10,6 +10,7 @@ import {
Filter,
Page,
createPage,
isUuid,
processLanguages,
} from "~/models/utils";
import { desc } from "~/models/utils/descriptions";
@ -30,11 +31,16 @@ export const series = new Elysia({ prefix: "/series", tags: ["series"] })
set,
}) => {
const langs = processLanguages(languages);
const ret = await getShow(id, {
const [ret] = await getShows({
limit: 1,
filter: and(
isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id),
eq(shows.kind, "serie"),
),
languages: langs,
fallbackLanguage: langs.includes("*"),
preferOriginal,
relations,
filters: eq(shows.kind, "serie"),
});
if (!ret) {
return error(404, {
@ -49,7 +55,7 @@ export const series = new Elysia({ prefix: "/series", tags: ["series"] })
});
}
set.headers["content-language"] = ret.language;
return ret.show;
return ret;
},
{
detail: {

View File

@ -1,6 +1,7 @@
import {
type ColumnsSelection,
type SQL,
type SQLWrapper,
type Subquery,
Table,
View,
@ -92,3 +93,17 @@ export function values(items: Record<string, unknown>[]) {
},
};
}
export const jsonbObjectAgg = (key: SQLWrapper, value: SQLWrapper) => {
return sql`jsonb_object_agg(${sql.join([key, value], sql.raw(","))})`;
};
export const jsonbBuildObject = (select: Record<string, SQLWrapper>) => {
const query = sql.join(
Object.entries(select).flatMap(([k, v]) => {
return [sql.raw(`'${k}'`), v];
}),
sql.raw(", "),
);
return sql`jsonb_build_object(${query})`;
};

View File

@ -60,6 +60,40 @@ export const madeInAbyss = {
banner: null,
trailerUrl: "https://www.youtube.com/watch?v=ePOyy6Wlk4s",
},
ja: {
name: "メイドインアビス",
tagline: "さぁ 大穴(アビス)へ――",
aliases: ["烈日の黄金郷"],
description:
"隅々まで探索されつくした世界に、唯一残された秘境の大穴『アビス』。どこまで続くとも知れない深く巨大なその縦穴には、奇妙奇怪な生物たちが生息し、今の人類では作りえない貴重な遺物が眠っている。「アビス」の不可思議に満ちた姿は人々を魅了し、冒険へと駆り立てた。そうして幾度も大穴に挑戦する冒険者たちは、次第に『探窟家』と呼ばれるようになっていった。 アビスの縁に築かれた街『オース』に暮らす孤児のリコは、いつか母のような偉大な探窟家になり、アビスの謎を解き明かすことを夢見ていた。そんなある日、リコはアビスを探窟中に、少年の姿をしたロボットを拾い…?",
tags: [
"android",
"amnesia",
"post-apocalyptic future",
"exploration",
"friendship",
"mecha",
"survival",
"curse",
"tragedy",
"orphan",
"based on manga",
"robot",
"dark fantasy",
"seinen",
"anime",
"drastic change of life",
"fantasy",
"adventure",
],
poster:
"https://image.tmdb.org/t/p/original/4Bh9qzB1Kau4RDaVQXVFdoJ0HcE.jpg",
thumbnail:
"https://image.tmdb.org/t/p/original/Df9XrvZFIeQfLKfu8evRmzvRsd.jpg",
logo: "https://image.tmdb.org/t/p/original/7hY3Q4GhkiYPBfn4UoVg0AO4Zgk.png",
banner: null,
trailerUrl: "https://www.youtube.com/watch?v=ePOyy6Wlk4s",
},
},
genres: [
"animation",

View File

@ -26,9 +26,9 @@ export const keysetPaginate = <
}: {
table: Table<"pk" | Sort<T, Remap>["sort"][number]["key"]>;
after: string | undefined;
sort: Sort<T, Remap>;
sort: Sort<T, Remap> | undefined;
}) => {
if (!after) return undefined;
if (!after || !sort) return undefined;
const cursor: After = JSON.parse(
Buffer.from(after, "base64").toString("utf-8"),
);

View File

@ -1,7 +1,7 @@
import { db, migrate } from "~/db";
import { shows, videos } from "~/db/schema";
import { madeInAbyss, madeInAbyssVideo } from "~/models/examples";
import { createSerie, createVideo } from "./helpers";
import { createSerie, createVideo, getSerie } from "./helpers";
// test file used to run manually using `bun tests/manual.ts`
@ -13,3 +13,5 @@ const [_, vid] = await createVideo(madeInAbyssVideo);
console.log(vid);
const [__, ser] = await createSerie(madeInAbyss);
console.log(ser);
const [___, got] = await getSerie(madeInAbyss.slug, { with: ["translations"] });
console.log(got);