54884f3e24
Feed-health pass over KOL_FEEDS: - raoulpal: stale Substack (last 2024-05) → Real Vision podcast feed - dampedspring: paywalled (0 entries) → free "Damped Spring 101" Substack - unchained: Cloudflare 403 → canonical Megaphone podcast feed - lynalden: Cloudflare 202 → FeedBurner mirror - glassnode: recovered via httpx http2=True (was 403 on HTTP/1.1) - browser User-Agent + Accept headers on feed fetch - removed dead feeds with no active replacement: placeholder, dragonfly, niccarter, eugene - pin h2==4.3.0 (required by http2=True) All 25 remaining feeds verified fetching real body content; newest post per feed ≤88d. Bundles in-flight KOL-module work already in the working tree (kol_x ingest, migration 027, tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
53 lines
1.6 KiB
Python
53 lines
1.6 KiB
Python
"""
|
|
API endpoints for the breakout signal monitor.
|
|
|
|
GET /api/signal/status — current state (enabled, recent signals)
|
|
POST /api/signal/toggle — flip the on/off switch (requires X-Ingest-Key)
|
|
GET /api/signal/history — last N signals (fired regardless of enabled state)
|
|
"""
|
|
|
|
from fastapi import APIRouter, Header, HTTPException
|
|
from typing import Optional
|
|
|
|
from app.config import settings
|
|
from app.services.funding_signal import (
|
|
set_enabled,
|
|
is_enabled,
|
|
get_recent_signals,
|
|
get_status,
|
|
)
|
|
|
|
router = APIRouter(prefix="/signal", tags=["signal"])
|
|
|
|
|
|
def _require_ingest_key(x_ingest_key: Optional[str]) -> None:
|
|
"""Fail-closed operator-only guard (same pattern as signals.py)."""
|
|
expected = settings.ingest_api_key
|
|
if not expected:
|
|
raise HTTPException(503, "toggle disabled (INGEST_API_KEY not configured)")
|
|
if not x_ingest_key:
|
|
raise HTTPException(401, "missing X-Ingest-Key header")
|
|
if x_ingest_key != expected:
|
|
raise HTTPException(401, "invalid X-Ingest-Key")
|
|
|
|
|
|
@router.get("/status")
|
|
async def status():
|
|
return get_status()
|
|
|
|
|
|
@router.post("/toggle")
|
|
async def toggle(enabled: bool, x_ingest_key: Optional[str] = Header(default=None)):
|
|
"""
|
|
Operator-only toggle — requires X-Ingest-Key header (same secret as signal ingest).
|
|
Example: POST /api/signal/toggle?enabled=true -H 'X-Ingest-Key: …'
|
|
"""
|
|
_require_ingest_key(x_ingest_key)
|
|
set_enabled(enabled)
|
|
return {"enabled": is_enabled()}
|
|
|
|
|
|
@router.get("/history")
|
|
async def history(limit: int = 20):
|
|
return get_recent_signals(limit)
|