gateway/modules/features/automation2/routeFeatureAutomation2.py
2026-03-22 15:25:29 +01:00

96 lines
3.5 KiB
Python

# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Automation2 routes - node-types, execute, info.
"""
import logging
from fastapi import APIRouter, Depends, Path, Query, Body
from modules.auth import limiter, getRequestContext, RequestContext
from modules.features.automation2.mainAutomation2 import getAutomation2Services
from modules.features.automation2.nodeRegistry import getNodeTypesForApi
from modules.workflows.automation2.executionEngine import executeGraph
logger = logging.getLogger(__name__)
router = APIRouter(
prefix="/api/automation2",
tags=["Automation2"],
responses={404: {"description": "Not found"}, 403: {"description": "Forbidden"}},
)
def _validateInstanceAccess(instanceId: str, context: RequestContext) -> str:
"""Validate user has access to the automation2 feature instance. Returns mandateId."""
from fastapi import HTTPException
from modules.interfaces.interfaceDbApp import getRootInterface
rootInterface = getRootInterface()
instance = rootInterface.getFeatureInstance(instanceId)
if not instance:
raise HTTPException(status_code=404, detail=f"Feature instance {instanceId} not found")
featureAccess = rootInterface.getFeatureAccess(str(context.user.id), instanceId)
if not featureAccess or not featureAccess.enabled:
raise HTTPException(status_code=403, detail="Access denied to this feature instance")
return str(instance.mandateId) if instance.mandateId else ""
@router.get("/{instanceId}/info")
@limiter.limit("60/minute")
def get_automation2_info(
instanceId: str = Path(..., description="Feature instance ID"),
context: RequestContext = Depends(getRequestContext),
) -> dict:
"""Minimal info endpoint - proves the feature works."""
_validateInstanceAccess(instanceId, context)
return {
"featureCode": "automation2",
"instanceId": instanceId,
"status": "ok",
"message": "Automation2 feature ready. Build from here.",
}
@router.get("/{instanceId}/node-types")
@limiter.limit("60/minute")
def get_node_types(
instanceId: str = Path(..., description="Feature instance ID"),
language: str = Query("en", description="Localization (en, de, fr)"),
context: RequestContext = Depends(getRequestContext),
) -> dict:
"""Return node types for the flow builder: static + I/O from methodDiscovery."""
mandateId = _validateInstanceAccess(instanceId, context)
services = getAutomation2Services(
context.user,
mandateId=mandateId,
featureInstanceId=instanceId,
)
return getNodeTypesForApi(services, language=language)
@router.post("/{instanceId}/execute")
@limiter.limit("30/minute")
async def post_execute(
instanceId: str = Path(..., description="Feature instance ID"),
body: dict = Body(..., description="{ workflowId?, graph: { nodes, connections } }"),
context: RequestContext = Depends(getRequestContext),
) -> dict:
"""Execute automation2 graph. Body: { workflowId?, graph: { nodes, connections } }."""
mandateId = _validateInstanceAccess(instanceId, context)
services = getAutomation2Services(
context.user,
mandateId=mandateId,
featureInstanceId=instanceId,
)
graph = body.get("graph") or body
workflowId = body.get("workflowId")
result = await executeGraph(
graph=graph,
services=services,
workflowId=workflowId,
instanceId=instanceId,
userId=str(context.user.id) if context.user else None,
mandateId=mandateId,
)
return result