Skip to content

Plan: CV Download Metrics Endpoint

Status: Planning Scope: Track CV download counts, expose via API


Goal

Track how many times the CV has been downloaded, broken down by locale (en/nl). Surfaced as a backend endpoint, visible in the admin area.


Approach Options

  • Add a cv_download_events table with timestamp, locale, ip_hash
  • Increment on every GET /api/download-cv call
  • New endpoint GET /api/metrics/cv-downloads returns { total: N, byLocale: { en: X, nl: Y } }
  • Pros: already have Postgres + SQLAlchemy; no extra infra; persistent
  • Cons: loses data if DB is reset; PII risk with IP (mitigated by hashing)

Option B — Application Insights custom event

  • In download_cv handler: TelemetryClient.track_event("cv_download", {"locale": locale})
  • Query endpoint calls KQL against Application Insights
  • Pros: zero DB schema change; 90-day retention by default
  • Cons: adds latency to metric reads; requires App Insights SDK wiring

Option C — Azure Monitor custom metric

  • Emit via azure-monitor-opentelemetry
  • Pros: integrates with existing dashboards
  • Cons: overkill for a portfolio; cost at scale

Schema

sql
CREATE TABLE cv_download_events (
    id            SERIAL PRIMARY KEY,
    downloaded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    locale        VARCHAR(10),
    ip_hash       VARCHAR(64)  -- sha256(ip), for dedup/abuse detection only
);

New endpoints

GET /api/metrics/cv-downloads
→ { total: 42, byLocale: { english: 30, nederlands: 12 }, lastDownloadedAt: "..." }

Access: feature-flagged or require X-Admin-Token header (reuse API_ADMIN_TOKEN).

Changes required

  1. Alembic migration: add_cv_download_events_table
  2. SQLAlchemy model: api/db/models/cv_download_event.py
  3. Update api/routers/cv.py → insert row on each download
  4. New router api/routers/metrics.pyGET /api/metrics/cv-downloads
  5. Pydantic response schema + OpenAPI type gen
  6. pytest tests: download increments counter; metrics returns correct total

Success Criteria

  • [ ] Every /api/download-cv call creates a row in cv_download_events
  • [ ] GET /api/metrics/cv-downloads returns correct totals
  • [ ] IP is stored as SHA-256 hash, never plaintext
  • [ ] Tests cover both download and metrics endpoints