From 8f5af49a6f7b8a06ac018fd53a33484eede499d5 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 14 May 2026 21:31:04 +0200 Subject: [PATCH] chore(version): update version and changelog for release 0.8.133 - Bumped APP_VERSION to 0.8.133 and updated the changelog to reflect recent changes. - Initiated Frontend Phase 4 Welle 1, introducing a centralized HTTP client in `frontend/src/api/client.js` while maintaining `utils/api.js` as a facade. - Documented the changes in the architecture roadmap and README for clarity on the new API structure. --- backend/version.py | 9 ++- docs/architecture/README.md | 1 + docs/architecture/UMSETZUNGSPLAN_ROADMAP.md | 13 ++-- frontend/src/api/client.js | 79 +++++++++++++++++++++ frontend/src/utils/api.js | 79 +-------------------- 5 files changed, 98 insertions(+), 83 deletions(-) create mode 100644 frontend/src/api/client.js diff --git a/backend/version.py b/backend/version.py index 6bc2182..1f9f247 100644 --- a/backend/version.py +++ b/backend/version.py @@ -1,6 +1,6 @@ # Shinkan Jinkendo Version Information -APP_VERSION = "0.8.132" +APP_VERSION = "0.8.133" BUILD_DATE = "2026-05-12" DB_SCHEMA_VERSION = "20260514062" @@ -36,6 +36,13 @@ MODULE_VERSIONS = { } CHANGELOG = [ + { + "version": "0.8.133", + "date": "2026-05-14", + "changes": [ + "Frontend Phase 4 Welle 1: frontend/src/api/client.js (request, ACTIVE_CLUB_STORAGE_KEY); utils/api.js importiert Client, bleibt Facade. Roadmap Phase 4 gestartet.", + ], + }, { "version": "0.8.132", "date": "2026-05-14", diff --git a/docs/architecture/README.md b/docs/architecture/README.md index ac5fe6c..2cf3501 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -9,6 +9,7 @@ Dieses Bündel ist die **Leitlinie für die große Refaktorierung** nach dem MVP | [ZIELBILD_ARCHITEKTUR.md](./ZIELBILD_ARCHITEKTUR.md) | Zielarchitektur (Frontend, API, Daten), Qualitätsziele, Einbindung neuer Features | | [SCHULDEN_UND_REMEDIATION.md](./SCHULDEN_UND_REMEDIATION.md) | Erfasste Architekturschuld, Reihenfolge und Massnahmen zur Behebung | | [UMSETZUNGSPLAN_ROADMAP.md](./UMSETZUNGSPLAN_ROADMAP.md) | Phasen, Meilensteine, Abnahmekriterien, Aufwandsschwerpunkte | +| [`frontend/src/api/client.js`](../../frontend/src/api/client.js) | Phase 4: zentraler HTTP-Client (`request`); `utils/api.js` bleibt Facade | | [BASELINE_SNAPSHOT.md](./BASELINE_SNAPSHOT.md) | Phase 0: Bundle-, API- und Last-Baseline (Messvorlagen, Vergleich nach Phase 2) | | [VERBINDLICHE_REGELN_SHINKAN.md](./VERBINDLICHE_REGELN_SHINKAN.md) | **Verbindliche** Shinkan-spezifische Regeln (Ergänzung zu den globalen Rules) | diff --git a/docs/architecture/UMSETZUNGSPLAN_ROADMAP.md b/docs/architecture/UMSETZUNGSPLAN_ROADMAP.md index 000942b..d53b3cd 100644 --- a/docs/architecture/UMSETZUNGSPLAN_ROADMAP.md +++ b/docs/architecture/UMSETZUNGSPLAN_ROADMAP.md @@ -8,6 +8,7 @@ - **Phase 2:** **abgeschlossen** (2026-05-14) — Indizes 058–062, Keyset `/api/exercises` + `/api/training-units`, **`/api/dashboard/kpis`** inkl. `training_home`, EXPLAIN-Vorlagen **`scripts/load/explain-readpaths.sql`**. - **Offen Phase 1:** Inbox optional **TTL** / nur bei sichtbarem Widget. - **Phase 3 (abgeschlossen 2026-05-14):** Übungsliste modularisiert; Trainingsplanung/Übungsformular: **Page-Dateien unter Soft-Limit** — Implementierung in `TrainingPlanningPageRoot.jsx`, `ExerciseFormPageRoot.jsx`, `ExercisesListPageRoot.jsx`; `pages/*.jsx` nur Re-Export. Playwright **Tests 9–10**. +- **Phase 4 (gestartet 2026-05-14):** API **Welle 1** — `frontend/src/api/client.js` (`request`); `utils/api.js` bleibt Facade. **Ziel:** Nach MVP eine **nachhaltige** Architektur für Wachstum, **Performance** (Server + schwache Clients) und **sichere Feature-Erweiterung**. **Leitdokumente:** [ZIELBILD_ARCHITEKTUR.md](./ZIELBILD_ARCHITEKTUR.md), [SCHULDEN_UND_REMEDIATION.md](./SCHULDEN_UND_REMEDIATION.md), [VERBINDLICHE_REGELN_SHINKAN.md](./VERBINDLICHE_REGELN_SHINKAN.md). @@ -92,13 +93,15 @@ ## Phase 4 – API-Client Modularisierung +**Status:** **gestartet** (2026-05-14) — Welle 1: **`frontend/src/api/client.js`** (`request`, `ACTIVE_CLUB_STORAGE_KEY`); **`utils/api.js`** bleibt vollständige Facade (bestehende Importe unverändert). + **Fokus:** Wartbarkeit für viele neue Features. -| Task | Bezug | -|------|--------| -| `frontend/src/api/` anlegen, `request`/`client` zentral | A2 | -| Facade: bestehende Importe von `utils/api` nicht sofort alle brechen; Migration in Wellen | A2 | -| Neue Endpoints nur noch in Domänen-Dateien | S3 | +| Task | Bezug | Status | +|------|-------|--------| +| `frontend/src/api/client.js` — zentraler HTTP-Client | A2 | erledigt (Welle 1) | +| Domänen-Module unter `frontend/src/api/` + schrittweise Entlastung von `api.js` | A2 | offen | +| Neue Endpoints primär in Domänen-Dateien | S3 | offen | **Abnahme:** Anteil neuer Module > X% der neuen Zeilen (Team-Ziel); Monolith wächst nicht weiter. diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js new file mode 100644 index 0000000..6d776e3 --- /dev/null +++ b/frontend/src/api/client.js @@ -0,0 +1,79 @@ +/** + * HTTP-Client: Token, Mandanten-Header, Fehler-Mapping. + * Alle API-Aufrufe laufen über request() — siehe utils/api.js (Facade) und künftige Domänenmodule. + */ + +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' + +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 + } +} diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index 5a46420..58f391b 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -6,84 +6,9 @@ import { stripHtmlToText } from './htmlUtils' import { normalizeAdvanceMode, parseComboRepSeriesCountUi } from './combinationMethodProfileUi' +import { request, ACTIVE_CLUB_STORAGE_KEY } from '../api/client.js' -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' - -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 } -} - -/** - * Generic API request with automatic token injection - */ -async function request(endpoint, options = {}) { - const token = localStorage.getItem('authToken') - const method = (options.method || 'GET').toUpperCase() - - const headers = mergeActiveClubHeader({ - ...options.headers, - }) - // GET ohne Body: kein Content-Type: application/json (manche Proxies/Headers stören sich) - 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.' - // Ursache oft: CORS (v. a. bei Fehler-Antworten ohne CORS-Header), Adblocker, falsche URL — - // Login kann trotzdem klappen wenn nur eine andere Route betroffen ist. - throw new Error(`${hint} [Technisch: ${e.message}; URL war ${endpoint}]`) - } - throw e - } -} +export { ACTIVE_CLUB_STORAGE_KEY } // ============================================================================ // Auth