"""Driver management APIs — Phase 10.1.""" from __future__ import annotations from uuid import UUID from fastapi import APIRouter, Depends, Query, status from sqlalchemy.ext.asyncio import AsyncSession from app.api.deps import get_db, get_pagination, require_tenant from app.api.permissions import require_permissions from app.commands.drivers import DriverCommands from app.models.types import DriverStatus from app.permissions.definitions import ( DRIVERS_ACTIVATE, DRIVERS_ARCHIVE, DRIVERS_BLOCK, DRIVERS_CREATE, DRIVERS_CREDENTIALS_MANAGE, DRIVERS_CREDENTIALS_VIEW, DRIVERS_DEACTIVATE, DRIVERS_DELETE, DRIVERS_DOCUMENTS_MANAGE, DRIVERS_DOCUMENTS_VIEW, DRIVERS_LIFECYCLE_VIEW, DRIVERS_RESUME, DRIVERS_SUSPEND, DRIVERS_UNBLOCK, DRIVERS_UPDATE, DRIVERS_VIEW, ) from app.queries.drivers import DriverQueries from app.schemas.drivers import ( DriverCreate, DriverCredentialCreate, DriverCredentialRead, DriverDocumentCreate, DriverDocumentRead, DriverLifecycleEventRead, DriverLifecycleRequest, DriverListResponse, DriverRead, DriverUpdate, ) from app.specifications.drivers import ALLOWED_SORT_FIELDS, DriverListSpec from shared.pagination import PaginationParams from shared.security import CurrentUser router = APIRouter() def _list_spec( status_filter: DriverStatus | None = Query(default=None, alias="status"), organization_id: UUID | None = Query(default=None), hub_id: UUID | None = Query(default=None), q: str | None = Query(default=None, max_length=100), sort_by: str = Query(default="created_at"), sort_dir: str = Query(default="desc", pattern="^(?i)(asc|desc)$"), ) -> DriverListSpec: if sort_by not in ALLOWED_SORT_FIELDS: sort_by = "created_at" return DriverListSpec( status=status_filter, organization_id=organization_id, hub_id=hub_id, q=q, sort_by=sort_by, sort_dir=sort_dir.lower(), ) @router.post("", response_model=DriverRead, status_code=status.HTTP_201_CREATED) async def create_driver( body: DriverCreate, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), user: CurrentUser = Depends(require_permissions(DRIVERS_CREATE)), ): return await DriverCommands(db).create(tenant_id, body, actor=user) @router.get("", response_model=DriverListResponse) async def list_drivers( tenant_id: UUID = Depends(require_tenant), pagination: PaginationParams = Depends(get_pagination), spec: DriverListSpec = Depends(_list_spec), db: AsyncSession = Depends(get_db), _user: CurrentUser = Depends(require_permissions(DRIVERS_VIEW)), ): items, total = await DriverQueries(db).list( tenant_id, offset=pagination.offset, limit=pagination.page_size, spec=spec, ) return DriverListResponse( items=items, total=total, page=pagination.page, page_size=pagination.page_size, ) @router.get("/{driver_id}", response_model=DriverRead) async def get_driver( driver_id: UUID, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), _user: CurrentUser = Depends(require_permissions(DRIVERS_VIEW)), ): return await DriverQueries(db).get(tenant_id, driver_id) @router.patch("/{driver_id}", response_model=DriverRead) async def update_driver( driver_id: UUID, body: DriverUpdate, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), user: CurrentUser = Depends(require_permissions(DRIVERS_UPDATE)), ): return await DriverCommands(db).update(tenant_id, driver_id, body, actor=user) @router.post("/{driver_id}/activate", response_model=DriverRead) async def activate_driver( driver_id: UUID, body: DriverLifecycleRequest, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), user: CurrentUser = Depends(require_permissions(DRIVERS_ACTIVATE)), ): return await DriverCommands(db).activate(tenant_id, driver_id, body, actor=user) @router.post("/{driver_id}/suspend", response_model=DriverRead) async def suspend_driver( driver_id: UUID, body: DriverLifecycleRequest, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), user: CurrentUser = Depends(require_permissions(DRIVERS_SUSPEND)), ): return await DriverCommands(db).suspend(tenant_id, driver_id, body, actor=user) @router.post("/{driver_id}/resume", response_model=DriverRead) async def resume_driver( driver_id: UUID, body: DriverLifecycleRequest, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), user: CurrentUser = Depends(require_permissions(DRIVERS_RESUME)), ): return await DriverCommands(db).resume(tenant_id, driver_id, body, actor=user) @router.post("/{driver_id}/deactivate", response_model=DriverRead) async def deactivate_driver( driver_id: UUID, body: DriverLifecycleRequest, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), user: CurrentUser = Depends(require_permissions(DRIVERS_DEACTIVATE)), ): return await DriverCommands(db).deactivate(tenant_id, driver_id, body, actor=user) @router.post("/{driver_id}/block", response_model=DriverRead) async def block_driver( driver_id: UUID, body: DriverLifecycleRequest, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), user: CurrentUser = Depends(require_permissions(DRIVERS_BLOCK)), ): return await DriverCommands(db).block(tenant_id, driver_id, body, actor=user) @router.post("/{driver_id}/unblock", response_model=DriverRead) async def unblock_driver( driver_id: UUID, body: DriverLifecycleRequest, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), user: CurrentUser = Depends(require_permissions(DRIVERS_UNBLOCK)), ): return await DriverCommands(db).unblock(tenant_id, driver_id, body, actor=user) @router.post("/{driver_id}/archive", response_model=DriverRead) async def archive_driver( driver_id: UUID, body: DriverLifecycleRequest, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), user: CurrentUser = Depends(require_permissions(DRIVERS_ARCHIVE)), ): return await DriverCommands(db).archive(tenant_id, driver_id, body, actor=user) @router.get("/{driver_id}/lifecycle", response_model=list[DriverLifecycleEventRead]) async def list_lifecycle( driver_id: UUID, limit: int = Query(default=100, ge=1, le=500), tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), _user: CurrentUser = Depends(require_permissions(DRIVERS_LIFECYCLE_VIEW)), ): return await DriverQueries(db).lifecycle(tenant_id, driver_id, limit=limit) @router.post( "/{driver_id}/credentials", response_model=DriverCredentialRead, status_code=status.HTTP_201_CREATED, ) async def add_credential( driver_id: UUID, body: DriverCredentialCreate, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), user: CurrentUser = Depends(require_permissions(DRIVERS_CREDENTIALS_MANAGE)), ): return await DriverCommands(db).add_credential( tenant_id, driver_id, body, actor=user ) @router.get("/{driver_id}/credentials", response_model=list[DriverCredentialRead]) async def list_credentials( driver_id: UUID, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), _user: CurrentUser = Depends(require_permissions(DRIVERS_CREDENTIALS_VIEW)), ): return await DriverQueries(db).credentials(tenant_id, driver_id) @router.post( "/{driver_id}/documents", response_model=DriverDocumentRead, status_code=status.HTTP_201_CREATED, ) async def add_document( driver_id: UUID, body: DriverDocumentCreate, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), user: CurrentUser = Depends(require_permissions(DRIVERS_DOCUMENTS_MANAGE)), ): return await DriverCommands(db).add_document( tenant_id, driver_id, body, actor=user ) @router.get("/{driver_id}/documents", response_model=list[DriverDocumentRead]) async def list_documents( driver_id: UUID, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), _user: CurrentUser = Depends(require_permissions(DRIVERS_DOCUMENTS_VIEW)), ): return await DriverQueries(db).documents(tenant_id, driver_id) @router.post("/{driver_id}/delete", response_model=DriverRead) async def soft_delete_driver( driver_id: UUID, tenant_id: UUID = Depends(require_tenant), db: AsyncSession = Depends(get_db), user: CurrentUser = Depends(require_permissions(DRIVERS_DELETE)), ): return await DriverCommands(db).soft_delete(tenant_id, driver_id, actor=user)