feat: first-party analytics tracker (no dashboard)#11
Merged
Conversation
Self-hosted, third-party-free pageview/event tracking that writes to our own Postgres. Query the analytics_event table directly in SQL. Backend: - AnalyticsEvent table + AnalyticsEventCreate model - POST /analytics/collect: public, attaches user via CurrentUserOptional when authed, anonymous otherwise; truncates strings; returns 204 - Alembic migration + conftest teardown + tests Frontend: - lib/analytics.ts: trackPageview/trackEvent POST to backend via fetch (keepalive), persists anon visitor_id, works logged-out, swallows errors - Pageview fired from router onResolved (module scope, not React render) so re-renders/StrictMode can't double-send; plus a 500ms same-path guard Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjK4vCPYkTE18K6nMk3VDy
Pre-commit CI regenerates the frontend SDK on backend changes and runs ruff-format; both flagged this branch. - Add AnalyticsService / AnalyticsEventCreate to generated client (schemas/sdk/types.gen.ts) for POST /analytics/collect - ruff-format models_agentique.py and test_analytics.py Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjK4vCPYkTE18K6nMk3VDy
Match generate-client output — a bare dict field emits additionalProperties: true in the JSON schema. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjK4vCPYkTE18K6nMk3VDy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Self-hosted, third-party-free pageview + event tracking. Events are written to our own Postgres (
analytics_eventtable). No dashboard — you query the table directly in SQL.Why
Wanted analytics without a third party (no PostHog/GA) and without standing up a separate service (no ClickHouse/Matomo container). This reuses the DB we already run.
Backend
models_agentique.py— newAnalyticsEventtable (nullableuser_idFK, JSONprops, indexedevent/path/visitor_id/created_at) +AnalyticsEventCreaterequest model.api/routes/analytics.py—POST /analytics/collect. Public; attaches the user viaCurrentUserOptionalwhen a valid bearer token is present, records anonymously otherwise. Truncates strings to 2048, returns204.a7b8c9d0e1f2, head),conftest.pyteardown, andtest_analytics.py(anonymous/authed, default event, custom props, truncation).Frontend
lib/analytics.ts—trackPageview/trackEventPOST to the backend viafetch(keepalive). Persists an anonymousvisitor_idin localStorage, works logged-out, and swallows all errors so analytics can never break the app.main.tsx— pageview fired fromrouter.subscribe("onResolved", ...).No double-sends on re-render
Tracking lives in the router subscription at module scope, not in a component render or
useEffect— so React re-renders and StrictMode's dev double-invoke can't trigger it. A 500ms same-path guard covers edge cases (duplicateonResolved, dev HMR re-registering the subscription).Notes
fetch, so nosdk.gen.ts/types.gen.tsregen needed.Querying examples
🤖 Generated with Claude Code
https://claude.ai/code/session_01VjK4vCPYkTE18K6nMk3VDy
Generated by Claude Code