first commit
This commit is contained in:
+151
@@ -0,0 +1,151 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from app.config import settings
|
||||
from app.database import AsyncSessionLocal, engine
|
||||
from app.models import Base
|
||||
from app.scrapers.truth_social import poll_truth_social, backfill_history
|
||||
from app.services.binance import run_binance_ws
|
||||
from app.ws.manager import manager
|
||||
|
||||
# Import all routers
|
||||
from app.api.posts import router as posts_router
|
||||
from app.api.prices import router as prices_router
|
||||
from app.api.trades import router as trades_router
|
||||
from app.api.performance import router as performance_router
|
||||
from app.api.subscribe import router as subscribe_router
|
||||
from app.api.user import router as user_router
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from typing import Optional
|
||||
_binance_task: Optional[asyncio.Task] = None
|
||||
_scheduler: Optional[AsyncIOScheduler] = None
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
global _binance_task, _scheduler
|
||||
|
||||
# 1. Create DB tables (dev convenience; production uses Alembic)
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
logger.info("Database tables ensured.")
|
||||
|
||||
# 2. Backfill historical posts on startup (fast, no Claude API call)
|
||||
asyncio.create_task(backfill_history(AsyncSessionLocal, limit=500))
|
||||
|
||||
# 3. Start Binance WebSocket background task
|
||||
_binance_task = asyncio.create_task(run_binance_ws(), name="binance_ws")
|
||||
logger.info("Binance WebSocket task started.")
|
||||
|
||||
# 3. Start Truth Social poller via APScheduler
|
||||
_scheduler = AsyncIOScheduler()
|
||||
_scheduler.add_job(
|
||||
poll_truth_social,
|
||||
"interval",
|
||||
seconds=settings.truth_social_poll_seconds,
|
||||
args=[AsyncSessionLocal],
|
||||
id="truth_social_poll",
|
||||
max_instances=1,
|
||||
coalesce=True,
|
||||
)
|
||||
_scheduler.start()
|
||||
logger.info(
|
||||
"Truth Social poller scheduled every %ds.", settings.truth_social_poll_seconds
|
||||
)
|
||||
|
||||
yield
|
||||
|
||||
# Shutdown
|
||||
logger.info("Shutting down background tasks...")
|
||||
if _scheduler:
|
||||
_scheduler.shutdown(wait=False)
|
||||
if _binance_task and not _binance_task.done():
|
||||
_binance_task.cancel()
|
||||
try:
|
||||
await _binance_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
await engine.dispose()
|
||||
logger.info("Shutdown complete.")
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="TrumpSignal API",
|
||||
version="1.0.0",
|
||||
description="Crypto trading signals derived from Trump's Truth Social posts.",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
# CORS
|
||||
allowed_origins = [
|
||||
settings.frontend_url,
|
||||
"http://localhost:3001",
|
||||
"http://localhost:3000",
|
||||
]
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=allowed_origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# REST routers
|
||||
app.include_router(posts_router, prefix="/api")
|
||||
app.include_router(prices_router, prefix="/api")
|
||||
app.include_router(trades_router, prefix="/api")
|
||||
app.include_router(performance_router, prefix="/api")
|
||||
app.include_router(subscribe_router, prefix="/api")
|
||||
app.include_router(user_router, prefix="/api")
|
||||
|
||||
|
||||
@app.get("/api/health")
|
||||
async def health():
|
||||
return {"status": "ok"}
|
||||
|
||||
if settings.environment == "development":
|
||||
from app.api.dev import router as dev_router
|
||||
app.include_router(dev_router, prefix="/api")
|
||||
|
||||
|
||||
# ── WebSocket endpoints ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.websocket("/ws/prices")
|
||||
async def ws_prices(websocket: WebSocket):
|
||||
"""Subscribe to live price ticks (type: 'price')."""
|
||||
await manager.connect(websocket)
|
||||
try:
|
||||
while True:
|
||||
# Keep connection alive; messages are pushed via manager.broadcast
|
||||
await websocket.receive_text()
|
||||
except WebSocketDisconnect:
|
||||
await manager.disconnect(websocket)
|
||||
except Exception as exc:
|
||||
logger.warning("WebSocket /ws/prices error: %s", exc)
|
||||
await manager.disconnect(websocket)
|
||||
|
||||
|
||||
@app.websocket("/ws/posts")
|
||||
async def ws_posts(websocket: WebSocket):
|
||||
"""Subscribe to new post events (type: 'new_post')."""
|
||||
await manager.connect(websocket)
|
||||
try:
|
||||
while True:
|
||||
await websocket.receive_text()
|
||||
except WebSocketDisconnect:
|
||||
await manager.disconnect(websocket)
|
||||
except Exception as exc:
|
||||
logger.warning("WebSocket /ws/posts error: %s", exc)
|
||||
await manager.disconnect(websocket)
|
||||
Reference in New Issue
Block a user