Add with=show to /videos/:id

This commit is contained in:
Zoe Roux 2025-07-19 22:08:33 +02:00
parent 899697e1aa
commit 34efb2b534
No known key found for this signature in database
2 changed files with 96 additions and 10 deletions

View File

@ -227,7 +227,6 @@ export const staffH = new Elysia({ tags: ["staff"] })
.from(watchlist) .from(watchlist)
.leftJoin(profiles, eq(watchlist.profilePk, profiles.pk)) .leftJoin(profiles, eq(watchlist.profilePk, profiles.pk))
.where(and(eq(profiles.id, sub), eq(watchlist.showPk, shows.pk))) .where(and(eq(profiles.id, sub), eq(watchlist.showPk, shows.pk)))
.limit(1)
.as("watchstatus"); .as("watchstatus");
const items = await db const items = await db

View File

@ -15,7 +15,15 @@ import { alias } from "drizzle-orm/pg-core";
import { Elysia, t } from "elysia"; import { Elysia, t } from "elysia";
import { auth } from "~/auth"; import { auth } from "~/auth";
import { db, type Transaction } from "~/db"; import { db, type Transaction } from "~/db";
import { entries, entryVideoJoin, shows, videos } from "~/db/schema"; import {
entries,
entryVideoJoin,
profiles,
shows,
showTranslations,
videos,
} from "~/db/schema";
import { watchlist } from "~/db/schema/watchlist";
import { import {
coalesce, coalesce,
conflictUpdateAllExcept, conflictUpdateAllExcept,
@ -30,10 +38,13 @@ import {
import { Entry } from "~/models/entry"; import { Entry } from "~/models/entry";
import { KError } from "~/models/error"; import { KError } from "~/models/error";
import { bubbleVideo } from "~/models/examples"; import { bubbleVideo } from "~/models/examples";
import { Movie, type MovieStatus } from "~/models/movie";
import { Serie, type Serie } from "~/models/serie";
import { import {
AcceptLanguage, AcceptLanguage,
buildRelations, buildRelations,
createPage, createPage,
type Image,
isUuid, isUuid,
keysetPaginate, keysetPaginate,
Page, Page,
@ -44,6 +55,7 @@ import {
} from "~/models/utils"; } from "~/models/utils";
import { desc as description } from "~/models/utils/descriptions"; import { desc as description } from "~/models/utils/descriptions";
import { Guess, Guesses, SeedVideo, Video } from "~/models/video"; import { Guess, Guesses, SeedVideo, Video } from "~/models/video";
import type { MovieWatchStatus, SerieWatchStatus } from "~/models/watchlist";
import { comment } from "~/utils"; import { comment } from "~/utils";
import { import {
entryProgressQ, entryProgressQ,
@ -56,6 +68,7 @@ import {
updateAvailableCount, updateAvailableCount,
updateAvailableSince, updateAvailableSince,
} from "./seed/insert/shows"; } from "./seed/insert/shows";
import { watchStatusQ } from "./shows/logic";
async function linkVideos( async function linkVideos(
tx: Transaction, tx: Transaction,
@ -206,9 +219,10 @@ const videoRelations = {
slugs: () => { slugs: () => {
return db return db
.select({ .select({
slugs: coalesce(jsonbAgg(entryVideoJoin.slug), sql`'[]'::jsonb`).as( slugs: coalesce<string[]>(
"slugs", jsonbAgg(entryVideoJoin.slug),
), sql`'[]'::jsonb`,
).as("slugs"),
}) })
.from(entryVideoJoin) .from(entryVideoJoin)
.where(eq(entryVideoJoin.videoPk, videos.pk)) .where(eq(entryVideoJoin.videoPk, videos.pk))
@ -242,6 +256,72 @@ const videoRelations = {
.where(eq(entryVideoJoin.videoPk, videos.pk)) .where(eq(entryVideoJoin.videoPk, videos.pk))
.as("entries"); .as("entries");
}, },
show: ({
languages,
preferOriginal,
}: {
languages: string[];
preferOriginal: boolean;
}) => {
const transQ = db
.selectDistinctOn([showTranslations.pk])
.from(showTranslations)
.orderBy(
showTranslations.pk,
sql`array_position(${sqlarr(languages)}, ${showTranslations.language})`,
)
.as("t");
const watchStatusQ = db
.select({
watchStatus: jsonbBuildObject<MovieWatchStatus & SerieWatchStatus>({
...getColumns(watchlist),
percent: watchlist.seenCount,
}).as("watchStatus"),
})
.from(watchlist)
.leftJoin(profiles, eq(watchlist.profilePk, profiles.pk))
.where(
and(
eq(profiles.id, sql.placeholder("userId")),
eq(watchlist.showPk, shows.pk),
),
);
return db
.select({
json: jsonbBuildObject<Serie | Movie>({
...getColumns(shows),
...getColumns(transQ),
// movie columns (status is only a typescript hint)
status: sql<MovieStatus>`${shows.status}`,
airDate: shows.startAir,
kind: sql<any>`${shows.kind}`,
isAvailable: sql<boolean>`${shows.availableCount} != 0`,
...(preferOriginal && {
poster: sql<Image>`coalesce(nullif(${shows.original}->'poster', 'null'::jsonb), ${transQ.poster})`,
thumbnail: sql<Image>`coalesce(nullif(${shows.original}->'thumbnail', 'null'::jsonb), ${transQ.thumbnail})`,
banner: sql<Image>`coalesce(nullif(${shows.original}->'banner', 'null'::jsonb), ${transQ.banner})`,
logo: sql<Image>`coalesce(nullif(${shows.original}->'logo', 'null'::jsonb), ${transQ.logo})`,
}),
watchStatus: sql`${watchStatusQ}`,
}).as("json"),
})
.from(shows)
.innerJoin(transQ, eq(shows.pk, transQ.pk))
.where(
eq(
shows.pk,
db
.select({ pk: entries.showPk })
.from(entries)
.innerJoin(entryVideoJoin, eq(entryVideoJoin.entryPk, entries.pk))
.where(eq(videos.pk, entryVideoJoin.videoPk)),
),
)
.as("show");
},
previous: ({ languages }: { languages: string[] }) => { previous: ({ languages }: { languages: string[] }) => {
return getNextVideoEntry({ languages, prev: true }); return getNextVideoEntry({ languages, prev: true });
}, },
@ -263,7 +343,7 @@ function getNextVideoEntry({
const evj = alias(entryVideoJoin, `evj_${prev ? "prev" : "next"}`); const evj = alias(entryVideoJoin, `evj_${prev ? "prev" : "next"}`);
return db return db
.select({ .select({
json: jsonbBuildObject<Entry>({ json: jsonbBuildObject<{ video: string; entry: Entry }>({
video: entryVideoJoin.slug, video: entryVideoJoin.slug,
entry: { entry: {
...getColumns(entries), ...getColumns(entries),
@ -274,7 +354,7 @@ function getNextVideoEntry({
createdAt: sql`to_char(${entries.createdAt}, 'YYYY-MM-DD"T"HH24:MI:SS"Z"')`, createdAt: sql`to_char(${entries.createdAt}, 'YYYY-MM-DD"T"HH24:MI:SS"Z"')`,
updatedAt: sql`to_char(${entries.updatedAt}, 'YYYY-MM-DD"T"HH24:MI:SS"Z"')`, updatedAt: sql`to_char(${entries.updatedAt}, 'YYYY-MM-DD"T"HH24:MI:SS"Z"')`,
}, },
}), }).as("json"),
}) })
.from(entries) .from(entries)
.innerJoin(transQ, eq(entries.pk, transQ.pk)) .innerJoin(transQ, eq(entries.pk, transQ.pk))
@ -337,9 +417,9 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
":id", ":id",
async ({ async ({
params: { id }, params: { id },
query: { with: relations }, query: { with: relations, preferOriginal },
headers: { "accept-language": langs }, headers: { "accept-language": langs },
jwt: { sub }, jwt: { sub, settings },
status, status,
}) => { }) => {
const languages = processLanguages(langs); const languages = processLanguages(langs);
@ -355,6 +435,7 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
videoRelations, videoRelations,
{ {
languages, languages,
preferOriginal: preferOriginal ?? settings.preferOriginal,
}, },
), ),
}) })
@ -382,10 +463,15 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
}), }),
}), }),
query: t.Object({ query: t.Object({
with: t.Array(t.UnionEnum(["previous", "next"]), { with: t.Array(t.UnionEnum(["previous", "next", "show"]), {
default: [], default: [],
description: "Include related entries in the response.", description: "Include related entries in the response.",
}), }),
preferOriginal: t.Optional(
t.Boolean({
description: description.preferOriginal,
}),
),
}), }),
headers: t.Object( headers: t.Object(
{ {
@ -423,6 +509,7 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
}), }),
), ),
), ),
show: t.Optional(t.Union([Movie, Serie])),
}), }),
]), ]),
404: { 404: {