mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Rewrite /movies/{id} to use the relational api
This commit is contained in:
parent
5ca1b19148
commit
f3f69a0def
@ -23,5 +23,7 @@ export const base = new Elysia({ name: "base" })
|
|||||||
details: error,
|
details: error,
|
||||||
} as KError;
|
} as KError;
|
||||||
}
|
}
|
||||||
|
console.error(code, error)
|
||||||
|
return error;
|
||||||
})
|
})
|
||||||
.as("plugin");
|
.as("plugin");
|
||||||
|
@ -3,10 +3,10 @@ import { Elysia, t } from "elysia";
|
|||||||
import { KError } from "~/models/error";
|
import { KError } from "~/models/error";
|
||||||
import { comment } from "~/utils";
|
import { comment } from "~/utils";
|
||||||
import { db } from "../db";
|
import { db } from "../db";
|
||||||
import { shows, showTranslations } from "../db/schema/shows";
|
import { shows, showTranslations } from "~/db/schema";
|
||||||
import { getColumns } from "../db/schema/utils";
|
import { getColumns, sqlarr } from "~/db/schema/utils";
|
||||||
import { bubble } from "../models/examples";
|
import { bubble } from "~/models/examples";
|
||||||
import { Movie, MovieStatus, MovieTranslation } from "../models/movie";
|
import { Movie, MovieStatus, MovieTranslation } from "~/models/movie";
|
||||||
import {
|
import {
|
||||||
Filter,
|
Filter,
|
||||||
type Image,
|
type Image,
|
||||||
@ -20,34 +20,6 @@ import {
|
|||||||
createPage,
|
createPage,
|
||||||
} from "~/models/utils";
|
} from "~/models/utils";
|
||||||
|
|
||||||
// drizzle is bugged and doesn't allow js arrays to be used in raw sql.
|
|
||||||
export function sqlarr(array: unknown[]) {
|
|
||||||
return `{${array.map((item) => `"${item}"`).join(",")}}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTranslationQuery = (languages: string[], forceFallback = false) => {
|
|
||||||
const fallback = forceFallback || languages.includes("*");
|
|
||||||
const query = db
|
|
||||||
.selectDistinctOn([showTranslations.pk])
|
|
||||||
.from(showTranslations)
|
|
||||||
.where(
|
|
||||||
fallback
|
|
||||||
? undefined
|
|
||||||
: eq(showTranslations.language, sql`any(${sqlarr(languages)})`),
|
|
||||||
)
|
|
||||||
.orderBy(
|
|
||||||
showTranslations.pk,
|
|
||||||
sql`array_position(${sqlarr(languages)}, ${showTranslations.language})`,
|
|
||||||
)
|
|
||||||
.as("t");
|
|
||||||
|
|
||||||
const { pk, ...col } = getColumns(query);
|
|
||||||
return [query, col] as const;
|
|
||||||
};
|
|
||||||
|
|
||||||
// we keep the pk for after handling. it will be removed by elysia's validators after.
|
|
||||||
const { kind, startAir, endAir, ...moviesCol } = getColumns(shows);
|
|
||||||
|
|
||||||
const movieFilters: FilterDef = {
|
const movieFilters: FilterDef = {
|
||||||
genres: {
|
genres: {
|
||||||
column: shows.genres,
|
column: shows.genres,
|
||||||
@ -72,25 +44,39 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
|
|||||||
async ({
|
async ({
|
||||||
params: { id },
|
params: { id },
|
||||||
headers: { "accept-language": languages },
|
headers: { "accept-language": languages },
|
||||||
|
query: { preferOriginal },
|
||||||
error,
|
error,
|
||||||
set,
|
set,
|
||||||
}) => {
|
}) => {
|
||||||
const langs = processLanguages(languages);
|
const langs = processLanguages(languages);
|
||||||
const [transQ, transCol] = getTranslationQuery(langs);
|
|
||||||
|
|
||||||
const idFilter = isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id);
|
const ret = await db.query.shows.findFirst({
|
||||||
|
columns: {
|
||||||
const [ret] = await db
|
kind: false,
|
||||||
.select({
|
startAir: false,
|
||||||
...moviesCol,
|
endAir: false,
|
||||||
status: sql<MovieStatus>`${moviesCol.status}`,
|
},
|
||||||
airDate: startAir,
|
extras: {
|
||||||
translation: transCol,
|
airDate: sql<string>`${shows.startAir}`.as("airDate"),
|
||||||
})
|
status: sql<MovieStatus>`${shows.status}`.as("status"),
|
||||||
.from(shows)
|
},
|
||||||
.leftJoin(transQ, eq(shows.pk, transQ.pk))
|
where: and(
|
||||||
.where(and(eq(shows.kind, "movie"), idFilter))
|
eq(shows.kind, "movie"),
|
||||||
.limit(1);
|
isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id),
|
||||||
|
),
|
||||||
|
with: {
|
||||||
|
translations: {
|
||||||
|
columns: {
|
||||||
|
pk: false,
|
||||||
|
},
|
||||||
|
where: eq(showTranslations.language, sql`any(${sqlarr(langs)})`),
|
||||||
|
orderBy: [
|
||||||
|
sql`array_position(${sqlarr(langs)}, ${showTranslations.language})`,
|
||||||
|
],
|
||||||
|
limit: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
return error(404, {
|
return error(404, {
|
||||||
@ -98,14 +84,15 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
|
|||||||
message: "Movie not found",
|
message: "Movie not found",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!ret.translation) {
|
const translation = ret.translations[0];
|
||||||
|
if (!translation) {
|
||||||
return error(422, {
|
return error(422, {
|
||||||
status: 422,
|
status: 422,
|
||||||
message: "Accept-Language header could not be satisfied.",
|
message: "Accept-Language header could not be satisfied.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
set.headers["content-language"] = ret.translation.language;
|
set.headers["content-language"] = translation.language;
|
||||||
return { ...ret, ...ret.translation };
|
return { ...ret, ...translation };
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
detail: {
|
detail: {
|
||||||
@ -117,6 +104,17 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
|
|||||||
example: bubble.slug,
|
example: bubble.slug,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
query: t.Object({
|
||||||
|
preferOriginal: t.Optional(
|
||||||
|
t.Boolean({
|
||||||
|
description: comment`
|
||||||
|
Prefer images in the original's language. If true, will return untranslated images instead of the translated ones.
|
||||||
|
|
||||||
|
If unspecified, kyoo will look at the current user's settings to decide what to do.
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
headers: t.Object({
|
headers: t.Object({
|
||||||
"accept-language": t.String({
|
"accept-language": t.String({
|
||||||
default: "*",
|
default: "*",
|
||||||
@ -197,11 +195,13 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
|
|||||||
}) => {
|
}) => {
|
||||||
const langs = processLanguages(languages);
|
const langs = processLanguages(languages);
|
||||||
|
|
||||||
|
// we keep the pk for after handling. it will be removed by elysia's validators after.
|
||||||
|
const { kind, startAir, endAir, ...moviesCol } = getColumns(shows);
|
||||||
|
|
||||||
const transQ = db
|
const transQ = db
|
||||||
.selectDistinctOn([showTranslations.pk])
|
.select()
|
||||||
.from(showTranslations)
|
.from(showTranslations)
|
||||||
.orderBy(
|
.orderBy(
|
||||||
showTranslations.pk,
|
|
||||||
sql`array_position(${sqlarr(langs)}, ${showTranslations.language})`,
|
sql`array_position(${sqlarr(langs)}, ${showTranslations.language})`,
|
||||||
)
|
)
|
||||||
.as("t");
|
.as("t");
|
||||||
@ -214,10 +214,10 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
|
|||||||
...transCol,
|
...transCol,
|
||||||
status: sql<MovieStatus>`${moviesCol.status}`,
|
status: sql<MovieStatus>`${moviesCol.status}`,
|
||||||
airDate: startAir,
|
airDate: startAir,
|
||||||
poster: sql<Image>`coalese(${showTranslations.poster}, ${poster})`,
|
poster: sql<Image>`coalesce(${showTranslations.poster}, ${poster})`,
|
||||||
thumbnail: sql<Image>`coalese(${showTranslations.thumbnail}, ${thumbnail})`,
|
thumbnail: sql<Image>`coalesce(${showTranslations.thumbnail}, ${thumbnail})`,
|
||||||
banner: sql<Image>`coalese(${showTranslations.banner}, ${banner})`,
|
banner: sql<Image>`coalesce(${showTranslations.banner}, ${banner})`,
|
||||||
logo: sql<Image>`coalese(${showTranslations.logo}, ${logo})`,
|
logo: sql<Image>`coalesce(${showTranslations.logo}, ${logo})`,
|
||||||
})
|
})
|
||||||
.from(shows)
|
.from(shows)
|
||||||
.innerJoin(transQ, eq(shows.pk, transQ.pk))
|
.innerJoin(transQ, eq(shows.pk, transQ.pk))
|
||||||
@ -227,7 +227,7 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
|
|||||||
eq(shows.pk, showTranslations.pk),
|
eq(shows.pk, showTranslations.pk),
|
||||||
eq(showTranslations.language, shows.originalLanguage),
|
eq(showTranslations.language, shows.originalLanguage),
|
||||||
// TODO: check user's settings before fallbacking to false.
|
// TODO: check user's settings before fallbacking to false.
|
||||||
sql`coalese(${preferOriginal}, false)`,
|
sql`coalesce(${preferOriginal ?? null}::boolean, false)`,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.where(and(filter, keysetPaginate({ table: shows, after, sort })))
|
.where(and(filter, keysetPaginate({ table: shows, after, sort })))
|
||||||
@ -267,7 +267,15 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
|
|||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
preferOriginal: t.Optional(t.Boolean()),
|
preferOriginal: t.Optional(
|
||||||
|
t.Boolean({
|
||||||
|
description: comment`
|
||||||
|
Prefer images in the original's language. If true, will return untranslated images instead of the translated ones.
|
||||||
|
|
||||||
|
If unspecified, kyoo will look at the current user's settings to decide what to do.
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
headers: t.Object({
|
headers: t.Object({
|
||||||
"accept-language": t.String({
|
"accept-language": t.String({
|
||||||
|
@ -77,3 +77,8 @@ export function conflictUpdateAllExcept<
|
|||||||
{} as Omit<Record<keyof T["_"]["columns"], SQL>, E[number]>,
|
{} as Omit<Record<keyof T["_"]["columns"], SQL>, E[number]>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// drizzle is bugged and doesn't allow js arrays to be used in raw sql.
|
||||||
|
export function sqlarr(array: unknown[]) {
|
||||||
|
return `{${array.map((item) => `"${item}"`).join(",")}}`;
|
||||||
|
}
|
||||||
|
@ -19,8 +19,22 @@ export const bubble: SeedMovie = {
|
|||||||
tagline: "Is she a calamity or a blessing?",
|
tagline: "Is she a calamity or a blessing?",
|
||||||
description:
|
description:
|
||||||
"In an abandoned Tokyo overrun by bubbles and gravitational abnormalities, one gifted young man has a fateful meeting with a mysterious girl.",
|
"In an abandoned Tokyo overrun by bubbles and gravitational abnormalities, one gifted young man has a fateful meeting with a mysterious girl.",
|
||||||
aliases: ["Baburu", "バブル:2022", "Bubble"],
|
aliases: ["Baburu", "Bubble"],
|
||||||
tags: ["adolescence", "disaster", "battle", "gravity", "anime"],
|
tags: ["adolescence", "disaster", "battle", "gravity", "anime"],
|
||||||
|
poster:
|
||||||
|
"https://image.tmdb.org/t/p/original/kk28Lk8oQBGjoHRGUCN2vxKb4O2.jpg",
|
||||||
|
thumbnail:
|
||||||
|
"https://image.tmdb.org/t/p/original/a8Q2g0g7XzAF6gcB8qgn37ccb9Y.jpg",
|
||||||
|
banner: null,
|
||||||
|
logo: null,
|
||||||
|
trailerUrl: "https://www.youtube.com/watch?v=vs7zsyIZkMM",
|
||||||
|
},
|
||||||
|
jp: {
|
||||||
|
name: "バブル:2022",
|
||||||
|
tagline: null,
|
||||||
|
description: null,
|
||||||
|
aliases: ["Baburu", "Bubble"],
|
||||||
|
tags: ["アニメ"],
|
||||||
poster:
|
poster:
|
||||||
"https://image.tmdb.org/t/p/original/65dad96VE8FJPEdrAkhdsuWMWH9.jpg",
|
"https://image.tmdb.org/t/p/original/65dad96VE8FJPEdrAkhdsuWMWH9.jpg",
|
||||||
thumbnail:
|
thumbnail:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user