debug line test

This commit is contained in:
Frank Schwenk
2026-06-09 21:21:52 +02:00
commit eaa019087e
43 changed files with 2666 additions and 0 deletions
+233
View File
@@ -0,0 +1,233 @@
# TakeYourMeds
Persönliche Medikamenten-PWA mit dezenten Push-Erinnerungen, Einnahme-Logging und sarkastischem Humor.
**Domain:** [medis.schwenk.online](https://medis.schwenk.online)
![App Icon](icon.png)
---
## Features
### Kern
- **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)
- **PWA:** installierbar, offline-fähige App-Shell, Service Worker
### Spaß
- **Dopamin-Drop** — Animation + Erfolgsspruch beim Loggen
- **Roast-of-the-Day** — täglicher sarkastischer Spruch (OpenRouter, gecacht)
- **KI-Orakel** — wöchentlicher Compliance-Report (OpenRouter, gecacht)
- **Easter Eggs** — Meilenstein-Badges (50/100 Logs, Streak 7/30/100)
- **~95 Humor-Texte** in `messages.yaml` (Reminder, Success, Missed, …)
### Nützlich
- **Snooze-Kette** — +15 / +30 Min (UI + Notification-Actions)
- **90-Tage Compliance-Heatmap**
- **Manifest Shortcuts** — „Genommen (Morgens/Mittags)" vom Homescreen
- **Badge API** — App-Icon zeigt Streak-Zahl
- **Offline-Queue** — Logs in IndexedDB, Sync bei Verbindung
---
## Stack
| Komponente | Technologie |
|------------|-------------|
| Backend | FastAPI + uvicorn |
| DB | SQLite (`data/medis.sqlite`) |
| Frontend | Vanilla JS (kein Build-Step) |
| Push | Web Push (pywebpush + VAPID) |
| Scheduler | APScheduler (8:00, 12:00, 21:00) |
| KI (optional) | OpenRouter |
| Deployment | Docker Compose + Traefik |
**KISS-Entscheidung:** Ein Container statt nginx+api — FastAPI serviert API und statische Dateien. Traefik-Labels aus `compose.example.yml` übernommen.
---
## Schnellstart (lokal)
```bash
# 1. venv + Dependencies
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
# 2. Konfiguration
cp .env.example .env
# APP_PIN und JWT_SECRET anpassen
# 3. VAPID keys (für Push)
python tools/gen_vapid.py
# Output in .env eintragen
# 4. Starten
uvicorn server.app:app --reload --host 0.0.0.0 --port 8000
```
App: http://localhost:8000
---
## Deployment (Docker + Traefik)
```bash
cp .env.example .env
# .env ausfüllen (PIN, JWT_SECRET, VAPID keys, optional OPENROUTER_API_KEY)
docker compose up -d --build
```
Traefik routet `medis.schwenk.online` → Container Port 8000 (TLS via `myresolver`).
Volumes:
- `./data` — SQLite-Datenbank
- `./meds.yaml` — Medikamenten-Zeitplan (read-only)
- `./messages.yaml` — Humor-Texte (read-only)
---
## Konfiguration
### `.env`
| Variable | Beschreibung |
|----------|--------------|
| `APP_PIN` | Login-PIN (46 Ziffern) |
| `JWT_SECRET` | Geheimer Key für JWT (min. 32 Zeichen empfohlen: `openssl rand -hex 32`) |
| `JWT_EXPIRE_DAYS` | Session-Laufzeit (Default: 90) |
| `VAPID_PRIVATE_KEY` | Web-Push Private Key (PEM) |
| `VAPID_PUBLIC_KEY` | Web-Push Public Key |
| `VAPID_CLAIMS_EMAIL` | mailto:-Adresse für VAPID |
| `OPENROUTER_API_KEY` | Optional — für Roast + Orakel |
| `OPENROUTER_MODEL` | Default: `google/gemini-2.0-flash-001` |
### `meds.yaml`
```yaml
timezone: Europe/Berlin
slots:
- id: morning
time: "08:00"
label: Morgens
meds:
- name: Elvanse
dose: "30mg"
reminder_window_minutes: 90 # Karenzzeit — siehe unten
```
**`reminder_window_minutes`:** Zeit nach der Soll-Uhrzeit, in der eine Dosis noch als „offen" gilt.
Beispiel: 08:00 + 90 Min → bis 09:30 Status `pending`/`overdue`, erst danach wird sie abends (~21:00) automatisch als `missed` markiert.
### `messages.yaml`
Kategorien: `reminder`, `success`, `missed`, `streak`, `snooze`, `easter_egg`, `roast_fallback`.
Texte frei editierbar — kein Neustart nötig (wird beim ersten Request geladen; Container-Neustart lädt neu).
---
## API
Alle Endpunkte unter `/api/*`. Auth via `Authorization: Bearer <token>` (außer `/api/auth/pin` und `/api/vapid-public-key`).
| Methode | Pfad | Beschreibung |
|---------|------|--------------|
| POST | `/api/auth/pin` | `{ "pin": "1234" }``{ "token": "..." }` |
| GET | `/api/vapid-public-key` | Public Key für Push-Subscription |
| GET | `/api/config` | Meds-Config |
| GET | `/api/today` | Heutige Slots + Status |
| POST | `/api/log` | `{ "slot_id", "status", "day?", "source?" }` |
| POST | `/api/snooze` | `{ "slot_id", "minutes": 15\|30 }` |
| GET | `/api/history?days=90` | Heatmap-Daten + Stats |
| GET | `/api/stats` | Streak, Compliance, Meilensteine |
| GET | `/api/roast` | Roast-of-the-Day |
| GET | `/api/oracle` | Wöchentliches KI-Orakel |
| POST | `/api/push/subscribe` | Web-Push Subscription speichern |
---
## PWA / Push einrichten
1. App im Browser öffnen (HTTPS erforderlich — lokal ohne Push)
2. Einloggen → Tab **Einstellungen****Push aktivieren**
3. Optional: **Auf Homescreen** installieren
4. Android-Shortcuts: Long-Press App-Icon → „Genommen (Morgens/Mittags)"
Notification-Actions: **Genommen ✓**, **+15 Min**, **+30 Min**
---
## Icons
- `icon.png` / `icon.svg` — Quelldateien im Repo-Root
- Werden nach `public/` kopiert für PWA (192, 512, 72 px)
- Manifest `theme_color`: `#F12F12`
---
## Entscheidungsprotokoll
| Entscheidung | Begründung |
|--------------|------------|
| **Ein FastAPI-Container** statt nginx+api | KISS — weniger Moving Parts, StaticFiles reicht für Single-User-PWA |
| **Vanilla JS** statt React/Vue | Kein Build-Step, schnelle Iteration, passt zu persönlicher App |
| **SQLite** statt Postgres | Single-User, eine Datei, Backup = `data/medis.sqlite` kopieren |
| **Web Push vom Server** statt Client-Timer | Zuverlässig auch bei geschlossener App; APScheduler im Container |
| **OpenRouter nur für Roast + Orakel** | Kosten/Latenz — Notifications nutzen statische `messages.yaml` |
| **Model: gemini-2.0-flash** | Günstig, schnell, gut genug für kurze deutsche Roasts |
| **PIN plain in .env** statt Hash | Single-User, unkritische Daten; `hmac.compare_digest` gegen Timing-Leaks |
| **JWT 90 Tage** | Lange Session auf persönlichem Gerät, kein ständiges PIN-Eingeben |
| **`reminder_window_minutes: 90`** | ADHS-realistisch — nicht sofort „verpasst" um 08:01 |
| **Badge = Streak**, nicht offene Dosen | Streak ist motivierender Dopamin-Hook |
| **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 |
---
## Projektstruktur
```
takeyourmeds/
├── compose.yml # Docker + Traefik
├── Dockerfile
├── meds.yaml # Medikamenten-Zeitplan
├── messages.yaml # Humor-Pool
├── icon.png / icon.svg # App-Icons (Quelle)
├── server/ # FastAPI Backend
├── public/ # PWA Frontend
├── tools/ # schema.sql, gen_vapid.py
└── data/ # SQLite (gitignored)
```
---
## Entwicklung
```bash
source .venv/bin/activate
uvicorn server.app:app --reload --port 8000
```
Service Worker cached aggressiv — für SW-Änderungen: DevTools → Application → Clear storage, oder Inkognito.
---
## Troubleshooting
| Problem | Lösung |
|---------|--------|
| Push kommt nicht | VAPID keys in `.env`? HTTPS? Push in Einstellungen aktiviert? |
| „VAPID keys nicht konfiguriert" | `python tools/gen_vapid.py` ausführen |
| KI-Roast zeigt Fallback | `OPENROUTER_API_KEY` fehlt oder API-Fehler — Fallback aus `messages.yaml` |
| Streak = 0 trotz Log | Streak zählt nur Tage wo **alle** Slots genommen wurden |
| SW zeigt alte Version | `version.json` bumpen + Cache leeren |
---
## Lizenz
Privates Projekt — persönliche Nutzung.