611 lines
22 KiB
Python
611 lines
22 KiB
Python
"""
|
|
API endpoints for autonomous operations and webhook management
|
|
"""
|
|
|
|
import asyncio
|
|
import logging
|
|
from typing import Any, Dict, List, Optional, Union
|
|
from datetime import datetime
|
|
|
|
from fastapi import APIRouter, HTTPException, Depends, BackgroundTasks, Query, Body
|
|
from fastapi.responses import JSONResponse
|
|
from pydantic import BaseModel, HttpUrl, Field
|
|
|
|
from ..autonomous.webhook_system import (
|
|
WebhookEvent, WebhookSecurityType, webhook_manager,
|
|
register_webhook, trigger_event, get_webhook_status, get_system_stats
|
|
)
|
|
from ..autonomous.autonomous_controller import (
|
|
AutomationTrigger, AutomationAction, AutomationStatus,
|
|
autonomous_controller, start_autonomous_operations,
|
|
stop_autonomous_operations, get_automation_status,
|
|
trigger_manual_execution
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/api/autonomous", tags=["autonomous"])
|
|
|
|
# Pydantic models for request/response validation
|
|
|
|
class WebhookRegistrationRequest(BaseModel):
|
|
"""Request model for webhook registration"""
|
|
url: HttpUrl = Field(..., description="Webhook URL endpoint")
|
|
events: List[WebhookEvent] = Field(..., description="List of events to subscribe to")
|
|
security_type: WebhookSecurityType = Field(WebhookSecurityType.HMAC_SHA256, description="Security method")
|
|
secret: Optional[str] = Field(None, description="Secret for webhook security (auto-generated if not provided)")
|
|
headers: Dict[str, str] = Field(default_factory=dict, description="Additional headers to send")
|
|
timeout_seconds: int = Field(30, ge=5, le=300, description="Request timeout in seconds")
|
|
retry_attempts: int = Field(3, ge=1, le=10, description="Number of retry attempts")
|
|
retry_delay_seconds: int = Field(5, ge=1, le=60, description="Delay between retries")
|
|
filter_conditions: Optional[Dict[str, Any]] = Field(None, description="Filter conditions for events")
|
|
|
|
class WebhookUpdateRequest(BaseModel):
|
|
"""Request model for webhook updates"""
|
|
url: Optional[HttpUrl] = Field(None, description="New webhook URL")
|
|
events: Optional[List[WebhookEvent]] = Field(None, description="Updated list of events")
|
|
security_type: Optional[WebhookSecurityType] = Field(None, description="Updated security method")
|
|
secret: Optional[str] = Field(None, description="Updated secret")
|
|
headers: Optional[Dict[str, str]] = Field(None, description="Updated headers")
|
|
timeout_seconds: Optional[int] = Field(None, ge=5, le=300, description="Updated timeout")
|
|
retry_attempts: Optional[int] = Field(None, ge=1, le=10, description="Updated retry attempts")
|
|
active: Optional[bool] = Field(None, description="Activate/deactivate webhook")
|
|
|
|
class ManualEventTriggerRequest(BaseModel):
|
|
"""Request model for manual event triggering"""
|
|
event: WebhookEvent = Field(..., description="Event type to trigger")
|
|
data: Dict[str, Any] = Field(..., description="Event data payload")
|
|
metadata: Optional[Dict[str, Any]] = Field(None, description="Additional metadata")
|
|
|
|
class AutomationRuleRequest(BaseModel):
|
|
"""Request model for automation rule creation"""
|
|
name: str = Field(..., min_length=1, max_length=100, description="Rule name")
|
|
description: str = Field(..., min_length=1, max_length=500, description="Rule description")
|
|
trigger: AutomationTrigger = Field(..., description="Trigger type")
|
|
action: AutomationAction = Field(..., description="Action to perform")
|
|
parameters: Dict[str, Any] = Field(default_factory=dict, description="Action parameters")
|
|
conditions: Dict[str, Any] = Field(default_factory=dict, description="Trigger conditions")
|
|
|
|
class AutomationRuleUpdateRequest(BaseModel):
|
|
"""Request model for automation rule updates"""
|
|
name: Optional[str] = Field(None, min_length=1, max_length=100, description="Updated name")
|
|
description: Optional[str] = Field(None, min_length=1, max_length=500, description="Updated description")
|
|
parameters: Optional[Dict[str, Any]] = Field(None, description="Updated parameters")
|
|
conditions: Optional[Dict[str, Any]] = Field(None, description="Updated conditions")
|
|
status: Optional[AutomationStatus] = Field(None, description="Updated status")
|
|
|
|
# Webhook Management Endpoints
|
|
|
|
@router.post("/webhooks/{webhook_id}", status_code=201)
|
|
async def register_webhook_endpoint(
|
|
webhook_id: str,
|
|
request: WebhookRegistrationRequest
|
|
):
|
|
"""
|
|
Register a new webhook endpoint.
|
|
|
|
Webhooks allow your application to receive real-time notifications
|
|
about YouTube Summarizer events such as completed transcriptions,
|
|
failed processing, batch completions, and system status changes.
|
|
"""
|
|
try:
|
|
success = webhook_manager.register_webhook(
|
|
webhook_id=webhook_id,
|
|
url=str(request.url),
|
|
events=request.events,
|
|
security_type=request.security_type,
|
|
secret=request.secret,
|
|
headers=request.headers,
|
|
timeout_seconds=request.timeout_seconds,
|
|
retry_attempts=request.retry_attempts,
|
|
retry_delay_seconds=request.retry_delay_seconds,
|
|
filter_conditions=request.filter_conditions
|
|
)
|
|
|
|
if not success:
|
|
raise HTTPException(status_code=400, detail="Failed to register webhook")
|
|
|
|
# Get the registered webhook details
|
|
webhook_status = webhook_manager.get_webhook_status(webhook_id)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Webhook {webhook_id} registered successfully",
|
|
"webhook": webhook_status
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error registering webhook {webhook_id}: {e}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.get("/webhooks/{webhook_id}")
|
|
async def get_webhook_details(webhook_id: str):
|
|
"""Get details and status of a specific webhook"""
|
|
webhook_status = webhook_manager.get_webhook_status(webhook_id)
|
|
|
|
if not webhook_status:
|
|
raise HTTPException(status_code=404, detail="Webhook not found")
|
|
|
|
return {
|
|
"success": True,
|
|
"webhook": webhook_status
|
|
}
|
|
|
|
@router.put("/webhooks/{webhook_id}")
|
|
async def update_webhook_endpoint(
|
|
webhook_id: str,
|
|
request: WebhookUpdateRequest
|
|
):
|
|
"""Update an existing webhook configuration"""
|
|
if webhook_id not in webhook_manager.webhooks:
|
|
raise HTTPException(status_code=404, detail="Webhook not found")
|
|
|
|
try:
|
|
# Prepare update data
|
|
updates = {}
|
|
for field, value in request.dict(exclude_unset=True).items():
|
|
if value is not None:
|
|
if field == "url":
|
|
updates[field] = str(value)
|
|
else:
|
|
updates[field] = value
|
|
|
|
success = webhook_manager.update_webhook(webhook_id, **updates)
|
|
|
|
if not success:
|
|
raise HTTPException(status_code=400, detail="Failed to update webhook")
|
|
|
|
webhook_status = webhook_manager.get_webhook_status(webhook_id)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Webhook {webhook_id} updated successfully",
|
|
"webhook": webhook_status
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error updating webhook {webhook_id}: {e}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.delete("/webhooks/{webhook_id}")
|
|
async def unregister_webhook_endpoint(webhook_id: str):
|
|
"""Unregister a webhook"""
|
|
success = webhook_manager.unregister_webhook(webhook_id)
|
|
|
|
if not success:
|
|
raise HTTPException(status_code=404, detail="Webhook not found")
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Webhook {webhook_id} unregistered successfully"
|
|
}
|
|
|
|
@router.post("/webhooks/{webhook_id}/activate")
|
|
async def activate_webhook_endpoint(webhook_id: str):
|
|
"""Activate a webhook"""
|
|
success = webhook_manager.activate_webhook(webhook_id)
|
|
|
|
if not success:
|
|
raise HTTPException(status_code=404, detail="Webhook not found")
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Webhook {webhook_id} activated"
|
|
}
|
|
|
|
@router.post("/webhooks/{webhook_id}/deactivate")
|
|
async def deactivate_webhook_endpoint(webhook_id: str):
|
|
"""Deactivate a webhook"""
|
|
success = webhook_manager.deactivate_webhook(webhook_id)
|
|
|
|
if not success:
|
|
raise HTTPException(status_code=404, detail="Webhook not found")
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Webhook {webhook_id} deactivated"
|
|
}
|
|
|
|
@router.get("/webhooks/{webhook_id}/deliveries/{delivery_id}")
|
|
async def get_delivery_status(webhook_id: str, delivery_id: str):
|
|
"""Get status of a specific webhook delivery"""
|
|
delivery_status = webhook_manager.get_delivery_status(delivery_id)
|
|
|
|
if not delivery_status:
|
|
raise HTTPException(status_code=404, detail="Delivery not found")
|
|
|
|
if delivery_status["webhook_id"] != webhook_id:
|
|
raise HTTPException(status_code=404, detail="Delivery not found for this webhook")
|
|
|
|
return {
|
|
"success": True,
|
|
"delivery": delivery_status
|
|
}
|
|
|
|
@router.post("/webhooks/test")
|
|
async def trigger_test_event(request: ManualEventTriggerRequest):
|
|
"""
|
|
Manually trigger a webhook event for testing purposes.
|
|
|
|
This endpoint allows you to test your webhook endpoints by manually
|
|
triggering events with custom data payloads.
|
|
"""
|
|
try:
|
|
delivery_ids = await trigger_event(
|
|
event=request.event,
|
|
data=request.data,
|
|
metadata=request.metadata
|
|
)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Triggered event {request.event}",
|
|
"delivery_ids": delivery_ids,
|
|
"webhooks_notified": len(delivery_ids)
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error triggering test event: {e}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.get("/webhooks")
|
|
async def list_webhooks():
|
|
"""List all registered webhooks with their status"""
|
|
webhooks = []
|
|
|
|
for webhook_id in webhook_manager.webhooks.keys():
|
|
webhook_status = webhook_manager.get_webhook_status(webhook_id)
|
|
if webhook_status:
|
|
webhooks.append(webhook_status)
|
|
|
|
return {
|
|
"success": True,
|
|
"total_webhooks": len(webhooks),
|
|
"webhooks": webhooks
|
|
}
|
|
|
|
@router.get("/webhooks/system/stats")
|
|
async def get_webhook_system_stats():
|
|
"""Get overall webhook system statistics"""
|
|
stats = webhook_manager.get_system_stats()
|
|
|
|
return {
|
|
"success": True,
|
|
"stats": stats
|
|
}
|
|
|
|
@router.post("/webhooks/system/cleanup")
|
|
async def cleanup_old_deliveries(days_old: int = Query(7, ge=1, le=30)):
|
|
"""Clean up old webhook delivery records"""
|
|
cleaned_count = webhook_manager.cleanup_old_deliveries(days_old)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Cleaned up {cleaned_count} delivery records older than {days_old} days",
|
|
"cleaned_count": cleaned_count
|
|
}
|
|
|
|
# Autonomous Operation Endpoints
|
|
|
|
@router.post("/automation/start")
|
|
async def start_automation():
|
|
"""Start the autonomous operation system"""
|
|
try:
|
|
await start_autonomous_operations()
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "Autonomous operations started",
|
|
"status": get_automation_status()
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error starting autonomous operations: {e}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.post("/automation/stop")
|
|
async def stop_automation():
|
|
"""Stop the autonomous operation system"""
|
|
try:
|
|
await stop_autonomous_operations()
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "Autonomous operations stopped",
|
|
"status": get_automation_status()
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error stopping autonomous operations: {e}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.get("/automation/status")
|
|
async def get_automation_system_status():
|
|
"""Get autonomous operation system status"""
|
|
status = get_automation_status()
|
|
|
|
return {
|
|
"success": True,
|
|
"status": status
|
|
}
|
|
|
|
@router.post("/automation/rules", status_code=201)
|
|
async def create_automation_rule(request: AutomationRuleRequest):
|
|
"""Create a new automation rule"""
|
|
try:
|
|
rule_id = autonomous_controller.add_rule(
|
|
name=request.name,
|
|
description=request.description,
|
|
trigger=request.trigger,
|
|
action=request.action,
|
|
parameters=request.parameters,
|
|
conditions=request.conditions
|
|
)
|
|
|
|
rule_status = autonomous_controller.get_rule_status(rule_id)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Automation rule '{request.name}' created",
|
|
"rule_id": rule_id,
|
|
"rule": rule_status
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error creating automation rule: {e}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.get("/automation/rules/{rule_id}")
|
|
async def get_automation_rule(rule_id: str):
|
|
"""Get details of a specific automation rule"""
|
|
rule_status = autonomous_controller.get_rule_status(rule_id)
|
|
|
|
if not rule_status:
|
|
raise HTTPException(status_code=404, detail="Automation rule not found")
|
|
|
|
return {
|
|
"success": True,
|
|
"rule": rule_status
|
|
}
|
|
|
|
@router.put("/automation/rules/{rule_id}")
|
|
async def update_automation_rule(
|
|
rule_id: str,
|
|
request: AutomationRuleUpdateRequest
|
|
):
|
|
"""Update an automation rule"""
|
|
if rule_id not in autonomous_controller.rules:
|
|
raise HTTPException(status_code=404, detail="Automation rule not found")
|
|
|
|
try:
|
|
# Prepare update data
|
|
updates = request.dict(exclude_unset=True)
|
|
|
|
success = autonomous_controller.update_rule(rule_id, **updates)
|
|
|
|
if not success:
|
|
raise HTTPException(status_code=400, detail="Failed to update automation rule")
|
|
|
|
rule_status = autonomous_controller.get_rule_status(rule_id)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Automation rule {rule_id} updated",
|
|
"rule": rule_status
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error updating automation rule {rule_id}: {e}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.delete("/automation/rules/{rule_id}")
|
|
async def delete_automation_rule(rule_id: str):
|
|
"""Delete an automation rule"""
|
|
success = autonomous_controller.remove_rule(rule_id)
|
|
|
|
if not success:
|
|
raise HTTPException(status_code=404, detail="Automation rule not found")
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Automation rule {rule_id} deleted"
|
|
}
|
|
|
|
@router.post("/automation/rules/{rule_id}/activate")
|
|
async def activate_automation_rule(rule_id: str):
|
|
"""Activate an automation rule"""
|
|
success = autonomous_controller.activate_rule(rule_id)
|
|
|
|
if not success:
|
|
raise HTTPException(status_code=404, detail="Automation rule not found")
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Automation rule {rule_id} activated"
|
|
}
|
|
|
|
@router.post("/automation/rules/{rule_id}/deactivate")
|
|
async def deactivate_automation_rule(rule_id: str):
|
|
"""Deactivate an automation rule"""
|
|
success = autonomous_controller.deactivate_rule(rule_id)
|
|
|
|
if not success:
|
|
raise HTTPException(status_code=404, detail="Automation rule not found")
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Automation rule {rule_id} deactivated"
|
|
}
|
|
|
|
@router.post("/automation/rules/{rule_id}/execute")
|
|
async def execute_automation_rule(rule_id: str, background_tasks: BackgroundTasks):
|
|
"""Manually execute an automation rule"""
|
|
if rule_id not in autonomous_controller.rules:
|
|
raise HTTPException(status_code=404, detail="Automation rule not found")
|
|
|
|
# Execute in background
|
|
background_tasks.add_task(trigger_manual_execution, rule_id)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Automation rule {rule_id} execution triggered",
|
|
"rule_id": rule_id
|
|
}
|
|
|
|
@router.get("/automation/rules")
|
|
async def list_automation_rules(
|
|
status: Optional[AutomationStatus] = Query(None, description="Filter by status"),
|
|
trigger: Optional[AutomationTrigger] = Query(None, description="Filter by trigger type"),
|
|
action: Optional[AutomationAction] = Query(None, description="Filter by action type")
|
|
):
|
|
"""List all automation rules with optional filters"""
|
|
rules = []
|
|
|
|
for rule_id in autonomous_controller.rules.keys():
|
|
rule_status = autonomous_controller.get_rule_status(rule_id)
|
|
if rule_status:
|
|
# Apply filters
|
|
if status and rule_status["status"] != status:
|
|
continue
|
|
if trigger and rule_status["trigger"] != trigger:
|
|
continue
|
|
if action and rule_status["action"] != action:
|
|
continue
|
|
|
|
rules.append(rule_status)
|
|
|
|
return {
|
|
"success": True,
|
|
"total_rules": len(rules),
|
|
"rules": rules,
|
|
"filters_applied": {
|
|
"status": status,
|
|
"trigger": trigger,
|
|
"action": action
|
|
}
|
|
}
|
|
|
|
@router.get("/automation/executions")
|
|
async def get_execution_history(
|
|
rule_id: Optional[str] = Query(None, description="Filter by rule ID"),
|
|
limit: int = Query(50, ge=1, le=200, description="Maximum number of executions to return")
|
|
):
|
|
"""Get automation execution history"""
|
|
executions = autonomous_controller.get_execution_history(rule_id, limit)
|
|
|
|
return {
|
|
"success": True,
|
|
"total_executions": len(executions),
|
|
"executions": executions,
|
|
"rule_id_filter": rule_id
|
|
}
|
|
|
|
# System Health and Monitoring
|
|
|
|
@router.get("/system/health")
|
|
async def get_system_health():
|
|
"""Get overall autonomous system health status"""
|
|
automation_status = get_automation_status()
|
|
webhook_stats = webhook_manager.get_system_stats()
|
|
|
|
# Overall health calculation
|
|
automation_health = "healthy" if automation_status["controller_status"] == "running" else "unhealthy"
|
|
webhook_health = "healthy" if webhook_stats["webhook_manager_status"] == "running" else "unhealthy"
|
|
|
|
overall_health = "healthy" if automation_health == "healthy" and webhook_health == "healthy" else "degraded"
|
|
|
|
return {
|
|
"success": True,
|
|
"overall_health": overall_health,
|
|
"timestamp": datetime.now().isoformat(),
|
|
"components": {
|
|
"automation_controller": {
|
|
"status": automation_health,
|
|
"details": automation_status
|
|
},
|
|
"webhook_manager": {
|
|
"status": webhook_health,
|
|
"details": webhook_stats
|
|
}
|
|
},
|
|
"recommendations": [
|
|
"Monitor webhook delivery success rates",
|
|
"Review automation rule execution patterns",
|
|
"Check system resource utilization",
|
|
"Validate external service connectivity"
|
|
]
|
|
}
|
|
|
|
@router.get("/system/metrics")
|
|
async def get_system_metrics():
|
|
"""Get comprehensive system metrics"""
|
|
automation_status = get_automation_status()
|
|
webhook_stats = webhook_manager.get_system_stats()
|
|
|
|
return {
|
|
"success": True,
|
|
"timestamp": datetime.now().isoformat(),
|
|
"metrics": {
|
|
"automation": {
|
|
"total_rules": automation_status["total_rules"],
|
|
"active_rules": automation_status["active_rules"],
|
|
"total_executions": automation_status["total_executions"],
|
|
"success_rate": automation_status["success_rate"],
|
|
"average_execution_time": automation_status["average_execution_time"]
|
|
},
|
|
"webhooks": {
|
|
"total_webhooks": webhook_stats["total_webhooks"],
|
|
"active_webhooks": webhook_stats["active_webhooks"],
|
|
"total_deliveries": webhook_stats["total_deliveries"],
|
|
"success_rate": webhook_stats["success_rate"],
|
|
"average_response_time": webhook_stats["average_response_time"],
|
|
"pending_deliveries": webhook_stats["pending_deliveries"]
|
|
},
|
|
"system": {
|
|
"services_available": automation_status["services_available"],
|
|
"uptime_seconds": 0 # Would calculate real uptime
|
|
}
|
|
}
|
|
}
|
|
|
|
# Event and Activity Logs
|
|
|
|
@router.get("/events")
|
|
async def get_recent_events(
|
|
limit: int = Query(100, ge=1, le=500, description="Maximum number of events to return"),
|
|
event_type: Optional[WebhookEvent] = Query(None, description="Filter by event type")
|
|
):
|
|
"""Get recent system events and activities"""
|
|
# This would integrate with a real event logging system
|
|
# For now, we'll return a mock response
|
|
|
|
mock_events = [
|
|
{
|
|
"id": "evt_001",
|
|
"event_type": "transcription.completed",
|
|
"timestamp": datetime.now().isoformat(),
|
|
"data": {"video_id": "abc123", "processing_time": 45.2},
|
|
"source": "pipeline"
|
|
},
|
|
{
|
|
"id": "evt_002",
|
|
"event_type": "automation_rule_executed",
|
|
"timestamp": datetime.now().isoformat(),
|
|
"data": {"rule_name": "Daily Cache Cleanup", "items_cleaned": 25},
|
|
"source": "automation_controller"
|
|
}
|
|
]
|
|
|
|
# Apply filters
|
|
if event_type:
|
|
mock_events = [e for e in mock_events if e["event_type"] == event_type]
|
|
|
|
return {
|
|
"success": True,
|
|
"total_events": len(mock_events),
|
|
"events": mock_events[:limit],
|
|
"filters_applied": {
|
|
"event_type": event_type,
|
|
"limit": limit
|
|
}
|
|
} |