From 643ce8376c59c256aa8fbb6554c94604346f816c Mon Sep 17 00:00:00 2001 From: Godopu Date: Tue, 25 Aug 2026 14:45:41 +0900 Subject: [PATCH] fix(api): ensure /api/v1 prefix normalization across all custom host URLs - Add normalizeApiUrl() to ensure custom host URLs (e.g. http://puclouds-2:8080) automatically append /api/v1 - Fix 404 error when querying /stats/summary instead of /api/v1/stats/summary --- refer_landing_page/lib/adminApi.ts | 24 +++++++++++++++++++----- refer_landing_page/lib/api.ts | 24 +++++++++++++++++++----- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/refer_landing_page/lib/adminApi.ts b/refer_landing_page/lib/adminApi.ts index f911eaf..021a804 100644 --- a/refer_landing_page/lib/adminApi.ts +++ b/refer_landing_page/lib/adminApi.ts @@ -3,17 +3,31 @@ * Preserves database IDs and handles authenticated mutating requests (POST, PUT, DELETE). */ +export function normalizeApiUrl(rawUrl?: string): string { + let url = (rawUrl || "").trim().replace(/\/+$/, ""); + if (!url) { + return "/api/v1"; + } + if (!url.endsWith("/api/v1")) { + url = `${url}/api/v1`; + } + return url; +} + export function getAdminApiBaseUrl(): string { if (typeof window !== "undefined") { - // Browser / CSR context: same-origin relative path by default. - return process.env.NEXT_PUBLIC_API_BASE_URL || "/api/v1"; + // Browser / CSR context: + if (process.env.NEXT_PUBLIC_API_BASE_URL) { + return normalizeApiUrl(process.env.NEXT_PUBLIC_API_BASE_URL); + } + return "/api/v1"; } // Server / build-time context: defensive fallback during static prerendering - return ( + const raw = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL || - "http://localhost:8080/api/v1" - ); + "http://localhost:8080/api/v1"; + return normalizeApiUrl(raw); } export interface AdminResearchProject { diff --git a/refer_landing_page/lib/api.ts b/refer_landing_page/lib/api.ts index e6f02cd..e439384 100644 --- a/refer_landing_page/lib/api.ts +++ b/refer_landing_page/lib/api.ts @@ -10,17 +10,31 @@ import type { DocStatus, } from "../content/types"; +export function normalizeApiUrl(rawUrl?: string): string { + let url = (rawUrl || "").trim().replace(/\/+$/, ""); + if (!url) { + return "/api/v1"; + } + if (!url.endsWith("/api/v1")) { + url = `${url}/api/v1`; + } + return url; +} + export function getApiBaseUrl(): string { if (typeof window !== "undefined") { - // Browser / CSR context: same-origin relative path by default. - return process.env.NEXT_PUBLIC_API_BASE_URL || "/api/v1"; + // Browser / CSR context: + if (process.env.NEXT_PUBLIC_API_BASE_URL) { + return normalizeApiUrl(process.env.NEXT_PUBLIC_API_BASE_URL); + } + return "/api/v1"; } // Server / build-time context: defensive fallback during static prerendering - return ( + const raw = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL || - "http://localhost:8080/api/v1" - ); + "http://localhost:8080/api/v1"; + return normalizeApiUrl(raw); } const FETCH_TIMEOUT_MS = 3000;