mirror of
https://github.com/blackboxprogramming/BlackRoad-Operating-System.git
synced 2026-03-17 09:37:55 -05:00
Add comprehensive multi-API integration support
This commit adds extensive API integration capabilities for deployment, payments, communications, and monitoring to BlackRoad OS. New API Integrations: - Railway API: Cloud deployment management (GraphQL) - Vercel API: Serverless deployment platform (REST) - Stripe API: Payment processing and billing - Twilio API: SMS, Voice, and WhatsApp messaging - Slack API: Team collaboration and notifications - Discord API: Community messaging and notifications - Sentry API: Error tracking and application monitoring Core Features: - Centralized API client manager with health checking - Comprehensive health monitoring endpoint (/api/health/*) - Automatic retry logic and rate limit handling - Unified status monitoring for all integrations Infrastructure: - Railway deployment configuration (railway.json, railway.toml) - Enhanced GitHub Actions workflows: * backend-tests.yml: Comprehensive test suite with PostgreSQL/Redis * railway-deploy.yml: Automated Railway deployment with notifications - Docker build validation in CI/CD pipeline Testing: - Comprehensive test suite for all API integrations - API connectivity verification in CI/CD - Mock-friendly architecture for testing without credentials Configuration: - Updated .env.example with all new API keys - Added stripe and sentry-sdk to requirements.txt - Registered all new routers in main.py - Updated API info endpoint with new integrations Documentation: - API_INTEGRATIONS.md: Complete setup and usage guide - Interactive API docs at /api/docs with all endpoints - Health check endpoints for monitoring All APIs are optional and gracefully handle missing credentials. The system provides clear status messages for configuration requirements.
This commit is contained in:
259
backend/app/routers/twilio.py
Normal file
259
backend/app/routers/twilio.py
Normal file
@@ -0,0 +1,259 @@
|
||||
"""
|
||||
Twilio API Integration Router
|
||||
|
||||
Provides endpoints for SMS, voice calls, and WhatsApp messaging.
|
||||
Twilio is a cloud communications platform.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
import httpx
|
||||
import base64
|
||||
import os
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/twilio", tags=["twilio"])
|
||||
|
||||
# Twilio API configuration
|
||||
TWILIO_ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID")
|
||||
TWILIO_AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN")
|
||||
TWILIO_PHONE_NUMBER = os.getenv("TWILIO_PHONE_NUMBER")
|
||||
|
||||
|
||||
class SMSMessage(BaseModel):
|
||||
"""SMS message model"""
|
||||
to: str
|
||||
message: str
|
||||
from_number: Optional[str] = None
|
||||
|
||||
|
||||
class WhatsAppMessage(BaseModel):
|
||||
"""WhatsApp message model"""
|
||||
to: str
|
||||
message: str
|
||||
|
||||
|
||||
class TwilioClient:
|
||||
"""Twilio REST API client"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
account_sid: Optional[str] = None,
|
||||
auth_token: Optional[str] = None,
|
||||
phone_number: Optional[str] = None
|
||||
):
|
||||
self.account_sid = account_sid or TWILIO_ACCOUNT_SID
|
||||
self.auth_token = auth_token or TWILIO_AUTH_TOKEN
|
||||
self.phone_number = phone_number or TWILIO_PHONE_NUMBER
|
||||
self.base_url = f"https://api.twilio.com/2010-04-01/Accounts/{self.account_sid}"
|
||||
|
||||
def _get_headers(self) -> Dict[str, str]:
|
||||
"""Get API request headers with basic auth"""
|
||||
if not self.account_sid or not self.auth_token:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Twilio credentials not configured"
|
||||
)
|
||||
|
||||
# Create basic auth header
|
||||
credentials = f"{self.account_sid}:{self.auth_token}"
|
||||
encoded = base64.b64encode(credentials.encode()).decode()
|
||||
|
||||
return {
|
||||
"Authorization": f"Basic {encoded}",
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
async def _request(
|
||||
self,
|
||||
method: str,
|
||||
endpoint: str,
|
||||
data: Optional[Dict] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Make API request"""
|
||||
headers = self._get_headers()
|
||||
url = f"{self.base_url}{endpoint}"
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
try:
|
||||
response = await client.request(
|
||||
method,
|
||||
url,
|
||||
headers=headers,
|
||||
data=data,
|
||||
timeout=30.0
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
except httpx.HTTPStatusError as e:
|
||||
logger.error(f"Twilio API error: {e.response.text}")
|
||||
raise HTTPException(
|
||||
status_code=e.response.status_code,
|
||||
detail=f"Twilio API error: {e.response.text}"
|
||||
)
|
||||
except httpx.HTTPError as e:
|
||||
logger.error(f"Twilio API request failed: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=f"Twilio API request failed: {str(e)}"
|
||||
)
|
||||
|
||||
async def send_sms(
|
||||
self,
|
||||
to: str,
|
||||
body: str,
|
||||
from_: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Send SMS message"""
|
||||
data = {
|
||||
"To": to,
|
||||
"From": from_ or self.phone_number,
|
||||
"Body": body
|
||||
}
|
||||
|
||||
if not data["From"]:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Twilio phone number not configured"
|
||||
)
|
||||
|
||||
return await self._request("POST", "/Messages.json", data=data)
|
||||
|
||||
async def send_whatsapp(
|
||||
self,
|
||||
to: str,
|
||||
body: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Send WhatsApp message"""
|
||||
data = {
|
||||
"To": f"whatsapp:{to}",
|
||||
"From": f"whatsapp:{self.phone_number}",
|
||||
"Body": body
|
||||
}
|
||||
|
||||
if not self.phone_number:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Twilio phone number not configured"
|
||||
)
|
||||
|
||||
return await self._request("POST", "/Messages.json", data=data)
|
||||
|
||||
async def get_message(self, message_sid: str) -> Dict[str, Any]:
|
||||
"""Get message details"""
|
||||
return await self._request("GET", f"/Messages/{message_sid}.json")
|
||||
|
||||
async def list_messages(self, limit: int = 20) -> Dict[str, Any]:
|
||||
"""List messages"""
|
||||
data = {"PageSize": limit}
|
||||
return await self._request("GET", "/Messages.json", data=data)
|
||||
|
||||
|
||||
# Initialize client
|
||||
twilio_client = TwilioClient()
|
||||
|
||||
|
||||
@router.get("/status")
|
||||
async def get_twilio_status():
|
||||
"""Get Twilio API connection status"""
|
||||
if not TWILIO_ACCOUNT_SID or not TWILIO_AUTH_TOKEN:
|
||||
return {
|
||||
"connected": False,
|
||||
"message": "Twilio credentials not configured. Set TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN."
|
||||
}
|
||||
|
||||
try:
|
||||
# Try to list messages as a health check
|
||||
await twilio_client.list_messages(limit=1)
|
||||
return {
|
||||
"connected": True,
|
||||
"message": "Twilio API connected successfully",
|
||||
"phone_number_configured": bool(TWILIO_PHONE_NUMBER)
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"connected": False,
|
||||
"message": f"Twilio API connection failed: {str(e)}"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/sms")
|
||||
async def send_sms(message: SMSMessage):
|
||||
"""Send SMS message"""
|
||||
try:
|
||||
result = await twilio_client.send_sms(
|
||||
to=message.to,
|
||||
body=message.message,
|
||||
from_=message.from_number
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
"message": result,
|
||||
"sid": result.get("sid")
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending SMS: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to send SMS: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.post("/whatsapp")
|
||||
async def send_whatsapp(message: WhatsAppMessage):
|
||||
"""Send WhatsApp message"""
|
||||
try:
|
||||
result = await twilio_client.send_whatsapp(
|
||||
to=message.to,
|
||||
body=message.message
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
"message": result,
|
||||
"sid": result.get("sid")
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending WhatsApp: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to send WhatsApp: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/messages/{message_sid}")
|
||||
async def get_message(message_sid: str):
|
||||
"""Get message details"""
|
||||
try:
|
||||
message = await twilio_client.get_message(message_sid)
|
||||
return message
|
||||
except HTTPException:
|
||||
raise
|
||||
|
||||
|
||||
@router.get("/messages")
|
||||
async def list_messages(limit: int = 20):
|
||||
"""List messages"""
|
||||
try:
|
||||
result = await twilio_client.list_messages(limit)
|
||||
return result
|
||||
except HTTPException:
|
||||
raise
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
async def twilio_health_check():
|
||||
"""Twilio API health check endpoint"""
|
||||
return {
|
||||
"service": "twilio",
|
||||
"status": "operational" if (TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN) else "not_configured",
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
Reference in New Issue
Block a user