mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-10-15 19:45:42 -04:00
fix(tmdb): keep the /3 prefix for custom base URLs and trailing slashes
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
4 files changed
+69
-12
No files matched your search
@@ -1,8 +1,9 @@
|
||||
import { access, constants, readdir } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
import { CACHE_DIR, DATA_DIR, DATABASE_URL, TMDB_API_BASE_URL } from "@sofa/config";
|
||||
import { CACHE_DIR, DATA_DIR, DATABASE_URL } from "@sofa/config";
|
||||
import { getLatestCronRuns, getTableCounts } from "@sofa/db/queries/system-health";
|
||||
import { tmdbApiUrl } from "@sofa/tmdb/config";
|
||||
|
||||
import { listBackups } from "./backup";
|
||||
import { imageCacheEnabled } from "./image-cache";
|
||||
@@ -98,7 +99,7 @@ async function getTmdbHealth(): Promise<SystemHealthData["tmdb"]> {
|
||||
|
||||
try {
|
||||
const start = performance.now();
|
||||
const res = await fetch(`${TMDB_API_BASE_URL}/configuration`, {
|
||||
const res = await fetch(tmdbApiUrl("/configuration"), {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: "application/json",
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { resolveTmdbBase, tmdbApiUrl } from "@sofa/tmdb/config";
|
||||
|
||||
describe("resolveTmdbBase", () => {
|
||||
test.each([
|
||||
[undefined, "https://api.themoviedb.org", false],
|
||||
["", "https://api.themoviedb.org", false],
|
||||
["https://api.themoviedb.org/3", "https://api.themoviedb.org", false],
|
||||
["https://api.themoviedb.org/3/", "https://api.themoviedb.org", false],
|
||||
["https://proxy.example/3", "https://proxy.example", false],
|
||||
["https://proxy.example/tmdb/3/", "https://proxy.example/tmdb", false],
|
||||
["https://tmdb.internal", "https://tmdb.internal", true],
|
||||
["https://tmdb.internal/", "https://tmdb.internal", true],
|
||||
["https://proxy.example/v3", "https://proxy.example/v3", true],
|
||||
])("resolves %j", (input, baseUrl, stripVersionPrefix) => {
|
||||
expect(resolveTmdbBase(input)).toEqual({ baseUrl, stripVersionPrefix });
|
||||
});
|
||||
});
|
||||
|
||||
describe("tmdbApiUrl", () => {
|
||||
test.each([
|
||||
[undefined, "https://api.themoviedb.org/3/configuration"],
|
||||
["", "https://api.themoviedb.org/3/configuration"],
|
||||
["https://proxy.example/3/", "https://proxy.example/3/configuration"],
|
||||
["https://tmdb.internal", "https://tmdb.internal/configuration"],
|
||||
])("builds the URL for %j", (input, expected) => {
|
||||
// An explicit undefined would fall back to process.env.TMDB_API_BASE_URL,
|
||||
// so use "" (treated as unset) to stay independent of the environment.
|
||||
expect(tmdbApiUrl("/configuration", input ?? "")).toBe(expected);
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import createClient, { type Middleware } from "openapi-fetch";
|
||||
|
||||
import { createLogger } from "@sofa/logger";
|
||||
|
||||
import { resolveTmdbBase } from "./config";
|
||||
import type { operations, paths } from "./schema";
|
||||
|
||||
const log = createLogger("tmdb");
|
||||
@@ -103,18 +104,13 @@ function getApiKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
// The default TMDB base URL ends with /3, matching the schema's /3/… paths.
|
||||
// Custom proxy URLs (e.g. https://tmdb.internal) may omit /3 — those worked
|
||||
// before because the old client built paths like /search/multi against the
|
||||
// custom base. To preserve that, we always strip /3 from the configured URL
|
||||
// and use the schema paths as-is (they already include /3/).
|
||||
const DEFAULT_TMDB_BASE = "https://api.themoviedb.org/3";
|
||||
const configuredBase = process.env.TMDB_API_BASE_URL || DEFAULT_TMDB_BASE;
|
||||
const isCustomBase = configuredBase !== DEFAULT_TMDB_BASE;
|
||||
// Schema paths start with /3/…. resolveTmdbBase decides whether the configured
|
||||
// base already carries /3 (keep paths as-is) or is a proxy mounted at the API
|
||||
// root (strip /3 from each request path). See ./config.
|
||||
const TMDB_VERSION_PREFIX_RE = /^\/3\//;
|
||||
const baseUrl = configuredBase.replace(/\/3\/?$/, "");
|
||||
const { baseUrl, stripVersionPrefix } = resolveTmdbBase(process.env.TMDB_API_BASE_URL);
|
||||
|
||||
const baseUrlRewriteMiddleware: Middleware | null = isCustomBase
|
||||
const baseUrlRewriteMiddleware: Middleware | null = stripVersionPrefix
|
||||
? {
|
||||
async onRequest({ request }) {
|
||||
// Custom proxy: strip the /3 prefix from schema paths so requests
|
||||
|
||||
@@ -6,3 +6,31 @@
|
||||
export function isTmdbConfigured(): boolean {
|
||||
return !!process.env.TMDB_API_READ_ACCESS_TOKEN?.trim();
|
||||
}
|
||||
|
||||
export const DEFAULT_TMDB_API_BASE_URL = "https://api.themoviedb.org/3";
|
||||
|
||||
/**
|
||||
* Work out how to reach the TMDB API from TMDB_API_BASE_URL. Schema paths
|
||||
* already start with `/3/`, so:
|
||||
* - a base ending in `/3` (the default, or a proxy mirroring TMDB's layout)
|
||||
* drops `/3` from the base and uses schema paths as-is;
|
||||
* - a base without `/3` (a proxy mounted at the API root, e.g.
|
||||
* https://tmdb.internal) keeps the base and strips `/3` from each path.
|
||||
* Trailing slashes are ignored.
|
||||
*/
|
||||
export function resolveTmdbBase(configured?: string): {
|
||||
baseUrl: string;
|
||||
stripVersionPrefix: boolean;
|
||||
} {
|
||||
const base = (configured?.trim() || DEFAULT_TMDB_API_BASE_URL).replace(/\/+$/, "");
|
||||
if (base.endsWith("/3")) {
|
||||
return { baseUrl: base.slice(0, -2), stripVersionPrefix: false };
|
||||
}
|
||||
return { baseUrl: base, stripVersionPrefix: true };
|
||||
}
|
||||
|
||||
/** Absolute URL for a TMDB API path such as "/configuration". */
|
||||
export function tmdbApiUrl(path: string, configured = process.env.TMDB_API_BASE_URL): string {
|
||||
const { baseUrl, stripVersionPrefix } = resolveTmdbBase(configured);
|
||||
return `${baseUrl}${stripVersionPrefix ? "" : "/3"}${path}`;
|
||||
}
|
||||
Reference in new issue
Block a user