mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-30 19:54:16 -04:00
Rework /studios/:id to use relational query
This commit is contained in:
parent
34926dab51
commit
4b46963eff
@ -89,7 +89,7 @@ const showRelations = {
|
|||||||
.from(studioTranslations)
|
.from(studioTranslations)
|
||||||
.orderBy(
|
.orderBy(
|
||||||
studioTranslations.pk,
|
studioTranslations.pk,
|
||||||
sql`array_position(${sqlarr(languages)}, ${studioTranslations.language}`,
|
sql`array_position(${sqlarr(languages)}, ${studioTranslations.language})`,
|
||||||
)
|
)
|
||||||
.as("t");
|
.as("t");
|
||||||
const { pk, language, ...studioTrans } = getColumns(studioTransQ);
|
const { pk, language, ...studioTrans } = getColumns(studioTransQ);
|
||||||
@ -158,13 +158,11 @@ export async function getShows({
|
|||||||
sql`array_position(${sqlarr(languages)}, ${showTranslations.language})`,
|
sql`array_position(${sqlarr(languages)}, ${showTranslations.language})`,
|
||||||
)
|
)
|
||||||
.as("t");
|
.as("t");
|
||||||
const { pk, ...transCol } = getColumns(transQ);
|
|
||||||
|
|
||||||
return await db
|
return await db
|
||||||
.select({
|
.select({
|
||||||
...getColumns(shows),
|
...getColumns(shows),
|
||||||
...transCol,
|
...getColumns(transQ),
|
||||||
lanugage: transQ.language,
|
|
||||||
|
|
||||||
// movie columns (status is only a typescript hint)
|
// movie columns (status is only a typescript hint)
|
||||||
status: sql<MovieStatus>`${shows.status}`,
|
status: sql<MovieStatus>`${shows.status}`,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { and, eq, exists, sql } from "drizzle-orm";
|
import type { StaticDecode } from "@sinclair/typebox";
|
||||||
|
import { type SQL, and, eq, exists, sql } from "drizzle-orm";
|
||||||
import Elysia, { t } from "elysia";
|
import Elysia, { t } from "elysia";
|
||||||
import { db } from "~/db";
|
import { db } from "~/db";
|
||||||
import {
|
import {
|
||||||
@ -7,7 +8,12 @@ import {
|
|||||||
studioTranslations,
|
studioTranslations,
|
||||||
studios,
|
studios,
|
||||||
} from "~/db/schema";
|
} from "~/db/schema";
|
||||||
import { getColumns, sqlarr } from "~/db/utils";
|
import {
|
||||||
|
getColumns,
|
||||||
|
jsonbBuildObject,
|
||||||
|
jsonbObjectAgg,
|
||||||
|
sqlarr,
|
||||||
|
} from "~/db/utils";
|
||||||
import { KError } from "~/models/error";
|
import { KError } from "~/models/error";
|
||||||
import { Movie } from "~/models/movie";
|
import { Movie } from "~/models/movie";
|
||||||
import { Serie } from "~/models/serie";
|
import { Serie } from "~/models/serie";
|
||||||
@ -18,11 +24,11 @@ import {
|
|||||||
Filter,
|
Filter,
|
||||||
Page,
|
Page,
|
||||||
Sort,
|
Sort,
|
||||||
|
buildRelations,
|
||||||
createPage,
|
createPage,
|
||||||
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";
|
||||||
@ -30,6 +36,83 @@ import { getShows, showFilters, showSort } from "./shows/logic";
|
|||||||
|
|
||||||
const studioSort = Sort(["slug", "createdAt"], { default: ["slug"] });
|
const studioSort = Sort(["slug", "createdAt"], { default: ["slug"] });
|
||||||
|
|
||||||
|
const studioRelations = {
|
||||||
|
translations: () => {
|
||||||
|
const { pk, language, ...trans } = getColumns(studioTranslations);
|
||||||
|
return db
|
||||||
|
.select({
|
||||||
|
json: jsonbObjectAgg(
|
||||||
|
language,
|
||||||
|
jsonbBuildObject<StudioTranslation>(trans),
|
||||||
|
).as("json"),
|
||||||
|
})
|
||||||
|
.from(studioTranslations)
|
||||||
|
.where(eq(studioTranslations.pk, shows.pk))
|
||||||
|
.as("translations");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getStudios({
|
||||||
|
after,
|
||||||
|
limit,
|
||||||
|
query,
|
||||||
|
sort,
|
||||||
|
filter,
|
||||||
|
languages,
|
||||||
|
fallbackLanguage = true,
|
||||||
|
relations = [],
|
||||||
|
}: {
|
||||||
|
after?: string;
|
||||||
|
limit: number;
|
||||||
|
query?: string;
|
||||||
|
sort?: StaticDecode<typeof studioSort>;
|
||||||
|
filter?: SQL;
|
||||||
|
languages: string[];
|
||||||
|
fallbackLanguage?: boolean;
|
||||||
|
preferOriginal?: boolean;
|
||||||
|
relations?: (keyof typeof studioRelations)[];
|
||||||
|
}) {
|
||||||
|
const transQ = db
|
||||||
|
.selectDistinctOn([studioTranslations.pk])
|
||||||
|
.from(studioTranslations)
|
||||||
|
.where(
|
||||||
|
!fallbackLanguage
|
||||||
|
? eq(studioTranslations.language, sql`any(${sqlarr(languages)})`)
|
||||||
|
: undefined,
|
||||||
|
)
|
||||||
|
.orderBy(
|
||||||
|
studioTranslations.pk,
|
||||||
|
sql`array_position(${sqlarr(languages)}, ${studioTranslations.language})`,
|
||||||
|
)
|
||||||
|
.as("t");
|
||||||
|
|
||||||
|
return await db
|
||||||
|
.select({
|
||||||
|
...getColumns(studios),
|
||||||
|
...getColumns(transQ),
|
||||||
|
...buildRelations(relations, studioRelations),
|
||||||
|
})
|
||||||
|
.from(studios)
|
||||||
|
[fallbackLanguage ? "innerJoin" : ("leftJoin" as "innerJoin")](
|
||||||
|
transQ,
|
||||||
|
eq(studios.pk, transQ.pk),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
filter,
|
||||||
|
query ? sql`${transQ.name} %> ${query}::text` : undefined,
|
||||||
|
keysetPaginate({ table: studios, after, sort }),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.orderBy(
|
||||||
|
...(query
|
||||||
|
? [sql`word_similarity(${query}::text, ${transQ.name})`]
|
||||||
|
: sortToSql(sort, studios)),
|
||||||
|
studios.pk,
|
||||||
|
)
|
||||||
|
.limit(limit);
|
||||||
|
}
|
||||||
|
|
||||||
export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
|
export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
|
||||||
.model({
|
.model({
|
||||||
studio: Studio,
|
studio: Studio,
|
||||||
@ -45,21 +128,12 @@ export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
|
|||||||
set,
|
set,
|
||||||
}) => {
|
}) => {
|
||||||
const langs = processLanguages(languages);
|
const langs = processLanguages(languages);
|
||||||
const ret = await db.query.studios.findFirst({
|
const [ret] = await getStudios({
|
||||||
where: isUuid(id) ? eq(studios.id, id) : eq(studios.slug, id),
|
limit: 1,
|
||||||
with: {
|
filter: isUuid(id) ? eq(studios.id, id) : eq(studios.slug, id),
|
||||||
selectedTranslation: selectTranslationQuery(
|
languages: langs,
|
||||||
studioTranslations,
|
fallbackLanguage: langs.includes("*"),
|
||||||
langs,
|
relations,
|
||||||
),
|
|
||||||
...(relations.includes("translations") && {
|
|
||||||
translations: {
|
|
||||||
columns: {
|
|
||||||
pk: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
return error(404, {
|
return error(404, {
|
||||||
@ -67,20 +141,14 @@ export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
|
|||||||
message: `No studio with the id or slug: '${id}'`,
|
message: `No studio with the id or slug: '${id}'`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const tr = ret.selectedTranslation[0];
|
if (!ret.language) {
|
||||||
set.headers["content-language"] = tr.language;
|
return error(422, {
|
||||||
return {
|
status: 422,
|
||||||
...ret,
|
message: "Accept-Language header could not be satisfied.",
|
||||||
...tr,
|
});
|
||||||
...(ret.translations && {
|
}
|
||||||
translations: Object.fromEntries(
|
set.headers["content-language"] = ret.language;
|
||||||
ret.translations.map(
|
return ret;
|
||||||
({ language, ...translation }) =>
|
|
||||||
[language, translation] as const,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
detail: {
|
detail: {
|
||||||
@ -150,35 +218,13 @@ export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
|
|||||||
request: { url },
|
request: { url },
|
||||||
}) => {
|
}) => {
|
||||||
const langs = processLanguages(languages);
|
const langs = processLanguages(languages);
|
||||||
const transQ = db
|
const items = await getStudios({
|
||||||
.selectDistinctOn([studioTranslations.pk])
|
limit,
|
||||||
.from(studioTranslations)
|
after,
|
||||||
.orderBy(
|
query,
|
||||||
studioTranslations.pk,
|
sort,
|
||||||
sql`array_position(${sqlarr(langs)}, ${studioTranslations.language}`,
|
languages: langs,
|
||||||
)
|
});
|
||||||
.as("t");
|
|
||||||
const { pk, ...transCol } = getColumns(transQ);
|
|
||||||
|
|
||||||
const items = await db
|
|
||||||
.select({
|
|
||||||
...getColumns(studios),
|
|
||||||
...transCol,
|
|
||||||
})
|
|
||||||
.from(studios)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
query ? sql`${transQ.name} %> ${query}::text` : undefined,
|
|
||||||
keysetPaginate({ table: studios, after, sort }),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.orderBy(
|
|
||||||
...(query
|
|
||||||
? [sql`word_similarity(${query}::text, ${transQ.name})`]
|
|
||||||
: sortToSql(sort, studios)),
|
|
||||||
studios.pk,
|
|
||||||
)
|
|
||||||
.limit(limit);
|
|
||||||
return createPage(items, { url, sort, limit });
|
return createPage(items, { url, sort, limit });
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -12,6 +12,7 @@ export const StudioTranslation = t.Object({
|
|||||||
name: t.String(),
|
name: t.String(),
|
||||||
logo: t.Nullable(Image),
|
logo: t.Nullable(Image),
|
||||||
});
|
});
|
||||||
|
export type StudioTranslation = typeof StudioTranslation.static;
|
||||||
|
|
||||||
export const Studio = t.Intersect([
|
export const Studio = t.Intersect([
|
||||||
Resource(),
|
Resource(),
|
||||||
|
@ -107,19 +107,3 @@ 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,
|
|
||||||
});
|
|
||||||
|
@ -5,7 +5,7 @@ import { Language } from "./language";
|
|||||||
export const Original = t.Object({
|
export const Original = t.Object({
|
||||||
language: Language({
|
language: Language({
|
||||||
description: "The language code this was made in.",
|
description: "The language code this was made in.",
|
||||||
examples: ["ja"]
|
examples: ["ja"],
|
||||||
}),
|
}),
|
||||||
name: t.String({
|
name: t.String({
|
||||||
description: "The name in the original language",
|
description: "The name in the original language",
|
||||||
|
@ -8,12 +8,12 @@ export const buildRelations = <
|
|||||||
>(
|
>(
|
||||||
enabled: R[],
|
enabled: R[],
|
||||||
relations: Rel,
|
relations: Rel,
|
||||||
params: P,
|
params?: P,
|
||||||
) => {
|
) => {
|
||||||
// we wrap that in a sql`` instead of using the builder because of this issue
|
// we wrap that in a sql`` instead of using the builder because of this issue
|
||||||
// https://github.com/drizzle-team/drizzle-orm/pull/1674
|
// https://github.com/drizzle-team/drizzle-orm/pull/1674
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
enabled.map((x) => [x, sql`${relations[x](params)}`]),
|
enabled.map((x) => [x, sql`${relations[x](params!)}`]),
|
||||||
) as {
|
) as {
|
||||||
[P in R]?: SQL<
|
[P in R]?: SQL<
|
||||||
ReturnType<Rel[P]>["_"]["selectedFields"] extends {
|
ReturnType<Rel[P]>["_"]["selectedFields"] extends {
|
||||||
|
@ -11,7 +11,7 @@ beforeAll(async () => {
|
|||||||
await db.delete(shows);
|
await db.delete(shows);
|
||||||
await db.insert(videos).values(bubbleVideo);
|
await db.insert(videos).values(bubbleVideo);
|
||||||
const [ret, body] = await createMovie(bubble);
|
const [ret, body] = await createMovie(bubble);
|
||||||
expect(ret.status).toBe(201)
|
expect(ret.status).toBe(201);
|
||||||
bubbleId = body.id;
|
bubbleId = body.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user