Feature: Gründer-Aktivierung nach Hunde-Profil mit Plausibilitätsprüfung
- is_founder_pending: bei Registrierung mit Code gesetzt (statt sofort is_founder) - dogs.py: erstes Hunde-Profil → Plausibilitätsprüfung → is_founder aktivieren - Prüfung: Name min. 2 Zeichen + Buchstaben, Rasse gültig, Geburtsjahr realistisch - Settings: gelbes 'Gründer-Platz reserviert' Badge mit Link zu Hunde-Profil - Onboarding-Toast informiert über nötiges Hunde-Profil - SW by-v566, APP_VER 543
This commit is contained in:
parent
7fd71342da
commit
230455c250
6 changed files with 75 additions and 10 deletions
|
|
@ -560,9 +560,10 @@ def _migrate(conn_factory):
|
||||||
("users", "ki_zucht_beschreibung", "INTEGER NOT NULL DEFAULT 1"),
|
("users", "ki_zucht_beschreibung", "INTEGER NOT NULL DEFAULT 1"),
|
||||||
("users", "ki_zucht_jahresbericht", "INTEGER NOT NULL DEFAULT 1"),
|
("users", "ki_zucht_jahresbericht", "INTEGER NOT NULL DEFAULT 1"),
|
||||||
# Partner-Code + Gründer-Lizenz
|
# Partner-Code + Gründer-Lizenz
|
||||||
("users", "is_founder", "INTEGER NOT NULL DEFAULT 0"),
|
("users", "is_founder", "INTEGER NOT NULL DEFAULT 0"),
|
||||||
("users", "is_partner", "INTEGER NOT NULL DEFAULT 0"),
|
("users", "is_partner", "INTEGER NOT NULL DEFAULT 0"),
|
||||||
("users", "founder_number", "INTEGER"),
|
("users", "founder_number", "INTEGER"),
|
||||||
|
("users", "is_founder_pending", "INTEGER NOT NULL DEFAULT 0"),
|
||||||
]
|
]
|
||||||
with conn_factory() as conn:
|
with conn_factory() as conn:
|
||||||
for table, column, col_type in migrations:
|
for table, column, col_type in migrations:
|
||||||
|
|
|
||||||
|
|
@ -97,9 +97,8 @@ async def register(data: RegisterRequest, response: Response, request: Request):
|
||||||
"SELECT COUNT(*) FROM users WHERE is_founder=1"
|
"SELECT COUNT(*) FROM users WHERE is_founder=1"
|
||||||
).fetchone()[0]
|
).fetchone()[0]
|
||||||
if total_founders < 100:
|
if total_founders < 100:
|
||||||
founder_num = total_founders + 1
|
# Pending — wird nach erstem Hunde-Profil mit Plausibilitätsprüfung aktiviert
|
||||||
updates["is_founder"] = 1
|
updates["is_founder_pending"] = 1
|
||||||
updates["founder_number"] = founder_num
|
|
||||||
set_clause = ", ".join(f"{k}=?" for k in updates)
|
set_clause = ", ".join(f"{k}=?" for k in updates)
|
||||||
conn.execute(
|
conn.execute(
|
||||||
f"UPDATE users SET {set_clause} WHERE id=?",
|
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,
|
"""SELECT id, name, real_name, email, rolle, is_premium, email_verified,
|
||||||
bio, wohnort, erfahrung, social_link,
|
bio, wohnort, erfahrung, social_link,
|
||||||
profil_sichtbarkeit, avatar_url, created_at,
|
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=?""",
|
FROM users WHERE id=?""",
|
||||||
(user["id"],)
|
(user["id"],)
|
||||||
).fetchone()
|
).fetchone()
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,41 @@ async def list_dogs(user=Depends(get_current_user)):
|
||||||
return result
|
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("")
|
@router.post("")
|
||||||
async def create_dog(data: DogCreate, user=Depends(get_current_user)):
|
async def create_dog(data: DogCreate, user=Depends(get_current_user)):
|
||||||
with db() as conn:
|
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",
|
"SELECT * FROM dogs WHERE user_id=? ORDER BY id DESC LIMIT 1",
|
||||||
(user["id"],)
|
(user["id"],)
|
||||||
).fetchone()
|
).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)
|
return dict(dog)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Router, State-Management, Navigation, Initialisierung.
|
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 APP_VERSION = '1.1.4'; // ← semantische Version, wird bei make release gesetzt
|
||||||
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,12 @@ window.Page_settings = (() => {
|
||||||
? `<span class="badge" style="background:#7c3aed;color:#fff;cursor:pointer" data-page="gruender">
|
? `<span class="badge" style="background:#7c3aed;color:#fff;cursor:pointer" data-page="gruender">
|
||||||
<svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px"><use href="/icons/phosphor.svg#key"></use></svg>
|
<svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px"><use href="/icons/phosphor.svg#key"></use></svg>
|
||||||
${u.founder_number ? `Gründer #${u.founder_number}` : 'Gründer'}
|
${u.founder_number ? `Gründer #${u.founder_number}` : 'Gründer'}
|
||||||
|
</span>`
|
||||||
|
: u.is_founder_pending
|
||||||
|
? `<span class="badge" style="background:#f59e0b;color:#fff;cursor:pointer" data-page="dog-profile"
|
||||||
|
title="Hunde-Profil anlegen um Gründer-Platz zu sichern">
|
||||||
|
<svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px"><use href="/icons/phosphor.svg#hourglass"></use></svg>
|
||||||
|
Gründer-Platz reserviert
|
||||||
</span>` : ''}
|
</span>` : ''}
|
||||||
${u.is_partner
|
${u.is_partner
|
||||||
? `<span class="badge" style="background:#0ea5e9;color:#fff">
|
? `<span class="badge" style="background:#0ea5e9;color:#fff">
|
||||||
|
|
@ -1525,7 +1531,9 @@ window.Page_settings = (() => {
|
||||||
_appState.activeDog = null;
|
_appState.activeDog = null;
|
||||||
|
|
||||||
document.getElementById('header-login-btn')?.remove();
|
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, Gründer ${_appState.user.name}! 🎉`
|
||||||
: `Willkommen bei Ban Yaro, ${_appState.user.name}!`;
|
: `Willkommen bei Ban Yaro, ${_appState.user.name}!`;
|
||||||
UI.toast.success(greeting);
|
UI.toast.success(greeting);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Offline-Cache + Push Notifications + Tile-Cache
|
Offline-Cache + Push Notifications + Tile-Cache
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
const CACHE_VERSION = 'by-v565';
|
const CACHE_VERSION = 'by-v566';
|
||||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||||
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache
|
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue