Skip to main content

System services

High-level services and how they communicate.


System architecture overview

Key directories

src/
├── app/api/ # API layer
│ ├── admin/ # Administrative endpoints
│ ├── strava/ # Strava integration endpoints
│ ├── activities/ # Activity data management
│ └── analytics/ # Analytics and metrics
├── components/ # React UI components
├── lib/ # Business logic and utilities
├── server/ # Background workers
└── types/ # TypeScript definitions

prisma/ # Database layer
├── migrations/ # Schema evolution history
└── schema.prisma # Current database schema

memory_bank/ # Documentation and decisions
scripts/ # Automation and tooling
.husky/ # Git hooks for quality assurance
.vscode/ # Development environment configuration

Service communication flow

Strava API ←→ Webhook Endpoints ←→ Job Queue ←→ Background Workers ←→ Database
↓ ↓
Web Interface ←→ API Routes ←→ Business Logic

Core services (stage 1)

1) API gateway (Next.js API routes)

Responsibility: OAuth handshakes, public API, admin endpoints.

Core Endpoints:

  • GET /api/activities — List activities with pagination and caching.
  • GET /api/activities/[id] — Get detailed activity data (excluding large fields).
  • GET /api/activities/[id]/thumbnail — Serve cached thumbnails with ETag support.
  • GET /api/activities/[id]/map — Get activity polylines for maps.
  • GET /api/activities/[id]/splits — Enhanced split data with real distance/duration.
  • GET /api/activities/[id]/streams — Get activity streams (GPS, HR, pace data).
  • GET /api/runs — Legacy endpoint for run data (compatibility).

Strava Integration:

  • GET /api/strava/auth — Initiate OAuth flow with Strava.
  • GET /api/strava/activities — Fetch activities from Strava API.
  • POST /api/strava/callback — OAuth callback handler with error management.
  • GET /api/strava/status — Check connection status and athlete info.
  • DELETE /api/strava/disconnect — Remove tokens, cleanup webhook subscriptions.
  • POST /api/strava/logout — Clear authentication and redirect.
  • POST /api/strava/sync — Manual sync trigger with token refresh.
  • GET /api/strava/splits/[id] — Get activity splits from Strava.
  • POST /api/strava/webhook — Real-time webhook endpoint for instant activity notifications.
  • GET /api/strava/export — CSV export functionality.

Analytics & Performance:

  • GET /api/analytics/peak — Peak performance analytics.
  • GET /api/analytics/best-efforts-cached — Pre-calculated best efforts with GPS analysis.

Notes:

  • Return friendly errors (with "what to try next").
  • Always include request id in responses/logs.

2) Auth and tokens

Responsibility: Strava OAuth, token refresh, revocation handling.

Behaviour:

  • Refresh proactively with safety window (5 mins before expiry).
  • On failure, mark token "degraded" and show reconnect UI.
  • Audit every refresh event.

3) Ingest and sync

Responsibility: Reliable import of new/updated activities.

Flow:

  1. Strava → Webhook (activity created/updated).
  2. Job Queue enqueues fetch task.
  3. Fetcher calls Strava API, writes to DB.
  4. Dedupe by (stravaId, start_date ± small window, duration).
  5. Audit all writes (created/updated/skipped).

SLAs:

  • New activity visible in < 30s.
  • Idempotent replays.

4) Background workers (working)

  • Thumbnail Worker: OpenStreetMap compositing + route overlays.
  • Activity Fetch Worker: Real-time webhook processing (<30s sync).
  • Strava Worker: Token refresh + polling fallback.
  • Job Queue System: Retry logic + exponential backoff.

Thumbnail Worker Notes:

  • Handle "Image to composite must have same dimensions or smaller" by resizing overlay to base.
  • Retry + cleanup on failure; fall back to route-only.
  • Store pointers to images, not blobs, in the core API responses.

5) Analytics (phase 2 - planned)

Responsibility: Precompute best efforts and rankings.

