From 0b38bc6ca510b7e2a1956c0569cf1e87fa93541e Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Sivakumar Date: Wed, 15 Jul 2026 01:17:44 +0530 Subject: [PATCH] fix(api): stop routing through a nonexistent /api/gateway rewrite layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The K8s ingress for this domain routes any /api/* path directly to the Rust gateway service — there is no rewrite/proxy layer in production that collapses /api/gateway/* down to /api/*. That rewrite only exists as a dev-only vite proxy, so lib/api.ts's request()/apiFetch() (used by Save Changes, Submit for Verification, document upload, wallet, jobs, marketplace, portfolio, and more) were 404ing on every call in production with "Route not found in gateway". ProfilePage.tsx and VerificationStatusPage.tsx each had their own local apiFetch with the same assumption, doubly broken (paths already included /api/, prefixed again). All three now hit the already-fully-qualified /api/... path directly, matching how the working signup/login calls have always done it. Co-Authored-By: Claude Sonnet 5 --- src/components/dashboard/ProfilePage.tsx | 7 +++++-- .../dashboard/VerificationStatusPage.tsx | 7 ++++--- src/lib/api.ts | 15 ++++++++------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/components/dashboard/ProfilePage.tsx b/src/components/dashboard/ProfilePage.tsx index 2e4e297..4f46b95 100644 --- a/src/components/dashboard/ProfilePage.tsx +++ b/src/components/dashboard/ProfilePage.tsx @@ -29,7 +29,10 @@ import { type DocField, } from "~/lib/profile-fields-config"; -const API = "/api"; +// The K8s ingress routes /api/* directly to the Rust gateway service in +// production — there is no /api/gateway rewrite layer, so paths below are +// hit as-is (each apiFetch() call already passes a fully-qualified /api/... path). +const API = ""; const PORTFOLIO_PREFIX: Record = { PHOTOGRAPHER: "photographers", @@ -800,7 +803,7 @@ export default function ProfilePage(props: Props) { : ""; const fd = new FormData(); fd.append("file", file); - const res = await fetch(`${API}/profile/photo`, { + const res = await fetch(`${API}/api/profile/photo`, { method: "POST", credentials: "include", headers: token ? { Authorization: `Bearer ${token}` } : {}, diff --git a/src/components/dashboard/VerificationStatusPage.tsx b/src/components/dashboard/VerificationStatusPage.tsx index c9b881c..721fc69 100644 --- a/src/components/dashboard/VerificationStatusPage.tsx +++ b/src/components/dashboard/VerificationStatusPage.tsx @@ -9,19 +9,20 @@ import { ShieldCheck, FileText, Activity } from 'lucide-solid'; import { CARD, BTN_ORANGE, BTN_GHOST } from '~/components/DashboardShell'; import { getDocFields } from '~/lib/profile-fields-config'; -const API = '/api/gateway'; const NAVY = '#0D0D2A'; const ORANGE = '#FF5E13'; type TabKey = 'approval_status' | 'documents' | 'activity'; +// The K8s ingress routes /api/* directly to the Rust gateway service in +// production — there is no /api/gateway rewrite layer, so paths (already +// fully-qualified /api/... from callers) are hit as-is. async function apiFetch(path: string, opts?: RequestInit) { const token = typeof window !== "undefined" ? window.sessionStorage.getItem("nxtgauge_access_token") || "" : ""; - const cleanPath = path.startsWith("/api/") ? path.slice(4) : path; - return fetch(`${API}${cleanPath}`, { + return fetch(path, { ...opts, credentials: "include", headers: { diff --git a/src/lib/api.ts b/src/lib/api.ts index a7f31c9..2339a2c 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,4 +1,7 @@ -const API = '/api/gateway'; +// The K8s ingress routes any /api/* path directly to the Rust gateway service — +// there is no /api/gateway indirection or rewrite in production (a dev-only +// vite proxy used to do that rewrite, but it doesn't exist in the deployed +// build). Callers already pass fully-qualified /api/... paths; hit them as-is. function getAuthHeaders(): Record { const token = typeof window !== 'undefined' @@ -12,8 +15,7 @@ function getAuthHeaders(): Record { } export async function apiFetch(path: string, options?: RequestInit): Promise { - const cleanPath = path.startsWith('/api/') ? path.slice(4) : path; - const res = await fetch(`${API}${cleanPath}`, { + const res = await fetch(path, { headers: getAuthHeaders(), credentials: 'include', ...options, @@ -47,8 +49,7 @@ export async function request( if (options?.body !== undefined) { init.body = JSON.stringify(options.body); } - const cleanPath = path.startsWith('/api/') ? path.slice(4) : path; - const res = await fetch(`${API}${cleanPath}`, init); + const res = await fetch(path, init); const raw = await res.text(); const data = raw ? JSON.parse(raw) : null; if (!res.ok) { @@ -223,7 +224,7 @@ export async function uploadDocument(rolePrefix: string, file: File, documentTyp ? (sessionStorage.getItem('nxtgauge_access_token') || '') : ''; - const res = await fetch(`${API}/api/${rolePrefix}/profile/documents`, { + const res = await fetch(`/api/${rolePrefix}/profile/documents`, { method: 'POST', headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}), @@ -257,7 +258,7 @@ export async function submitCompanyProfileWithDocuments( // Append a stable filename so the backend can derive document_type from the stem fd.append('documents', file, `${documentType}_${file.name}`); } - const res = await fetch(`${API}/api/companies/profile/submit-with-documents`, { + const res = await fetch(`/api/companies/profile/submit-with-documents`, { method: 'POST', headers: token ? { Authorization: `Bearer ${token}` } : {}, credentials: 'include',