mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-07-14 18:15:56 -04:00
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:
@@ -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),
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user