|
|
""" |
|
|
Smart Data Endpoints - NEVER Returns 404 |
|
|
Uses 305+ free resources with intelligent fallback |
|
|
""" |
|
|
|
|
|
import time |
|
|
import logging |
|
|
from typing import Optional, List |
|
|
from fastapi import APIRouter, Depends, Query, HTTPException |
|
|
|
|
|
from api.hf_auth import verify_hf_token |
|
|
from utils.logger import setup_logger |
|
|
import sys |
|
|
sys.path.insert(0, '/workspace') |
|
|
from core.smart_fallback_manager import get_fallback_manager |
|
|
from workers.data_collection_agent import get_data_collection_agent |
|
|
|
|
|
logger = setup_logger("smart_data_endpoints") |
|
|
|
|
|
router = APIRouter(prefix="/api/smart", tags=["smart_fallback"]) |
|
|
|
|
|
|
|
|
@router.get("/market") |
|
|
async def get_market_data_smart( |
|
|
limit: int = Query(100, ge=1, le=500, description="Number of coins"), |
|
|
auth: bool = Depends(verify_hf_token) |
|
|
): |
|
|
""" |
|
|
Get market data with SMART FALLBACK |
|
|
|
|
|
- Tries up to 21 different market data APIs |
|
|
- NEVER returns 404 |
|
|
- Automatically switches to working source |
|
|
- Uses proxy for blocked exchanges |
|
|
- Returns data from best available source |
|
|
|
|
|
Categories tried: |
|
|
- market_data_apis (21 sources) |
|
|
- Market Data (17 sources) |
|
|
- Plus local cache |
|
|
""" |
|
|
try: |
|
|
logger.info(f"🔍 Smart Market Data Request (limit={limit})") |
|
|
|
|
|
fallback_manager = get_fallback_manager() |
|
|
|
|
|
|
|
|
data = await fallback_manager.fetch_with_fallback( |
|
|
category='market_data_apis', |
|
|
endpoint_path='/coins/markets', |
|
|
params={ |
|
|
'vs_currency': 'usd', |
|
|
'order': 'market_cap_desc', |
|
|
'per_page': limit, |
|
|
'page': 1 |
|
|
}, |
|
|
max_attempts=15 |
|
|
) |
|
|
|
|
|
if not data: |
|
|
|
|
|
logger.warning("⚠️ Primary category failed, trying alternate...") |
|
|
data = await fallback_manager.fetch_with_fallback( |
|
|
category='Market Data', |
|
|
endpoint_path='/v1/cryptocurrency/listings/latest', |
|
|
params={'limit': limit}, |
|
|
max_attempts=10 |
|
|
) |
|
|
|
|
|
if not data: |
|
|
raise HTTPException( |
|
|
status_code=503, |
|
|
detail="All data sources temporarily unavailable. Please try again in a moment." |
|
|
) |
|
|
|
|
|
|
|
|
items = data if isinstance(data, list) else data.get('data', []) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"source": "smart_fallback", |
|
|
"count": len(items), |
|
|
"items": items[:limit], |
|
|
"timestamp": int(time.time() * 1000), |
|
|
"note": "Data from best available source using smart fallback" |
|
|
} |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"❌ Smart market data error: {e}") |
|
|
raise HTTPException( |
|
|
status_code=500, |
|
|
detail=f"Failed to fetch market data: {str(e)}" |
|
|
) |
|
|
|
|
|
|
|
|
@router.get("/news") |
|
|
async def get_news_smart( |
|
|
limit: int = Query(20, ge=1, le=100, description="Number of news items"), |
|
|
auth: bool = Depends(verify_hf_token) |
|
|
): |
|
|
""" |
|
|
Get crypto news with SMART FALLBACK |
|
|
|
|
|
- Tries 15 different news APIs |
|
|
- NEVER returns 404 |
|
|
- Automatically finds working source |
|
|
""" |
|
|
try: |
|
|
logger.info(f"🔍 Smart News Request (limit={limit})") |
|
|
|
|
|
fallback_manager = get_fallback_manager() |
|
|
|
|
|
data = await fallback_manager.fetch_with_fallback( |
|
|
category='news_apis', |
|
|
endpoint_path='/news', |
|
|
params={'limit': limit}, |
|
|
max_attempts=10 |
|
|
) |
|
|
|
|
|
if not data: |
|
|
|
|
|
data = await fallback_manager.fetch_with_fallback( |
|
|
category='News', |
|
|
endpoint_path='/v1/news', |
|
|
params={'limit': limit}, |
|
|
max_attempts=5 |
|
|
) |
|
|
|
|
|
if not data: |
|
|
raise HTTPException( |
|
|
status_code=503, |
|
|
detail="News sources temporarily unavailable" |
|
|
) |
|
|
|
|
|
news_items = data if isinstance(data, list) else data.get('news', []) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"source": "smart_fallback", |
|
|
"count": len(news_items), |
|
|
"news": news_items[:limit], |
|
|
"timestamp": int(time.time() * 1000) |
|
|
} |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"❌ Smart news error: {e}") |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
@router.get("/sentiment") |
|
|
async def get_sentiment_smart( |
|
|
symbol: Optional[str] = Query(None, description="Crypto symbol (e.g., BTC)"), |
|
|
auth: bool = Depends(verify_hf_token) |
|
|
): |
|
|
""" |
|
|
Get sentiment analysis with SMART FALLBACK |
|
|
|
|
|
- Tries 12 sentiment APIs |
|
|
- NEVER returns 404 |
|
|
- Real-time sentiment from multiple sources |
|
|
""" |
|
|
try: |
|
|
logger.info(f"🔍 Smart Sentiment Request (symbol={symbol})") |
|
|
|
|
|
fallback_manager = get_fallback_manager() |
|
|
|
|
|
endpoint = f"/sentiment/{symbol}" if symbol else "/sentiment/global" |
|
|
|
|
|
data = await fallback_manager.fetch_with_fallback( |
|
|
category='sentiment_apis', |
|
|
endpoint_path=endpoint, |
|
|
max_attempts=8 |
|
|
) |
|
|
|
|
|
if not data: |
|
|
data = await fallback_manager.fetch_with_fallback( |
|
|
category='Sentiment', |
|
|
endpoint_path=endpoint, |
|
|
max_attempts=5 |
|
|
) |
|
|
|
|
|
if not data: |
|
|
raise HTTPException( |
|
|
status_code=503, |
|
|
detail="Sentiment sources temporarily unavailable" |
|
|
) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"source": "smart_fallback", |
|
|
"sentiment": data, |
|
|
"timestamp": int(time.time() * 1000) |
|
|
} |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"❌ Smart sentiment error: {e}") |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
@router.get("/whale-alerts") |
|
|
async def get_whale_alerts_smart( |
|
|
limit: int = Query(20, ge=1, le=100), |
|
|
auth: bool = Depends(verify_hf_token) |
|
|
): |
|
|
""" |
|
|
Get whale tracking alerts with SMART FALLBACK |
|
|
|
|
|
- Tries 9 whale tracking APIs |
|
|
- NEVER returns 404 |
|
|
- Real-time large transactions |
|
|
""" |
|
|
try: |
|
|
logger.info(f"🔍 Smart Whale Alerts Request (limit={limit})") |
|
|
|
|
|
fallback_manager = get_fallback_manager() |
|
|
|
|
|
data = await fallback_manager.fetch_with_fallback( |
|
|
category='whale_tracking_apis', |
|
|
endpoint_path='/whales', |
|
|
params={'limit': limit}, |
|
|
max_attempts=7 |
|
|
) |
|
|
|
|
|
if not data: |
|
|
data = await fallback_manager.fetch_with_fallback( |
|
|
category='Whale-Tracking', |
|
|
endpoint_path='/transactions', |
|
|
params={'limit': limit}, |
|
|
max_attempts=5 |
|
|
) |
|
|
|
|
|
if not data: |
|
|
raise HTTPException( |
|
|
status_code=503, |
|
|
detail="Whale tracking sources temporarily unavailable" |
|
|
) |
|
|
|
|
|
alerts = data if isinstance(data, list) else data.get('transactions', []) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"source": "smart_fallback", |
|
|
"count": len(alerts), |
|
|
"alerts": alerts[:limit], |
|
|
"timestamp": int(time.time() * 1000) |
|
|
} |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"❌ Smart whale alerts error: {e}") |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
@router.get("/blockchain/{chain}") |
|
|
async def get_blockchain_data_smart( |
|
|
chain: str, |
|
|
auth: bool = Depends(verify_hf_token) |
|
|
): |
|
|
""" |
|
|
Get blockchain data with SMART FALLBACK |
|
|
|
|
|
- Tries 40+ block explorers |
|
|
- NEVER returns 404 |
|
|
- Supports: ethereum, bsc, polygon, tron, etc. |
|
|
""" |
|
|
try: |
|
|
logger.info(f"🔍 Smart Blockchain Request (chain={chain})") |
|
|
|
|
|
fallback_manager = get_fallback_manager() |
|
|
|
|
|
data = await fallback_manager.fetch_with_fallback( |
|
|
category='block_explorers', |
|
|
endpoint_path=f'/{chain}/latest', |
|
|
max_attempts=10 |
|
|
) |
|
|
|
|
|
if not data: |
|
|
data = await fallback_manager.fetch_with_fallback( |
|
|
category='Block Explorer', |
|
|
endpoint_path=f'/api?module=stats&action=ethprice', |
|
|
max_attempts=10 |
|
|
) |
|
|
|
|
|
if not data: |
|
|
raise HTTPException( |
|
|
status_code=503, |
|
|
detail=f"Blockchain explorers for {chain} temporarily unavailable" |
|
|
) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"source": "smart_fallback", |
|
|
"chain": chain, |
|
|
"data": data, |
|
|
"timestamp": int(time.time() * 1000) |
|
|
} |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"❌ Smart blockchain error: {e}") |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
@router.get("/health-report") |
|
|
async def get_health_report(auth: bool = Depends(verify_hf_token)): |
|
|
""" |
|
|
Get health report of all 305+ resources |
|
|
|
|
|
Shows: |
|
|
- Total resources |
|
|
- Active/degraded/failed counts |
|
|
- Top performing sources |
|
|
- Failing sources that need attention |
|
|
""" |
|
|
try: |
|
|
fallback_manager = get_fallback_manager() |
|
|
agent = get_data_collection_agent() |
|
|
|
|
|
health_report = fallback_manager.get_health_report() |
|
|
agent_stats = agent.get_stats() |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"health_report": health_report, |
|
|
"agent_stats": agent_stats, |
|
|
"timestamp": int(time.time() * 1000) |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ Health report error: {e}") |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
@router.get("/stats") |
|
|
async def get_smart_stats(auth: bool = Depends(verify_hf_token)): |
|
|
""" |
|
|
Get statistics about smart fallback system |
|
|
|
|
|
Shows: |
|
|
- Total resources available (305+) |
|
|
- Resources by category |
|
|
- Collection statistics |
|
|
- Performance metrics |
|
|
""" |
|
|
try: |
|
|
fallback_manager = get_fallback_manager() |
|
|
agent = get_data_collection_agent() |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"total_resources": fallback_manager._count_total_resources(), |
|
|
"resources_by_category": { |
|
|
category: len(resources) |
|
|
for category, resources in fallback_manager.resources.items() |
|
|
}, |
|
|
"agent_stats": agent.get_stats(), |
|
|
"timestamp": int(time.time() * 1000) |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ Stats error: {e}") |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
|
|
|
@router.post("/cleanup-failed") |
|
|
async def cleanup_failed_resources( |
|
|
max_age_hours: int = Query(24, description="Max age in hours"), |
|
|
auth: bool = Depends(verify_hf_token) |
|
|
): |
|
|
""" |
|
|
Manually trigger cleanup of failed resources |
|
|
|
|
|
Removes resources that have been failing for longer than max_age_hours |
|
|
""" |
|
|
try: |
|
|
fallback_manager = get_fallback_manager() |
|
|
|
|
|
removed = fallback_manager.cleanup_failed_resources(max_age_hours=max_age_hours) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"removed_count": len(removed), |
|
|
"removed_resources": removed, |
|
|
"timestamp": int(time.time() * 1000) |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ Cleanup error: {e}") |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|