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:
387
backend/app/routers/api_health.py
Normal file
387
backend/app/routers/api_health.py
Normal file
@@ -0,0 +1,387 @@
|
||||
"""
|
||||
API Health Check Router
|
||||
|
||||
Comprehensive health check endpoint for all external API integrations.
|
||||
Provides status monitoring for Railway, Vercel, Stripe, Twilio, Slack, Discord, Sentry, and more.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import Dict, List, Optional, Any
|
||||
from datetime import datetime
|
||||
import asyncio
|
||||
import os
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/health", tags=["health"])
|
||||
|
||||
|
||||
class APIHealthStatus(BaseModel):
|
||||
"""Health status for a single API"""
|
||||
name: str
|
||||
status: str # connected, not_configured, error
|
||||
message: str
|
||||
last_checked: str
|
||||
configuration: Dict[str, bool]
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
class SystemHealthStatus(BaseModel):
|
||||
"""Overall system health status"""
|
||||
status: str # healthy, degraded, unhealthy
|
||||
timestamp: str
|
||||
total_apis: int
|
||||
connected_apis: int
|
||||
not_configured_apis: int
|
||||
error_apis: int
|
||||
apis: Dict[str, APIHealthStatus]
|
||||
|
||||
|
||||
async def check_api_status(name: str, check_func) -> Dict[str, Any]:
|
||||
"""Check individual API status"""
|
||||
try:
|
||||
result = await check_func()
|
||||
return {
|
||||
"name": name,
|
||||
"status": "connected" if result.get("connected") else "not_configured",
|
||||
"message": result.get("message", ""),
|
||||
"last_checked": datetime.utcnow().isoformat(),
|
||||
"configuration": {
|
||||
k: v for k, v in result.items()
|
||||
if k.endswith("_configured") or k == "connected"
|
||||
},
|
||||
"error": None
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Health check failed for {name}: {e}")
|
||||
return {
|
||||
"name": name,
|
||||
"status": "error",
|
||||
"message": f"Health check failed: {str(e)}",
|
||||
"last_checked": datetime.utcnow().isoformat(),
|
||||
"configuration": {},
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
|
||||
@router.get("/all", response_model=SystemHealthStatus)
|
||||
async def check_all_apis():
|
||||
"""
|
||||
Comprehensive health check for all external APIs.
|
||||
|
||||
Checks connectivity and configuration for:
|
||||
- GitHub API
|
||||
- Railway API
|
||||
- Vercel API
|
||||
- Stripe API
|
||||
- Twilio API (SMS & WhatsApp)
|
||||
- Slack API
|
||||
- Discord API
|
||||
- Sentry API
|
||||
- OpenAI API
|
||||
- Hugging Face API
|
||||
- DigitalOcean API
|
||||
- AWS S3
|
||||
"""
|
||||
|
||||
# Import API clients
|
||||
from .railway import get_railway_status
|
||||
from .vercel import get_vercel_status
|
||||
from .stripe import get_stripe_status
|
||||
from .twilio import get_twilio_status
|
||||
from .slack import get_slack_status
|
||||
from .discord import get_discord_status
|
||||
from .sentry import get_sentry_status
|
||||
|
||||
# Define all API checks
|
||||
api_checks = {
|
||||
"railway": get_railway_status,
|
||||
"vercel": get_vercel_status,
|
||||
"stripe": get_stripe_status,
|
||||
"twilio": get_twilio_status,
|
||||
"slack": get_slack_status,
|
||||
"discord": get_discord_status,
|
||||
"sentry": get_sentry_status,
|
||||
}
|
||||
|
||||
# Add checks for existing APIs
|
||||
api_checks.update({
|
||||
"github": lambda: check_github_status(),
|
||||
"openai": lambda: check_openai_status(),
|
||||
"huggingface": lambda: check_huggingface_status(),
|
||||
"digitalocean": lambda: check_digitalocean_status(),
|
||||
"aws": lambda: check_aws_status(),
|
||||
})
|
||||
|
||||
# Run all checks concurrently
|
||||
tasks = [
|
||||
check_api_status(name, func)
|
||||
for name, func in api_checks.items()
|
||||
]
|
||||
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
# Process results
|
||||
apis = {}
|
||||
connected_count = 0
|
||||
not_configured_count = 0
|
||||
error_count = 0
|
||||
|
||||
for result in results:
|
||||
if isinstance(result, Exception):
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
apis[result["name"]] = result
|
||||
|
||||
if result["status"] == "connected":
|
||||
connected_count += 1
|
||||
elif result["status"] == "not_configured":
|
||||
not_configured_count += 1
|
||||
else:
|
||||
error_count += 1
|
||||
|
||||
# Determine overall system health
|
||||
total_apis = len(apis)
|
||||
if connected_count == total_apis:
|
||||
overall_status = "healthy"
|
||||
elif connected_count > 0:
|
||||
overall_status = "degraded"
|
||||
else:
|
||||
overall_status = "unhealthy"
|
||||
|
||||
return SystemHealthStatus(
|
||||
status=overall_status,
|
||||
timestamp=datetime.utcnow().isoformat(),
|
||||
total_apis=total_apis,
|
||||
connected_apis=connected_count,
|
||||
not_configured_apis=not_configured_count,
|
||||
error_apis=error_count,
|
||||
apis=apis
|
||||
)
|
||||
|
||||
|
||||
@router.get("/summary")
|
||||
async def get_health_summary():
|
||||
"""Get a quick summary of API health"""
|
||||
health = await check_all_apis()
|
||||
|
||||
return {
|
||||
"status": health.status,
|
||||
"timestamp": health.timestamp,
|
||||
"summary": {
|
||||
"total": health.total_apis,
|
||||
"connected": health.connected_apis,
|
||||
"not_configured": health.not_configured_apis,
|
||||
"errors": health.error_apis
|
||||
},
|
||||
"connected_apis": [
|
||||
name for name, api in health.apis.items()
|
||||
if api.status == "connected"
|
||||
],
|
||||
"not_configured_apis": [
|
||||
name for name, api in health.apis.items()
|
||||
if api.status == "not_configured"
|
||||
],
|
||||
"error_apis": [
|
||||
name for name, api in health.apis.items()
|
||||
if api.status == "error"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{api_name}")
|
||||
async def check_specific_api(api_name: str):
|
||||
"""Check health of a specific API"""
|
||||
api_checks = {
|
||||
"railway": lambda: __import__("app.routers.railway", fromlist=["get_railway_status"]).get_railway_status(),
|
||||
"vercel": lambda: __import__("app.routers.vercel", fromlist=["get_vercel_status"]).get_vercel_status(),
|
||||
"stripe": lambda: __import__("app.routers.stripe", fromlist=["get_stripe_status"]).get_stripe_status(),
|
||||
"twilio": lambda: __import__("app.routers.twilio", fromlist=["get_twilio_status"]).get_twilio_status(),
|
||||
"slack": lambda: __import__("app.routers.slack", fromlist=["get_slack_status"]).get_slack_status(),
|
||||
"discord": lambda: __import__("app.routers.discord", fromlist=["get_discord_status"]).get_discord_status(),
|
||||
"sentry": lambda: __import__("app.routers.sentry", fromlist=["get_sentry_status"]).get_sentry_status(),
|
||||
"github": check_github_status,
|
||||
"openai": check_openai_status,
|
||||
"huggingface": check_huggingface_status,
|
||||
"digitalocean": check_digitalocean_status,
|
||||
"aws": check_aws_status,
|
||||
}
|
||||
|
||||
if api_name.lower() not in api_checks:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"API '{api_name}' not found. Available APIs: {', '.join(api_checks.keys())}"
|
||||
)
|
||||
|
||||
check_func = api_checks[api_name.lower()]
|
||||
result = await check_api_status(api_name, check_func)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# Helper functions for existing APIs
|
||||
|
||||
async def check_github_status():
|
||||
"""Check GitHub API status"""
|
||||
github_token = os.getenv("GITHUB_TOKEN")
|
||||
if not github_token:
|
||||
return {
|
||||
"connected": False,
|
||||
"message": "GitHub token not configured",
|
||||
"token_configured": False
|
||||
}
|
||||
|
||||
import httpx
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(
|
||||
"https://api.github.com/user",
|
||||
headers={"Authorization": f"token {github_token}"},
|
||||
timeout=10.0
|
||||
)
|
||||
response.raise_for_status()
|
||||
return {
|
||||
"connected": True,
|
||||
"message": "GitHub API connected successfully",
|
||||
"token_configured": True
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"connected": False,
|
||||
"message": f"GitHub API connection failed: {str(e)}",
|
||||
"token_configured": True
|
||||
}
|
||||
|
||||
|
||||
async def check_openai_status():
|
||||
"""Check OpenAI API status"""
|
||||
openai_key = os.getenv("OPENAI_API_KEY")
|
||||
if not openai_key:
|
||||
return {
|
||||
"connected": False,
|
||||
"message": "OpenAI API key not configured",
|
||||
"key_configured": False
|
||||
}
|
||||
|
||||
import httpx
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(
|
||||
"https://api.openai.com/v1/models",
|
||||
headers={"Authorization": f"Bearer {openai_key}"},
|
||||
timeout=10.0
|
||||
)
|
||||
response.raise_for_status()
|
||||
return {
|
||||
"connected": True,
|
||||
"message": "OpenAI API connected successfully",
|
||||
"key_configured": True
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"connected": False,
|
||||
"message": f"OpenAI API connection failed: {str(e)}",
|
||||
"key_configured": True
|
||||
}
|
||||
|
||||
|
||||
async def check_huggingface_status():
|
||||
"""Check Hugging Face API status"""
|
||||
hf_token = os.getenv("HUGGINGFACE_TOKEN")
|
||||
if not hf_token:
|
||||
return {
|
||||
"connected": False,
|
||||
"message": "Hugging Face token not configured",
|
||||
"token_configured": False
|
||||
}
|
||||
|
||||
import httpx
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(
|
||||
"https://huggingface.co/api/whoami-v2",
|
||||
headers={"Authorization": f"Bearer {hf_token}"},
|
||||
timeout=10.0
|
||||
)
|
||||
response.raise_for_status()
|
||||
return {
|
||||
"connected": True,
|
||||
"message": "Hugging Face API connected successfully",
|
||||
"token_configured": True
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"connected": False,
|
||||
"message": f"Hugging Face API connection failed: {str(e)}",
|
||||
"token_configured": True
|
||||
}
|
||||
|
||||
|
||||
async def check_digitalocean_status():
|
||||
"""Check DigitalOcean API status"""
|
||||
do_token = os.getenv("DIGITALOCEAN_TOKEN")
|
||||
if not do_token:
|
||||
return {
|
||||
"connected": False,
|
||||
"message": "DigitalOcean token not configured",
|
||||
"token_configured": False
|
||||
}
|
||||
|
||||
import httpx
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(
|
||||
"https://api.digitalocean.com/v2/account",
|
||||
headers={"Authorization": f"Bearer {do_token}"},
|
||||
timeout=10.0
|
||||
)
|
||||
response.raise_for_status()
|
||||
return {
|
||||
"connected": True,
|
||||
"message": "DigitalOcean API connected successfully",
|
||||
"token_configured": True
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"connected": False,
|
||||
"message": f"DigitalOcean API connection failed: {str(e)}",
|
||||
"token_configured": True
|
||||
}
|
||||
|
||||
|
||||
async def check_aws_status():
|
||||
"""Check AWS S3 status"""
|
||||
aws_key = os.getenv("AWS_ACCESS_KEY_ID")
|
||||
aws_secret = os.getenv("AWS_SECRET_ACCESS_KEY")
|
||||
|
||||
if not aws_key or not aws_secret:
|
||||
return {
|
||||
"connected": False,
|
||||
"message": "AWS credentials not configured",
|
||||
"key_configured": bool(aws_key),
|
||||
"secret_configured": bool(aws_secret)
|
||||
}
|
||||
|
||||
try:
|
||||
import boto3
|
||||
from botocore.exceptions import ClientError
|
||||
|
||||
s3 = boto3.client('s3')
|
||||
s3.list_buckets()
|
||||
|
||||
return {
|
||||
"connected": True,
|
||||
"message": "AWS S3 connected successfully",
|
||||
"key_configured": True,
|
||||
"secret_configured": True
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"connected": False,
|
||||
"message": f"AWS S3 connection failed: {str(e)}",
|
||||
"key_configured": True,
|
||||
"secret_configured": True
|
||||
}
|
||||
Reference in New Issue
Block a user