youtube-summarizer/backend/api/autonomous.py

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
}
}