Feature: Trauer-Feature, Futter-Verträglichkeit, Multi-Hund-Fixes, Wetter-Ort (Sprint 47)

- dog-profile.js: Verstorben-Button, Gedenkseite, KI-Abschiedstext
- database.py: futter_eintraege/reaktionen, route_dogs, exercise_progress.dog_id
- routes/ernaehrung.py: Futter-Verträglichkeit mit 20 Reaktionstypen + Analyse
- routes/routen.py: route_dogs Many-to-Many, Routen editierbar
- routes/training.py: exercise_progress per dog_id
- routes/ki.py: /ki/abschied Trauer-KI
- weather.py: Nominatim Ortsname parallel geladen
- ui.js: dogChip/bindDogChip, visualViewport-Modal
- api.js: gedenken, gedenkseite, futter-Methoden, route_dogs
- worlds.js: Ortsname im Wetter-Chip
- uebungen.js: _progressLoaded-Flag, dog-spezifischer Fortschritt
- trainingsplaene.js: dog_id Unterstützung
- diary.js/health.js: P-Badge Cleanup
- map.js: Wetter-Ort-Anzeige entfernt
- wetter.js: Ort in Wetter-Detail
This commit is contained in:
rene 2026-05-11 19:28:38 +02:00
parent 1ce802c8dc
commit bda61a0e40
16 changed files with 713 additions and 181 deletions

View file

@ -85,28 +85,43 @@ async def update_exercise(exercise_id: int, body: ExerciseUpdate, _=Depends(requ
# ------------------------------------------------------------------
class ProgressUpdate(BaseModel):
exercise_id: str
status: Optional[str] = None # null/noch-nicht/manchmal/meistens/sitzt
status: Optional[str] = None
dog_id: Optional[int] = None
@router.get("/progress")
async def get_progress(user=Depends(get_current_user)):
async def get_progress(dog_id: Optional[int] = None, user=Depends(get_current_user)):
uid = user["id"]
with db() as conn:
rows = conn.execute(
"SELECT exercise_id, status, updated_at FROM exercise_progress WHERE user_id=?",
(uid,)
).fetchall()
if dog_id:
rows = conn.execute(
"SELECT exercise_id, status, updated_at FROM exercise_progress WHERE dog_id=?",
(dog_id,)
).fetchall()
else:
rows = conn.execute(
"SELECT exercise_id, status, updated_at FROM exercise_progress WHERE user_id=?",
(uid,)
).fetchall()
return [dict(r) for r in rows]
@router.post("/progress")
async def upsert_progress(body: ProgressUpdate, user=Depends(get_current_user)):
uid = user["id"]
with db() as conn:
conn.execute("""
INSERT INTO exercise_progress (user_id, exercise_id, status)
VALUES (?,?,?)
ON CONFLICT(user_id, exercise_id) DO UPDATE
SET status=excluded.status, updated_at=datetime('now')
""", (uid, body.exercise_id, body.status))
if body.dog_id:
conn.execute("""
INSERT INTO exercise_progress (user_id, dog_id, exercise_id, status)
VALUES (?,?,?,?)
ON CONFLICT(dog_id, exercise_id) DO UPDATE
SET status=excluded.status, updated_at=datetime('now')
""", (uid, body.dog_id, body.exercise_id, body.status))
else:
conn.execute("""
INSERT INTO exercise_progress (user_id, exercise_id, status)
VALUES (?,?,?)
ON CONFLICT(dog_id, exercise_id) DO UPDATE
SET status=excluded.status, updated_at=datetime('now')
""", (uid, body.exercise_id, body.status))
return {"ok": True}
# ------------------------------------------------------------------
@ -115,15 +130,22 @@ async def upsert_progress(body: ProgressUpdate, user=Depends(get_current_user)):
class PlanProgress(BaseModel):
item_key: str
checked: bool
dog_id: Optional[int] = None
@router.get("/plan-progress")
async def get_plan_progress(user=Depends(get_current_user)):
async def get_plan_progress(dog_id: Optional[int] = None, user=Depends(get_current_user)):
uid = user["id"]
with db() as conn:
rows = conn.execute(
"SELECT item_key, checked FROM training_plan_progress WHERE user_id=?",
(uid,)
).fetchall()
if dog_id:
rows = conn.execute(
"SELECT item_key, checked FROM training_plan_progress WHERE dog_id=?",
(dog_id,)
).fetchall()
else:
rows = conn.execute(
"SELECT item_key, checked FROM training_plan_progress WHERE user_id=?",
(uid,)
).fetchall()
return [dict(r) for r in rows]
@router.post("/plan-progress")
@ -132,13 +154,13 @@ async def upsert_plan_progress(body: PlanProgress, user=Depends(get_current_user
with db() as conn:
if body.checked:
conn.execute("""
INSERT OR REPLACE INTO training_plan_progress (user_id, item_key, checked)
VALUES (?,?,1)
""", (uid, body.item_key))
INSERT OR REPLACE INTO training_plan_progress (user_id, dog_id, item_key, checked)
VALUES (?,?,?,1)
""", (uid, body.dog_id, body.item_key))
else:
conn.execute(
"DELETE FROM training_plan_progress WHERE user_id=? AND item_key=?",
(uid, body.item_key)
"DELETE FROM training_plan_progress WHERE dog_id=? AND item_key=?",
(body.dog_id, body.item_key)
)
return {"ok": True}
@ -149,13 +171,19 @@ GRUNDKOMMANDOS_ORDER = ['Sitz', 'Platz', 'Bleib', 'Hier / Komm', 'Fuß', 'Aus /
TRICKS_FIRST = ['Pfote / Schütteln', 'Dreh', 'Auf die Decke', 'Nasenarbeit / Suchen']
@router.get("/suggestions")
async def get_suggestions(user=Depends(get_current_user)):
async def get_suggestions(dog_id: Optional[int] = None, user=Depends(get_current_user)):
uid = user["id"]
with db() as conn:
rows = conn.execute(
"SELECT exercise_id, status FROM exercise_progress WHERE user_id=?",
(uid,)
).fetchall()
if dog_id:
rows = conn.execute(
"SELECT exercise_id, status FROM exercise_progress WHERE dog_id=?",
(dog_id,)
).fetchall()
else:
rows = conn.execute(
"SELECT exercise_id, status FROM exercise_progress WHERE user_id=?",
(uid,)
).fetchall()
progress = {r["exercise_id"]: r["status"] for r in rows}