/** * HTTP-Client: Token, Mandanten-Header, Fehler-Mapping. * Alle API-Aufrufe laufen über request() — siehe utils/api.js (Facade) und Domänenmodule (planning.js, exercises.js). */ export const API_URL = import.meta.env.VITE_API_URL || '' /** LocalStorage + Request-Header für Mandanten-Kontext */ export const ACTIVE_CLUB_STORAGE_KEY = 'shinkan_active_club_id' export function mergeActiveClubHeader(headers = {}) { const cid = localStorage.getItem(ACTIVE_CLUB_STORAGE_KEY) if (cid && /^\d+$/.test(String(cid).trim())) { return { ...headers, 'X-Active-Club-Id': String(cid).trim() } } return { ...headers } } /** * Generischer API-Aufruf inkl. X-Auth-Token und X-Active-Club-Id. */ export async function request(endpoint, options = {}) { const token = localStorage.getItem('authToken') const method = (options.method || 'GET').toUpperCase() const headers = mergeActiveClubHeader({ ...options.headers, }) if (method !== 'GET' && method !== 'HEAD') { if (!headers['Content-Type'] && !headers['content-type']) { headers['Content-Type'] = 'application/json' } } if (token) { headers['X-Auth-Token'] = token } const url = `${API_URL}${endpoint}` try { const response = await fetch(url, { ...options, headers, }) if (!response.ok) { const text = await response.text() let parsed = null try { parsed = JSON.parse(text) } catch { parsed = null } if (parsed?.detail != null) { const d = parsed.detail throw new Error(typeof d === 'string' ? d : JSON.stringify(d)) } if (response.status === 502) { throw new Error( 'HTTP 502 (Bad Gateway): Der Reverse-Proxy hat die API nicht korrekt erreicht. Ist `shinkan-api` aktiv (`docker compose ps`, `docker logs shinkan-api`)? Bei Host-Routing nur einen Weg verwenden — alles auf Port 3003 (Nginx nach `backend:8000`) oder sauber `/api` → Backend-Port.' ) } const snippet = (text || '').replace(/\s+/g, ' ').trim().slice(0, 180) throw new Error(snippet ? `HTTP ${response.status}: ${snippet}` : `HTTP ${response.status}`) } return response.json() } catch (e) { if (e instanceof TypeError && (e.message === 'Failed to fetch' || e.message.includes('fetch'))) { const hint = API_URL && API_URL.length > 0 ? `Verbindung zum API unter ${API_URL} fehlgeschlagen. Läuft das Backend (z. B. Port 8098) und ist CORS erlaubt?` : 'Kein VITE_API_URL gesetzt: Anfragen gehen an die Frontend-URL und schlagen oft fehl. Setze in .env z. B. VITE_API_URL=http://localhost:8098 und starte Vite neu.' throw new Error(`${hint} [Technisch: ${e.message}; URL war ${endpoint}]`) } throw e } }