158 lines
4.6 KiB
Python
158 lines
4.6 KiB
Python
|
|
"""
|
||
|
|
Script Flow Management API.
|
||
|
|
[AC-AISVC-71, AC-AISVC-72, AC-AISVC-73] Script flow CRUD endpoints.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import logging
|
||
|
|
import uuid
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from fastapi import APIRouter, Depends, Header, HTTPException
|
||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
|
|
||
|
|
from app.core.database import get_session
|
||
|
|
from app.models.entities import ScriptFlowCreate, ScriptFlowUpdate
|
||
|
|
from app.services.flow.flow_service import ScriptFlowService
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
router = APIRouter(prefix="/admin/script-flows", tags=["Script Flows"])
|
||
|
|
|
||
|
|
|
||
|
|
def get_tenant_id(x_tenant_id: str = Header(..., alias="X-Tenant-Id")) -> str:
|
||
|
|
"""Extract tenant ID from header."""
|
||
|
|
if not x_tenant_id:
|
||
|
|
raise HTTPException(status_code=400, detail="X-Tenant-Id header is required")
|
||
|
|
return x_tenant_id
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("")
|
||
|
|
async def list_flows(
|
||
|
|
tenant_id: str = Depends(get_tenant_id),
|
||
|
|
is_enabled: bool | None = None,
|
||
|
|
session: AsyncSession = Depends(get_session),
|
||
|
|
) -> dict[str, Any]:
|
||
|
|
"""
|
||
|
|
[AC-AISVC-72] List all script flows for a tenant.
|
||
|
|
"""
|
||
|
|
logger.info(f"[AC-AISVC-72] Listing script flows for tenant={tenant_id}, is_enabled={is_enabled}")
|
||
|
|
|
||
|
|
service = ScriptFlowService(session)
|
||
|
|
flows = await service.list_flows(tenant_id, is_enabled)
|
||
|
|
|
||
|
|
data = []
|
||
|
|
for f in flows:
|
||
|
|
linked_rule_count = await service._get_linked_rule_count(tenant_id, f.id)
|
||
|
|
data.append({
|
||
|
|
"id": str(f.id),
|
||
|
|
"name": f.name,
|
||
|
|
"description": f.description,
|
||
|
|
"step_count": len(f.steps),
|
||
|
|
"is_enabled": f.is_enabled,
|
||
|
|
"linked_rule_count": linked_rule_count,
|
||
|
|
"created_at": f.created_at.isoformat(),
|
||
|
|
"updated_at": f.updated_at.isoformat(),
|
||
|
|
})
|
||
|
|
|
||
|
|
return {"data": data}
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("", status_code=201)
|
||
|
|
async def create_flow(
|
||
|
|
body: ScriptFlowCreate,
|
||
|
|
tenant_id: str = Depends(get_tenant_id),
|
||
|
|
session: AsyncSession = Depends(get_session),
|
||
|
|
) -> dict[str, Any]:
|
||
|
|
"""
|
||
|
|
[AC-AISVC-71] Create a new script flow.
|
||
|
|
"""
|
||
|
|
logger.info(f"[AC-AISVC-71] Creating script flow for tenant={tenant_id}, name={body.name}")
|
||
|
|
|
||
|
|
service = ScriptFlowService(session)
|
||
|
|
|
||
|
|
try:
|
||
|
|
flow = await service.create_flow(tenant_id, body)
|
||
|
|
except ValueError as e:
|
||
|
|
raise HTTPException(status_code=400, detail=str(e))
|
||
|
|
|
||
|
|
return {
|
||
|
|
"id": str(flow.id),
|
||
|
|
"name": flow.name,
|
||
|
|
"description": flow.description,
|
||
|
|
"step_count": len(flow.steps),
|
||
|
|
"is_enabled": flow.is_enabled,
|
||
|
|
"created_at": flow.created_at.isoformat(),
|
||
|
|
"updated_at": flow.updated_at.isoformat(),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/{flow_id}")
|
||
|
|
async def get_flow_detail(
|
||
|
|
flow_id: uuid.UUID,
|
||
|
|
tenant_id: str = Depends(get_tenant_id),
|
||
|
|
session: AsyncSession = Depends(get_session),
|
||
|
|
) -> dict[str, Any]:
|
||
|
|
"""
|
||
|
|
[AC-AISVC-73] Get script flow detail with complete step definitions.
|
||
|
|
"""
|
||
|
|
logger.info(f"[AC-AISVC-73] Getting flow detail for tenant={tenant_id}, id={flow_id}")
|
||
|
|
|
||
|
|
service = ScriptFlowService(session)
|
||
|
|
detail = await service.get_flow_detail(tenant_id, flow_id)
|
||
|
|
|
||
|
|
if not detail:
|
||
|
|
raise HTTPException(status_code=404, detail="Flow not found")
|
||
|
|
|
||
|
|
return detail
|
||
|
|
|
||
|
|
|
||
|
|
@router.put("/{flow_id}")
|
||
|
|
async def update_flow(
|
||
|
|
flow_id: uuid.UUID,
|
||
|
|
body: ScriptFlowUpdate,
|
||
|
|
tenant_id: str = Depends(get_tenant_id),
|
||
|
|
session: AsyncSession = Depends(get_session),
|
||
|
|
) -> dict[str, Any]:
|
||
|
|
"""
|
||
|
|
[AC-AISVC-73] Update script flow definition.
|
||
|
|
"""
|
||
|
|
logger.info(f"[AC-AISVC-73] Updating flow for tenant={tenant_id}, id={flow_id}")
|
||
|
|
|
||
|
|
service = ScriptFlowService(session)
|
||
|
|
|
||
|
|
try:
|
||
|
|
flow = await service.update_flow(tenant_id, flow_id, body)
|
||
|
|
except ValueError as e:
|
||
|
|
raise HTTPException(status_code=400, detail=str(e))
|
||
|
|
|
||
|
|
if not flow:
|
||
|
|
raise HTTPException(status_code=404, detail="Flow not found")
|
||
|
|
|
||
|
|
return {
|
||
|
|
"id": str(flow.id),
|
||
|
|
"name": flow.name,
|
||
|
|
"description": flow.description,
|
||
|
|
"step_count": len(flow.steps),
|
||
|
|
"is_enabled": flow.is_enabled,
|
||
|
|
"created_at": flow.created_at.isoformat(),
|
||
|
|
"updated_at": flow.updated_at.isoformat(),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@router.delete("/{flow_id}", status_code=204)
|
||
|
|
async def delete_flow(
|
||
|
|
flow_id: uuid.UUID,
|
||
|
|
tenant_id: str = Depends(get_tenant_id),
|
||
|
|
session: AsyncSession = Depends(get_session),
|
||
|
|
) -> None:
|
||
|
|
"""
|
||
|
|
Delete a script flow.
|
||
|
|
"""
|
||
|
|
logger.info(f"Deleting flow for tenant={tenant_id}, id={flow_id}")
|
||
|
|
|
||
|
|
service = ScriptFlowService(session)
|
||
|
|
success = await service.delete_flow(tenant_id, flow_id)
|
||
|
|
|
||
|
|
if not success:
|
||
|
|
raise HTTPException(status_code=404, detail="Flow not found")
|