LetsBeBiz-Redesign/letsbe-orchestrator/app/playbooks/calcom.py

208 lines
5.5 KiB
Python

"""Cal.com scheduling deployment playbook.
Defines the steps required to:
1. Set up Cal.com on a tenant server (ENV_UPDATE + DOCKER_RELOAD)
2. Perform initial setup via Playwright automation (create admin account)
Tenant servers must have stacks and env templates under /opt/letsbe.
"""
import uuid
from typing import Any
from urllib.parse import urlparse
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.task import Task, TaskStatus
class CompositeStep(BaseModel):
"""A single step in a composite playbook."""
type: str = Field(..., description="Task type (e.g., ENV_UPDATE, DOCKER_RELOAD)")
payload: dict[str, Any] = Field(
default_factory=dict, description="Payload for this step"
)
# LetsBe standard paths
CALCOM_ENV_PATH = "/opt/letsbe/env/calcom.env"
CALCOM_STACK_DIR = "/opt/letsbe/stacks/calcom"
def build_calcom_setup_steps(*, domain: str) -> list[CompositeStep]:
"""
Build the sequence of steps required to set up Cal.com.
Assumes the env file already exists at /opt/letsbe/env/calcom.env
(created by provisioning/env_setup.sh).
Args:
domain: The domain for Cal.com (e.g., "cal.example.com")
Returns:
List of 2 CompositeStep objects:
1. ENV_UPDATE - patches NEXT_PUBLIC_WEBAPP_URL, NEXTAUTH_URL
2. DOCKER_RELOAD - restarts the calcom stack with pull=True
"""
steps = [
# Step 1: Update environment variables
CompositeStep(
type="ENV_UPDATE",
payload={
"path": CALCOM_ENV_PATH,
"updates": {
"NEXT_PUBLIC_WEBAPP_URL": f"https://{domain}",
"NEXTAUTH_URL": f"https://{domain}",
},
},
),
# Step 2: Reload Docker stack
CompositeStep(
type="DOCKER_RELOAD",
payload={
"compose_dir": CALCOM_STACK_DIR,
"pull": True,
},
),
]
return steps
async def create_calcom_setup_task(
*,
db: AsyncSession,
tenant_id: uuid.UUID,
agent_id: uuid.UUID | None,
domain: str,
) -> Task:
"""
Create and persist a COMPOSITE task for Cal.com setup.
Args:
db: Async database session
tenant_id: UUID of the tenant
agent_id: Optional UUID of the agent to assign the task to
domain: The domain for Cal.com
Returns:
The created Task object with type="COMPOSITE"
"""
steps = build_calcom_setup_steps(domain=domain)
task = Task(
tenant_id=tenant_id,
agent_id=agent_id,
type="COMPOSITE",
payload={"steps": [step.model_dump() for step in steps]},
status=TaskStatus.PENDING.value,
)
db.add(task)
await db.commit()
await db.refresh(task)
return task
# =============================================================================
# Initial Setup via Playwright
# =============================================================================
def build_calcom_initial_setup_step(
*,
base_url: str,
admin_email: str,
admin_password: str | None = None,
admin_username: str = "admin",
admin_name: str = "Admin",
) -> dict[str, Any]:
"""
Build a PLAYWRIGHT task payload for Cal.com initial setup.
This creates the admin account on a fresh Cal.com installation.
Args:
base_url: The base URL for Cal.com (e.g., "https://cal.example.com")
admin_email: Email address for the admin account
admin_password: Password for admin (auto-generated if None)
admin_username: Username for the admin account
admin_name: Display name for the admin account
Returns:
Task payload dict with type="PLAYWRIGHT"
"""
parsed = urlparse(base_url)
allowed_domain = parsed.netloc
inputs: dict[str, Any] = {
"base_url": base_url,
"admin_email": admin_email,
"admin_username": admin_username,
"admin_name": admin_name,
}
if admin_password:
inputs["admin_password"] = admin_password
return {
"scenario": "calcom_initial_setup",
"inputs": inputs,
"options": {
"allowed_domains": [allowed_domain],
},
"timeout": 120,
}
async def create_calcom_initial_setup_task(
*,
db: AsyncSession,
tenant_id: uuid.UUID,
agent_id: uuid.UUID,
base_url: str,
admin_email: str,
admin_password: str | None = None,
admin_username: str = "admin",
admin_name: str = "Admin",
) -> Task:
"""
Create and persist a PLAYWRIGHT task for Cal.com initial setup.
Args:
db: Async database session
tenant_id: UUID of the tenant
agent_id: UUID of the agent to assign the task to
base_url: The base URL for Cal.com
admin_email: Email address for the admin account
admin_password: Password for admin (auto-generated if None)
admin_username: Username for the admin account
admin_name: Display name for the admin account
Returns:
The created Task object with type="PLAYWRIGHT"
"""
payload = build_calcom_initial_setup_step(
base_url=base_url,
admin_email=admin_email,
admin_password=admin_password,
admin_username=admin_username,
admin_name=admin_name,
)
task = Task(
tenant_id=tenant_id,
agent_id=agent_id,
type="PLAYWRIGHT",
payload=payload,
status=TaskStatus.PENDING.value,
)
db.add(task)
await db.commit()
await db.refresh(task)
return task