move automations api into vertical slice with service layer

This commit is contained in:
CREDO23 2026-05-27 18:56:16 +02:00
parent d84240a630
commit dd6bc30f98
6 changed files with 107 additions and 57 deletions

View file

@ -0,0 +1,12 @@
"""HTTP layer for the automations feature."""
from __future__ import annotations
from fastapi import APIRouter
from .automation import router as automation_router
router = APIRouter()
router.include_router(automation_router)
__all__ = ["router"]

View file

@ -0,0 +1,22 @@
"""Routes for the ``Automation`` resource."""
from __future__ import annotations
from typing import Any
from fastapi import APIRouter, Body, Depends
from app.automations.services import AutomationService, get_automation_service
router = APIRouter()
@router.post("/automations/{automation_id}/run")
async def run_automation_now(
automation_id: int,
payload: dict[str, Any] | None = Body(default=None),
service: AutomationService = Depends(get_automation_service),
) -> dict[str, Any]:
"""Fire a manual run."""
run = await service.run_now(automation_id=automation_id, payload=payload)
return {"run_id": run.id, "status": run.status.value}

View file

@ -0,0 +1,7 @@
"""Service layer for the automations feature."""
from __future__ import annotations
from .automation import AutomationService, get_automation_service
__all__ = ["AutomationService", "get_automation_service"]

View file

@ -0,0 +1,65 @@
"""``AutomationService`` — orchestration for the ``Automation`` resource."""
from __future__ import annotations
from typing import Any
from fastapi import Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from app.automations.dispatch import DispatchError
from app.automations.persistence.models.automation import Automation
from app.automations.persistence.models.run import AutomationRun
from app.automations.triggers.manual import dispatch_manual_run
from app.db import Permission, User, get_async_session
from app.users import current_active_user
from app.utils.rbac import check_permission
class AutomationService:
"""Service for the ``Automation`` resource."""
def __init__(self, *, session: AsyncSession, user: User) -> None:
self.session = session
self.user = user
async def run_now(
self,
*,
automation_id: int,
payload: dict[str, Any] | None,
) -> AutomationRun:
"""Fire a manual run for ``automation_id``."""
automation = await self._get_automation_or_raise(automation_id)
await check_permission(
self.session,
self.user,
automation.search_space_id,
Permission.AUTOMATIONS_EXECUTE.value,
"You don't have permission to execute automations in this search space",
)
try:
return await dispatch_manual_run(
session=self.session,
automation_id=automation_id,
payload=payload,
)
except DispatchError as exc:
raise HTTPException(status_code=422, detail=str(exc)) from exc
async def _get_automation_or_raise(self, automation_id: int) -> Automation:
"""Get the automation by id; 404 if missing."""
automation = await self.session.get(Automation, automation_id)
if automation is None:
raise HTTPException(
status_code=404, detail=f"automation {automation_id} not found"
)
return automation
def get_automation_service(
session: AsyncSession = Depends(get_async_session),
user: User = Depends(current_active_user),
) -> AutomationService:
return AutomationService(session=session, user=user)

View file

@ -7,7 +7,7 @@ from .agent_revert_route import router as agent_revert_router
from .airtable_add_connector_route import ( from .airtable_add_connector_route import (
router as airtable_add_connector_router, router as airtable_add_connector_router,
) )
from .automations_routes import router as automations_router from app.automations.api import router as automations_router
from .chat_comments_routes import router as chat_comments_router from .chat_comments_routes import router as chat_comments_router
from .circleback_webhook_route import router as circleback_webhook_router from .circleback_webhook_route import router as circleback_webhook_router
from .clickup_add_connector_route import router as clickup_add_connector_router from .clickup_add_connector_route import router as clickup_add_connector_router

View file

@ -1,56 +0,0 @@
"""Routes for automations. v1: manual ``Run now``."""
from __future__ import annotations
from typing import Any
from fastapi import APIRouter, Body, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.automations.dispatch import DispatchError
from app.automations.persistence.models.automation import Automation
from app.automations.triggers.manual import dispatch_manual_run
from app.db import Permission, User, get_async_session
from app.users import current_active_user
from app.utils.rbac import check_permission
router = APIRouter()
@router.post("/automations/{automation_id}/run")
async def run_automation_now(
automation_id: int,
payload: dict[str, Any] | None = Body(default=None),
session: AsyncSession = Depends(get_async_session),
user: User = Depends(current_active_user),
) -> dict[str, Any]:
"""Fire an automation manually. Returns the new run id and status."""
search_space_id = (
await session.execute(
select(Automation.search_space_id).where(Automation.id == automation_id)
)
).scalar_one_or_none()
if search_space_id is None:
raise HTTPException(
status_code=404, detail=f"automation {automation_id} not found"
)
await check_permission(
session,
user,
search_space_id,
Permission.AUTOMATIONS_EXECUTE.value,
"You don't have permission to execute automations in this search space",
)
try:
run = await dispatch_manual_run(
session=session,
automation_id=automation_id,
payload=payload,
)
except DispatchError as exc:
raise HTTPException(status_code=422, detail=str(exc)) from exc
return {"run_id": run.id, "status": run.status.value}