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 @@
- ${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