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

@ -58,6 +58,7 @@ class RouteCreate(BaseModel):
is_public: Optional[bool] = False
hunde_tauglichkeit: Optional[str] = None # eingeschränkt | gut | sehr_gut | premium
client_time: Optional[str] = None
dog_ids: Optional[List[int]] = None # Welche Hunde mitgegangen sind
class RouteUpdate(BaseModel):
name: Optional[str] = None
@ -69,6 +70,9 @@ class RouteUpdate(BaseModel):
is_public: Optional[bool] = None
hunde_tauglichkeit: Optional[str] = None
class RouteDogs(BaseModel):
dog_ids: List[int]
def _simplify_track(track: list, max_pts: int = 40) -> list:
"""Reduziert GPS-Track auf max_pts Punkte für Vorschau."""
@ -168,7 +172,26 @@ async def create_route(data: RouteCreate, user=Depends(get_current_user)):
int(data.is_public) if data.is_public is not None else 1,
data.hunde_tauglichkeit, is_valid, ct,
))
row = conn.execute("SELECT * FROM routes WHERE id = ?", (cur.lastrowid,)).fetchone()
route_id = cur.lastrowid
row = conn.execute("SELECT * FROM routes WHERE id = ?", (route_id,)).fetchone()
# Hunde zuordnen — entweder explizit oder alle Hunde des Users
dog_ids = data.dog_ids or []
if not dog_ids:
# Fallback: alle Hunde des Users
all_dogs = conn.execute(
"SELECT id FROM dogs WHERE user_id=?", (user['id'],)
).fetchall()
dog_ids = [d['id'] for d in all_dogs]
for did in dog_ids:
try:
conn.execute(
"INSERT OR IGNORE INTO route_dogs (route_id, dog_id) VALUES (?,?)",
(route_id, did)
)
except Exception:
pass
update_streak(user['id'], conn)
check_and_award(user['id'], conn)
result = _parse(row)
@ -317,9 +340,14 @@ async def get_route(route_id: int):
"SELECT r.*, u.name AS user_name FROM routes r LEFT JOIN users u ON u.id = r.user_id WHERE r.id = ?",
(route_id,)
).fetchone()
if not row:
raise HTTPException(404, "Route nicht gefunden.")
return _parse(row)
if not row:
raise HTTPException(404, "Route nicht gefunden.")
dog_rows = conn.execute(
"SELECT dog_id FROM route_dogs WHERE route_id = ?", (route_id,)
).fetchall()
result = _parse(row)
result['dog_ids'] = [r['dog_id'] for r in dog_rows]
return result
# ------------------------------------------------------------------
@ -346,6 +374,26 @@ async def update_route(route_id: int, data: RouteUpdate, user=Depends(get_curren
return _parse(row)
# ------------------------------------------------------------------
# PATCH /api/routes/{id}/dogs — Hunde der Route aktualisieren
# ------------------------------------------------------------------
@router.patch("/{route_id}/dogs")
async def update_route_dogs(route_id: int, data: RouteDogs, user=Depends(get_current_user)):
with db() as conn:
row = conn.execute("SELECT user_id FROM routes WHERE id = ?", (route_id,)).fetchone()
if not row:
raise HTTPException(404, "Route nicht gefunden.")
if row['user_id'] != user['id']:
raise HTTPException(403, "Nicht berechtigt.")
conn.execute("DELETE FROM route_dogs WHERE route_id = ?", (route_id,))
for did in data.dog_ids:
conn.execute(
"INSERT OR IGNORE INTO route_dogs (route_id, dog_id) VALUES (?, ?)",
(route_id, did)
)
return {"ok": True}
# ------------------------------------------------------------------
# PATCH /api/routes/{id}/trim — Route kürzen (Datenschutz)
# ------------------------------------------------------------------