Matt Mabrey 2de76d52e6 T11.4: usage export endpoint (raw + sanitized CSV)
CR-019 / wave 11. Adds GET /api/usage/export, reusing _usage_query()'s
scoped filters (from/to/project_id/username/tool) and the same
require_user_manager gate as /api/usage/summary. Two modes:

- raw (default): real usernames, for internal admin use.
- sanitize=true: usernames replaced with an HMAC-SHA256 pseudonym
  (keyed with auth.SECRET_KEY, 16 hex chars, 'u_' prefix) so the file
  can be fed into PowerBI or another external reporting tool without
  carrying real identities. HMAC chosen over a plain hash since the
  username space is small enough to brute-force a bare digest.

Both modes emit at, username, project_id, tool, event as columns and
deliberately omit the detail JSON column in both modes to avoid an
identity leak riding along inside free-form detail data. Response is
returned with a Content-Disposition: attachment header and a filename
that encodes mode + date.

Verified locally against a throwaway SQLite DB with two seeded users
and four seeded UsageEvent rows:
  - raw export contains the real usernames and matches the summary
    endpoint's event_count for the same session state
  - sanitized export contains no real username or email anywhere in
    the file body, across two independently-issued export calls
  - the same real user maps to the same pseudonym both within one
    export and across the two separate export calls
  - raw and sanitized rows line up 1:1 on at/tool/event for the same
    filter set
  - the tool= filter narrows the export the same way it narrows the
    summary
  - a plain project_user is refused with 403; an unauthenticated
    request is refused with 401
  - full smoke test (27/27) and seed_demo.py both still pass
2026-09-23 12:25:05 -07:00
2026-09-23 11:24:49 -07:00
Description
No description provided
11 MiB
Languages
Python 49.3%
JavaScript 32.6%
CSS 8.9%
HTML 8.7%
Shell 0.4%