Token-Budget Hard-Stop + Banner bei aufgebrauchtem Budget

- check_license() liefert jetzt unlimited_budget, credits_total, credits_used,
  read_only_reason. Bei nicht-unlimited UND credits_used >= credits_total wird
  status=budget_exceeded, read_only=True gesetzt.
- require_writable_license blockiert mit 403 + X-License-Status-Header je nach Reason.
- /api/auth/me liefert read_only_reason und unlimited_budget; credits_percent_used
  wird nicht mehr auf 100 gekappt (echte Prozente).
- Frontend: Banner-Text dynamisch je nach reason (budget_exceeded/expired/...).
  Refresh-Button bei read_only deaktiviert + Tooltip. Globaler 403-Handler in
  api.js: bei X-License-Status -> Banner + Toast aktualisieren.
Dieser Commit ist enthalten in:
Claude Code
2026-05-02 20:16:25 +00:00
Ursprung 2b1e8c3632
Commit ee83f38edf
6 geänderte Dateien mit 123 neuen und 12 gelöschten Zeilen

Datei anzeigen

@@ -40,12 +40,25 @@ async def require_writable_license(
) -> dict:
"""Dependency die sicherstellt, dass die Lizenz Schreibzugriff erlaubt.
Blockiert neue Lagen/Refreshes bei abgelaufener Lizenz (Nur-Lesen-Modus).
Blockiert neue Lagen/Refreshes bei abgelaufener Lizenz, deaktivierter Org
oder aufgebrauchtem Token-Budget (Hard-Stop).
"""
lic = current_user.get("license", {})
if lic.get("read_only"):
reason = lic.get("read_only_reason") or "expired"
if reason == "budget_exceeded":
detail = "Token-Budget aufgebraucht. Bitte Verwaltung kontaktieren."
elif reason == "expired":
detail = "Lizenz abgelaufen. Nur Lesezugriff moeglich."
elif reason == "no_license":
detail = "Keine aktive Lizenz. Bitte Verwaltung kontaktieren."
elif reason == "org_disabled":
detail = "Organisation deaktiviert. Bitte Support kontaktieren."
else:
detail = lic.get("message") or "Nur Lesezugriff moeglich."
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Lizenz abgelaufen oder widerrufen. Nur Lesezugriff moeglich.",
detail=detail,
headers={"X-License-Status": reason},
)
return current_user