Session 2026-04-19: Navigation, Kompass, Übungsfortschritt

Routen-Navigation:
- POI-Marker: farbige Kreise mit Phosphor-Icons (wie Hauptkarte)
- Screensaver: Navi-Pfeil dreht sich via DeviceOrientationEvent (iOS+Android)
- Pfeil-Dämpfung: EMA α=0.12 mit Wrap-Around
- GPS-Distanz-Bug: Fortschritt nur wenn <500m zur Route
- fitBounds: User-Position nur wenn <20km von Route
- Screensaver: "zur Route" vs "verbleibend" kontextabhängig
- Richtungspfeile entlang Route (blau, max 7 Stück)
- Umkehren ins Route-Detail verschoben, Detail-Map rebuildet sich
- rk-header z-index:10 (Leaflet-Tiles liefen drüber)
- 2-Sek. Screensaver-Entsperrung

km-Tracking:
- route_walks Tabelle
- POST /api/routes/{id}/walked (≥50%)
- total_km = erstellte Routes + gelaufene route_walks
- Toast bei neuem Badge

Übungsfortschritt:
- exercise_progress + training_plan_progress Tabellen
- GET/POST /api/training/progress, /plan-progress, /suggestions
- uebungen.js: API-first + localStorage-Fallback + Auto-Migration
- Empfehlungs-Banner (regelbasiert)
- Toast bei "sitzt"
This commit is contained in:
rene 2026-04-19 20:33:01 +02:00
parent 390176383f
commit 9a78121a3e
25 changed files with 2487 additions and 248 deletions

View file

@ -177,6 +177,32 @@ def init_db():
anz_bewertungen INTEGER DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS route_walks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
route_id INTEGER REFERENCES routes(id) ON DELETE SET NULL,
walked_km REAL NOT NULL,
walked_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_route_walks_user ON route_walks(user_id);
CREATE TABLE IF NOT EXISTS exercise_progress (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
exercise_id TEXT NOT NULL,
status TEXT,
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(user_id, exercise_id)
);
CREATE INDEX IF NOT EXISTS idx_exercise_progress_user ON exercise_progress(user_id);
CREATE TABLE IF NOT EXISTS training_plan_progress (
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
item_key TEXT NOT NULL,
checked INTEGER NOT NULL DEFAULT 1,
checked_at TEXT NOT NULL DEFAULT (datetime('now')),
PRIMARY KEY (user_id, item_key)
);
-- GASSI-TREFFEN
CREATE TABLE IF NOT EXISTS walks (
@ -486,6 +512,16 @@ def _migrate(conn_factory):
("forum_threads", "thread_lat", "REAL"),
("forum_threads", "thread_lon", "REAL"),
("forum_threads", "thread_ort", "TEXT"),
# Referral (idempotent, falls try/except-Block oben fehlgeschlagen ist)
("users", "referral_code", "TEXT"),
("users", "referred_by", "INTEGER"),
# Routen: Original-Werte für Gamification (bleiben nach Kürzen erhalten)
("routes", "original_km", "REAL"),
("routes", "original_dauer_min","INTEGER"),
# Gamification: Streaks
("users", "current_streak", "INTEGER NOT NULL DEFAULT 0"),
("users", "max_streak", "INTEGER NOT NULL DEFAULT 0"),
("users", "last_activity_date","TEXT"),
]
with conn_factory() as conn:
for table, column, col_type in migrations:
@ -848,3 +884,16 @@ def _migrate(conn_factory):
logger.info("Migration: referral_code + referred_by zu users hinzugefügt.")
except Exception:
pass
# Gamification: Badge-Tabelle
conn.executescript("""
CREATE TABLE IF NOT EXISTS user_badges (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
badge_id TEXT NOT NULL,
earned_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(user_id, badge_id)
);
CREATE INDEX IF NOT EXISTS idx_user_badges_user ON user_badges(user_id);
""")
logger.info("Migration: user_badges Tabelle bereit.")