Implement FastAPI Temporal MVP pipeline

This commit is contained in:
Codex
2026-03-27 00:10:28 +08:00
commit cc03da8a94
52 changed files with 3619 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
"""Review application service."""
from fastapi import HTTPException, status
from sqlalchemy import select
from app.api.schemas.review import PendingReviewResponse, SubmitReviewRequest, SubmitReviewResponse
from app.application.services.workflow_service import WorkflowService
from app.domain.enums import OrderStatus, ReviewTaskStatus
from app.infra.db.models.asset import AssetORM
from app.infra.db.models.order import OrderORM
from app.infra.db.models.review_task import ReviewTaskORM
from app.infra.db.models.workflow_run import WorkflowRunORM
from app.workers.workflows.types import ReviewSignalPayload
class ReviewService:
"""Application service for review flows."""
def __init__(self) -> None:
self.workflow_service = WorkflowService()
async def list_pending_reviews(self, session) -> list[PendingReviewResponse]:
"""Return all pending review tasks."""
result = await session.execute(
select(ReviewTaskORM, WorkflowRunORM)
.join(WorkflowRunORM, WorkflowRunORM.order_id == ReviewTaskORM.order_id)
.where(ReviewTaskORM.status == ReviewTaskStatus.PENDING)
.order_by(ReviewTaskORM.created_at.asc())
)
return [
PendingReviewResponse(
review_task_id=review_task.id,
order_id=review_task.order_id,
workflow_id=workflow_run.workflow_id,
current_step=workflow_run.current_step,
created_at=review_task.created_at,
)
for review_task, workflow_run in result.all()
]
async def submit_review(self, session, order_id: int, payload: SubmitReviewRequest) -> SubmitReviewResponse:
"""Persist the review submission and signal the Temporal workflow."""
order = await session.get(OrderORM, order_id)
if order is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Order not found")
if order.status != OrderStatus.WAITING_REVIEW:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Order is not waiting for review",
)
workflow_result = await session.execute(
select(WorkflowRunORM).where(WorkflowRunORM.order_id == order_id)
)
workflow_run = workflow_result.scalar_one_or_none()
if workflow_run is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Workflow not found")
if payload.selected_asset_id is not None:
asset = await session.get(AssetORM, payload.selected_asset_id)
if asset is None or asset.order_id != order_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Selected asset does not belong to the order",
)
pending_result = await session.execute(
select(ReviewTaskORM)
.where(
ReviewTaskORM.order_id == order_id,
ReviewTaskORM.status == ReviewTaskStatus.PENDING,
)
.order_by(ReviewTaskORM.created_at.desc())
)
review_task = pending_result.scalars().first()
if review_task is None:
review_task = ReviewTaskORM(order_id=order_id, status=ReviewTaskStatus.SUBMITTED)
session.add(review_task)
review_task.status = ReviewTaskStatus.SUBMITTED
review_task.decision = payload.decision
review_task.reviewer_id = payload.reviewer_id
review_task.selected_asset_id = payload.selected_asset_id
review_task.comment = payload.comment
await session.commit()
try:
await self.workflow_service.signal_review(
workflow_run.workflow_id,
ReviewSignalPayload(
decision=payload.decision,
reviewer_id=payload.reviewer_id,
selected_asset_id=payload.selected_asset_id,
comment=payload.comment,
),
)
except Exception as exc:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=f"Failed to signal Temporal workflow: {exc}",
) from exc
return SubmitReviewResponse(
order_id=order_id,
workflow_id=workflow_run.workflow_id,
decision=payload.decision,
status="submitted",
)