feat(core): use TMDB combined credits for full person filmography

The people.detail endpoint now fetches a person's complete filmography
from TMDB's combined_credits API (cast + crew) instead of only returning
locally-enriched titles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 16:13:43 -04:00
co-authored by Claude Opus 4.6
parent 4ce285f721
commit d0eca7cf32
2 changed files with 37 additions and 9 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
import { ORPCError } from "@orpc/server"; import { ORPCError } from "@orpc/server";
import { import {
getLocalFilmography, fetchFullFilmography,
getOrFetchPerson, getOrFetchPerson,
getOrFetchPersonByTmdbId, getOrFetchPersonByTmdbId,
} from "@sofa/core/person"; } from "@sofa/core/person";
@@ -15,7 +15,7 @@ export const detail = os.people.detail
if (!person) if (!person)
throw new ORPCError("NOT_FOUND", { message: "Person not found" }); throw new ORPCError("NOT_FOUND", { message: "Person not found" });
const filmography = getLocalFilmography(person.id); const filmography = await fetchFullFilmography(person.id);
const userStatuses = getUserStatusesByTitleIds( const userStatuses = getUserStatusesByTitleIds(
context.user.id, context.user.id,
filmography.map((c) => c.titleId), filmography.map((c) => c.titleId),
+35 -7
View File
@@ -217,19 +217,29 @@ export async function fetchFullFilmography(
const credits = await getPersonCombinedCredits(person.tmdbId); const credits = await getPersonCombinedCredits(person.tmdbId);
// Filter to valid cast entries // Schema types combined credits as movie-only; TV entries also carry
// Schema types combined credits cast as movie-only; TV entries also carry
// `name` and `first_air_date` at runtime, so widen the type minimally. // `name` and `first_air_date` at runtime, so widen the type minimally.
type CastEntry = (typeof credits)["cast"] extends (infer E)[] | undefined type CastEntry = (typeof credits)["cast"] extends (infer E)[] | undefined
? E & { name?: string; first_air_date?: string } ? E & { name?: string; first_air_date?: string }
: never; : never;
type CrewEntry = (typeof credits)["crew"] extends (infer E)[] | undefined
? E & { name?: string; first_air_date?: string }
: never;
const validCast = ((credits.cast ?? []) as CastEntry[]).filter( const validCast = ((credits.cast ?? []) as CastEntry[]).filter(
(c) => c.media_type === "movie" || c.media_type === "tv", (c) => c.media_type === "movie" || c.media_type === "tv",
); );
if (validCast.length === 0) return []; const validCrew = ((credits.crew ?? []) as CrewEntry[]).filter(
(c) => c.media_type === "movie" || c.media_type === "tv",
);
if (validCast.length === 0 && validCrew.length === 0) return [];
// Collect all unique TMDB IDs from both cast and crew
const allEntries = [...validCast.map((c) => c), ...validCrew.map((c) => c)];
const tmdbIds = [...new Set(allEntries.map((c) => c.id))];
// Batch prefetch existing titles (1 query) // Batch prefetch existing titles (1 query)
const tmdbIds = [...new Set(validCast.map((c) => c.id))];
const existingTitles = db const existingTitles = db
.select({ id: titles.id, tmdbId: titles.tmdbId }) .select({ id: titles.id, tmdbId: titles.tmdbId })
.from(titles) .from(titles)
@@ -240,11 +250,11 @@ export async function fetchFullFilmography(
); );
// Batch insert missing titles in a transaction // Batch insert missing titles in a transaction
const newCast = validCast.filter((c) => !titleIdMap.has(c.id)); const newEntries = allEntries.filter((c) => !titleIdMap.has(c.id));
if (newCast.length > 0) { if (newEntries.length > 0) {
const insertedTmdbIds = new Set<number>(); const insertedTmdbIds = new Set<number>();
db.transaction((tx) => { db.transaction((tx) => {
for (const c of newCast) { for (const c of newEntries) {
if (insertedTmdbIds.has(c.id)) continue; if (insertedTmdbIds.has(c.id)) continue;
insertedTmdbIds.add(c.id); insertedTmdbIds.add(c.id);
const row = tx const row = tx
@@ -304,6 +314,24 @@ export async function fetchFullFilmography(
job: null, job: null,
}); });
} }
for (const c of validCrew) {
const tid = titleIdMap.get(c.id);
if (!tid) continue;
results.push({
titleId: tid,
tmdbId: c.id,
type: c.media_type as "movie" | "tv",
title: c.title ?? c.name ?? "Unknown",
posterPath: tmdbImageUrl(c.poster_path ?? null, "posters"),
posterThumbHash: null,
releaseDate: c.release_date ?? null,
firstAirDate: c.first_air_date ?? null,
voteAverage: c.vote_average,
character: null,
department: c.department ?? "Crew",
job: c.job ?? null,
});
}
return results; return results;
} }