import logging from fastapi import APIRouter, Depends, HTTPException, Response, status from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from sqlalchemy.orm import Session from app.core.security import verify_access_token from app.db.database import get_db from app.models.user import User 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"], ) bearer_scheme = HTTPBearer(auto_error=False) def get_current_user( credentials: HTTPAuthorizationCredentials | None = Depends(bearer_scheme), db: Session = Depends(get_db), ) -> User: if credentials is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Nicht authentifiziert", headers={"WWW-Authenticate": "Bearer"}, ) username = verify_access_token(credentials.credentials) user = UserRepository.get_by_username(db, username) if user is None or not user.is_active: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Nicht authentifiziert", headers={"WWW-Authenticate": "Bearer"}, ) return user def require_role(*allowed_roles: str): def dependency(current_user: User = Depends(get_current_user)) -> User: if current_user.role not in allowed_roles: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Keine Berechtigung", ) return current_user return dependency 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, ) @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(get_current_user), ): 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(get_current_user), ): 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(get_current_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(get_current_user), ): db_user = get_user_or_404(db, user_id) 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(get_current_user), ): 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(get_current_user), ): 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)