docs: refresh CLAUDE.md (beat service, TanStack Query, env vars, no tests)
Build and push images / validate (push) Successful in 18s
Build and push images / build-backend (push) Successful in 30s
Build and push images / build-worker (push) Successful in 29s
Build and push images / build-frontend (push) Successful in 34s

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 13:19:55 +01:00
parent 0e4bc7b444
commit 0dd6eba589
+11 -2
View File
@@ -29,6 +29,8 @@ Everything runs in Docker Compose. There is no way to run individual services wi
The app is served on port 80 by nginx, which proxies `/api/*` to the backend (port 8000) and serves the React SPA for everything else. The app is served on port 80 by nginx, which proxies `/api/*` to the backend (port 8000) and serves the React SPA for everything else.
There are no automated tests. Verification is done by running the app and observing behaviour.
## Building and deploying ## Building and deploying
`docker-compose.yml` — build from source (dev/CI). `docker-compose.yml` — build from source (dev/CI).
@@ -56,6 +58,7 @@ docker compose -f docker-compose.deploy.yml up -d
| `redis` | Celery broker + result backend | | `redis` | Celery broker + result backend |
| `backend` | FastAPI (async) — uvicorn, single worker | | `backend` | FastAPI (async) — uvicorn, single worker |
| `worker` | Celery worker — synchronous SQLAlchemy (asyncio incompatible with prefork) | | `worker` | Celery worker — synchronous SQLAlchemy (asyncio incompatible with prefork) |
| `beat` | Celery Beat scheduler — runs `sync_all_garmin_connect` every 30 minutes |
| `frontend` | React SPA built by Vite at container build time | | `frontend` | React SPA built by Vite at container build time |
| `nginx` | Reverse proxy, serves the SPA | | `nginx` | Reverse proxy, serves the SPA |
@@ -69,7 +72,7 @@ docker compose -f docker-compose.deploy.yml up -d
- `services/wellness_parser.py` — parses Garmin wellness FIT files (metrics, sleep, HRV, SPO2, etc.) - `services/wellness_parser.py` — parses Garmin wellness FIT files (metrics, sleep, HRV, SPO2, etc.)
- `services/route_matcher.py` — bounding-box pre-filter + DTW (Dynamic Time Warping) for GPS track similarity - `services/route_matcher.py` — bounding-box pre-filter + DTW (Dynamic Time Warping) for GPS track similarity
- `services/garmin_connect_sync.py` — Garmin Connect API integration; `authenticate_garmin()` tries stored OAuth token first, falls back to email/password; Garmin credentials stored Fernet-encrypted using `SECRET_KEY` as the key - `services/garmin_connect_sync.py` — Garmin Connect API integration; `authenticate_garmin()` tries stored OAuth token first, falls back to email/password; Garmin credentials stored Fernet-encrypted using `SECRET_KEY` as the key
- `workers/tasks.py` — Celery tasks: `process_activity_file`, `parse_wellness_fit`, `detect_route`, `compute_personal_records`, `process_garmin_health_zip` - `workers/tasks.py` — Celery tasks: `process_activity_file`, `parse_wellness_fit`, `detect_route`, `compute_personal_records`, `process_garmin_health_zip`, `sync_all_garmin_connect` (beat-scheduled)
### Key design decisions ### Key design decisions
@@ -86,6 +89,7 @@ docker compose -f docker-compose.deploy.yml up -d
- `App.jsx` — React Router v6, `RequireAuth` wrapper, all routes defined here - `App.jsx` — React Router v6, `RequireAuth` wrapper, all routes defined here
- `hooks/useAuth.js` — Zustand store for auth state, reads JWT from `localStorage`, handles PocketID token-in-URL flow - `hooks/useAuth.js` — Zustand store for auth state, reads JWT from `localStorage`, handles PocketID token-in-URL flow
- `utils/api.js` — Axios instance with JWT interceptor and 401→redirect handler - `utils/api.js` — Axios instance with JWT interceptor and 401→redirect handler
- TanStack Query (`@tanstack/react-query`) handles all server-state fetching and caching; Zustand is used only for auth state
- `utils/format.js` — shared formatting helpers: `formatDuration`, `formatPace`, `formatDistance`, `formatCadence`, `hrZoneColor`, `sportIcon`, `sportColor`, etc. - `utils/format.js` — shared formatting helpers: `formatDuration`, `formatPace`, `formatDistance`, `formatCadence`, `hrZoneColor`, `sportIcon`, `sportColor`, etc.
- `pages/` — one file per route; includes `SegmentsPage` for route segment management - `pages/` — one file per route; includes `SegmentsPage` for route segment management
- `components/activity/``ActivityMap` (Leaflet), `MetricTimeline` (Recharts), `HRZoneBar`, `LapTable` - `components/activity/``ActivityMap` (Leaflet), `MetricTimeline` (Recharts), `HRZoneBar`, `LapTable`
@@ -100,9 +104,14 @@ Required in `.env` (or passed to Docker Compose):
| Variable | Purpose | | Variable | Purpose |
|----------|---------| |----------|---------|
| `DATABASE_URL` | Full async DB URL (`postgresql+asyncpg://...`) | | `DATABASE_URL` | Full async DB URL (`postgresql+asyncpg://...`) |
| `SECRET_KEY` | JWT signing key — generate with `openssl rand -hex 32` | | `SECRET_KEY` | JWT signing key — generate with `openssl rand -hex 32`; also used as Fernet key for Garmin credentials |
| `ADMIN_USERNAME` | Admin account username (default: `admin`) |
| `ADMIN_PASSWORD` | Seeds the admin user on first start | | `ADMIN_PASSWORD` | Seeds the admin user on first start |
| `REDIS_URL` | Celery broker | | `REDIS_URL` | Celery broker |
| `DB_USER` / `DB_PASSWORD` | Postgres credentials (compose-level; default: `milevault`) |
| `REDIS_PASSWORD` | Redis auth (compose-level; default: `milevault`) |
| `HTTP_PORT` | Host port for nginx (default: `80`) |
| `FILE_STORE_PATH` | Where uploaded FIT files are stored (default: `/data/files`) |
| `BASE_URL` | Used for PocketID OAuth callback redirect URI | | `BASE_URL` | Used for PocketID OAuth callback redirect URI |
| `VITE_MAPBOX_TOKEN` | Optional — enables satellite tile layer | | `VITE_MAPBOX_TOKEN` | Optional — enables satellite tile layer |
| `POCKETID_ISSUER` / `POCKETID_CLIENT_ID` / `POCKETID_CLIENT_SECRET` | Optional OIDC | | `POCKETID_ISSUER` / `POCKETID_CLIENT_ID` / `POCKETID_CLIENT_SECRET` | Optional OIDC |