86 lines
3.0 KiB
Python

"""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}