96 lines
3.5 KiB
Python
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
|