Frank Schwenk 1cada42370 feat: replace Web Push with ntfy for medication reminders
Single notification path via ntfy HTTP publish for reliable Android delivery;
remove VAPID, push subscriptions, and SW push handlers. PWA settings show
topic subscribe link; humor texts and deep-link actions unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 10:42:58 +02:00
2026-06-09 21:21:52 +02:00
2026-06-09 21:21:52 +02:00
2026-06-09 21:21:52 +02:00
2026-06-09 21:21:52 +02:00
2026-06-09 21:21:52 +02:00
2026-06-09 21:21:52 +02:00
2026-06-09 21:21:52 +02:00
2026-06-10 09:43:50 +02:00

TakeYourMeds

Persönliche Medikamenten-PWA mit dezenten ntfy-Erinnerungen, Einnahme-Logging und sarkastischem Humor.

Domain: medis.schwenk.online

App Icon


Features

Kern

  • ntfy-Erinnerungen um 8:00 und 12:00 (Europe/Berlin) — dezent, 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)
  • 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 ntfy (ntfy.schwenk.online)
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)

# 1. venv + Dependencies
python3 -m venv .venv
source .venv/bin/activate
pip install -e .

# 2. Konfiguration
cp .env.example .env
# APP_PIN, JWT_SECRET, NTFY_TOKEN anpassen

# 3. Starten
uvicorn server.app:app --reload --host 0.0.0.0 --port 8000

App: http://localhost:8000


Deployment (Docker + Traefik)

cp .env.example .env
# .env ausfüllen (PIN, JWT_SECRET, NTFY_TOKEN, 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)
NTFY_URL ntfy-Server (Default: https://ntfy.schwenk.online)
NTFY_TOPIC Topic für Reminder (Default: takeyourmeds)
NTFY_TOKEN Access-Token zum Publizieren (Bearer)
NTFY_CLICK_URL PWA-URL für Tap/Actions (Default: https://medis.schwenk.online)
OPENROUTER_API_KEY Optional — für Roast-of-the-Day
OPENROUTER_MODEL Default: google/gemini-2.0-flash-001

meds.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).

Methode Pfad Beschreibung
POST /api/auth/pin { "pin": "1234" }{ "token": "..." }
GET /api/notify-config ntfy Topic + Subscribe-URL
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

ntfy einrichten

  1. Server: NTFY_TOKEN in .env auf boka setzen, Container neu starten
  2. Handy: ntfy-App → Topic takeyourmeds abonnieren (oder Link unter Einstellungen in der PWA)
  3. Optional: PWA auf Homescreen — für Loggen/Snooze; Reminder kommen über ntfy

Notification-Actions in ntfy: Genommen ✓, +15 Min, +30 Min (öffnen PWA mit Deep-Link)

Test von der Shell:

curl -d "Test" -H "Title: Med-Time" -H "Authorization: Bearer $NTFY_TOKEN" \
  https://ntfy.schwenk.online/takeyourmeds

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
ntfy vom Server statt Web Push Zuverlässig auf Android; eine Notification-Logik
OpenRouter nur für Roast 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 ntfy
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
└── data/                # SQLite (gitignored)

Entwicklung

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
Reminder kommt nicht NTFY_TOKEN in .env? Topic in ntfy-App abonniert?
„ntfy nicht konfiguriert" NTFY_URL + NTFY_TOPIC in .env setzen
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.

S
Description
No description provided
Readme 454 KiB
Languages
Python 53.4%
JavaScript 26.5%
CSS 10.4%
HTML 9.2%
Dockerfile 0.5%