"""FastAPI entry point for persistent product workbench usage statistics.""" import os from contextlib import asynccontextmanager from urllib.parse import urlsplit from fastapi import FastAPI, HTTPException, Request from fastapi.middleware.cors import CORSMiddleware from .catalog import APPLICATIONS from .database import initialize, session, utc_now @asynccontextmanager async def lifespan(_app: FastAPI): initialize() yield app = FastAPI(title="蝶雅产品AI中心统计 API", version="1.0.0", lifespan=lifespan) cors_origins = [origin.strip() for origin in os.getenv("PORTAL_CORS_ORIGINS", "").split(",") if origin.strip()] if cors_origins: app.add_middleware( CORSMiddleware, allow_origins=cors_origins, allow_methods=["GET", "POST"], allow_headers=["Content-Type"], ) @app.get("/api/health") def health(): with session() as connection: connection.execute("SELECT 1").fetchone() return {"status": "ok"} @app.get("/api/stats") def get_stats(): with session() as connection: rows = connection.execute( "SELECT app_id, click_count, updated_at FROM application_usage ORDER BY app_id" ).fetchall() counts = {row["app_id"]: row for row in rows} items = [ { **application, "click_count": counts[app_id]["click_count"] if app_id in counts else 0, "updated_at": counts[app_id]["updated_at"] if app_id in counts else None, } for app_id, application in APPLICATIONS.items() ] timestamps = [item["updated_at"] for item in items if item["updated_at"]] return { "items": items, "total_clicks": sum(item["click_count"] for item in items), "updated_at": max(timestamps) if timestamps else None, } @app.post("/api/apps/{app_id}/click", status_code=201) def record_click(app_id: str, request: Request): application = APPLICATIONS.get(app_id) if application is None: raise HTTPException(status_code=404, detail="工作台不存在") origin = request.headers.get("origin") request_host = request.headers.get("x-forwarded-host") or request.headers.get("host", "") origin_host = urlsplit(origin).netloc if origin else "" if origin and origin_host.lower() != request_host.lower(): allowed = {item.strip().rstrip("/") for item in os.getenv("PORTAL_CORS_ORIGINS", "").split(",") if item.strip()} if origin.rstrip("/") not in allowed: raise HTTPException(status_code=403, detail="来源不受允许") now = utc_now() with session() as connection: connection.execute( """INSERT INTO application_usage (app_id, click_count, updated_at) VALUES (?, 1, ?) ON CONFLICT(app_id) DO UPDATE SET click_count = click_count + 1, updated_at = excluded.updated_at""", (app_id, now), ) row = connection.execute( "SELECT click_count FROM application_usage WHERE app_id = ?", (app_id,) ).fetchone() return {"app_id": app_id, "click_count": row["click_count"], "updated_at": now}