feat(2a): Erlaubte Sprachen pro Nutzer (Admin-Vorgabe)

Der Admin schaltet pro Nutzer Sprachen frei (Toggle-Chips in der
Nutzerverwaltung). Die Haupt-App zeigt dann nur diese Sprachen; bei genau
einer erlaubten Sprache wird das Sprachmenü ausgeblendet (kein Stress).

- admin.py: PUT /admin/users/{id}/prefs (allowed_languages), list_users
  liefert allowed_languages mit.
- dependencies.py: resolve_route klemmt die Sprache auf eine erlaubte
  (Backend-Durchsetzung, nicht nur UI).
- app.js: applyAllowedLanguages (Haupt-UI-Filter) + Sprach-Chips je Nutzer
  im Admin. app.js v=44.
- Tests: Klemmung + Admin-Endpunkt. 213 passed.

Login-/nginx-Umbau (Ein-Klick-Link) folgt nach separater Design-Abstimmung.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-25 04:06:09 +02:00
commit cc354938df
7 changed files with 103 additions and 6 deletions

View file

@ -17,7 +17,7 @@ from app.auth import is_admin_user, require_admin, require_admin_or_user
from app.config import settings
from app.dependencies import get_store
from app.runtime_config import RUNTIME_SETTABLE, invalidate_cache, runtime_settings
from app.schemas import MemoryCreate, MemoryOut, UserCreate, UserCreated, UserUpdate
from app.schemas import AdminUserPrefsUpdate, MemoryCreate, MemoryOut, UserCreate, UserCreated, UserUpdate
router = APIRouter()
@ -94,6 +94,18 @@ async def reset_token(user_id: str):
return UserCreated(user_id=user.id, display_name=user.display_name, token=token)
@router.put("/admin/users/{user_id}/prefs", dependencies=[Depends(require_admin)])
async def update_user_prefs(user_id: str, payload: AdminUserPrefsUpdate):
"""Setzt Nutzer-Einstellungen (z. B. erlaubte Sprachen). Merge mit bestehenden Prefs."""
store = get_store()
current = store.get_user(user_id)
if current is None:
raise HTTPException(status_code=404, detail=f"Nutzer {user_id!r} nicht gefunden.")
new = {k: v for k, v in payload.model_dump().items() if v is not None}
updated = store.set_user_prefs(user_id, {**current.prefs, **new})
return {"user_id": updated.id, "prefs": updated.prefs}
@router.put("/admin/users/{user_id}", dependencies=[Depends(require_admin)])
async def update_user(user_id: str, payload: UserUpdate):
"""Aktualisiert den Anzeigenamen eines Nutzers (z. B. nach erstem SSO-Login)."""
@ -122,6 +134,7 @@ async def list_users():
"display_name": u.display_name,
"external_id": u.external_id,
"created_at": u.created_at,
"allowed_languages": (u.prefs or {}).get("allowed_languages", ""),
}
for u in get_store().list_users()
]