From 487dacc7c74b5b11300098e96cfdaa000d669ac7 Mon Sep 17 00:00:00 2001 From: rene Date: Sun, 7 Jun 2026 20:04:43 +0200 Subject: [PATCH] =?UTF-8?q?Fix:=20/breeder/my-editor=20Endpoint=20(Crash?= =?UTF-8?q?=20'Cannot=20destructure=20profile')=20+=20L=C3=A4ufigkeit=20in?= =?UTF-8?q?=20Z=C3=BCchter-Bereich?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit breeder-editor.js (aus 459cd42) rief /api/breeder/my-editor auf — Endpoint existierte nie (gleicher Worktree-Verlust wie /partner/my-profile). Jetzt gebaut: profile + litters + storage_mb/limit; ohne Profil klare 404 statt Destrukturierungs-Crash. 3 Tests. Läufigkeit (Rene): eigener HUND-Chip entfällt, stattdessen vierte Karte im Züchter-Bereich (Zyklen, Progesterontests, Deckdaten). Suite: 58 passed. --- VERSION | 2 +- backend/routes/breeder.py | 37 ++++++++++++++++++++ backend/static/index.html | 24 ++++++------- backend/static/js/app.js | 2 +- backend/static/js/pages/breeder-dashboard.js | 15 ++++++-- backend/static/js/worlds.js | 3 +- backend/static/landing.html | 2 +- backend/static/sw.js | 2 +- tests/test_breeder_editor.py | 33 +++++++++++++++++ 9 files changed, 100 insertions(+), 20 deletions(-) create mode 100644 tests/test_breeder_editor.py diff --git a/VERSION b/VERSION index 4c8735e..f845c1c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1266 \ No newline at end of file +1267 \ No newline at end of file diff --git a/backend/routes/breeder.py b/backend/routes/breeder.py index e53a1d4..8279229 100644 --- a/backend/routes/breeder.py +++ b/backend/routes/breeder.py @@ -491,6 +491,43 @@ class BreederProfileUpdate(BaseModel): website: Optional[str] = Field(None, max_length=500) beschreibung: Optional[str] = Field(None, max_length=10000) +@router.get("/breeder/my-editor") +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.)""" + with db() as conn: + profile = conn.execute( + "SELECT * FROM breeder_profiles WHERE user_id=?", (user["id"],) + ).fetchone() + if not profile: + raise HTTPException(404, "Noch kein Züchter-Profil angelegt.") + profile = dict(profile) + litters = [dict(r) for r in conn.execute( + "SELECT * FROM litters WHERE breeder_id=? ORDER BY created_at DESC", + (profile["id"],) + ).fetchall()] + + # Speicherverbrauch der Züchter-Medien (MEDIA_DIR/breeders/{breeder_id}/**) + media_dir = os.getenv("MEDIA_DIR", "/data/media") + base = os.path.join(media_dir, "breeders", str(profile["id"])) + total = 0 + if os.path.isdir(base): + for root, _dirs, files in os.walk(base): + for f in files: + try: + total += os.path.getsize(os.path.join(root, f)) + except OSError: + pass + + return { + "profile": profile, + "litters": litters, + "storage_mb": round(total / (1024 * 1024), 4), + "storage_limit_mb": 200, + } + + @router.put("/breeder/profile") async def update_breeder_profile(body: BreederProfileUpdate, user=Depends(require_breeder)): with db() as conn: diff --git a/backend/static/index.html b/backend/static/index.html index 80ab2df..207852f 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -86,14 +86,14 @@ Ban Yaro - + - - - - - + + + + + @@ -620,11 +620,11 @@ - - - - - + + + + + @@ -634,7 +634,7 @@ - + diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 9b433b3..aa3b099 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 = '1266'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '1267'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator) window.APP_VERSION = APP_VERSION; diff --git a/backend/static/js/pages/breeder-dashboard.js b/backend/static/js/pages/breeder-dashboard.js index 4fde179..ac66f30 100644 --- a/backend/static/js/pages/breeder-dashboard.js +++ b/backend/static/js/pages/breeder-dashboard.js @@ -112,8 +112,19 @@ window.Page_breeder_dashboard = (() => { -
- ${UI.icon('info')} Läufigkeit & Trächtigkeit findest du wie gewohnt in der HUND-Welt. + +
+
+
+ +
+
+
Läufigkeit & Trächtigkeit
+
Zyklen, Progesterontests, Deckdaten, Meilensteine
+
+ +
`; } diff --git a/backend/static/js/worlds.js b/backend/static/js/worlds.js index 936b348..84830ec 100644 --- a/backend/static/js/worlds.js +++ b/backend/static/js/worlds.js @@ -573,7 +573,6 @@ window.Worlds = (() => { { icon:'certificate', label:'Züchter', page:'breeder-dashboard', role:'breeder', fab:[{ icon:'notebook', color:'#10B981', label:'Wurf anlegen', sub:'Neuen Wurf eintragen', page:'litters', action:'openNew' }, { icon:'tree-structure', color:'#8B5CF6', label:'Zuchthund eintragen', sub:'Neuen Hund anlegen', page:'zuchthunde', action:'openNew' }] }, - { icon:'thermometer', label:'Läufigkeit', page:'laeufi', role:'breeder' }, { icon:'sparkle', label:'Social', page:'social', role:'social', fab:[{ icon:'sparkle', color:'#EC4899', label:'Social-Post', sub:'Beitrag erstellen', page:'social', action:'openNew' }] }, { icon:'shield-check', label:'Moderation', page:'moderation', role:'mod' }, @@ -589,7 +588,7 @@ window.Worlds = (() => { const _DEFAULT_CONFIG = { jetzt: ['notes','expenses','erste-hilfe','playdate','chat','wetter','social','moderation','partner-dashboard','admin'], hund: ['diary','health','uebungen','trainingsplaene','adoption','sitting','wiki','wurfboerse', - 'breeder-dashboard','laeufi','ernaehrung','personality'], + 'breeder-dashboard','ernaehrung','personality'], welt: ['map','forum','friends','walks','poison','recalls','lost','routes','events', 'jobs','knigge','movies','reise'], }; diff --git a/backend/static/landing.html b/backend/static/landing.html index 9ead496..654b373 100644 --- a/backend/static/landing.html +++ b/backend/static/landing.html @@ -4,7 +4,7 @@ - + Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz diff --git a/backend/static/sw.js b/backend/static/sw.js index 46913ce..24b1305 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -4,7 +4,7 @@ ============================================================ */ // ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab -const VER = '1266'; +const VER = '1267'; const CACHE_VERSION = `by-v${VER}`; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten diff --git a/tests/test_breeder_editor.py b/tests/test_breeder_editor.py new file mode 100644 index 0000000..bd520d8 --- /dev/null +++ b/tests/test_breeder_editor.py @@ -0,0 +1,33 @@ +"""Smoke-Tests fuer den Zuechter-Profil-Editor-Endpoint (/breeder/my-editor).""" + + +def test_my_editor_requires_breeder(client, user): + r = client.get("/api/breeder/my-editor", headers=user["headers"]) + assert r.status_code == 403 + + +def test_my_editor_without_profile_404(client, admin): + """Admin ohne Zuechterprofil -> klare 404-Meldung statt Frontend-Crash.""" + r = client.get("/api/breeder/my-editor", headers=admin["headers"]) + assert r.status_code == 404 + assert "Profil" in r.json()["detail"] + + +def test_my_editor_with_profile(client, user): + """Zuechter mit Profil -> profile + litters + storage.""" + from database import db + with db() as conn: + uid = conn.execute("SELECT id FROM users WHERE email=?", (user["email"],)).fetchone()["id"] + conn.execute("UPDATE users SET rolle='breeder' WHERE id=?", (uid,)) + conn.execute( + """INSERT INTO breeder_profiles (user_id, zwingername, rasse_text, verein, stadt) + VALUES (?,?,?,?,?)""", + (uid, "Vom Teststall", "Labrador", "VDH", "Ebersberg") + ) + r = client.get("/api/breeder/my-editor", headers=user["headers"]) + assert r.status_code == 200, r.text + d = r.json() + assert d["profile"]["zwingername"] == "Vom Teststall" + assert isinstance(d["litters"], list) + assert d["storage_limit_mb"] == 200 + assert d["storage_mb"] >= 0