from __future__ import annotations import uuid from fastapi import APIRouter, Depends, Query from sqlalchemy import select from sqlalchemy.orm import Session from app.api.deps import get_db, require_operations_manager from app.core.errors import AppError from app.models.outbox import OutboxEvent from app.schemas import AutomationRunOut, CurrentUser from app.services.audit import record_audit_event router = APIRouter(prefix="/api/v1/workflows", tags=["workflows"]) def _to_out(event: OutboxEvent) -> AutomationRunOut: return AutomationRunOut( event_id=str(event.event_id), event_type=event.event_type, aggregate_ref=event.payload_json.get("aggregate_ref", ""), status=event.delivery_status, attempts=event.attempts, last_error=event.last_error, last_error_code=event.last_error_code, occurred_at=event.occurred_at, ) @router.get("", response_model=list[AutomationRunOut]) def list_workflows( status: str | None = Query(default=None), db: Session = Depends(get_db), _user: CurrentUser = Depends(require_operations_manager), ) -> list[AutomationRunOut]: stmt = select(OutboxEvent).order_by(OutboxEvent.occurred_at.desc()) if status: stmt = stmt.where(OutboxEvent.delivery_status == status) events = db.scalars(stmt).all() return [_to_out(e) for e in events] @router.post("/{event_id}/retry", response_model=AutomationRunOut) def retry_workflow( event_id: str, db: Session = Depends(get_db), user: CurrentUser = Depends(require_operations_manager), ) -> AutomationRunOut: try: parsed_id = uuid.UUID(event_id) except ValueError as exc: raise AppError("INVALID_EVENT_ID", "event_id must be a UUID.", status_code=422) from exc event = db.scalar(select(OutboxEvent).where(OutboxEvent.event_id == parsed_id)) if event is None: raise AppError("EVENT_NOT_FOUND", "Workflow event not found.", status_code=404) if event.delivery_status != "failed": raise AppError( "NOT_RETRYABLE", f"Event is '{event.delivery_status}', not 'failed'; " "only failed deliveries can be retried.", status_code=409, ) event.delivery_status = "pending" event.next_attempt_at = None record_audit_event( db, actor_type="user", actor_label=user.display_name, action="workflow_retry", entity_type="outbox_event", metadata={"event_id": event_id, "previous_attempts": event.attempts}, ) db.commit() return _to_out(event)