# backend/app/api/v1/users.py from datetime import timedelta from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import HTTPBearer from sqlmodel import Session, select from app.core.db import get_session from app.core.auth import ( authenticate_user, create_access_token, get_password_hash, get_current_active_user, get_token_expiration_minutes, require_admin, require_write_access, require_any_access ) from app.schemas.models import User from app.schemas.schemas import ( UserCreate, UserUpdate, UserLogin, Token, UserResponse ) router = APIRouter(prefix="/users", tags=["users"]) @router.post("/login", response_model=Token) def login(user_credentials: UserLogin, session: Session = Depends(get_session)): """Authenticate user and return JWT token with role-based expiration.""" user = authenticate_user(session, user_credentials.username, user_credentials.password) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password", headers={"WWW-Authenticate": "Bearer"}, ) # Get role-based expiration time expire_minutes = get_token_expiration_minutes(user.role) access_token_expires = timedelta(minutes=expire_minutes) # Create token with user data access_token = create_access_token( data={ "sub": user.username, "user_id": user.id, "role": user.role.value }, expires_delta=access_token_expires ) return Token( access_token=access_token, token_type="bearer", expires_in=expire_minutes * 60, # Convert to seconds user=UserResponse( id=user.id, username=user.username, role=user.role ) ) @router.get("/me", response_model=UserResponse) def get_current_user_info(current_user: User = Depends(get_current_active_user)): """Get current user information from token.""" return UserResponse( id=current_user.id, username=current_user.username, role=current_user.role ) @router.get("/", response_model=list[UserResponse]) def get_all_users( session: Session = Depends(get_session), current_user: User = Depends(require_any_access), skip: int = 0, limit: int = 100 ): """Get all users (requires any authenticated role).""" statement = select(User).offset(skip).limit(limit) users = session.exec(statement).all() return [ UserResponse(id=user.id, username=user.username, role=user.role) for user in users ] @router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED) def create_user( user: UserCreate, session: Session = Depends(get_session), current_user: User = Depends(require_admin) ): """Create a new user (admin only).""" # Check if username already exists statement = select(User).where(User.username == user.username) existing_user = session.exec(statement).first() if existing_user: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Username already registered" ) # Create new user with hashed password hashed_password = get_password_hash(user.password) db_user = User( username=user.username, password_hash=hashed_password, role=user.role ) session.add(db_user) session.commit() session.refresh(db_user) return UserResponse( id=db_user.id, username=db_user.username, role=db_user.role ) @router.get("/{user_id}", response_model=UserResponse) def get_user( user_id: int, session: Session = Depends(get_session), current_user: User = Depends(require_any_access) ): """Get specific user by ID (requires authentication).""" user = session.get(User, user_id) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) return UserResponse( id=user.id, username=user.username, role=user.role ) # Update to handle user self-updating password @router.put("/{user_id}", response_model=UserResponse) def update_user( user_id: int, user_update: UserUpdate, session: Session = Depends(get_session), current_user: UserResponse = Depends(require_admin) ): """Update specific user (admin only).""" user = session.get(User, user_id) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) # Update only provided fields update_data = user_update.model_dump(exclude_unset=True) for key, value in update_data.items(): setattr(user, key, value) session.add(user) session.commit() session.refresh(user) return UserResponse( id=user.id, username=user.username, role=user.role ) @router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_user( user_id: int, session: Session = Depends(get_session), current_user: User = Depends(require_admin) ): """Delete specific user (admin only).""" user = session.get(User, user_id) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) # Prevent self-deletion if user.id == current_user.id: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot delete your own account" ) session.delete(user) session.commit() return None