From ad3b73d68755bba22bbe2c45bcaec34d2417dc7a Mon Sep 17 00:00:00 2001 From: rene Date: Wed, 29 Apr 2026 19:00:53 +0200 Subject: [PATCH] =?UTF-8?q?Cleanup:=20training=5Fexercises.js=5Fexercise?= =?UTF-8?q?=5Fid=20=E2=80=94=20ID-Mismatch=20bereinigt,=20JOIN=20mit=20exe?= =?UTF-8?q?rcise=5Fprogress,=20Fu=C3=9F-Umbenennung=20=E2=80=94=20SW=20by-?= =?UTF-8?q?v508,=20APP=5FVER=20485?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/database.py | 21 +++++++++++ backend/routes/dogs.py | 72 +++++++++++++++++++++++++------------- backend/routes/training.py | 9 ++--- backend/static/js/app.js | 2 +- backend/static/sw.js | 2 +- 5 files changed, 75 insertions(+), 31 deletions(-) diff --git a/backend/database.py b/backend/database.py index 2ff12c3..22926a4 100644 --- a/backend/database.py +++ b/backend/database.py @@ -1482,3 +1482,24 @@ def _migrate(conn_factory): ); CREATE INDEX IF NOT EXISTS idx_bj_user ON breeder_jahresberichte(user_id, jahr DESC); """) + + # js_exercise_id zu training_exercises — verbindet training_exercises mit exercise_progress + existing_te = [row[1] for row in conn.execute("PRAGMA table_info(training_exercises)").fetchall()] + if 'js_exercise_id' not in existing_te: + import re as _re + _CAT_TO_TAB = { + 'Grundkommando': 'grundkommandos', 'Trick': 'tricks', + 'Problemverhalten': 'problemverhalten', 'Mentale Auslastung': 'mentale-auslastung', + 'Hundesport': 'hundesport', 'Körperpflege': 'koerperpflege', + 'Welpe Basics': 'welpe-basics', 'Grundlagen': 'grundlagen', + } + conn.execute("ALTER TABLE training_exercises ADD COLUMN js_exercise_id TEXT") + # "Fuß (Leinenführigkeit)" → "Fuß" (damit es mit alten exercise_progress-Einträgen matcht) + conn.execute("UPDATE training_exercises SET name='Fuß' WHERE exercise_id='gk_fuss'") + rows = conn.execute("SELECT id, name, kategorie FROM training_exercises").fetchall() + for row in rows: + tab = _CAT_TO_TAB.get(row['kategorie'], row['kategorie'].lower().replace(' ', '-')) + js_id = tab + '_' + _re.sub(r'[\s/]+', '_', row['name']) + conn.execute("UPDATE training_exercises SET js_exercise_id=? WHERE id=?", (js_id, row['id'])) + conn.execute("CREATE INDEX IF NOT EXISTS idx_te_js_id ON training_exercises(js_exercise_id)") + logger.info("Migration: training_exercises.js_exercise_id hinzugefügt, 'Fuß' bereinigt.") diff --git a/backend/routes/dogs.py b/backend/routes/dogs.py index 069b09a..8e176f8 100644 --- a/backend/routes/dogs.py +++ b/backend/routes/dogs.py @@ -159,36 +159,58 @@ async def get_welcome_dashboard(dog_id: int, user=Depends(get_current_user)): "SELECT COUNT(*) AS n FROM diary WHERE dog_id=?", (dog_id,) ).fetchone()["n"] - # Tagesübung — aus exercise_progress im JS-Format (tab_Name), tagesstabil + # Tagesübung — JOIN mit training_exercises via js_exercise_id, tagesstabil import datetime as _dt day_num = (_dt.date.today() - _dt.date(2024, 1, 1)).days - # Nur exercise_ids im JS-Format (starten mit bekanntem Tab-Namen) - _KNOWN_PREFIXES = ( - 'grundkommandos_', 'tricks_', 'problemverhalten_', - 'mentale-auslastung_', 'hundesport_', 'koerperpflege_', 'welpe-basics_', - 'grundlagen_', - ) - raw_progress = conn.execute( - """SELECT exercise_id FROM exercise_progress - WHERE user_id = ? AND status IN ('noch-nicht', 'manchmal', 'meistens') - ORDER BY updated_at ASC LIMIT 50""", - (user["id"],) - ).fetchall() - - valid = [r["exercise_id"] for r in raw_progress - if any(r["exercise_id"].startswith(p) for p in _KNOWN_PREFIXES)] + # Versuche JOIN (funktioniert wenn js_exercise_id-Spalte vorhanden) + try: + joined = conn.execute( + """SELECT ep.exercise_id, te.name, te.kategorie AS kategorie_raw, + te.schwierigkeit, te.js_exercise_id + FROM exercise_progress ep + JOIN training_exercises te ON te.js_exercise_id = ep.exercise_id + WHERE ep.user_id = ? AND ep.status IN ('noch-nicht', 'manchmal', 'meistens') + ORDER BY ep.updated_at ASC LIMIT 50""", + (user["id"],) + ).fetchall() + except Exception: + joined = [] daily_exercise = None - if valid: - ex_id = valid[day_num % len(valid)] - # Tab + Name aus exercise_id ableiten - for prefix in _KNOWN_PREFIXES: - if ex_id.startswith(prefix): - tab = prefix.rstrip('_') - name = ex_id[len(prefix):].replace('_', ' ') - daily_exercise = {"exercise_id": ex_id, "name": name, "kategorie": tab} - break + if joined: + row = joined[day_num % len(joined)] + # Tab-ID aus exercise_id ableiten (alles vor erstem '_' + '_') + ex_id = row["exercise_id"] + tab = ex_id.split('_')[0] if '_' in ex_id else ex_id + daily_exercise = { + "exercise_id": ex_id, + "name": row["name"], + "kategorie": tab, + "schwierigkeit": row["schwierigkeit"], + } + else: + # Fallback: exercise_progress ohne JOIN (Legacy-IDs ohne Matching in DB) + _KNOWN_PREFIXES = ( + 'grundkommandos_', 'tricks_', 'problemverhalten_', + 'mentale-auslastung_', 'hundesport_', 'koerperpflege_', 'welpe-basics_', + ) + raw = conn.execute( + """SELECT exercise_id FROM exercise_progress + WHERE user_id = ? AND status IN ('noch-nicht', 'manchmal', 'meistens') + ORDER BY updated_at ASC LIMIT 50""", + (user["id"],) + ).fetchall() + valid = [r["exercise_id"] for r in raw + if any(r["exercise_id"].startswith(p) for p in _KNOWN_PREFIXES)] + if valid: + ex_id = valid[day_num % len(valid)] + for prefix in _KNOWN_PREFIXES: + if ex_id.startswith(prefix): + tab = prefix.rstrip('_') + name = ex_id[len(prefix):].replace('_', ' ') + daily_exercise = {"exercise_id": ex_id, "name": name, "kategorie": tab} + break return { "random_photo": random_photo, diff --git a/backend/routes/training.py b/backend/routes/training.py index a7c0951..48e37fe 100644 --- a/backend/routes/training.py +++ b/backend/routes/training.py @@ -29,7 +29,7 @@ async def get_exercises(): } with db() as conn: rows = conn.execute(""" - SELECT exercise_id, name, kategorie, schwierigkeit, alter_ab, + SELECT exercise_id, js_exercise_id, name, kategorie, schwierigkeit, alter_ab, dauer, beschreibung, schritte, tipp FROM training_exercises ORDER BY kategorie, name """).fetchall() @@ -37,9 +37,10 @@ async def get_exercises(): for r in rows: tab = CAT_TO_TAB.get(r['kategorie'], r['kategorie'].lower().replace(' ', '-')) by_tab.setdefault(tab, []).append({ - 'exercise_id': r['exercise_id'], - 'name': r['name'], - 'kategorie': tab, + 'exercise_id': r['exercise_id'], + 'js_exercise_id': r['js_exercise_id'], + 'name': r['name'], + 'kategorie': tab, 'schwierigkeit': r['schwierigkeit'] or 'mittel', 'alter': r['alter_ab'], 'dauer': r['dauer'], diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 47bebe5..65730e5 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '484'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '485'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const App = (() => { diff --git a/backend/static/sw.js b/backend/static/sw.js index c8ded63..e48c785 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -3,7 +3,7 @@ Offline-Cache + Push Notifications + Tile-Cache ============================================================ */ -const CACHE_VERSION = 'by-v507'; +const CACHE_VERSION = 'by-v508'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten