{"id":51048333,"url":"https://github.com/pepperonas/drill","last_synced_at":"2026-06-22T15:02:00.742Z","repository":{"id":364723167,"uuid":"1268819336","full_name":"pepperonas/drill","owner":"pepperonas","description":"train. track. transform. — Multitenant fitness \u0026 body tracking PWA with Google auth, charts, gamification and motivational emails. Material 3 Expressive.","archived":false,"fork":false,"pushed_at":"2026-06-14T07:02:17.000Z","size":869,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-14T09:04:55.839Z","etag":null,"topics":["express","fitness","gamification","google-oauth","health-tracking","material-design-3","pwa","react","sqlite","vite"],"latest_commit_sha":null,"homepage":"https://drill.celox.io","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pepperonas.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-14T01:15:33.000Z","updated_at":"2026-06-14T07:02:20.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/pepperonas/drill","commit_stats":null,"previous_names":["pepperonas/drill"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/pepperonas/drill","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pepperonas%2Fdrill","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pepperonas%2Fdrill/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pepperonas%2Fdrill/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pepperonas%2Fdrill/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pepperonas","download_url":"https://codeload.github.com/pepperonas/drill/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pepperonas%2Fdrill/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34653715,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-22T02:00:06.391Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["express","fitness","gamification","google-oauth","health-tracking","material-design-3","pwa","react","sqlite","vite"],"created_at":"2026-06-22T15:01:59.876Z","updated_at":"2026-06-22T15:02:00.736Z","avatar_url":"https://github.com/pepperonas.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n[![drill — train. track. transform.](public/og-image.png)](https://drill.celox.io)\n\n# drill\n\n### **train. track. transform.**\n\nEine multitenant Fitness- \u0026 Körper-Tracking-PWA mit Google-Login, Charts, Gamification und Motivations-E-Mails — im **Material 3 Expressive** Look.\n\n[![Live](https://img.shields.io/badge/live-drill.celox.io-c6ff00?style=for-the-badge\u0026logo=pwa\u0026logoColor=14160f)](https://drill.celox.io)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge)](LICENSE)\n\n![React](https://img.shields.io/badge/React-18-61dafb?logo=react\u0026logoColor=white)\n![Vite](https://img.shields.io/badge/Vite-5-646cff?logo=vite\u0026logoColor=white)\n![Node.js](https://img.shields.io/badge/Node.js-%E2%89%A518-339933?logo=nodedotjs\u0026logoColor=white)\n![Express](https://img.shields.io/badge/Express-4-000000?logo=express\u0026logoColor=white)\n![SQLite](https://img.shields.io/badge/SQLite-better--sqlite3-003b57?logo=sqlite\u0026logoColor=white)\n![Recharts](https://img.shields.io/badge/Recharts-2-ff7300)\n![Material 3](https://img.shields.io/badge/Material%203-Expressive-c6ff00?logo=materialdesign\u0026logoColor=14160f)\n\n![Google OAuth](https://img.shields.io/badge/Auth-Google%20OAuth-4285F4?logo=google\u0026logoColor=white)\n![PWA](https://img.shields.io/badge/PWA-installable-5a0fc8?logo=pwa\u0026logoColor=white)\n![Gamification](https://img.shields.io/badge/Gamification-XP%20%C2%B7%20Streaks%20%C2%B7%20Badges-ffb59c)\n![Emails](https://img.shields.io/badge/Emails-nodemailer%20%2B%20cron-ffd34d)\n![GDPR](https://img.shields.io/badge/GDPR-export%20%26%20delete-34a853)\n![Tests](https://img.shields.io/badge/tests-passing-brightgreen?logo=nodedotjs\u0026logoColor=white)\n![PRs welcome](https://img.shields.io/badge/PRs-welcome-c6ff00)\n\n\u003c/div\u003e\n\n---\n\n## ✨ Features\n\n- **🔐 Google-Login (multitenant)** — jeder Account ein isolierter Mandant, eigene Daten, eigene Streaks.\n- **🧩 Universelles Tracker-System — alles frei nach eigenem Gusto** — der Nutzer definiert selbst, *was* und *wie* er trackt:\n  - **6 Eingabetypen:** Zahl (freie Einheit), Skala (z. B. 1–5), Ja/Nein-Gewohnheit, Dauer, Auswahl (eigene Optionen), Notiz/Freitext\n  - **Frei konfigurierbar:** Name, Symbol, Farbe, Kategorie, **Ziel + Richtung** (erhöhen/senken/halten), XP pro Eintrag\n  - **Vorlagen-Galerie** (Gewicht, Schlaf, Wasser, Stimmung, Schritte …) für 1-Tap-Anlage – alles bleibt editierbar\n  - **Editierbare Picker:** eigene Check-in-Aktivitäten \u0026 Workout-Kategorien; **Übungs-Bibliothek** mit Autocomplete\n- **🎯 Ziele \u0026 Bestleistungen** — Ziel-Fortschritt pro Tracker; **automatische Personal-Records** (Epley-1RM) im Training mit Glückwunsch.\n- **📈 Flexible Charts \u0026 Insights** — Zeitraum-Wahl (7T/30T/90T/1J/alles), gleitender Durchschnitt, **Korrelations-Analyse** zweier Tracker (Pearson-r + Scatter, z. B. Schlaf vs. Stimmung).\n- **📊 Motivierende Statistik-Seite** — kumulative **XP-Wachstumskurve** mit Level-Markern, **Aktivitäts-Heatmap** (Intensität = XP/Tag), **Wochen-Rhythmus** (Check-ins vs. Workouts), **Balance-Radar** über alle Lebensbereiche und **Trainings-Donut**.\n- **📅 Anwesenheit \u0026 Training** — täglicher Check-in mit 17-Wochen-Heatmap; Workouts mit Kategorie, Dauer und Sätzen (Übung · kg · Wdh.).\n- **🥗 Ernährung** — Kalorien \u0026 Makros **oder** einfache Tagesbewertung + Wasser.\n- **🎮 Volle Gamification** — XP pro Aktivität (pro Tracker einstellbar), Level-Kurve, Tages-**Streaks** mit Bonus, **29 freischaltbare Erfolge** inkl. erreichbarer Langzeit-Meilensteine (100-Tage-Serie, 365 Check-ins gesamt, Level 50, 500 t Volumen).\n- **🧊 Konfigurierbarer Streak-Schutz** — voll anpassbares „Streak-Freeze\"-System: **Wertung** (max. Anzahl, Verdienst-Modi: pro Streak-Meilenstein / pro X Check-ins / Wochengeschenk / bei Level-Up, ob ein geschützter Tag die Serie *wachsen lässt* oder nur *erhält*, automatischer Einsatz) **und Gestaltung** (Name, Symbol, Farbe, Beschreibung). Verpasste Tage werden automatisch überbrückt, solange Schilde vorhanden sind.\n- **📧 Motivations-E-Mails** — wöchentlicher Report, Streak-in-Gefahr-Alert, täglicher Nudge. Double-Opt-in + 1-Klick-Abmeldung, geplant via `node-cron`.\n- **🎨 Material 3 Expressive + 4 Themes** — tonal surfaces, 10-stufige Shape-Scale, Spring-Motion, emphasized Typography (Roboto Flex). **4 umschaltbare Themes** (Electric Lime · Ember · Aqua · Grape), jeweils komplette tonale Paletten; Auswahl in den Einstellungen, persistiert, mit „Wash\"-Übergang. Gut sichtbare Animationen: Seitenübergänge, gestaffeltes Karten-Einblenden, Streak-Ring-Pop, animierte Fortschrittsbalken.\n- **📱 PWA** — installierbar, offline-Shell, Service Worker mit versioniertem Cache.\n- **🛡️ DSGVO** — vollständiger JSON-Export und unwiderrufliche Konto-/Datenlöschung in den Einstellungen.\n\n## 📊 Statistik \u0026 Visualisierung\n\n\u003cdiv align=\"center\"\u003e\n\n![drill Statistik — XP-Wachstum, Heatmap, Wochen-Rhythmus, Balance-Radar, Trainings-Donut](public/screenshot-stats.png)\n\n\u003c/div\u003e\n\n## 🏗️ Architektur\n\n```\n┌─────────────────────────────┐         ┌──────────────────────────────┐\n│  Frontend (React + Vite)    │  /api   │  Backend (Express)           │\n│  Material 3 Expressive PWA  │ ──────▶ │  better-sqlite3 · HMAC-Cookie │\n│  Recharts · React Router    │ cookie  │  Google OAuth · node-cron     │\n└─────────────────────────────┘         │  nodemailer (Hostinger SMTP)  │\n                                         └──────────────┬───────────────┘\n                                                        │\n                                                  SQLite (WAL)\n```\n\n- **Sessions:** HMAC-SHA256-signierte Cookies (kein JWT-Lib), `secure` nur über HTTPS.\n- **OAuth:** Authorization-Code-Flow ohne SDK; ID-Token-Verifikation via Google `tokeninfo`.\n- **Tracker-System:** voll generisch — ein `trackers`-Eintrag definiert Typ/Einheit/Ziel/XP, `tracker_entries` hält die Werte. Neue Metrik = neuer Datensatz, **keine** Schemaänderung. Logik in `server/trackers.js` (Seeding, Ziel-Fortschritt, 1RM, Korrelation, gleitender Durchschnitt).\n- **Gamification:** server-autoritativ — append-only XP-Ledger + denormalisiertes Rollup auf dem User; jeder Tracker-Eintrag vergibt seine konfigurierbare XP. **XP ist rückgängig-fähig:** jedes Event trägt eine Quell-`ref`; beim Löschen einer Aktion werden genau deren XP abgezogen und das Level neu berechnet (Erfolge bleiben erhalten). `scripts/rebuild-xp.js` baut den Ledger deterministisch neu auf.\n- **Zeitzonen:** alle „Tage\" als `YYYY-MM-DD` in der User-Zeitzone, damit Streaks zur Wanduhr passen.\n\n### Datenmodell (Auszug)\n\n`users` *(inkl. `theme`)* · **`trackers`** (frei definierte Tracker) + **`tracker_entries`** · **`user_options`** (editierbare Picker) · **`personal_records`** · **`streak_freeze`** (Konfig + Stand) + **`freeze_events`** (Ledger) · `checkins` · `workouts` + `workout_sets` · `nutrition_logs` · `metrics` *(legacy, migriert in `trackers`)* · `xp_events` *(mit Quell-`ref` für reversible XP)* · `user_achievements` · `email_prefs` · `email_log`\n\nMigrationen sind append-only (`server/migrations.js`): `002_trackers` legt das Tracker-System an und überführt bestehende `metrics`-Werte verlustfrei in Tracker der Kategorie *body*; `003_streak_freeze` ergänzt den konfigurierbaren Streak-Schutz; `004_xp_ref` macht XP rückgängig-fähig; `005_user_theme` speichert das gewählte Theme pro Account.\n\n### API-Überblick (Auszug, alles unter `/api`)\n\n| Methode \u0026 Pfad | Zweck |\n|---|---|\n| `GET/POST /trackers`, `PUT/DELETE /trackers/:id` | Tracker verwalten (seedet Defaults bei leerem Konto) |\n| `POST /trackers/reorder` | Reihenfolge speichern |\n| `GET/POST /trackers/:id/entries`, `DELETE /entries/:id` | Einträge (vergeben Tracker-XP) |\n| `GET /trackers/:id/series?range=\u0026avg=` | Zeitreihe + gleitender Durchschnitt + Ziel |\n| `GET /insights/correlation?a=\u0026b=\u0026range=` | Pearson-r + ausgerichtete Paare zweier Tracker |\n| `GET/POST /options/:domain`, `DELETE /options/:id` | editierbare Picker (`activity`, `workout_category`) |\n| `GET /records` · `GET /exercises` | Personal Records · Übungs-Bibliothek |\n| `GET/PUT /streak-freeze` | Streak-Schutz lesen / konfigurieren (Wertung + Gestaltung) |\n| `GET /dashboard` · `GET /gamification` · `GET /stats` | Aggregate (Ziele/PRs/Level/Streak · Erfolge · XP-Kurve/Heatmap/Radar) |\n| `POST /checkins` · `POST /workouts` · `POST /nutrition` | spezialisiertes Tracking (Streak / Sätze+PR / Makros) |\n| `DELETE /checkins/:day` · `/workouts/:id` · `/entries/:id` | Rückgängig — zieht die vergebene XP wieder ab |\n| `GET/PUT /me` · `GET /export` · `DELETE /me` | Profil \u0026 Theme · DSGVO-Export · Konto löschen |\n| `GET /auth/google` · `GET /auth/callback` | Google-OAuth |\n\n## 🚀 Lokale Entwicklung\n\n```bash\n# Backend\ncd server\ncp .env.example .env          # Google-OAuth-Credentials + (optional) SMTP eintragen\nnpm install\nnpm run dev                   # http://127.0.0.1:4252\n\n# Frontend (zweites Terminal)\ncd ..\nnpm install\nnpm run dev                   # http://localhost:5180  (proxyt /api -\u003e :4252)\n```\n\n\u003e Für lokalen OAuth-Test in der Google Cloud Console `http://localhost:5180/api/auth/callback`\n\u003e als Redirect-URI hinterlegen und `APP_ORIGIN=http://localhost:5180` setzen\n\u003e (HTTP relaxt automatisch das `secure`-Cookie-Flag).\n\n### Tests\n\n```bash\ncd server \u0026\u0026 npm test         # node:test — 66 Tests\n```\nAbgedeckt:\n- **Gamification** — Level-Kurve, Streaks, Achievements, **reversible XP** (Undo zieht ab) \u0026 `rebuildXp`.\n- **Tracker-Logik** — 1RM, Ziel-Fortschritt, Korrelation/Pearson, gleitender Durchschnitt, Default-Seeding.\n- **Streak-Freeze** — Überbrückung grow/preserve, Verdienst-Milestones idempotent, Wochengeschenk,\n  ISO-Woche, Cap/Consume; plus **Cron-Auto-Bridge** (täglicher Freeze-Einsatz / Streak lapst ohne Schild).\n- **Analytics** — XP-Kurve (kumulativ), Heatmap, Wochen-Buckets, Balance-Radar, Kategorien, Level-Marker.\n- **Zeit-Mathematik** — `dayInTz`/`addDays`/`diffDays` über Monats-/Jahres-/Schaltjahr-Grenzen.\n- **Session-HMAC** — Roundtrip, Manipulations-/Ablauf-/Falschsecret-Erkennung.\n- **E-Mail-Templates** — HTML-Escaping (XSS), Confirm-/Unsubscribe-Links.\n- **Stats-/Rate-Limiter-Helfer** \u0026 **DB-Flows** (Einträge, PRs, Optionen, Cascade-Delete).\n- **API-Integrationstests** — App in-memory gebootet, Tracker- + Streak-Freeze-Endpunkte über HTTP mit\n  Session-Cookie (inkl. Undo zieht XP ab, Korrelation, PR-Erkennung).\n\n## 📦 Build \u0026 Deploy\n\n```bash\nnpm run build                 # -\u003e dist/   (statische PWA)\n```\n\nVollständige Produktions-Deployment-Anleitung (nginx, systemd, certbot, Backups): **[DEPLOY.md](DEPLOY.md)**.\n\n## ⚙️ Umgebungsvariablen\n\n| Variable | Beschreibung |\n|----------|--------------|\n| `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` | OAuth-Credentials |\n| `OAUTH_REDIRECT_URI` | `https://drill.celox.io/api/auth/callback` |\n| `APP_ORIGIN` | öffentliche Basis-URL |\n| `SESSION_SECRET` | `openssl rand -hex 32` |\n| `SMTP_USER` / `SMTP_PASS` | Hostinger-SMTP (leer = E-Mails aus) |\n| `TZ_NAME` | Zeitzone für Streaks \u0026 Cron (`Europe/Berlin`) |\n\nSiehe [`server/.env.example`](server/.env.example) für die vollständige Liste.\n\n## 🎮 XP \u0026 Erfolge\n\n| Aktion | XP |\n|--------|----|\n| Check-in | 25 (+ Streak-Bonus) |\n| Workout | 40 |\n| Ernährung/Tag | 15 |\n| Körpermetrik | 10 |\n\n**29 Erfolge** über alle Bereiche. Serien-Erfolge enden bei der erreichbaren 100-Tage-Marke\n(ein verpasster Tag setzt eine Serie zurück); echte Langzeitziele laufen über *kumulative*\nZähler, die Pausen verzeihen:\n\n- **Streaks (am Stück):** Eine Woche (7) · Zwei Wochen (14) · Eiserne Disziplin (30) · Durchmarsch (60) · **Unaufhaltsam (100)**\n- **Check-ins (gesamt):** Erster Schritt (1) · Halbes Hundert (50) · Hundertmal dabei (100) · Dauergast (250) · **Ein ganzes Jahr (365)**\n- **Workouts (gesamt):** Aufgewärmt (10) · Stammgast (50) · Eisenfreund (100) · **Hantel-Veteran (250)**\n- **Volumen (gesamt):** Tonnenweise (25 t) · Schwergewicht (100 t) · **Kraftwerk (500 t)**\n- **Bestleistungen:** Neuer Rekord (1) · Rekordjäger (10 PRs)\n- **Level:** 5 · 10 · 20 · **50 (👑)**\n- **Daten:** Vermessen (1 Eintrag) · Vielseitig (8 Tracker) · Datensammler (100 Einträge) · **Quantified Self (500)**\n- **Ernährung:** Bewusst (7 Tage) · Ernährungsprofi (30 Tage)\n\n## 📄 Lizenz\n\n[MIT](LICENSE) © 2026 Martin Pfeffer\n\n\u003cdiv align=\"center\"\u003e\u003csub\u003eBuilt with ❤️ and 🏋️ — train. track. transform.\u003c/sub\u003e\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpepperonas%2Fdrill","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpepperonas%2Fdrill","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpepperonas%2Fdrill/lists"}