import { de } from '../strings/de' /** * Basis-URL der GerbilManagerWebAPI. * Dev (Vite dev server / e2e mock): absolute URL, damit Playwright-Route-Interception greift. * Produktion (Vite build → nginx): relativer Pfad /api; nginx proxyt zum API-Container. * Aspire (VITE_API_BASE_URL gesetzt): überschreibt immer (LAN-IP + Port für Handy-Zugriff). */ export const API_BASE_URL: string = import.meta.env.VITE_API_BASE_URL ?? (import.meta.env.PROD ? '/api' : 'http://localhost:5179') export class ApiError extends Error { readonly status: number | null /** The request path that failed (for diagnostics/logging). */ readonly path?: string /** Parsed error response body, if any (e.g. { code, ... } for 400s). */ readonly body?: unknown constructor(message: string, status: number | null, path?: string, body?: unknown) { super(message) this.name = 'ApiError' this.status = status this.path = path this.body = body } } /** Read a `code` field off a parsed ApiError body, if present. */ export function errorCode(err: unknown): string | null { if (err instanceof ApiError && err.body && typeof err.body === 'object' && 'code' in err.body) { const code = (err.body as { code: unknown }).code return typeof code === 'string' ? code : null } return null } function errorMessageFor(status: number): string { if (status === 404) return de.api.errors.notFound if (status >= 500) return de.api.errors.server return de.api.errors.unknown } async function request(path: string, init?: RequestInit): Promise { let response: Response try { response = await fetch(`${API_BASE_URL}${path}`, { headers: { 'Content-Type': 'application/json', ...init?.headers }, ...init, }) } catch { throw new ApiError(de.api.errors.network, null, path) } if (!response.ok) { // Best-effort parse of a JSON error body (e.g. { code: "InvalidParentGender" }). let body: unknown try { body = await response.clone().json() } catch { body = undefined } throw new ApiError(errorMessageFor(response.status), response.status, path, body) } if (response.status === 204) { return undefined as T } return (await response.json()) as T } export const api = { get: (path: string) => request(path), post: (path: string, body: unknown) => request(path, { method: 'POST', body: JSON.stringify(body) }), put: (path: string, body: unknown) => request(path, { method: 'PUT', body: JSON.stringify(body) }), delete: (path: string) => request(path, { method: 'DELETE' }), } /** * Ressourcen-Pfade der API (siehe GerbilManagerWebAPI/Controllers). * Alle drei Ressourcen bieten volle CRUD-Endpunkte: * GET /{resource}, GET /{resource}/{id}, POST, PUT /{id}, DELETE /{id} */ export const resources = { gerbils: '/gerbils', litters: '/litters', contacts: '/contacts', enclosures: '/enclosures', colorVarieties: '/color-varieties', contracts: '/contracts', } as const