diff --git a/backend/database.py b/backend/database.py
index e428a56..9822d42 100644
--- a/backend/database.py
+++ b/backend/database.py
@@ -560,9 +560,10 @@ def _migrate(conn_factory):
("users", "ki_zucht_beschreibung", "INTEGER NOT NULL DEFAULT 1"),
("users", "ki_zucht_jahresbericht", "INTEGER NOT NULL DEFAULT 1"),
# Partner-Code + Gründer-Lizenz
- ("users", "is_founder", "INTEGER NOT NULL DEFAULT 0"),
- ("users", "is_partner", "INTEGER NOT NULL DEFAULT 0"),
- ("users", "founder_number", "INTEGER"),
+ ("users", "is_founder", "INTEGER NOT NULL DEFAULT 0"),
+ ("users", "is_partner", "INTEGER NOT NULL DEFAULT 0"),
+ ("users", "founder_number", "INTEGER"),
+ ("users", "is_founder_pending", "INTEGER NOT NULL DEFAULT 0"),
]
with conn_factory() as conn:
for table, column, col_type in migrations:
diff --git a/backend/routes/auth.py b/backend/routes/auth.py
index e46cda0..fb30584 100644
--- a/backend/routes/auth.py
+++ b/backend/routes/auth.py
@@ -97,9 +97,8 @@ async def register(data: RegisterRequest, response: Response, request: Request):
"SELECT COUNT(*) FROM users WHERE is_founder=1"
).fetchone()[0]
if total_founders < 100:
- founder_num = total_founders + 1
- updates["is_founder"] = 1
- updates["founder_number"] = founder_num
+ # Pending — wird nach erstem Hunde-Profil mit Plausibilitätsprüfung aktiviert
+ updates["is_founder_pending"] = 1
set_clause = ", ".join(f"{k}=?" for k in updates)
conn.execute(
f"UPDATE users SET {set_clause} WHERE id=?",
@@ -198,7 +197,7 @@ async def me(user=Depends(get_current_user)):
"""SELECT id, name, real_name, email, rolle, is_premium, email_verified,
bio, wohnort, erfahrung, social_link,
profil_sichtbarkeit, avatar_url, created_at,
- is_founder, is_partner, founder_number
+ is_founder, is_partner, founder_number, is_founder_pending
FROM users WHERE id=?""",
(user["id"],)
).fetchone()
diff --git a/backend/routes/dogs.py b/backend/routes/dogs.py
index 8e176f8..74f1c95 100644
--- a/backend/routes/dogs.py
+++ b/backend/routes/dogs.py
@@ -78,6 +78,41 @@ async def list_dogs(user=Depends(get_current_user)):
return result
+def _is_plausible_dog(name: str, rasse: str, geburtstag) -> tuple[bool, str]:
+ """Einfache Plausibilitätsprüfung für Hunde-Profile."""
+ import re, datetime
+ name = (name or "").strip()
+ rasse = (rasse or "").strip()
+
+ if len(name) < 2:
+ return False, "Der Name muss mindestens 2 Zeichen haben."
+ if not re.search(r'[a-zA-ZäöüÄÖÜß]', name):
+ return False, "Der Name muss mindestens einen Buchstaben enthalten."
+ if len(set(name.lower())) < 2:
+ return False, "Bitte einen echten Namen eingeben."
+
+ if rasse and len(rasse) < 2:
+ return False, "Bitte eine gültige Rasse eingeben."
+ if rasse and not re.search(r'[a-zA-ZäöüÄÖÜß]', rasse):
+ return False, "Die Rasse muss Buchstaben enthalten."
+
+ if geburtstag:
+ try:
+ if isinstance(geburtstag, str):
+ year = int(geburtstag[:4])
+ else:
+ year = geburtstag.year
+ now = datetime.date.today().year
+ if year > now:
+ return False, "Das Geburtsdatum liegt in der Zukunft."
+ if year < now - 30:
+ return False, "Das Geburtsdatum ist unrealistisch."
+ except Exception:
+ pass
+
+ return True, ""
+
+
@router.post("")
async def create_dog(data: DogCreate, user=Depends(get_current_user)):
with db() as conn:
@@ -93,6 +128,28 @@ async def create_dog(data: DogCreate, user=Depends(get_current_user)):
"SELECT * FROM dogs WHERE user_id=? ORDER BY id DESC LIMIT 1",
(user["id"],)
).fetchone()
+
+ # Gründer-Aktivierung: erstes Hunde-Profil + is_founder_pending
+ user_row = conn.execute(
+ "SELECT is_founder_pending, is_founder FROM users WHERE id=?",
+ (user["id"],)
+ ).fetchone()
+ if user_row and user_row["is_founder_pending"] and not user_row["is_founder"]:
+ dog_count = conn.execute(
+ "SELECT COUNT(*) FROM dogs WHERE user_id=?", (user["id"],)
+ ).fetchone()[0]
+ if dog_count == 1: # genau dieser erste Hund
+ plausible, reason = _is_plausible_dog(data.name, data.rasse, data.geburtstag)
+ if plausible:
+ total = conn.execute(
+ "SELECT COUNT(*) FROM users WHERE is_founder=1"
+ ).fetchone()[0]
+ if total < 100:
+ conn.execute(
+ "UPDATE users SET is_founder=1, founder_number=?, is_founder_pending=0 WHERE id=?",
+ (total + 1, user["id"])
+ )
+
return dict(dog)
diff --git a/backend/static/js/app.js b/backend/static/js/app.js
index 692526b..3680a86 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 = '542'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
+const APP_VER = '543'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.1.4'; // ← semantische Version, wird bei make release gesetzt
const IS_STAGING = location.hostname === 'staging.banyaro.app';
diff --git a/backend/static/js/pages/settings.js b/backend/static/js/pages/settings.js
index 9a6b596..8f45cf6 100644
--- a/backend/static/js/pages/settings.js
+++ b/backend/static/js/pages/settings.js
@@ -149,6 +149,12 @@ window.Page_settings = (() => {
? `
${u.founder_number ? `Gründer #${u.founder_number}` : 'Gründer'}
+ `
+ : u.is_founder_pending
+ ? `
+
+ Gründer-Platz reserviert
` : ''}
${u.is_partner
? `
@@ -1525,7 +1531,9 @@ window.Page_settings = (() => {
_appState.activeDog = null;
document.getElementById('header-login-btn')?.remove();
- const greeting = _appState.user.is_founder
+ const greeting = _appState.user.is_founder_pending
+ ? `Willkommen, ${_appState.user.name}! 🎉 Dein Gründer-Platz ist reserviert — leg jetzt dein Hunde-Profil an um ihn zu sichern!`
+ : _appState.user.is_founder
? `Willkommen, Gründer ${_appState.user.name}! 🎉`
: `Willkommen bei Ban Yaro, ${_appState.user.name}!`;
UI.toast.success(greeting);
diff --git a/backend/static/sw.js b/backend/static/sw.js
index d493e2a..54f2cc0 100644
--- a/backend/static/sw.js
+++ b/backend/static/sw.js
@@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache
============================================================ */
-const CACHE_VERSION = 'by-v565';
+const CACHE_VERSION = 'by-v566';
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache