"""Admin-Endpoints: User-CRUD. Nur für Admins.""" from __future__ import annotations import logging from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel, Field from sqlmodel import Session, select from app.core.auth import admin_required, get_user_by_username, hash_password from app.core.db import get_session from app.models.user import User router = APIRouter(prefix="/api/admin", tags=["admin"], dependencies=[Depends(admin_required)]) log = logging.getLogger("voice-agent.admin") class CreateUserIn(BaseModel): username: str = Field(min_length=3, max_length=64, pattern=r"^[A-Za-z0-9._@-]+$") password: str = Field(min_length=8, max_length=200) is_admin: bool = False class ResetPasswordIn(BaseModel): password: str = Field(min_length=8, max_length=200) def _user_dict(u: User) -> dict: return { "id": u.id, "username": u.username, "is_admin": u.is_admin, "created_at": u.created_at.isoformat(), } @router.get("/users") def list_users(session: Session = Depends(get_session)): users = session.exec(select(User).order_by(User.created_at)).all() return [_user_dict(u) for u in users] @router.post("/users", status_code=status.HTTP_201_CREATED) def create_user(payload: CreateUserIn, session: Session = Depends(get_session)): if get_user_by_username(session, payload.username.strip()): raise HTTPException(status.HTTP_409_CONFLICT, f"Benutzer '{payload.username}' existiert bereits") user = User( username=payload.username.strip(), password_hash=hash_password(payload.password), is_admin=payload.is_admin, ) session.add(user) session.commit() session.refresh(user) log.info("User angelegt: %s admin=%s", user.username, user.is_admin) return _user_dict(user) @router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_user(user_id: int, session: Session = Depends(get_session), me: User = Depends(admin_required)): if user_id == me.id: raise HTTPException(status.HTTP_400_BAD_REQUEST, "Eigenen Account kann man nicht löschen") user = session.get(User, user_id) if not user: raise HTTPException(status.HTTP_404_NOT_FOUND, "Benutzer nicht gefunden") # Sicherheit: mind. ein Admin muss bleiben if user.is_admin: admins = session.exec(select(User).where(User.is_admin == True)).all() # noqa: E712 if len(admins) <= 1: raise HTTPException(status.HTTP_400_BAD_REQUEST, "Letzten Admin kann man nicht löschen") session.delete(user) session.commit() log.info("User gelöscht: id=%d username=%s", user_id, user.username) return None @router.post("/users/{user_id}/password") def reset_password(user_id: int, payload: ResetPasswordIn, session: Session = Depends(get_session)): user = session.get(User, user_id) if not user: raise HTTPException(status.HTTP_404_NOT_FOUND, "Benutzer nicht gefunden") user.password_hash = hash_password(payload.password) session.add(user) session.commit() log.info("Passwort zurückgesetzt: user=%s", user.username) return {"ok": True}