fix(server): store raw TMDB paths in browse upserts to prevent double-resolution

The explore, discover, and search procedures were calling tmdbImageUrl()
on raw TMDB paths before passing them to ensureBrowseTitlesExist() and
ensureBrowsePersonsExist(). This stored already-resolved paths like
/images/posters/abc.jpg in the DB. When getOrFetchTitle() later read
these rows and called tmdbImageUrl() again, the result was a doubled
path (/images/posters/images/posters/abc.jpg) that returned 404.

Fix: pass raw TMDB paths for DB storage, resolve via tmdbImageUrl() only
when building the client response.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 12:00:45 -04:00
co-authored by Claude Opus 4.6
parent 885b594a97
commit 98aa7326d5
3 changed files with 23 additions and 10 deletions
+2 -1
View File
@@ -41,7 +41,7 @@ export const discover = os.discover.use(authed).handler(async ({ input, context
tmdbId: r.id,
type: input.type,
title: r.title ?? r.name ?? "",
posterPath: tmdbImageUrl(r.poster_path ?? null, "posters"),
posterPath: r.poster_path ?? null,
releaseDate: (r.release_date as string | undefined) ?? null,
firstAirDate: (r.first_air_date as string | undefined) ?? null,
voteAverage: r.vote_average ?? null,
@@ -52,6 +52,7 @@ export const discover = os.discover.use(authed).handler(async ({ input, context
const entry = titleMap.get(`${item.tmdbId}-${item.type}`);
return Object.assign(item, {
id: entry?.id ?? "",
posterPath: tmdbImageUrl(item.posterPath, "posters"),
posterThumbHash: entry?.posterThumbHash ?? null,
});
});
+5 -3
View File
@@ -33,7 +33,7 @@ export const trending = os.explore.trending.use(authed).handler(async ({ input,
tmdbId: r.id as number,
type: mediaType as "movie" | "tv",
title: ((r.title ?? r.name) as string) || "",
posterPath: tmdbImageUrl((r.poster_path as string) ?? null, "posters"),
posterPath: (r.poster_path as string) ?? null,
releaseDate: (r.release_date as string | undefined) ?? null,
firstAirDate: (r.first_air_date as string | undefined) ?? null,
voteAverage: (r.vote_average as number | undefined) ?? null,
@@ -52,7 +52,7 @@ export const trending = os.explore.trending.use(authed).handler(async ({ input,
tmdbId: heroResult.id as number,
type: heroResult.media_type as "movie" | "tv",
title: ((heroResult.title ?? heroResult.name) as string | undefined) ?? "",
posterPath: tmdbImageUrl((heroResult.poster_path as string) ?? null, "posters"),
posterPath: (heroResult.poster_path as string) ?? null,
releaseDate: (heroResult.release_date as string | undefined) ?? null,
firstAirDate: (heroResult.first_air_date as string | undefined) ?? null,
voteAverage: (heroResult.vote_average as number | undefined) ?? null,
@@ -66,6 +66,7 @@ export const trending = os.explore.trending.use(authed).handler(async ({ input,
const entry = titleMap.get(`${item.tmdbId}-${item.type}`);
return Object.assign(item, {
id: entry?.id ?? "",
posterPath: tmdbImageUrl(item.posterPath, "posters"),
posterThumbHash: entry?.posterThumbHash ?? null,
});
});
@@ -115,7 +116,7 @@ export const popular = os.explore.popular.use(authed).handler(async ({ input, co
tmdbId: r.id as number,
type: input.type,
title: ((r.title ?? r.name) as string) || "",
posterPath: tmdbImageUrl((r.poster_path as string) ?? null, "posters"),
posterPath: (r.poster_path as string) ?? null,
releaseDate: (r.release_date as string | undefined) ?? null,
firstAirDate: (r.first_air_date as string | undefined) ?? null,
voteAverage: (r.vote_average as number | undefined) ?? null,
@@ -126,6 +127,7 @@ export const popular = os.explore.popular.use(authed).handler(async ({ input, co
const entry = titleMap.get(`${item.tmdbId}-${item.type}`);
return Object.assign(item, {
id: entry?.id ?? "",
posterPath: tmdbImageUrl(item.posterPath, "posters"),
posterThumbHash: entry?.posterThumbHash ?? null,
});
});
+16 -6
View File
@@ -31,7 +31,7 @@ export const search = os.search.use(authed).handler(async ({ input }) => {
type: "person" as const,
title: r.name ?? "",
posterPath: null,
profilePath: tmdbImageUrl(r.profile_path ?? null, "profiles"),
profilePath: r.profile_path ?? null,
overview: null,
releaseDate: null,
popularity: r.popularity ?? null,
@@ -53,7 +53,12 @@ export const search = os.search.use(authed).handler(async ({ input }) => {
})),
);
return {
results: personItems.map((r) => Object.assign(r, { id: personMap.get(r.tmdbId) })),
results: personItems.map((r) =>
Object.assign(r, {
id: personMap.get(r.tmdbId),
profilePath: tmdbImageUrl(r.profilePath, "profiles"),
}),
),
page: personResults.page ?? input.page,
totalPages: personResults.total_pages ?? 1,
totalResults: personResults.total_results ?? 0,
@@ -89,7 +94,7 @@ export const search = os.search.use(authed).handler(async ({ input }) => {
type: "person" as const,
title: r.name ?? "Unknown",
posterPath: null,
profilePath: tmdbImageUrl(r.profile_path ?? null, "profiles"),
profilePath: r.profile_path ?? null,
overview: null,
releaseDate: null,
popularity: r.popularity ?? null,
@@ -108,7 +113,7 @@ export const search = os.search.use(authed).handler(async ({ input }) => {
title: r.title ?? r.name ?? "",
overview: r.overview ?? null,
releaseDate: r.release_date ?? r.first_air_date ?? null,
posterPath: tmdbImageUrl(r.poster_path ?? null, "posters"),
posterPath: r.poster_path ?? null,
profilePath: null,
popularity: r.popularity ?? null,
voteAverage: r.vote_average ?? null,
@@ -137,9 +142,14 @@ export const search = os.search.use(authed).handler(async ({ input }) => {
);
const results = mapped.map((r) => {
if (r.type === "person") return Object.assign(r, { id: personMap.get(r.tmdbId) });
if (r.type === "person") {
return Object.assign(r, {
id: personMap.get(r.tmdbId),
profilePath: tmdbImageUrl(r.profilePath, "profiles"),
});
}
const entry = titleMap.get(`${r.tmdbId}-${r.type}`);
return Object.assign(r, { id: entry?.id });
return Object.assign(r, { id: entry?.id, posterPath: tmdbImageUrl(r.posterPath, "posters") });
});
return {