Fix: Ernährung Hund-spezifisch, Erinnerungen in Settings, Übung des Tages per Hund (SW by-v872)

- ernaehrung.js: onDogChange setzt activeTab zurück, Hund klar sichtbar
- settings.js: Erinnerungen-Sektion lädt verstorbene Hunde + öffnet Gedenkseite
- dogs.py: GET /dogs/verstorben Endpoint (korrekte Route-Reihenfolge vor /{dog_id})
- dogs.py: Übung des Tages filtert jetzt nach dog_id statt user_id (sitzt-Übungen korrekt ausgeschlossen)
- Routen zeigen verstorbene Hunde korrekt als Teilnehmer (route_dogs ohne verstorben-Filter)
This commit is contained in:
rene 2026-05-11 19:25:00 +02:00
parent 265d3d4fe2
commit 1ce802c8dc
8 changed files with 1106 additions and 28 deletions

View file

@ -41,7 +41,7 @@ class DogUpdate(BaseModel):
async def list_dogs(user=Depends(get_current_user)):
with db() as conn:
own = conn.execute(
"SELECT *, NULL AS shared_by, NULL AS share_role FROM dogs WHERE user_id=? ORDER BY id",
"SELECT *, NULL AS shared_by, NULL AS share_role FROM dogs WHERE user_id=? AND (verstorben_am IS NULL) ORDER BY id",
(user["id"],)
).fetchall()
shared = conn.execute(
@ -255,15 +255,16 @@ async def get_welcome_dashboard(dog_id: int, user=Depends(get_current_user)):
day_num = (_dt.date.today() - _dt.date(2024, 1, 1)).days
# Versuche JOIN (funktioniert wenn js_exercise_id-Spalte vorhanden)
# Nur Übungen des aktiven Hundes, 'sitzt' ausschließen
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')
WHERE ep.dog_id = ? AND ep.status IN ('noch-nicht', 'manchmal', 'meistens')
ORDER BY ep.updated_at ASC LIMIT 50""",
(user["id"],)
(dog_id,)
).fetchall()
except Exception:
joined = []
@ -288,9 +289,9 @@ async def get_welcome_dashboard(dog_id: int, user=Depends(get_current_user)):
)
raw = conn.execute(
"""SELECT exercise_id FROM exercise_progress
WHERE user_id = ? AND status IN ('noch-nicht', 'manchmal', 'meistens')
WHERE dog_id = ? AND status IN ('noch-nicht', 'manchmal', 'meistens')
ORDER BY updated_at ASC LIMIT 50""",
(user["id"],)
(dog_id,)
).fetchall()
valid = [r["exercise_id"] for r in raw
if any(r["exercise_id"].startswith(p) for p in _KNOWN_PREFIXES)]
@ -779,6 +780,21 @@ async def get_hunde_buch(
return HTMLResponse(content=html_page)
# ------------------------------------------------------------------
# GET /api/dogs/verstorben — Alle verstorbenen Hunde des Users
# ------------------------------------------------------------------
@router.get("/verstorben")
async def get_verstorbene_hunde(user=Depends(get_current_user)):
with db() as conn:
rows = conn.execute(
"""SELECT id, name, rasse, foto_url, verstorben_am, geburtstag
FROM dogs WHERE user_id=? AND verstorben_am IS NOT NULL
ORDER BY verstorben_am DESC""",
(user["id"],)
).fetchall()
return [dict(r) for r in rows]
@router.get("/{dog_id}")
async def get_dog(dog_id: int, user=Depends(get_current_user)):
with db() as conn:
@ -1208,14 +1224,15 @@ async def get_dog_timeline(dog_id: int, user=Depends(get_current_user)):
"ref_id": r["id"],
})
# --- Routen ---
# --- Routen (nur Routen wo dieser Hund mitgegangen ist) ---
route_rows = conn.execute(
"""SELECT id, name, distanz_km,
date(created_at) AS datum
FROM routes
WHERE user_id=?
ORDER BY created_at ASC""",
(user["id"],)
"""SELECT r.id, r.name, r.distanz_km,
date(r.created_at) AS datum
FROM routes r
JOIN route_dogs rd ON rd.route_id = r.id AND rd.dog_id = ?
WHERE r.user_id = ?
ORDER BY r.created_at ASC""",
(dog_id, user["id"])
).fetchall()
route_first = True
@ -1263,3 +1280,108 @@ async def get_dog_timeline(dog_id: int, user=Depends(get_current_user)):
"geburtstag": dog["geburtstag"],
"events": events,
}
# ------------------------------------------------------------------
# POST /api/dogs/{id}/gedenken — Hund als verstorben markieren
# ------------------------------------------------------------------
class GedenkenData(BaseModel):
verstorben_am: str # YYYY-MM-DD
@router.post("/{dog_id}/gedenken")
async def mark_verstorben(dog_id: int, data: GedenkenData, user=Depends(get_current_user)):
with db() as conn:
updated = conn.execute(
"UPDATE dogs SET verstorben_am=? WHERE id=? AND user_id=?",
(data.verstorben_am, dog_id, user["id"])
).rowcount
if not updated:
raise HTTPException(404, "Hund nicht gefunden.")
return {"ok": True}
# ------------------------------------------------------------------
# GET /api/dogs/{id}/gedenkseite — Memorial-Daten
# ------------------------------------------------------------------
@router.get("/{dog_id}/gedenkseite")
async def get_gedenkseite(dog_id: int, user=Depends(get_current_user)):
with db() as conn:
dog = conn.execute(
"SELECT * FROM dogs WHERE id=? AND user_id=?", (dog_id, user["id"])
).fetchone()
if not dog:
raise HTTPException(404)
dog = dict(dog)
# Statistiken
km_total = conn.execute(
"SELECT COALESCE(ROUND(SUM(distanz_km),1),0) AS km FROM routes r "
"JOIN route_dogs rd ON rd.route_id=r.id WHERE rd.dog_id=?", (dog_id,)
).fetchone()["km"]
diary_count = conn.execute(
"SELECT COUNT(*) FROM diary WHERE dog_id=?", (dog_id,)
).fetchone()[0]
media_count = conn.execute(
"SELECT COUNT(*) FROM diary_media dm JOIN diary d ON d.id=dm.diary_id "
"WHERE d.dog_id=? AND dm.media_type='image'", (dog_id,)
).fetchone()[0]
training_count = conn.execute(
"SELECT COUNT(*) FROM training_sessions WHERE dog_id=?", (dog_id,)
).fetchone()[0]
# Letzter Tagebucheintrag
last_entry = conn.execute(
"SELECT titel, datum FROM diary WHERE dog_id=? ORDER BY datum DESC LIMIT 1",
(dog_id,)
).fetchone()
# Erste und letzte Aufnahme
first_entry = conn.execute(
"SELECT datum FROM diary WHERE dog_id=? ORDER BY datum ASC LIMIT 1",
(dog_id,)
).fetchone()
# Letzte 6 Fotos für Galerie
photos = conn.execute(
"""SELECT dm.url FROM diary_media dm
JOIN diary d ON d.id=dm.diary_id
WHERE d.dog_id=? AND dm.media_type='image'
AND dm.img_width IS NOT NULL AND dm.img_width > dm.img_height
ORDER BY d.datum DESC, dm.id DESC LIMIT 6""",
(dog_id,)
).fetchall()
if not photos:
photos = conn.execute(
"""SELECT dm.url FROM diary_media dm
JOIN diary d ON d.id=dm.diary_id
WHERE d.dog_id=? AND dm.media_type='image'
ORDER BY d.datum DESC, dm.id DESC LIMIT 6""",
(dog_id,)
).fetchall()
# Gemeinsame Zeit berechnen
joined = dog.get("geburtstag") or (first_entry["datum"] if first_entry else None)
passed = dog.get("verstorben_am")
gemeinsam_tage = None
if joined and passed:
try:
from datetime import date as _date
d1 = _date.fromisoformat(joined)
d2 = _date.fromisoformat(passed)
gemeinsam_tage = (d2 - d1).days
except Exception:
pass
return {
"dog": dog,
"km_total": km_total,
"diary_count": diary_count,
"media_count": media_count,
"training_count": training_count,
"last_entry": dict(last_entry) if last_entry else None,
"gemeinsam_tage": gemeinsam_tage,
"photos": [r["url"] for r in photos],
}