Implement FastAPI Temporal MVP pipeline
This commit is contained in:
22
app/api/routers/assets.py
Normal file
22
app/api/routers/assets.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""Asset routes."""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.schemas.asset import AssetRead
|
||||
from app.application.services.asset_service import AssetService
|
||||
from app.infra.db.session import get_db_session
|
||||
|
||||
router = APIRouter(prefix="/orders", tags=["assets"])
|
||||
asset_service = AssetService()
|
||||
|
||||
|
||||
@router.get("/{order_id}/assets", response_model=list[AssetRead])
|
||||
async def list_order_assets(
|
||||
order_id: int,
|
||||
session: AsyncSession = Depends(get_db_session),
|
||||
) -> list[AssetRead]:
|
||||
"""List assets generated for an order."""
|
||||
|
||||
return await asset_service.list_order_assets(session, order_id)
|
||||
|
||||
13
app/api/routers/health.py
Normal file
13
app/api/routers/health.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Health check routes."""
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
router = APIRouter(tags=["health"])
|
||||
|
||||
|
||||
@router.get("/healthz")
|
||||
async def healthcheck() -> dict[str, str]:
|
||||
"""Return a simple health check response."""
|
||||
|
||||
return {"status": "ok"}
|
||||
|
||||
32
app/api/routers/orders.py
Normal file
32
app/api/routers/orders.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""Order routes."""
|
||||
|
||||
from fastapi import APIRouter, Depends, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.schemas.order import CreateOrderRequest, CreateOrderResponse, OrderDetailResponse
|
||||
from app.application.services.order_service import OrderService
|
||||
from app.infra.db.session import get_db_session
|
||||
|
||||
router = APIRouter(prefix="/orders", tags=["orders"])
|
||||
order_service = OrderService()
|
||||
|
||||
|
||||
@router.post("", response_model=CreateOrderResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_order(
|
||||
payload: CreateOrderRequest,
|
||||
session: AsyncSession = Depends(get_db_session),
|
||||
) -> CreateOrderResponse:
|
||||
"""Create a new image pipeline order."""
|
||||
|
||||
return await order_service.create_order(session, payload)
|
||||
|
||||
|
||||
@router.get("/{order_id}", response_model=OrderDetailResponse)
|
||||
async def get_order(
|
||||
order_id: int,
|
||||
session: AsyncSession = Depends(get_db_session),
|
||||
) -> OrderDetailResponse:
|
||||
"""Fetch order details."""
|
||||
|
||||
return await order_service.get_order(session, order_id)
|
||||
|
||||
32
app/api/routers/reviews.py
Normal file
32
app/api/routers/reviews.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""Review routes."""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.schemas.review import PendingReviewResponse, SubmitReviewRequest, SubmitReviewResponse
|
||||
from app.application.services.review_service import ReviewService
|
||||
from app.infra.db.session import get_db_session
|
||||
|
||||
router = APIRouter(prefix="/reviews", tags=["reviews"])
|
||||
review_service = ReviewService()
|
||||
|
||||
|
||||
@router.get("/pending", response_model=list[PendingReviewResponse])
|
||||
async def list_pending_reviews(
|
||||
session: AsyncSession = Depends(get_db_session),
|
||||
) -> list[PendingReviewResponse]:
|
||||
"""List review tasks waiting for manual input."""
|
||||
|
||||
return await review_service.list_pending_reviews(session)
|
||||
|
||||
|
||||
@router.post("/{order_id}/submit", response_model=SubmitReviewResponse)
|
||||
async def submit_review(
|
||||
order_id: int,
|
||||
payload: SubmitReviewRequest,
|
||||
session: AsyncSession = Depends(get_db_session),
|
||||
) -> SubmitReviewResponse:
|
||||
"""Submit a review decision for a workflow."""
|
||||
|
||||
return await review_service.submit_review(session, order_id, payload)
|
||||
|
||||
22
app/api/routers/workflows.py
Normal file
22
app/api/routers/workflows.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""Workflow routes."""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.schemas.workflow import WorkflowStatusResponse
|
||||
from app.application.services.workflow_service import WorkflowService
|
||||
from app.infra.db.session import get_db_session
|
||||
|
||||
router = APIRouter(prefix="/workflows", tags=["workflows"])
|
||||
workflow_service = WorkflowService()
|
||||
|
||||
|
||||
@router.get("/{order_id}", response_model=WorkflowStatusResponse)
|
||||
async def get_workflow_status(
|
||||
order_id: int,
|
||||
session: AsyncSession = Depends(get_db_session),
|
||||
) -> WorkflowStatusResponse:
|
||||
"""Fetch persisted workflow status for an order."""
|
||||
|
||||
return await workflow_service.get_workflow_status(session, order_id)
|
||||
|
||||
23
app/api/schemas/asset.py
Normal file
23
app/api/schemas/asset.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""Asset API schemas."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
from app.domain.enums import AssetType, WorkflowStepName
|
||||
|
||||
|
||||
class AssetRead(BaseModel):
|
||||
"""Serialized asset response."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
order_id: int
|
||||
asset_type: AssetType
|
||||
step_name: WorkflowStepName | None
|
||||
uri: str
|
||||
metadata_json: dict[str, Any] | None
|
||||
created_at: datetime
|
||||
|
||||
47
app/api/schemas/order.py
Normal file
47
app/api/schemas/order.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""Order API schemas."""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.api.schemas.asset import AssetRead
|
||||
from app.domain.enums import CustomerLevel, OrderStatus, ServiceMode, WorkflowStepName
|
||||
|
||||
|
||||
class CreateOrderRequest(BaseModel):
|
||||
"""Request payload for creating an order."""
|
||||
|
||||
customer_level: CustomerLevel
|
||||
service_mode: ServiceMode
|
||||
model_id: int
|
||||
pose_id: int
|
||||
garment_asset_id: int
|
||||
scene_ref_asset_id: int
|
||||
|
||||
|
||||
class CreateOrderResponse(BaseModel):
|
||||
"""Response returned after an order has been created."""
|
||||
|
||||
order_id: int
|
||||
workflow_id: str
|
||||
status: OrderStatus
|
||||
|
||||
|
||||
class OrderDetailResponse(BaseModel):
|
||||
"""Order detail response."""
|
||||
|
||||
order_id: int
|
||||
customer_level: CustomerLevel
|
||||
service_mode: ServiceMode
|
||||
status: OrderStatus
|
||||
model_id: int
|
||||
pose_id: int
|
||||
garment_asset_id: int
|
||||
scene_ref_asset_id: int
|
||||
final_asset_id: int | None
|
||||
workflow_id: str | None
|
||||
current_step: WorkflowStepName | None
|
||||
final_asset: AssetRead | None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
36
app/api/schemas/review.py
Normal file
36
app/api/schemas/review.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""Review API schemas."""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.domain.enums import ReviewDecision, WorkflowStepName
|
||||
|
||||
|
||||
class SubmitReviewRequest(BaseModel):
|
||||
"""Request payload for review submission."""
|
||||
|
||||
decision: ReviewDecision
|
||||
reviewer_id: int
|
||||
selected_asset_id: int | None = None
|
||||
comment: str | None = None
|
||||
|
||||
|
||||
class SubmitReviewResponse(BaseModel):
|
||||
"""Response returned after a review signal is sent."""
|
||||
|
||||
order_id: int
|
||||
workflow_id: str
|
||||
decision: ReviewDecision
|
||||
status: str
|
||||
|
||||
|
||||
class PendingReviewResponse(BaseModel):
|
||||
"""Response model for pending review items."""
|
||||
|
||||
review_task_id: int
|
||||
order_id: int
|
||||
workflow_id: str
|
||||
current_step: WorkflowStepName | None
|
||||
created_at: datetime
|
||||
|
||||
38
app/api/schemas/workflow.py
Normal file
38
app/api/schemas/workflow.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""Workflow API schemas."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
from app.domain.enums import OrderStatus, StepStatus, WorkflowStepName
|
||||
|
||||
|
||||
class WorkflowStepRead(BaseModel):
|
||||
"""Serialized workflow step record."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
workflow_run_id: int
|
||||
step_name: WorkflowStepName
|
||||
step_status: StepStatus
|
||||
input_json: dict[str, Any] | None
|
||||
output_json: dict[str, Any] | None
|
||||
error_message: str | None
|
||||
started_at: datetime
|
||||
ended_at: datetime | None
|
||||
|
||||
|
||||
class WorkflowStatusResponse(BaseModel):
|
||||
"""Serialized workflow run details."""
|
||||
|
||||
order_id: int
|
||||
workflow_id: str
|
||||
workflow_type: str
|
||||
workflow_status: OrderStatus
|
||||
current_step: WorkflowStepName | None
|
||||
steps: list[WorkflowStepRead]
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
Reference in New Issue
Block a user