feat: overhaul with modular API, agent endpoints, migrations, backup and tests

- Split monolithic app.py into server/api/ routers
- Add Pydantic schemas for input validation
- Add /agent/* API with separate AGENT_TOKEN auth
- Replace static schema.sql with versioned migrations (datenerhaltend)
- Add tools/backup_db.py for SQLite backups
- Add Dockerfile HEALTHCHECK and compose.yml AGENT_TOKEN env
- Add pytest tests for user and agent API
- Update README with API docs and deployment safety notes
- Bump version to 1.2.0
This commit is contained in:
Frank Schwenk
2026-09-21 12:46:44 +02:00
parent 7cec3030c1
commit 88bdf6f34b
31 changed files with 942 additions and 184 deletions
+53 -8
View File
@@ -14,7 +14,9 @@ Persönliche Medikamenten-PWA mit dezenten Push-Erinnerungen, Einnahme-Logging u
- **Push-Erinnerungen** um 8:00 und 12:00 (`Europe/Berlin`) — dezent (`silent`), kein Alarm
- **Einnahme loggen:** genommen / verpasst / snooze
- **PIN-Login** mit langer JWT-Session (90 Tage)
- **Agent-API** für externe Automatisierung (`/agent/*`) mit separatem Token
- **PWA:** installierbar, offline-fähige App-Shell, Service Worker
- **DB-Migrationen** — versionierte SQL-Skripte für deploymentsichere Schema-Updates
### Spaß
- **Dopamin-Drop** — Animation + Erfolgsspruch beim Loggen
@@ -78,7 +80,7 @@ App: http://localhost:8000
```bash
cp .env.example .env
# .env ausfüllen (PIN, JWT_SECRET, VAPID keys, optional OPENROUTER_API_KEY)
# .env ausfüllen (PIN, JWT_SECRET, AGENT_TOKEN, VAPID keys, optional OPENROUTER_API_KEY)
docker compose up -d --build
```
@@ -86,9 +88,22 @@ docker compose up -d --build
Traefik routet `medis.schwenk.online` → Container Port 8000 (TLS via `myresolver`).
Volumes:
- `./data` — SQLite-Datenbank
- `./data` — SQLite-Datenbank + automatische Backups (`data/backups/`)
- `./meds.yaml` — Medikamenten-Zeitplan (read-only)
- `./messages.yaml` — Humor-Texte (read-only)
- `./tools/migrations` — Schema-Migrationen (read-only)
### Keine Daten verlieren beim Deploy
1. **DB-Migrationen:** `tools/migrations/*.sql` werden beim Container-Start automatisch angewendet (`schema_version`-Tabelle). Neue Tabellen/Spalten kommen ohne Datenverlust dazu.
2. **Backup vor Updates:**
```bash
python tools/backup_db.py
# oder im Container:
docker compose exec app python tools/backup_db.py
```
3. **Volumes nicht löschen:** `docker compose down` ohne `-v` lässt `./data` bestehen. Nie `data/` manuell löschen.
4. **Healthcheck:** Der Container meldet sich über `/api/health/`; Docker wartet mit dem "healthy"-Status, bis die App antwortet.
---
@@ -101,6 +116,7 @@ Volumes:
| `APP_PIN` | Login-PIN (4–6 Ziffern) |
| `JWT_SECRET` | Geheimer Key für JWT (min. 32 Zeichen empfohlen: `openssl rand -hex 32`) |
| `JWT_EXPIRE_DAYS` | Session-Laufzeit (Default: 90) |
| `AGENT_TOKEN` | Langer zufälliger Token für die `/agent/*` API (leer = deaktiviert) |
| `VAPID_PRIVATE_KEY` | Web-Push Private Key (PEM) |
| `VAPID_PUBLIC_KEY` | Web-Push Public Key |
| `VAPID_CLAIMS_EMAIL` | mailto:-Adresse für VAPID |
@@ -133,12 +149,13 @@ Texte frei editierbar — kein Neustart nötig (wird beim ersten Request geladen
## API
Alle Endpunkte unter `/api/*`. Auth via `Authorization: Bearer <token>` (außer `/api/auth/pin` und `/api/vapid-public-key`).
Alle Endpunkte unter `/api/*`. Auth via `Authorization: Bearer <token>` (außer `/api/auth/pin`, `/api/health/` und `/api/vapid-public-key`).
| Methode | Pfad | Beschreibung |
|---------|------|--------------|
| GET | `/api/health/` | Liveness + App-Version |
| POST | `/api/auth/pin` | `{ "pin": "1234" }` → `{ "token": "..." }` |
| GET | `/api/vapid-public-key` | Public Key für Push-Subscription |
| GET | `/api/vapid-public-key` | Public Key für Push-Subscription (Legacy-Pfad) |
| GET | `/api/config` | Meds-Config |
| GET | `/api/today` | Heutige Slots + Status |
| POST | `/api/log` | `{ "slot_id", "status", "day?", "source?" }` |
@@ -146,8 +163,24 @@ Alle Endpunkte unter `/api/*`. Auth via `Authorization: Bearer <token>` (außer
| GET | `/api/history?days=90` | Heatmap-Daten + Stats |
| GET | `/api/stats` | Streak, Compliance, Meilensteine |
| GET | `/api/roast` | Roast-of-the-Day |
| GET | `/api/motivation` | Tägliche Motivation |
| POST | `/api/push/subscribe` | Web-Push Subscription speichern |
### Agent-API
Separate API für externe Agents / Automatisierung. Aktivieren mit `AGENT_TOKEN` in `.env`. Auth ebenfalls via `Authorization: Bearer <agent_token>`.
| Methode | Pfad | Beschreibung |
|---------|------|--------------|
| GET | `/agent/health` | Status, App-Version, DB-Schema-Version |
| GET | `/agent/status` | Heutiger Stand + Stats |
| POST | `/agent/log` | `{ "slot_id", "status", "day?", "source?" }` |
| GET | `/agent/history?days=90` | Heatmap-Daten + Stats |
| GET | `/agent/config` | Meds-Config |
| POST | `/agent/notify` | `{ "title", "body", "slot_id?", "tag?" }` — Push an alle Subscriptions |
Die Agent-API läuft auf denselben Tabellen wie die App; `source` ist frei wählbar, z. B. `"voice"`, `"watch"`, `"cron"`.
---
## PWA / Push einrichten
@@ -186,6 +219,8 @@ Notification-Actions: **Genommen ✓**, **+15 Min**, **+30 Min**
| **Icons: PNG für alle Größen** | 556×556 Quelle — Browser skaliert; kein ImageMagick nötig |
| **Abend-Check 21:00** | Markiert überfällige Slots als `missed`, optional Push |
| **Offline-Queue via IndexedDB** | Einfacher als SW-only; Flush bei App-Start + Background-Sync |
| **Agent-API mit separatem Token** | Externe Automatisierung ohne User-PIN; lässt sich gezielt abschalten |
| **Versionierte SQL-Migrationen** | Kein Alembic-Overhead; deploymentsicher, datenerhaltend |
---
@@ -193,16 +228,20 @@ Notification-Actions: **Genommen ✓**, **+15 Min**, **+30 Min**
```
takeyourmeds/
├── server/ # FastAPI Backend
│ ├── api/ # API-Router (auth, intake, push, stats, content, agent, health)
│ ├── schemas.py # Pydantic-Input-Modelle
│ ├── migrations.py # DB-Migrationssystem
│ └── ...
├── frontend/ # PWA-Quellen (JS, CSS, Templates)
├── public/ # Build-Output (index.html, sw.js, assets/)
├── tools/ # migrations/, gen_vapid.py, build_frontend.py, backup_db.py
├── compose.yml # Docker + Traefik
├── Dockerfile
├── meds.yaml # Medikamenten-Zeitplan
├── messages.yaml # Humor-Pool
├── icon.png / icon.svg # App-Icons (Quelle)
├── server/ # FastAPI Backend
├── public/ # Build-Output (index.html, sw.js, assets/)
├── tools/ # schema.sql, gen_vapid.py, build_frontend.py
└── data/ # SQLite (gitignored)
└── data/ # SQLite + Backups (gitignored)
```
---
@@ -219,6 +258,12 @@ Quellen liegen in `frontend/`; Build-Output in `public/` (gehashte JS/CSS unter
Service Worker: bei Deploy neuer `CACHE`-Name automatisch via Build. Update-Banner erscheint bei veralteter Client-Version.
### Tests
```bash
python -m pytest tests/ -v
```
---
## Troubleshooting