Skip to content

Add dev-side pageview analytics via proxy.ts + Postgres, with an Admin Analytics tab - #208

Merged
calebyhan merged 2 commits into
mainfrom
206/pageview-analytics
Aug 21, 2026
Merged

Add dev-side pageview analytics via proxy.ts + Postgres, with an Admin Analytics tab#208
calebyhan merged 2 commits into
mainfrom
206/pageview-analytics

Conversation

@calebyhan

Copy link
Copy Markdown
Contributor

We wanted lightweight, self-hosted pageview analytics for the site — similar in spirit to Google Analytics, but without embedding a third-party script or cookie on the live pages users see. GA was ruled out because it ships visitor data off our infra and requires cookie consent, which is disproportionate for a UNC-affiliated public site. Instead, pageviews are captured server-side and surfaced only in a new admin-only tab — nothing changes about what a visitor's browser loads.

Changes:

  • Added a Next.js proxy.ts request interceptor that fires a non-blocking POST per real pageview (prefetches filtered out), because that's the only way to observe traffic without adding a client-side script
  • Added a PageView table plus POST /api/analytics/pageview for ingest, protected by a shared secret header and a per-IP rate limit, since the proxy has no admin session to authenticate with
  • Added GET /api/admin/analytics/summary (daily pageviews, unique visitors, top pages, top referrers), gated by the existing get_current_user admin auth like every other admin route
  • Added an "Analytics" tab to the admin dashboard (Recharts line chart + stat tiles + top-pages/referrers tables) so the data is actually visible somewhere
  • visitor_hash is a daily-rotating, non-reversible hash of IP + user agent — no raw IP is stored and no cookie is set, since unique-visitor counting shouldn't require either
  • Built as proxy.ts rather than middleware.ts — Next.js 16 renamed the convention and made Node.js the only, non-configurable runtime for it, which the original issue plan hadn't accounted for
  • Wired a new ANALYTICS_INGEST_SECRET into deploy/cloudapps/scripts/apply-secrets.sh and template.yaml (same secretKeyRef pattern as JWT_SECRET), because adding the env var to the app alone doesn't reach the running OpenShift pods

Deployment — action needed after merge: run ./deploy/cloudapps/scripts/apply-environment.sh senate once (requires oc access) so the new secret gets generated and injected into both Deployments. Safe to merge without doing this right away — if the secret isn't present yet, the backend ingest route returns 503 and the frontend proxy just skips sending events, so nothing breaks in the meantime.

Verified: 588/588 backend tests pass (15 new), ruff clean, tsc clean, vitest passes, next build succeeds.

Closes #206

…n Analytics tab

We wanted lightweight, self-hosted pageview analytics without embedding a
third-party script or cookie on the live site — GA was ruled out for exactly
that reason. Pageviews are now captured server-side by a Next.js request
interceptor and written to a new PageView table; the data is
surfaced only in a new admin-only Analytics tab (pageviews-over-time chart,
unique visitors, top pages, top referrers).

visitor_hash is a daily-rotating, non-reversible hash of IP + user agent —
no raw IP is stored and no cookie is set. The ingest endpoint is
unauthenticated (the interceptor has no admin session) but gated by a
shared secret header plus a per-IP rate limit; the admin summary endpoint
follows the existing get_current_user pattern used by every other admin
router.

Built as proxy.ts rather than middleware.ts: Next.js 16 renamed the
convention and made Node.js the only, non-configurable runtime for it,
which the original plan hadn't accounted for.

Verified: 588/588 backend tests pass (15 new), ruff clean, tsc clean,
vitest passes, and `next build` succeeds with /admin/analytics in the
static route table and Proxy (Middleware) correctly registered.

Fixes #206
The new pageview ingest endpoint needs a secret shared between the
frontend's proxy.ts and the backend, following the same pattern already
used for JWT_SECRET: apply-secrets.sh now generates/reuses it in the
senate-secrets OpenShift Secret, and template.yaml injects it into both
the backend and frontend Deployments via secretKeyRef.
@github-actions

Copy link
Copy Markdown

Test Results

588 tests  +15   588 ✅ +15   51s ⏱️ +2s
  1 suites ± 0     0 💤 ± 0 
  1 files   ± 0     0 ❌ ± 0 

Results for commit 63bd2c9. ± Comparison against base commit acb8eb5.

@MasonMines2006 MasonMines2006 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@calebyhan
calebyhan merged commit b5ceec3 into main Aug 21, 2026
3 checks passed
@calebyhan
calebyhan deleted the 206/pageview-analytics branch August 21, 2026 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add dev-side pageview analytics (middleware + Postgres) with a new Admin Analytics tab

2 participants