T11.2: capture usage events (CR-019)
POST /api/usage/ping writes one page_open UsageEvent per authenticated page load, identity from the session (get_current_user), never from the client. Wired from exactly one place - auth-guard.js's proceed(), after wp-auth-ready - so this can't drift into six separate per-page copies. okta_callback now also writes one login event per sign-in. Verified locally: unauthenticated ping -> 401; a real fake-Okta sign-in writes exactly one login row and one page_open row, no duplicates.
This commit is contained in:
@@ -756,6 +756,11 @@ async def okta_callback(request: Request, db: Session = Depends(get_db)):
|
||||
user.failed_attempts = 0
|
||||
user.locked_until = None
|
||||
user.last_login_at = models.utcnow()
|
||||
# CR-019: one login event per Okta sign-in, written here rather than
|
||||
# inferred from session creation elsewhere, so there is exactly one
|
||||
# source of truth for "did this person sign in" - not one per page load
|
||||
# afterward (T11.2 covers that separately, as page_open events).
|
||||
db.add(models.UsageEvent(id=gen_id("uev"), username=user.username, tool="", event="login"))
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
|
||||
@@ -3374,6 +3379,29 @@ def create_feedback(body: CommentIn, user: models.User = Depends(auth.get_curren
|
||||
return _save_comment(body, db, user)
|
||||
|
||||
|
||||
# ── CR-019: usage/activity metrics ──────────────────────────────────────────
|
||||
class UsageIn(BaseModel):
|
||||
tool: str = ""
|
||||
project_id: Optional[str] = None
|
||||
|
||||
|
||||
@app.post("/api/usage/ping")
|
||||
def usage_ping(body: UsageIn, user: models.User = Depends(auth.get_current_user), db: Session = Depends(get_db)):
|
||||
"""One usage_events row per authenticated page load. Called exactly once,
|
||||
from auth-guard.js after wp-auth-ready fires (T11.2) - never duplicated
|
||||
per page's own script, the same lesson S4's per-page nav already taught
|
||||
this codebase. `user` comes from the session via get_current_user, never
|
||||
from anything the client claims - identity here is a server-enforced
|
||||
fact, matching every other write in this file, not a client-reported one."""
|
||||
tool = (body.tool or "").strip()[:40]
|
||||
db.add(models.UsageEvent(
|
||||
id=gen_id("uev"), username=user.username, project_id=body.project_id,
|
||||
tool=tool, event="page_open",
|
||||
))
|
||||
db.commit()
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@app.get("/api/comments")
|
||||
def list_comments(
|
||||
source: Optional[str] = Query(None),
|
||||
|
||||
Reference in New Issue
Block a user