86 lines
3.0 KiB
Python
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}
|