letsbe-orchestrator/app/routes/playbooks.py

100 lines
2.9 KiB
Python
Raw Normal View History

"""Playbook endpoints for triggering infrastructure automation."""
import uuid
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel, Field
from sqlalchemy import select
from app.db import AsyncSessionDep
from app.models.agent import Agent
from app.models.task import Task
from app.models.tenant import Tenant
from app.playbooks.chatwoot import create_chatwoot_setup_task
from app.schemas.task import TaskResponse
router = APIRouter(prefix="/tenants/{tenant_id}", tags=["Playbooks"])
class ChatwootSetupRequest(BaseModel):
"""Request body for Chatwoot setup playbook."""
agent_id: uuid.UUID | None = Field(
None, description="Optional agent UUID to assign the task to"
)
domain: str = Field(
..., min_length=1, description="Domain for Chatwoot (e.g., support.example.com)"
)
# --- Helper functions ---
async def get_tenant_by_id(db: AsyncSessionDep, tenant_id: uuid.UUID) -> Tenant | None:
"""Retrieve a tenant by ID."""
result = await db.execute(select(Tenant).where(Tenant.id == tenant_id))
return result.scalar_one_or_none()
async def get_agent_by_id(db: AsyncSessionDep, agent_id: uuid.UUID) -> Agent | None:
"""Retrieve an agent by ID."""
result = await db.execute(select(Agent).where(Agent.id == agent_id))
return result.scalar_one_or_none()
# --- Route handlers ---
@router.post(
"/chatwoot/setup",
response_model=TaskResponse,
status_code=status.HTTP_201_CREATED,
)
async def setup_chatwoot(
tenant_id: uuid.UUID,
request: ChatwootSetupRequest,
db: AsyncSessionDep,
) -> Task:
"""
Trigger Chatwoot setup playbook for a tenant.
Creates a COMPOSITE task with the following steps:
1. **ENV_UPDATE**: Set FRONTEND_URL and BACKEND_URL in chatwoot.env
2. **DOCKER_RELOAD**: Restart the Chatwoot stack with pull=True
## Request Body
- **agent_id**: Optional agent UUID to assign the task to immediately
- **domain**: The domain for Chatwoot (e.g., "support.example.com")
## Response
Returns the created Task with type="COMPOSITE" and status="pending".
The SysAdmin Agent will pick up this task and execute the steps in sequence.
"""
# Validate tenant exists
tenant = await get_tenant_by_id(db, tenant_id)
if tenant is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Tenant {tenant_id} not found",
)
# Validate agent exists if provided
if request.agent_id is not None:
agent = await get_agent_by_id(db, request.agent_id)
if agent is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Agent {request.agent_id} not found",
)
# Create the COMPOSITE task
task = await create_chatwoot_setup_task(
db=db,
tenant_id=tenant_id,
agent_id=request.agent_id,
domain=request.domain,
)
return task