""" FastAPI application assembly for the AlphaX Agent web surface. """ from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from app.db.schema import init_db from app.db import auth_db from app.db.system_logs import record_exception from app.db.analytics import get_all_recommendations, get_cron_run_logs, get_cron_run_summary, get_review_stats, get_stats from app.db.recommendation_queries import get_active_recommendations, get_active_recommendations_deduped from app.web.routes_admin import build_router as build_admin_router from app.web.routes_auth import router as auth_router from app.web.routes_chat import router as chat_router from app.web.routes_content import build_router as build_content_router from app.web.routes_market import router as market_router from app.web.routes_live_trading import router as live_trading_router from app.web.routes_paper_trading import router as paper_trading_router from app.web.routes_pages import build_router as build_pages_router from app.web.routes_recommendations import router as recommendations_router from app.web.routes_review_center import router as review_center_router from app.web.routes_strategy import router as strategy_router from app.web.shared import current_request from app.web.shared import require_active_subscription as _require_active_subscription REPO_ROOT = Path(__file__).resolve().parents[2] STOCK_REPORT_TEMPLATE = (REPO_ROOT / "templates" / "stock_report_template.html").read_text(encoding="utf-8") HTML_PAGE = (REPO_ROOT / "static" / "app.html").read_text(encoding="utf-8") @asynccontextmanager async def lifespan(app: FastAPI): init_db() bootstrap_admin = auth_db.ensure_default_admin() if bootstrap_admin.get("created"): print(f"默认管理员已创建: {bootstrap_admin.get('email')}") yield app = FastAPI(title="AlphaX Agent", lifespan=lifespan) templates = Jinja2Templates(directory=str(REPO_ROOT / "static")) app.mount("/static", StaticFiles(directory=str(REPO_ROOT / "static")), name="static") app.include_router(auth_router) app.include_router(chat_router) app.include_router(recommendations_router) app.include_router(review_center_router) app.include_router(strategy_router) app.include_router(paper_trading_router) app.include_router(live_trading_router) app.include_router(market_router) app.include_router(build_admin_router(templates)) app.include_router(build_content_router(REPO_ROOT)) app.include_router(build_pages_router(templates, REPO_ROOT, STOCK_REPORT_TEMPLATE)) @app.middleware("http") async def bind_current_request(request: Request, call_next): token = current_request.set(request) try: return await call_next(request) finally: current_request.reset(token) @app.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): user = None try: user = auth_db.get_user_by_session_token(request.cookies.get("altcoin_session", "")) except Exception: user = None log_id = record_exception( exc, source="web", request_method=request.method, request_path=request.url.path, query_string=request.url.query, user_email=(user or {}).get("email", ""), user_id=(user or {}).get("id", 0), status_code=500, context={ "client": request.client.host if request.client else "", "user_agent": request.headers.get("user-agent", ""), }, ) return JSONResponse( status_code=500, content={"detail": "系统内部错误", "error_id": log_id}, )