113 lines
4.4 KiB
Python
113 lines
4.4 KiB
Python
"""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",
|
|
)
|
|
|