amazon-transcreation/backend/app/api/v1/clients.py
DJP f271343bc0 feat: wire job wizard and dashboard to real backend API
- Job wizard now calls real API: create job → upload source → launch
- Dashboard and monitoring pages use live data instead of mock data
- Monitoring page polls every 3s while job is active
- Backend enriches job responses with client_name, created_by_name,
  source_line_count from eager-loaded relationships
- Frontend response mappers handle backend→frontend type differences
  (lowercase enum values, field name mapping, computed progress/stage)
- Source file parser accepts column aliases (Line type, Context notes)
  with case-insensitive matching for real-world Excel files
- Clients list endpoint accessible to all authenticated users
- Fixed uploadSource to use PUT, uploadSupplementary per-file
- Removed all hardcoded mock data from useJobs hook

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 14:18:47 -04:00

105 lines
3.5 KiB
Python

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()