from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_current_user, get_db, require_role from app.models.client import Client from app.schemas.client import ClientCreate, ClientResponse, ClientUpdate from app.schemas.common import PaginatedResponse router = APIRouter(prefix="/clients", tags=["clients"]) @router.post( "", response_model=ClientResponse, status_code=status.HTTP_201_CREATED, ) async def create_client( body: ClientCreate, db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_role(["admin"])), ) -> ClientResponse: """Create a new client (admin only).""" client = Client(name=body.name, settings=body.settings) db.add(client) await db.flush() return ClientResponse.model_validate(client) @router.get("", response_model=PaginatedResponse[ClientResponse]) async def list_clients( page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ) -> PaginatedResponse[ClientResponse]: """List all clients.""" count_result = await db.execute(select(func.count(Client.id))) total = count_result.scalar() or 0 result = await db.execute( select(Client) .order_by(Client.created_at.desc()) .offset((page - 1) * page_size) .limit(page_size) ) clients = [ClientResponse.model_validate(c) for c in result.scalars().all()] pages = (total + page_size - 1) // page_size if total > 0 else 1 return PaginatedResponse( items=clients, total=total, page=page, page_size=page_size, pages=pages ) @router.get("/{client_id}", response_model=ClientResponse) async def get_client( client_id: UUID, db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ) -> ClientResponse: """Get a client by ID.""" result = await db.execute(select(Client).where(Client.id == client_id)) client = result.scalar_one_or_none() if client is None: raise HTTPException(status_code=404, detail="Client not found") return ClientResponse.model_validate(client) @router.put("/{client_id}", response_model=ClientResponse) async def update_client( client_id: UUID, body: ClientUpdate, db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_role(["admin"])), ) -> ClientResponse: """Update a client (admin only).""" result = await db.execute(select(Client).where(Client.id == client_id)) client = result.scalar_one_or_none() if client is None: raise HTTPException(status_code=404, detail="Client not found") update_data = body.model_dump(exclude_unset=True) for field, value in update_data.items(): setattr(client, field, value) await db.flush() return ClientResponse.model_validate(client) @router.delete("/{client_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_client( client_id: UUID, db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_role(["admin"])), ) -> None: """Delete a client (admin only).""" result = await db.execute(select(Client).where(Client.id == client_id)) client = result.scalar_one_or_none() if client is None: raise HTTPException(status_code=404, detail="Client not found") await db.delete(client) await db.flush()