Jobs:

  • Distances: 400m, 800m, 1km, 1mi, 5k, 10k, 21.1k, 42.2k, 50k.
  • Windows: all-time, 2y, 1y, 3m.
  • Outputs: rank tables per window.

Principles:

  • Deterministic calculations (no hidden magic).
  • "Explain this number" link from UI.

6) UI (web)

Responsibility: Fast, readable surfaces.

Pages:

  • Feed with filters + persistence.
  • Details page (AU 24h, splits, HR opt-in).
  • Stats (Distance / Duration / Calories / Speed / Altitude × Year/Month/Week/Day).
  • CSV export.

Performance:

  • Exclude heavy fields.

Admin APIs

7 Admin endpoints (/api/admin/*)

Webhook Management:

  • GET /api/admin/webhooks — List existing webhook subscriptions (with cache support & ?refresh=true).
  • POST /api/admin/webhooks — Create new webhook subscription.
  • DELETE /api/admin/webhooks — Delete webhook subscription(s).

Smart caching architecture: The webhook management system uses intelligent database caching for optimal performance:

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│ UI Request │───▶│ Cache Check │───▶│ Strava API │
│ │ │ (5min TTL) │ │ (if expired) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌─────────────────┐
│ Cached Response │◀───│ Update Cache │
│ ~10-50ms │ │ Store in DB │
└──────────────────┘ └─────────────────┘

Performance Benefits:

  • Instant UI - Cache hits respond in 10-50ms vs 500-1000ms API calls.
  • 95% fewer API calls - Only refresh on expiry or manual force refresh.
  • Real-time updates - Cache immediately updated after create/delete operations.
  • Athlete tracking - Maintains audit trail while respecting app-wide webhook nature.

Activity Job Queue:

  • GET /api/admin/activity-jobs — Get job queue statistics and recent jobs.
  • POST /api/admin/activity-jobs — Create manual activity fetch jobs.
  • DELETE /api/admin/activity-jobs — Clear completed/failed jobs.

Performance Analytics:

  • POST /api/admin/calculate-best-efforts — Calculate best efforts for all existing activities.
  • POST /api/admin/clear-best-efforts — Clear all best efforts for recalculation.

Data Synchronization:

  • GET /api/admin/sync-streams — Get status of streams sync.
  • POST /api/admin/sync-streams — Download GPS, HR, cadence, power, altitude data from Strava.
  • POST /api/admin/resync — Force resync to populate new fields (updates fastestPaceSecPerKm).

Thumbnail Management:

  • GET /api/admin/thumbnails — Get thumbnail generation queue status.
  • POST /api/admin/thumbnails — Queue thumbnail generation (single activity or bulk).
  • API p95 < 50ms.

Admin APIs

7 endpoints built.

  • /api/admin/webhooks → webhook subscription management (GUI + API).
  • /api/admin/activity-jobs → monitor fetch queue with stats.
  • /api/admin/thumbnails → bulk thumbnail regeneration.
  • /api/admin/calculate-best-efforts → GPS-based performance analysis.
  • /api/admin/clear-best-efforts → reset calculations.
  • /api/admin/sync-streams → download GPS/HR data from Strava.
  • /api/admin/resync → force refresh of derived fields.

Cross-cutting

Observability

  • Structured logs with req_id.
  • One dashboard: ingest → worker → DB health.
  • Error taxonomy: user-facing vs internal.

Data

  • Postgres primary.
  • Prisma schema (activities, splits, tokens, audits, jobs).
  • Backups: nightly dump + verify restore.

Security

  • Secrets via K8s (mounted, not baked).
  • RBAC for admin endpoints.
  • Data minimisation & retention.

Deployment (phase 6 - planned)

  • GitLab main; GitHub mirror.
  • FluxCD reconciles clusters from Git.
  • Dev in K8s mirrors prod shape.

Planned extensions (future stages)

Stage 2: Apple health worker

  • Ingest metrics from HealthKit once iOS app is live.

Stage 4: Nutrition worker

  • Store and summarise daily calories and sugar.

Stage 5: Music worker

  • Log Apple Music/Spotify track metadata during runs.