from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi.security import OAuth2PasswordRequestForm from typing import Optional, List from app.core.auth import get_current_active_user, User from app.core.database import get_database from app.services.user_service import UserService from app.schemas.user import ( UserCreate, UserUpdate, UserResponse, UserLogin, Token ) router = APIRouter() def get_user_service(db=Depends(get_database)) -> UserService: """Dependency to get user service""" return UserService(db) @router.post("/login", response_model=Token) async def login( form_data: OAuth2PasswordRequestForm = Depends(), user_service: UserService = Depends(get_user_service) ): """ Login endpoint for OAuth2 password flow Returns JWT access token on successful authentication """ user = await user_service.authenticate_user(form_data.username, form_data.password) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password", headers={"WWW-Authenticate": "Bearer"}, ) token = await user_service.create_access_token_for_user(user) return token @router.get("/me", response_model=UserResponse) async def get_current_user_info( current_user: User = Depends(get_current_active_user), user_service: UserService = Depends(get_user_service) ): """Get current authenticated user info""" user = await user_service.get_user_by_username(current_user.username) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) return UserResponse( _id=str(user.id), username=user.username, email=user.email, full_name=user.full_name, role=user.role, disabled=user.disabled, created_at=user.created_at, last_login=user.last_login ) @router.get("/", response_model=List[UserResponse]) async def get_users( role: Optional[str] = Query(None, description="Filter by role (admin/editor/viewer)"), disabled: Optional[bool] = Query(None, description="Filter by disabled status"), search: Optional[str] = Query(None, description="Search in username, email, or full name"), current_user: User = Depends(get_current_active_user), user_service: UserService = Depends(get_user_service) ): """Get all users (admin only)""" # Check if user is admin if current_user.role != "admin": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Only admins can list users" ) users = await user_service.get_users(role=role, disabled=disabled, search=search) return [ UserResponse( _id=str(u.id), username=u.username, email=u.email, full_name=u.full_name, role=u.role, disabled=u.disabled, created_at=u.created_at, last_login=u.last_login ) for u in users ] @router.get("/stats") async def get_user_stats( current_user: User = Depends(get_current_active_user), user_service: UserService = Depends(get_user_service) ): """Get user statistics (admin only)""" if current_user.role != "admin": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Only admins can view user statistics" ) stats = await user_service.get_user_stats() return stats @router.get("/{user_id}", response_model=UserResponse) async def get_user( user_id: str, current_user: User = Depends(get_current_active_user), user_service: UserService = Depends(get_user_service) ): """Get a user by ID (admin only or own user)""" # Check if user is viewing their own profile user = await user_service.get_user_by_id(user_id) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"User with ID {user_id} not found" ) # Allow users to view their own profile, or admins to view any profile if user.username != current_user.username and current_user.role != "admin": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized to view this user" ) return UserResponse( _id=str(user.id), username=user.username, email=user.email, full_name=user.full_name, role=user.role, disabled=user.disabled, created_at=user.created_at, last_login=user.last_login ) @router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED) async def create_user( user_data: UserCreate, current_user: User = Depends(get_current_active_user), user_service: UserService = Depends(get_user_service) ): """Create a new user (admin only)""" if current_user.role != "admin": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Only admins can create users" ) try: user = await user_service.create_user(user_data) except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e) ) return UserResponse( _id=str(user.id), username=user.username, email=user.email, full_name=user.full_name, role=user.role, disabled=user.disabled, created_at=user.created_at, last_login=user.last_login ) @router.put("/{user_id}", response_model=UserResponse) async def update_user( user_id: str, user_data: UserUpdate, current_user: User = Depends(get_current_active_user), user_service: UserService = Depends(get_user_service) ): """Update a user (admin only or own user with restrictions)""" user = await user_service.get_user_by_id(user_id) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"User with ID {user_id} not found" ) # Check permissions is_own_user = user.username == current_user.username is_admin = current_user.role == "admin" if not is_own_user and not is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized to update this user" ) # Regular users can only update their own email and full_name if is_own_user and not is_admin: if user_data.role is not None or user_data.disabled is not None: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Cannot change role or disabled status" ) try: updated_user = await user_service.update_user(user_id, user_data) except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e) ) if not updated_user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"User with ID {user_id} not found" ) return UserResponse( _id=str(updated_user.id), username=updated_user.username, email=updated_user.email, full_name=updated_user.full_name, role=updated_user.role, disabled=updated_user.disabled, created_at=updated_user.created_at, last_login=updated_user.last_login ) @router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_user( user_id: str, current_user: User = Depends(get_current_active_user), user_service: UserService = Depends(get_user_service) ): """Delete a user (admin only)""" if current_user.role != "admin": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Only admins can delete users" ) # Prevent self-deletion user = await user_service.get_user_by_id(user_id) if user and user.username == current_user.username: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot delete your own user account" ) success = await user_service.delete_user(user_id) if not success: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"User with ID {user_id} not found" ) return None @router.post("/{user_id}/toggle", response_model=UserResponse) async def toggle_user_status( user_id: str, current_user: User = Depends(get_current_active_user), user_service: UserService = Depends(get_user_service) ): """Toggle user disabled status (admin only)""" if current_user.role != "admin": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Only admins can toggle user status" ) # Prevent self-toggle user = await user_service.get_user_by_id(user_id) if user and user.username == current_user.username: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot toggle your own user status" ) updated_user = await user_service.toggle_user_status(user_id) if not updated_user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"User with ID {user_id} not found" ) return UserResponse( _id=str(updated_user.id), username=updated_user.username, email=updated_user.email, full_name=updated_user.full_name, role=updated_user.role, disabled=updated_user.disabled, created_at=updated_user.created_at, last_login=updated_user.last_login ) @router.post("/change-password") async def change_password( old_password: str, new_password: str, current_user: User = Depends(get_current_active_user), user_service: UserService = Depends(get_user_service) ): """Change current user's password""" user = await user_service.get_user_by_username(current_user.username) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) success = await user_service.change_password( str(user.id), old_password, new_password ) if not success: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Incorrect old password" ) return {"message": "Password changed successfully"}