feat(web): series detail metadata header

This commit is contained in:
Miguel Palhas
2026-08-24 00:37:53 +01:00
parent b413718fd1
commit ed210b29d3
5 changed files with 265 additions and 75 deletions
+41
View File
@@ -2,6 +2,7 @@
// release schemas — same reasoning as search.ts: the generated client
// (src/api/) is uncommitted, so CI's tsc cannot see it.
import type { MetadataCastMember, MetadataTrailer } from "./movie";
import type { ActionOutcome, MovieRelease, ReleasesOutcome, WaiveOutcome } from "./releases";
import { errorDetail, probedAttributeTags, waiverOverride } from "./releases";
@@ -239,6 +240,46 @@ export async function waiveAndGrabTv(
return { kind: "done", overrideWritten };
}
/* ---- §9.6 rich detail --------------------------------------------------- */
/** Rich detail for one library series' §9.6 page (#146). */
export interface SeriesMetadata {
tmdb_id: number;
overview: string | null;
tagline: string | null;
genres: string[];
/** Minutes per episode. */
runtime: number | null;
status: string;
poster_path: string | null;
backdrop_path: string | null;
/** `null` when TMDB has no votes for the title (#156). */
vote_average: number | null;
vote_count: number;
homepage: string | null;
/** §9.6 links out to TVDB for series; absent when the id is unknown. */
tvdb_id: number | null;
cast: MetadataCastMember[];
trailer: MetadataTrailer | null;
}
export type SeriesMetadataOutcome =
| { kind: "metadata"; metadata: SeriesMetadata }
| { kind: "error"; detail: string };
/** One request for everything above the season tree (§9.6). */
export async function seriesMetadata(seriesId: number): Promise<SeriesMetadataOutcome> {
try {
const response = await fetch(`/api/series/${seriesId}/metadata`);
if (!response.ok) {
return { kind: "error", detail: await errorDetail(response) };
}
return { kind: "metadata", metadata: (await response.json()) as SeriesMetadata };
} catch {
return { kind: "error", detail: "daemon unreachable" };
}
}
/* ---- display helpers --------------------------------------------------- */
/** Wanted / on-disk counts for one season, whatever its number. */