177 lines
5.4 KiB
Python
177 lines
5.4 KiB
Python
"""Admin users endpoints."""
|
|
|
|
from typing import Annotated, Any
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
|
|
|
from app.core.database import get_db
|
|
from app.core.dependencies import get_admin_user
|
|
from app.models.plan import Plan
|
|
from app.models.user import User
|
|
from app.repositories.plan import PlanRepository
|
|
from app.repositories.user import SortOrder, UserRepository, UserSortField, UserStatus
|
|
from app.schemas.auth import UserResponse
|
|
from app.schemas.user import UserUpdate
|
|
|
|
router = APIRouter(
|
|
prefix="/users",
|
|
tags=["admin-users"],
|
|
dependencies=[Depends(get_admin_user)],
|
|
)
|
|
|
|
|
|
def _user_to_response(user: User) -> UserResponse:
|
|
"""Convert User model to UserResponse."""
|
|
return UserResponse(
|
|
id=user.id,
|
|
email=user.email,
|
|
name=user.name,
|
|
picture=user.picture,
|
|
role=user.role,
|
|
credits=user.credits,
|
|
is_active=user.is_active,
|
|
plan={
|
|
"id": user.plan.id,
|
|
"name": user.plan.name,
|
|
"max_credits": user.plan.max_credits,
|
|
"features": [], # Add features if needed
|
|
}
|
|
if user.plan
|
|
else {},
|
|
created_at=user.created_at,
|
|
updated_at=user.updated_at,
|
|
)
|
|
|
|
|
|
@router.get("/")
|
|
async def list_users( # noqa: PLR0913
|
|
session: Annotated[AsyncSession, Depends(get_db)],
|
|
page: Annotated[int, Query(description="Page number", ge=1)] = 1,
|
|
limit: Annotated[int, Query(description="Items per page", ge=1, le=100)] = 50,
|
|
search: Annotated[str | None, Query(description="Search in name or email")] = None,
|
|
sort_by: Annotated[
|
|
UserSortField,
|
|
Query(description="Sort by field"),
|
|
] = UserSortField.NAME,
|
|
sort_order: Annotated[SortOrder, Query(description="Sort order")] = SortOrder.ASC,
|
|
status_filter: Annotated[
|
|
UserStatus,
|
|
Query(description="Filter by status"),
|
|
] = UserStatus.ALL,
|
|
) -> dict[str, Any]:
|
|
"""Get all users with pagination, search, and filters (admin only)."""
|
|
user_repo = UserRepository(session)
|
|
users, total_count = await user_repo.get_all_with_plan_paginated(
|
|
page=page,
|
|
limit=limit,
|
|
search=search,
|
|
sort_by=sort_by,
|
|
sort_order=sort_order,
|
|
status_filter=status_filter,
|
|
)
|
|
|
|
total_pages = (total_count + limit - 1) // limit # Ceiling division
|
|
|
|
return {
|
|
"users": [_user_to_response(user) for user in users],
|
|
"total": total_count,
|
|
"page": page,
|
|
"limit": limit,
|
|
"total_pages": total_pages,
|
|
}
|
|
|
|
|
|
@router.get("/{user_id}")
|
|
async def get_user(
|
|
user_id: int,
|
|
session: Annotated[AsyncSession, Depends(get_db)],
|
|
) -> UserResponse:
|
|
"""Get a specific user by ID (admin only)."""
|
|
user_repo = UserRepository(session)
|
|
user = await user_repo.get_by_id_with_plan(user_id)
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User not found",
|
|
)
|
|
return _user_to_response(user)
|
|
|
|
|
|
@router.patch("/{user_id}")
|
|
async def update_user(
|
|
user_id: int,
|
|
user_update: UserUpdate,
|
|
session: Annotated[AsyncSession, Depends(get_db)],
|
|
) -> UserResponse:
|
|
"""Update a user (admin only)."""
|
|
user_repo = UserRepository(session)
|
|
user = await user_repo.get_by_id_with_plan(user_id)
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User not found",
|
|
)
|
|
|
|
update_data = user_update.model_dump(exclude_unset=True)
|
|
|
|
# If plan_id is being updated, validate it exists
|
|
if "plan_id" in update_data:
|
|
plan_repo = PlanRepository(session)
|
|
plan = await plan_repo.get_by_id(update_data["plan_id"])
|
|
if not plan:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Plan not found",
|
|
)
|
|
|
|
updated_user = await user_repo.update(user, update_data)
|
|
# Need to refresh the plan relationship after update
|
|
await session.refresh(updated_user, ["plan"])
|
|
return _user_to_response(updated_user)
|
|
|
|
|
|
@router.post("/{user_id}/disable")
|
|
async def disable_user(
|
|
user_id: int,
|
|
session: Annotated[AsyncSession, Depends(get_db)],
|
|
) -> dict[str, str]:
|
|
"""Disable a user (admin only)."""
|
|
user_repo = UserRepository(session)
|
|
user = await user_repo.get_by_id_with_plan(user_id)
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User not found",
|
|
)
|
|
|
|
await user_repo.update(user, {"is_active": False})
|
|
return {"message": "User disabled successfully"}
|
|
|
|
|
|
@router.post("/{user_id}/enable")
|
|
async def enable_user(
|
|
user_id: int,
|
|
session: Annotated[AsyncSession, Depends(get_db)],
|
|
) -> dict[str, str]:
|
|
"""Enable a user (admin only)."""
|
|
user_repo = UserRepository(session)
|
|
user = await user_repo.get_by_id_with_plan(user_id)
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User not found",
|
|
)
|
|
|
|
await user_repo.update(user, {"is_active": True})
|
|
return {"message": "User enabled successfully"}
|
|
|
|
|
|
@router.get("/plans/list")
|
|
async def list_plans(
|
|
session: Annotated[AsyncSession, Depends(get_db)],
|
|
) -> list[Plan]:
|
|
"""Get all plans for user editing (admin only)."""
|
|
plan_repo = PlanRepository(session)
|
|
return await plan_repo.get_all()
|