import logging from fastapi import APIRouter, Depends, HTTPException, Response, status from sqlalchemy.orm import Session from starlette.requests import Request 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 from app.services.audit_service import sanitize, write_audit_log 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, request: Request, 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}) created_user = UserRepository.create(db, user) write_audit_log( db, action="users.create", entity_type="users", entity_id=created_user.id, entity_label=created_user.username, actor=current_user, request=request, after_data=created_user, ) return created_user @router.put("/{user_id}", response_model=UserResponse) def update_user( user_id: int, user: UserUpdate, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("users.update")), ): db_user = get_user_or_404(db, user_id) before_data = sanitize(db_user) 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}, ) updated_user = UserRepository.update(db, db_user, user) write_audit_log( db, action="users.update", entity_type="users", entity_id=updated_user.id, entity_label=updated_user.username, actor=current_user, request=request, before_data=before_data, after_data=updated_user, ) return updated_user @router.put("/{user_id}/password", response_model=UserResponse) def update_user_password( user_id: int, password_update: UserPasswordUpdate, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("users.password.update")), ): db_user = get_user_or_404(db, user_id) before_data = sanitize(db_user) logger.info( "users.password_update", extra={"actor_user_id": current_user.id, "target_user_id": user_id}, ) updated_user = UserRepository.update_password(db, db_user, password_update) write_audit_log( db, action="users.password_update", entity_type="users", entity_id=updated_user.id, entity_label=updated_user.username, actor=current_user, request=request, before_data=before_data, after_data=updated_user, metadata={"password_changed": True}, ) return updated_user @router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_user( user_id: int, request: Request, 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) before_data = sanitize(db_user) entity_label = db_user.username logger.info( "users.delete", extra={"actor_user_id": current_user.id, "target_user_id": user_id}, ) UserRepository.delete(db, db_user) write_audit_log( db, action="users.delete", entity_type="users", entity_id=user_id, entity_label=entity_label, actor=current_user, request=request, before_data=before_data, ) return Response(status_code=status.HTTP_204_NO_CONTENT)