Feature: Subscription-Tier-System (standard/pro/breeder + _test), has_pro_access(), Admin-Tier-UI (SW by-v734)

This commit is contained in:
rene 2026-05-06 18:39:27 +02:00
parent bcc7c27556
commit 71f29dcce0
8 changed files with 104 additions and 12 deletions

View file

@ -81,12 +81,15 @@ def require_admin(user=Depends(get_current_user)):
# ------------------------------------------------------------------
# Schemas
# ------------------------------------------------------------------
_VALID_TIERS = {"standard", "pro", "breeder", "standard_test", "pro_test", "breeder_test"}
class UserPatch(BaseModel):
rolle: Optional[str] = None # user | moderator | admin
is_moderator: Optional[int] = None
is_banned: Optional[int] = None
ban_reason: Optional[str] = None
is_social_media: Optional[int] = None
rolle: Optional[str] = None # user | moderator | admin
is_moderator: Optional[int] = None
is_banned: Optional[int] = None
ban_reason: Optional[str] = None
is_social_media: Optional[int] = None
subscription_tier: Optional[str] = None
class WikiEnrichBody(BaseModel):
limit: int = 10
@ -331,7 +334,7 @@ async def list_users(
SELECT u.id, u.name, {_email_col}, u.rolle, u.is_premium,
u.is_moderator, u.is_banned, u.ban_reason,
u.is_founder, u.is_partner, u.founder_number,
u.created_at, u.last_login,
u.created_at, u.last_login, u.subscription_tier,
(SELECT COUNT(*) FROM dogs d WHERE d.user_id=u.id) AS dog_count,
(SELECT COUNT(*) FROM forum_threads t WHERE t.user_id=u.id AND t.is_deleted=0) AS thread_count,
ROUND(COALESCE((SELECT SUM(r.distanz_km) FROM routes r WHERE r.user_id=u.id), 0), 1) AS total_km,
@ -365,6 +368,10 @@ async def patch_user(uid: int, data: UserPatch, user=Depends(require_mod)):
raise HTTPException(403, "is_moderator darf nur von Admins geändert werden.")
if data.is_social_media is not None and user["rolle"] != "admin":
raise HTTPException(403, "is_social_media darf nur von Admins geändert werden.")
if data.subscription_tier is not None and user["rolle"] != "admin":
raise HTTPException(403, "subscription_tier darf nur von Admins geändert werden.")
if data.subscription_tier is not None and data.subscription_tier not in _VALID_TIERS:
raise HTTPException(400, f"Ungültiger Tier. Erlaubt: {', '.join(sorted(_VALID_TIERS))}")
with db() as conn:
target = conn.execute("SELECT id, rolle, name FROM users WHERE id=?", (uid,)).fetchone()
@ -385,7 +392,7 @@ async def patch_user(uid: int, data: UserPatch, user=Depends(require_mod)):
cols = ", ".join(f"{k}=?" for k in updates)
conn.execute(f"UPDATE users SET {cols} WHERE id=?", [*updates.values(), uid])
row = conn.execute(
"SELECT id, name, email, rolle, is_moderator, is_banned, ban_reason FROM users WHERE id=?",
"SELECT id, name, email, rolle, is_moderator, is_banned, ban_reason, subscription_tier FROM users WHERE id=?",
(uid,)
).fetchone()
@ -395,6 +402,8 @@ async def patch_user(uid: int, data: UserPatch, user=Depends(require_mod)):
detail_parts.append("gesperrt" if updates["is_banned"] else "entsperrt")
if "rolle" in updates:
detail_parts.append(f"Rolle→{updates['rolle']}")
if "subscription_tier" in updates:
detail_parts.append(f"Tier→{updates['subscription_tier']}")
_audit(conn, user, "user_patch", f"user:{uid} ({target['name']})", ", ".join(detail_parts) or None)
return dict(row)