Architecture overview
Athlora is a non-monolithic web app: a React SPA and an Express API are separate deployables communicating over HTTP/JSON. This is a hard project requirement — no framework that fuses frontend and backend is allowed.
High-level diagram
┌────────────────────────┐ HTTP (JSON) ┌────────────────────────┐
│ React + Vite (Vercel) │ ────────────────────────► │ Express API (Render) │
│ /frontend │ /api/v1/* (Bearer JWT) │ /backend │
└────────────────────────┘ └────────────┬───────────┘
│ Auth0 (login) │ SQL
│ ▼
│ ┌────────────────────────┐
└──────────── Auth0 tenant ────────────────────│ PostgreSQL (Neon) │
│ migrations in /db │
└────────────────────────┘
Frontend
- State & structure: feature folders (
src/features/*). Shared primitives insrc/components. - API access: shared typed fetch client in
src/api/client.tspreserves structured API error status/code/details; per-resource wrappers are added as features land. The Auth0 bridge withholds authenticated content untilPUT /api/v1/auth/mehas synchronized the application user. - Offline (Stage 2+): Dexie/IndexedDB mirror for live-logging writes, PWA service worker, background sync, Socket.IO live updates.
- Design: CSS variables from
src/styles/tokens.css, CSS modules per component, Google Fonts loaded inindex.html.
Backend
- Routing: resource routers under
src/routesmatching the database tables. - Services: pure, unit-testable functions for result derivation (
src/services/resultDerivation.ts, tested) and (Stage 3) merge rules — never buried in route handlers. - Database access:
pgpool insrc/db/client.ts; sequential SQL files insrc/db/migrationsare checksum-tracked (line-ending-normalized) and applied before production startup.0001_init.sqland0002_contract_100m.sqlare applied to Neon. - Auth:
src/middleware/auth.tsverifies Auth0 JWT issuer and audience viajose, then resolves the verified subject to a typed application-user UUID/Auth0 ID/role context on resource routes.PUT /api/v1/auth/meintentionally uses token verification only so new identities can synchronize. Central ownership services scope athlete, event, timeline, participant and result access without disclosing cross-coach resources; public results pages (Stage 3) will be explicitly allow-listed.
Data flow for a live result
- A coach logs an attempt on the Live Event screen.
- The frontend POSTs a
timeline_entriesrow (offline: writes to IndexedDB first, syncs later). - The API stores the append-only entry (soft-deletable, versioned).
- The API recomputes the derived
resultsrow for that athlete/discipline. - Other connected clients receive the update (Socket.IO, Stage 2).
Deployment
- Frontend → Vercel (
https://athlora-deploy.vercel.app) - Backend → Render (
https://athlora-deploy.onrender.com) - Docs site → Cloudflare Pages (
https://athlora-deploy.pages.dev) - Postgres → Neon (Frankfurt)
Design decisions
- UUIDs everywhere — client-generated IDs can be valid PKs, enabling offline creation without ID collisions.
- Soft deletes — undo is a tombstone (
deleted_at), not a destructive delete. - Derived results with manual override — stats come from the timeline log, but a coach can correct with
manual_override+ audit trail. - Timed vs measured disciplines — the UI and result rules branch on whether a discipline is track (time) or field (distance/height).
- Non-enumerating ownership — owner IDs and audit actors come from authenticated server context, never request payloads. A resource that is missing, malformed, nested under the wrong parent or owned by another coach produces the same generic not-found response.
Implementation status
Implemented at the scaffold stage: frontend shell with feature placeholders and synchronized-auth gating, backend route/middleware/service shell with typed application-user resolution and centralized ownership guards, tokens, migrations, CI workflow and all automated checks. Still to build in Stage 1: real CRUD for athletes/events, Open-Meteo weather, live timeline logging endpoints + UI, and results/dashboard wiring (see the dev plan).
AI declaration
This document was generated with the assistance of opencode[deepseek-v4-flash-free] and opencode[gpt-5.6-sol].