No description
  • TypeScript 97.5%
  • Dockerfile 1.9%
  • JavaScript 0.4%
  • Shell 0.2%
Find a file
2026-06-26 19:04:27 +03:00
prisma feat: add GeneratedInsight model and endpoints for insights history retrieval 2026-06-26 19:04:27 +03:00
src feat: add GeneratedInsight model and endpoints for insights history retrieval 2026-06-26 19:04:27 +03:00
.dockerignore first commit 2026-06-21 15:51:45 +03:00
.env.example fix: update environment variable for analytics database configuration 2026-06-22 22:09:24 +03:00
.gitignore first commit 2026-06-21 15:51:45 +03:00
docker-entrypoint.sh first commit 2026-06-21 15:51:45 +03:00
Dockerfile Update Dockerfile to ignore scripts during pnpm install 2026-06-21 16:34:57 +03:00
jest.config.cjs first commit 2026-06-21 15:51:45 +03:00
package.json first commit 2026-06-21 15:51:45 +03:00
pnpm-lock.yaml first commit 2026-06-21 15:51:45 +03:00
README.md first commit 2026-06-21 15:51:45 +03:00
tsconfig.json first commit 2026-06-21 15:51:45 +03:00

analytics-be

Universal, multi-tenant analytics ingestion service. Express 5 + TypeScript + Prisma + Postgres + Redis. Sharevent is the first consumer; any project plugs in with a public browser API key. See ../../ANALYTICS.md for the full design.

Run locally

cp .env.example .env          # set DATABASE_URL / REDIS_URL / ADMIN_API_KEY
pnpm install
createdb analytics            # one-time: separate DB on the shared Postgres
pnpm prisma:generate
pnpm migrate:dev               # apply schema
pnpm dev                       # http://localhost:3002/v1/health

In Docker the service is wired into INFRA/sharevent-infra/docker-compose.dev.yml (analytics service). Run CREATE DATABASE analytics; on the db container once.

API (/v1, port 3002)

Endpoint Auth Purpose
POST /ingest api key (body) batch: { apiKey, session, events[], movement[] }
POST /experiments/:key/assign api key (body) { apiKey, anonId }{ variantKey }, idempotent
POST /experiments/:key/convert api key (body) { apiKey, anonId, goal }, deduped
GET /experiments/:key/results?projectId= admin key exposures/conversions/rate per variant
POST /projects admin key { name, origins[] }
GET /projects admin key list projects + keys
POST /projects/:id/keys admin key mint a public browser key
POST /experiments admin key { projectId, key, variants[] } (weights sum to 100)
GET /health none liveness
  • API key travels in the request body (sendBeacon can't set headers) and is origin-scoped — only usable from the project's whitelisted origins. The key→project lookup is cached in Redis for KEY_CACHE_TTL_S.
  • Admin key is the env ADMIN_API_KEY, sent as the x-admin-key header.
  • SDK requests use Content-Type: text/plain (CORS-simple → no preflight); the server parses the body as JSON regardless. Ingest is rate-limited per apiKey+IP (RATE_LIMIT_PER_MIN) and capped at MAX_BODY_BYTES (→ 413).

Retention

A best-effort sweep deletes Event/MovementSample/Session rows older than RETENTION_DAYS (default 90) on the RETENTION_SWEEP_CRON_MS interval. Set RETENTION_DAYS=0 to disable. Partitioning is deferred.

Quick smoke

ADMIN=dev-admin-key-1234567890
# create a project + key
curl -sX POST localhost:3002/v1/projects -H "x-admin-key: $ADMIN" \
  -H 'content-type: application/json' \
  -d '{"name":"sharevent","origins":["http://localhost:5173"]}'
curl -sX POST localhost:3002/v1/projects/<id>/keys -H "x-admin-key: $ADMIN" \
  -H 'content-type: application/json' -d '{"label":"web"}'
# ingest a synthetic batch
curl -sX POST localhost:3002/v1/ingest -H 'content-type: text/plain' -d '{
  "apiKey":"ak_...","session":{"sessionId":"s1","anonId":"a1"},
  "events":[{"type":"page_view","path":"/"}],"movement":[]
}'