from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from sqlalchemy import select, func from typing import List from pydantic import BaseModel from app.database import get_db from app.models.db import User from app.services.auth import hash_password from app.services.auth_deps import get_current_user, require_admin # To protect endpoints router = APIRouter(prefix="/users", tags=["users"]) # Pydantic models class UserListItem(BaseModel): id: int username: str email: str | None full_name: str | None is_active: bool roles: list[str] last_login_at: str | None created_at: str class Config: orm_mode = True class UserStatusUpdate(BaseModel): is_active: bool @router.get("/list") async def get_user_list( db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) # Protect this route ): """Get a list of all users""" # Ensure only admins can see all users if "admin" not in [role.name for role in current_user.roles]: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized") result = await db.execute( select(User).options(selectinload(User.roles)).order_by(User.id) ) users = result.scalars().all() users_list = [] for user in users: users_list.append({ "id": user.id, "username": user.username, "email": user.email, "full_name": user.full_name, "is_active": user.is_active, "roles": [role.name for role in user.roles], "last_login_at": user.last_login_at.isoformat() if user.last_login_at else None, "created_at": user.created_at.isoformat() }) return {"users": users_list} @router.put("/{user_id}/status") async def update_user_status( user_id: int, status_update: UserStatusUpdate, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """Update a user's active status""" if "admin" not in [role.name for role in current_user.roles]: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized") result = await db.execute(select(User).where(User.id == user_id)) user = result.scalar_one_or_none() if not user: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") user.is_active = status_update.is_active await db.commit() return {"message": "User status updated successfully"} @router.post("/{user_id}/reset-password") async def reset_user_password( user_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """Reset a user's password to the default""" if "admin" not in [role.name for role in current_user.roles]: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized") result = await db.execute(select(User).where(User.id == user_id)) user = result.scalar_one_or_none() if not user: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") # Hardcoded default password for now. # TODO: Move to a configurable system parameter. default_password = "password123" user.password_hash = hash_password(default_password) await db.commit() return {"message": f"Password for user {user.username} has been reset."} @router.get("/count", response_model=dict) async def get_user_count( db: AsyncSession = Depends(get_db), current_admin_user: User = Depends(require_admin) # Ensure only admin can access ): """ Get the total count of registered users. """ result = await db.execute(select(func.count(User.id))) total_users = result.scalar_one() return {"total_users": total_users}