import logging from fastapi import APIRouter, Depends, HTTPException, Response, status from sqlalchemy.orm import Session from app.core.rbac import get_user_permission_names, require_permission from app.db.database import get_db from app.models.user import User from app.repositories.rbac_repository import RbacRepository from app.repositories.user_repository import UserRepository from app.schemas.user import UserCreate, UserPasswordUpdate, UserResponse, UserUpdate logger = logging.getLogger(__name__) router = APIRouter( prefix="/users", tags=["Users"], ) def get_user_or_404(db: Session, user_id: int) -> User: db_user = UserRepository.get_by_id(db, user_id) if db_user is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Benutzer nicht gefunden", ) return db_user def raise_conflict(field: str) -> None: if field == "username": detail = "Benutzername ist bereits vergeben" else: detail = "E-Mail ist bereits vergeben" raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=detail, ) def require_actor_permission(current_user: User, permission: str) -> None: if permission not in get_user_permission_names(current_user): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Keine Berechtigung", ) def apply_role(db: Session, user: UserCreate | UserUpdate) -> None: role = None if user.role_id is not None: role = RbacRepository.get_role_by_id(db, user.role_id) else: role = RbacRepository.get_role_by_name(db, user.role) if role is None: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Rolle nicht gefunden", ) user.role_id = role.id user.role = role.name @router.get("", response_model=list[UserResponse]) @router.get("/", response_model=list[UserResponse], include_in_schema=False) def get_users( db: Session = Depends(get_db), current_user: User = Depends(require_permission("users.read")), ): logger.info("users.list", extra={"actor_user_id": current_user.id}) return UserRepository.get_all(db) @router.get("/{user_id}", response_model=UserResponse) def get_user( user_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_permission("users.read")), ): logger.info( "users.detail", extra={"actor_user_id": current_user.id, "target_user_id": user_id}, ) return get_user_or_404(db, user_id) @router.post("", response_model=UserResponse, status_code=status.HTTP_201_CREATED) @router.post( "/", response_model=UserResponse, status_code=status.HTTP_201_CREATED, include_in_schema=False, ) def create_user( user: UserCreate, db: Session = Depends(get_db), current_user: User = Depends(require_permission("users.create")), ): require_actor_permission(current_user, "roles.assign") apply_role(db, user) conflict = UserRepository.find_conflict( db, username=user.username, email=str(user.email), ) if conflict is not None: raise_conflict(conflict[0]) logger.info("users.create", extra={"actor_user_id": current_user.id}) return UserRepository.create(db, user) @router.put("/{user_id}", response_model=UserResponse) def update_user( user_id: int, user: UserUpdate, db: Session = Depends(get_db), current_user: User = Depends(require_permission("users.update")), ): db_user = get_user_or_404(db, user_id) apply_role(db, user) if user.role_id != db_user.role_id: require_actor_permission(current_user, "roles.assign") conflict = UserRepository.find_conflict( db, username=user.username, email=str(user.email), exclude_user_id=user_id, ) if conflict is not None: raise_conflict(conflict[0]) logger.info( "users.update", extra={"actor_user_id": current_user.id, "target_user_id": user_id}, ) return UserRepository.update(db, db_user, user) @router.put("/{user_id}/password", response_model=UserResponse) def update_user_password( user_id: int, password_update: UserPasswordUpdate, db: Session = Depends(get_db), current_user: User = Depends(require_permission("users.password.update")), ): db_user = get_user_or_404(db, user_id) logger.info( "users.password_update", extra={"actor_user_id": current_user.id, "target_user_id": user_id}, ) return UserRepository.update_password(db, db_user, password_update) @router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_user( user_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_permission("users.delete")), ): if current_user.id == user_id: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Der aktuell angemeldete Benutzer darf sich nicht selbst löschen", ) db_user = get_user_or_404(db, user_id) logger.info( "users.delete", extra={"actor_user_id": current_user.id, "target_user_id": user_id}, ) UserRepository.delete(db, db_user) return Response(status_code=status.HTTP_204_NO_CONTENT)