A full-stack sports performance tracking application with per-user databases, scientific training algorithms, and multi-platform integration.
- Overview
- Features
- Tech Stack
- Architecture
- Quick Start
- Configuration
- Running the Project
- Testing
- API Reference
- Database
- Security
- Deployment
- Project Structure
- Contributing
- License
DrawRun is a comprehensive sports performance tracking platform that combines scientific training methodologies with modern web technologies. It provides athletes with tools for performance analysis, training plan management, and social features, all while maintaining strict security and data privacy standards.
Key differentiators:
- Per-user SQLite databases for complete data isolation
- Scientific algorithms based on validated research (Banister model, ACWR, HRV analysis)
- Multi-platform sync with Strava, Garmin, Suunto, Polar, Apple Health, and Samsung Health
- Adaptive coaching engine with real-time plan adjustments
- Enterprise-grade security with AES-256-GCM encryption and 2FA
- PMC (Performance Management Chart) with Fitness, Fatigue, and Form calculations
- TSS/TRIMP calculation for various sports
- VDOT estimation and training paces
- HRV (Heart Rate Variability) analysis
- Critical Power modeling
- Race time predictions
- Adaptive training plans with AI-driven adjustments
- Session feedback integration
- Missed session handling
- VMA/VO2max testing tools
- External event integration
- Strava (OAuth2)
- Garmin (credentials-based)
- Suunto (v1 & v2)
- Polar
- Apple Health
- Samsung Health
- Adidas
- Friends system with request/accept workflow
- Groups and challenges
- Social feed with activity sharing
- Leaderboards
- Chat functionality
- JWT with refresh token rotation
- AES-256-GCM encryption for credentials
- TOTP-based 2FA
- GDPR compliance (export/delete)
- Rate limiting and CORS protection
- Runtime: Node.js 18+
- Framework: Express 5
- Database: SQLite (sql.js) with per-user architecture
- Authentication: JWT + Refresh Tokens
- Logging: Winston
- Testing: Jest (107 tests, 7 suites)
- Security: Helmet.js, bcryptjs, express-rate-limit
- Framework: Next.js 14 (App Router)
- Language: TypeScript 5 (strict mode)
- Styling: Tailwind CSS
- State Management: Zustand
- Data Fetching: React Query (@tanstack/react-query)
- Forms: React Hook Form + Zod
- Testing: Vitest + Testing Library
- UI Components: Custom components with Lucide icons
- Process Management: PM2 (production)
- Reverse Proxy: Nginx
- SSL: Let's Encrypt (Certbot)
- Containerization: Docker support (backend)
-
Per-User Databases
- Each user has their own
user_<email>.db - Shared data in
main.db(users, refresh tokens, migrations) - Complete data isolation between users
- Each user has their own
-
LRU Cache
- Max 100 open database connections
- Evicted entries persisted to disk before closing
- Prevents memory exhaustion
-
Token Management
- Access tokens: 15-minute expiry
- Refresh tokens: 7-day expiry with rotation
- Stored in
sessionStorage(not localStorage)
-
Scientific Algorithms
- Based on peer-reviewed research
- Banister impulse-response model
- ACWR (Acute:Chronic Workload Ratio)
- HRV trend analysis
- Clone the repository:
git clone https://github.com/lomicbourlotroche/DrawRun.git cd DrawRun
# Backend configuration
cd backend
cp .env.example .env
# Edit .env with your values (see Configuration section)
# Frontend configuration
cd ../frontend
# Create .env.local with:
# NEXT_PUBLIC_API_URL=http://localhost:3000Required for production:
# Security (GENERATE THESE!)
JWT_SECRET=openssl rand -base64 64
CREDENTIALS_SECRET=openssl rand -base64 32
BCRYPT_ROUNDS=12
# Server
PORT=3000
NODE_ENV=production
# Database
DATA_DIR=../../DrawRun-Data
# CORS
CORS_ORIGINS=https://yourdomain.com
# Rate Limiting
API_RATE_LIMIT=100
AUTH_RATE_LIMIT=5Optional integrations:
# Strava OAuth2
STRAVA_CLIENT_ID=your_client_id
STRAVA_CLIENT_SECRET=your_client_secret
STRAVA_CALLBACK_URL=http://localhost:3001/auth/strava/callback
# Email (SMTP)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your_smtp_user
SMTP_PASS=your_smtp_password
SMTP_FROM=noreply@yourdomain.com
# 2FA
TOTP_ISSUER=DrawRun
# Redis (optional, falls back to in-memory)
# REDIS_URL=redis://localhost:6379
# Push Notifications
VAPID_PUBLIC_KEY=your_vapid_public_key
VAPID_PRIVATE_KEY=your_vapid_private_keyGenerate secrets:
openssl rand -base64 64 # For JWT_SECRET
openssl rand -base64 32 # For CREDENTIALS_SECRETBackend (port 3000):
cd backend
npm run devNote: In development mode, the backend runs tests before starting the server.
Frontend (port 3001):
cd frontend
npm run devAccess:
- Frontend: http://localhost:3001
- Backend API: http://localhost:3000
- Health check: http://localhost:3000/health
Backend:
cd backend
npm startFrontend:
cd frontend
npm run build
npm startcd backend
# Run all tests
npm test
# Run specific suite
npm test -- --testPathPattern=auth
# Watch mode
npm run test:watch
# Coverage report
npm run test:coverageTest suites:
algorithms.test.js(55 tests) - Scientific algorithmsauth.test.js(14 tests) - JWT, refresh, encryptioncrypto.test.js(5 tests) - AES-256-GCM encrypt/decryptdatabase.test.js(12 tests) - LRU cache, migrationsvalidators.test.js(21 tests) - Input validationroutes.test.js(3 tests) - Route structureroutes/activities.test.js(7 tests) - Activities API
Property-based tests (fast-check):
- Property 1-3, 13: LRU cache behavior
- Property 4-7: Token refresh flow
- Property 8-9: Auth store behavior
- Property 10: Error boundary
- Property 11: Credential encryption
- Property 12: Encrypt/decrypt round-trip
cd frontend
# Run all tests
npm run test
# Watch mode
npm run test:watch
# Coverage report
npm run test:coverage
# UI mode
npm run test:uiTest files:
tests/lib/api.test.ts(26 tests) - ApiClientsrc/stores/index.test.ts- Zustand auth storecomponents/providers/ErrorBoundary.test.tsx- ErrorBoundary
# Backend
cd backend
npm run lint
npm run lint:fix
# Frontend
cd frontend
npm run lint| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/auth/login |
- | Login, returns tokens |
| POST | /api/auth/register |
- | Register new user |
| POST | /api/auth/refresh |
- | Rotate refresh token |
| POST | /api/auth/logout |
JWT | Logout |
| POST | /api/auth/change-password |
JWT | Change password |
| POST | /api/auth/forgot-password/request |
- | Send OTP |
| POST | /api/auth/forgot-password/confirm |
- | Reset with OTP |
| POST | /api/auth/credentials/garmin |
JWT | Save Garmin credentials (encrypted) |
| POST | /api/auth/credentials/suunto |
JWT | Save Suunto credentials (encrypted) |
| POST | /api/auth/credentials/strava |
JWT | Save Strava credentials (encrypted) |
| POST | /api/auth/2fa/setup |
JWT | Generate TOTP secret |
| POST | /api/auth/2fa/enable |
JWT | Enable 2FA |
| POST | /api/auth/2fa/disable |
JWT | Disable 2FA |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /health |
- | Health check + version |
| GET | /api/profile |
JWT | Get user profile |
| PUT | /api/profile |
JWT | Update profile |
| GET | /api/activities |
JWT | List activities (paginated) |
| POST | /api/activities/create |
JWT | Create manual activity |
| GET | /api/pmc |
JWT | PMC data |
| POST | /api/sync |
JWT | Trigger sync |
| GET | /api/sync/status |
JWT | Sync status |
| GET | /api/metrics |
JWT | Performance metrics |
| POST | /api/metrics/recalculate |
JWT | Recalculate metrics |
| Method | Path | Description |
|---|---|---|
| GET | /api/coach/profile |
Coach profile |
| POST | /api/coach/start-plan |
Start adaptive plan |
| POST | /api/coach/plan-feedback |
Submit session feedback |
| GET | /api/coach/plan/:id |
Get plan details |
| POST | /api/coach/session-missed |
Report missed session |
| GET | /api/coach/progress/:id |
Plan progress |
| POST | /api/coach/schedule-test |
Schedule VMA test |
| POST | /api/coach/submit-test-results |
Submit test results |
| POST | /api/coach/external-event |
Add external event |
| GET | /api/coach/gamification/:id |
Gamification data |
| POST | /api/coach/match-activity |
Match activity to session |
| GET | /api/coach/pending-sessions |
Pending sessions |
| Method | Path | Description |
|---|---|---|
| GET | /api/social/friends |
Friend list |
| POST | /api/social/friends/request |
Send friend request |
| POST | /api/social/friends/accept |
Accept request |
| GET | /api/social/feed |
Social feed |
| GET | /api/social/leaderboard |
Leaderboard |
| GET | /api/social/groups |
Groups |
| POST | /api/social/groups |
Create group |
| GET | /api/social/challenges/public |
Public challenges |
Access interactive API documentation at: http://localhost:3000/api-docs (when server is running)
Main Database (DrawRun-Data/main.db):
users- User accountsrefresh_tokens- Refresh token storageschema_migrations- Migration tracking
Per-User Databases (DrawRun-Data/user_<email>.db):
activities- User activitiestraining_plans- Training planssessions- Plan sessionsmetrics- Calculated metricssocial_*- Social data
Migrations run automatically at startup. To add a new migration, edit backend/src/database.js:
{
version: '003_your_migration_name',
description: 'Human-readable description',
up: (db) => {
try { db.run('ALTER TABLE users ADD COLUMN new_col TEXT'); } catch (_) {}
},
}// Main DB operations
const user = await dbGetMain('SELECT * FROM users WHERE id = ?', [userId]);
await dbRunMain('UPDATE users SET ... WHERE id = ?', [...values, userId]);
// User DB operations
const userDb = await getUserDb(userId);
const activities = await dbAllUser(userDb, 'SELECT * FROM activities', []);
await dbRunUser(userDb, 'INSERT INTO activities ...', [...values]);- Credentials: AES-256-GCM encryption before database storage
- Passwords: bcrypt hashing (12 rounds)
- Tokens: JWT with short-lived access tokens
- API endpoints: 100 requests per 15 minutes
- Auth endpoints: 5 requests per 15 minutes
- Configurable CORS origins
- Helmet.js security headers
- Content Security Policy (CSP)
- Never use
console.login backend (use Winston logger) - All sensitive data encrypted before storage
- Parameterized SQL queries (no string concatenation)
- Tokens stored in
sessionStorageonly (notlocalStorage)
For detailed deployment instructions, see DEPLOYMENT.md.
Quick overview:
- VPS Setup: Ubuntu 22.04+, Node.js 18+, Nginx, PM2
- Clone & Install:
git clone,npm install --production - Configure: Set up
.envwith production values - Start Services: PM2 for backend and frontend
- Reverse Proxy: Nginx configuration
- SSL: Certbot for Let's Encrypt certificates
cd backend
npm run docker:build
npm run docker:runDrawRun-New/
├── backend/ # Express 5 API
│ ├── index.js # Entry point
│ ├── package.json # v4.1.0
│ ├── .env.example # Environment template
│ ├── src/
│ │ ├── database.js # LRU cache, per-user DB, migrations
│ │ ├── auth.js # JWT auth, refresh, 2FA, credentials
│ │ ├── auth2fa.js # TOTP / QR code 2FA
│ │ ├── jwt_tokens.js # Token generation, verification
│ │ ├── crypto_utils.js # AES-256-GCM encrypt/decrypt
│ │ ├── logger.js # Winston logger
│ │ ├── validators.js # Input validation
│ │ ├── algorithms/ # Scientific algorithms
│ │ │ ├── index.js # Cardiovascular, PMC, TrainingLoad
│ │ │ ├── tss.js # TSS/TRIMP calculation
│ │ │ ├── metrics.js # Calculated metrics
│ │ │ └── sports.js # Sports management
│ │ ├── routes/ # API routes
│ │ │ ├── activities.js
│ │ │ ├── coach.js
│ │ │ ├── social.js
│ │ │ ├── profile.js
│ │ │ ├── pmc.js
│ │ │ ├── sync.js
│ │ │ ├── metrics.js
│ │ │ ├── preferences.js
│ │ │ ├── onboarding.js
│ │ │ ├── overtraining.js
│ │ │ └── tss.js
│ │ ├── services/ # External services
│ │ │ ├── strava.js
│ │ │ ├── garmin.js
│ │ │ ├── suunto_sync.js
│ │ │ ├── polar_sync.js
│ │ │ ├── apple_health_sync.js
│ │ │ └── ...
│ │ ├── middleware/ # Express middleware
│ │ │ ├── auth.js # JWT verification
│ │ │ └── security.js # Helmet, rate limiting, CORS
│ │ └── utils/ # Utility functions
│ ├── tests/ # Jest test suites
│ │ ├── algorithms.test.js # 55 tests
│ │ ├── auth.test.js # 14 tests
│ │ ├── crypto.test.js # 5 tests
│ │ ├── database.test.js # 12 tests
│ │ ├── validators.test.js # 21 tests
│ │ ├── routes.test.js # 3 tests
│ │ └── routes/
│ │ └── activities.test.js # 7 tests
│ ├── scripts/ # Utility scripts
│ │ ├── backup.js
│ │ └── restore.js
│ └── logs/ # Winston log files
│
├── frontend/ # Next.js 14 App Router
│ ├── app/ # Next.js app directory
│ │ ├── layout.tsx
│ │ ├── page.tsx # Landing page
│ │ ├── login/page.tsx
│ │ └── app/ # Authenticated app
│ │ ├── layout.tsx
│ │ ├── page.tsx # Dashboard
│ │ ├── activities/
│ │ ├── coach/
│ │ ├── performance/
│ │ ├── profile/
│ │ └── social/
│ ├── components/ # React components
│ │ ├── ui/ # Base UI components
│ │ ├── layout/ # AppLayout, Sidebar, Header
│ │ ├── features/ # Feature components
│ │ │ ├── activities/
│ │ │ ├── auth/
│ │ │ ├── coach/
│ │ │ ├── dashboard/
│ │ │ ├── onboarding/
│ │ │ ├── performance/
│ │ │ └── social/
│ │ └── providers/ # Context providers
│ ├── src/ # Source code
│ │ ├── lib/ # Utilities
│ │ │ ├── api.ts # ApiClient
│ │ │ ├── constants.ts
│ │ │ ├── utils.ts
│ │ │ └── i18n.ts
│ │ ├── types/ # TypeScript interfaces
│ │ │ ├── index.ts
│ │ │ └── sports.ts
│ │ └── stores/ # Zustand stores
│ │ └── index.ts
│ ├── tests/ # Vitest tests
│ │ └── lib/
│ │ └── api.test.ts
│ ├── next.config.js
│ ├── tsconfig.json
│ ├── tailwind.config.js
│ └── package.json
│
├── DrawRun-Data/ # SQLite databases (gitignored)
│ ├── main.db # Users + migrations
│ └── user_<email>.db # Per-user data
│
├── AGENTS.md # AI agent guidelines
├── DEPLOYMENT.md # Deployment guide
└── README.md # This file
Backend (JavaScript):
'use strict'at top of every file- Async/await (no raw Promises)
- Parameterized SQL queries
- Winston logger (no
console.log) - Error responses:
res.status(4xx).json({ error: 'message' })
Frontend (TypeScript):
- Strict mode enabled
- No
anytypes - Import order: React → External → Internal → Relative
- All HTTP calls through
apifrom@/lib/api - PascalCase components, camelCase functions
If you're an AI agent working on this codebase, read AGENTS.md first. It contains:
- Complete project overview
- Build & run commands
- Critical rules (what to do and what to never do)
- Token & auth flow
- Database patterns
- Testing requirements
- Security checklist
- Ensure all tests pass (
npm testin backend,npm run testin frontend) - Run linters (
npm run lintin both) - Update documentation if needed
- Create PR with clear description
This project is licensed under the ISC License - see the LICENSE file for details.
- Scientific algorithms based on research by Banister, Daniels, Seiler, Gabbett, and others
- Built with modern web technologies: Next.js, Express, SQLite, Tailwind CSS
Version: 4.1.0
Last Updated: May 2026
For questions or support, please open an issue on GitHub.