Züchter-Editor: Wurfnamen sichtbar, 'undefined Medien' gefixt, Mitgliedschaften & Zertifikate

Rene: 'Züchter sollten mehr Einfluss haben — Wurfnamen (B-Wurf), Mitglied-
schaften und Zertifikate fürs Profil.'

- Wurfnamen: Infrastruktur existierte komplett (wurf_rang/wurf_name in DB,
  Backend, Wurfverwaltungs-Formular) — war nur im Editor und auf der
  öffentlichen Seite unsichtbar. Editor-Karten zeigen jetzt 'B-Wurf · Name'
  (Feldname-Bug geburtsdatum→geburt_datum), öffentliche Wurf-Karten bekommen
  den Rang/Namen als Badge, Hinweis im Editor verlinkt zur Vergabe.
- 'undefined Medien': my-editor lieferte kein foto_count (+photos fürs
  Profil-Grid) — ergänzt.
- NEU Mitgliedschaften & Zertifikate: entity_type 'certificate' im Foto-
  System (Ownership wie breeder), Editor-Sektion mit Upload (Bezeichnung als
  Caption) + Löschen, öffentliche Profilseite zeigt eigene Sektion mit
  Logos/Urkunden (klickbar, lazy). Public-Endpoint liefert result.zertifikate.

Tests: my-editor inkl. Wurfname/foto_count, Zertifikat-Roundtrip bis zur
öffentlichen Seite. Suite: 61 passed.
This commit is contained in:
rene 2026-06-07 21:00:14 +02:00
parent dfffd07a96
commit 5f01abc590
10 changed files with 186 additions and 28 deletions

View file

@ -402,7 +402,8 @@ async def breeder_public_profile(zwingername: str):
# Sichtbare Würfe
wuerfe = conn.execute("""
SELECT id, vater_name, mutter_name, geburt_datum, erwartetes_datum,
SELECT id, wurf_rang, wurf_name, vater_name, mutter_name,
geburt_datum, erwartetes_datum,
status, welpen_gesamt, welpen_verfuegbar, preis_spanne, beschreibung
FROM litters
WHERE breeder_id=? AND sichtbar=1 AND status != 'abgeschlossen'
@ -410,6 +411,19 @@ async def breeder_public_profile(zwingername: str):
""", (breeder_id,)).fetchall()
result["wuerfe"] = [dict(w) for w in wuerfe]
# Mitgliedschaften & Zertifikate (öffentliche Logos/Badges mit Caption)
certs = conn.execute("""
SELECT id, file_path, thumbnail_path, caption FROM breeder_photos
WHERE breeder_id=? AND entity_type='certificate' AND visibility='public'
ORDER BY sort_order
""", (breeder_id,)).fetchall()
result["zertifikate"] = [{
"id": c["id"],
"url": f"/media/{c['file_path']}",
"thumbnail_url": f"/media/{c['thumbnail_path']}" if c["thumbnail_path"] else f"/media/{c['file_path']}",
"caption": c["caption"],
} for c in certs]
# Gesundheits-Statistik (aggregiert über alle öffentlichen Hunde)
hd_stats = conn.execute("""
SELECT ergebnis, COUNT(*) as cnt FROM dog_health_tests
@ -496,6 +510,7 @@ async def breeder_my_editor(user=Depends(require_breeder)):
"""Daten für den Profil-Editor: Profil + eigene Würfe + Speicherverbrauch.
(Frontend breeder-editor.js stammt aus 459cd42 dieser Lese-Endpoint
ging damals im Worktree-Merge verloren, wie /partner/my-profile.)"""
from routes.breeder_photos import _photo_dict
with db() as conn:
profile = conn.execute(
"SELECT * FROM breeder_profiles WHERE user_id=?", (user["id"],)
@ -503,8 +518,20 @@ async def breeder_my_editor(user=Depends(require_breeder)):
if not profile:
raise HTTPException(404, "Noch kein Züchter-Profil angelegt.")
profile = dict(profile)
profile["photos"] = [_photo_dict(r) for r in conn.execute(
"SELECT * FROM breeder_photos WHERE breeder_id=? AND entity_type='breeder' ORDER BY sort_order",
(profile["id"],)
).fetchall()]
# Mitgliedschaften & Zertifikate (Logos/Badges fürs öffentliche Profil)
profile["certificates"] = [_photo_dict(r) for r in conn.execute(
"SELECT * FROM breeder_photos WHERE breeder_id=? AND entity_type='certificate' ORDER BY sort_order",
(profile["id"],)
).fetchall()]
litters = [dict(r) for r in conn.execute(
"SELECT * FROM litters WHERE breeder_id=? ORDER BY created_at DESC",
"""SELECT l.*,
(SELECT COUNT(*) FROM breeder_photos p
WHERE p.entity_type='litter' AND p.entity_id=l.id) AS foto_count
FROM litters l WHERE l.breeder_id=? ORDER BY l.created_at DESC""",
(profile["id"],)
).fetchall()]