Stabilize API routing, CORS, and settings save behavior
This commit is contained in:
@@ -97,11 +97,11 @@ async function runApiTests(): Promise<void> {
|
||||
assert(await thumbnail.text() === 'preview-bytes', 'Thumbnail blob bytes mismatch');
|
||||
assert(await preview.text() === 'preview-bytes', 'Preview blob bytes mismatch');
|
||||
assert(
|
||||
requestUrls[0] === '/api/v1/documents/doc-1/thumbnail',
|
||||
requestUrls[0] === 'http://localhost:8000/api/v1/documents/doc-1/thumbnail',
|
||||
`Unexpected thumbnail URL ${requestUrls[0]}`,
|
||||
);
|
||||
assert(
|
||||
requestUrls[1] === '/api/v1/documents/doc-1/preview',
|
||||
requestUrls[1] === 'http://localhost:8000/api/v1/documents/doc-1/preview',
|
||||
`Unexpected preview URL ${requestUrls[1]}`,
|
||||
);
|
||||
assert(requestAuthHeaders[0] === null, `Expected no auth header for thumbnail request, got "${requestAuthHeaders[0]}"`);
|
||||
|
||||
@@ -14,7 +14,7 @@ import type {
|
||||
} from '../types';
|
||||
|
||||
/**
|
||||
* Resolves backend base URL from environment with same-origin proxy fallback.
|
||||
* Resolves backend base URL from environment with host-derived HTTP fallback.
|
||||
*/
|
||||
function resolveApiBase(): string {
|
||||
const envValue = import.meta.env?.VITE_API_BASE;
|
||||
@@ -25,7 +25,10 @@ function resolveApiBase(): string {
|
||||
}
|
||||
}
|
||||
|
||||
return '/api/v1';
|
||||
if (typeof window !== 'undefined' && window.location?.hostname) {
|
||||
return `${window.location.protocol}//${window.location.hostname}:8000/api/v1`;
|
||||
}
|
||||
return 'http://localhost:8000/api/v1';
|
||||
}
|
||||
|
||||
const API_BASE = resolveApiBase();
|
||||
@@ -54,6 +57,8 @@ let runtimeTokenResolver: ApiTokenResolver | null = null;
|
||||
|
||||
type ApiRequestInit = Omit<RequestInit, 'headers'> & { headers?: HeadersInit };
|
||||
|
||||
type ApiErrorPayload = { detail?: string } | null;
|
||||
|
||||
/**
|
||||
* Normalizes candidate token values by trimming whitespace and filtering non-string values.
|
||||
*/
|
||||
@@ -163,6 +168,21 @@ function apiRequest(input: string, init: ApiRequestInit = {}): Promise<Response>
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts backend error detail text from JSON payloads when available.
|
||||
*/
|
||||
async function responseErrorDetail(response: Response): Promise<string> {
|
||||
try {
|
||||
const payload = (await response.json()) as ApiErrorPayload;
|
||||
if (payload && typeof payload.detail === 'string' && payload.detail.trim()) {
|
||||
return payload.detail.trim();
|
||||
}
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes query parameters while skipping undefined and null values.
|
||||
*/
|
||||
@@ -607,7 +627,8 @@ export async function updateAppSettings(payload: AppSettingsUpdate): Promise<App
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to update settings');
|
||||
const detail = await responseErrorDetail(response);
|
||||
throw new Error(detail ? `Failed to update settings: ${detail}` : 'Failed to update settings');
|
||||
}
|
||||
return response.json() as Promise<AppSettings>;
|
||||
}
|
||||
|
||||
@@ -10,11 +10,5 @@ export default defineConfig({
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/api/v1': {
|
||||
target: process.env.VITE_DEV_PROXY_TARGET ?? 'http://localhost:8000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user