An AI-powered web application for tracking rowing workouts with analytics, training plans, motion-capture posture analysis, and achievement tracking. Built for rowers who use SmartRow equipment. This app was completely written by AI.
Rowing Tracker is a modern, AI-powered web app built specifically for rowers who use SmartRow equipment. Workouts are imported either via automated SmartRow.fit sync or manual CSV/ZIP upload, and the app turns them into deep analytics, personalized AI training plans, automated coaching insights, and motion-capture posture analysis. Each rower gets a private workspace with data stored in PostgreSQL (local or Supabase) and isolated by authenticated user.
- AI Coach with Memory: Chat with an intelligent rowing coach that remembers your conversation history, analyzes your training data, and references uploaded documents (PDFs, images) to provide personalized advice
- Organized Conversations: Filter chat sessions by type (regular chats vs. chart explanations) with visual indicators and quick navigation back to analyzed charts
- Tool-Enabled Conversations: The AI can automatically call tools like
get_sessions,get_achievements, andget_memory_documentsto fetch your workout history, streak progress, awards, and uploaded PDFs/images without you leaving the chat - AI Training Plans: Generate fully personalized, multi-week training programs based on your fitness level, goals, and rowing history—or choose from proven templates
- AI Performance Insights: Receive automated, data-driven insights about your workouts, trends, and areas for improvement directly in your dashboard with archive search functionality
- AI Personal Context: Write a personal description or select documents from your memory to automatically create a system prompt addition that keeps your health considerations, limitations, and goals in mind across the AI Coach, training plans, and insights
- Configurable AI System Prompts: Fine-tune the base system prompt, chat prompt, training plan prompt, and insights prompt from the Settings → Advanced Configuration panel with one-click "reset to default" controls
- Dashboard: Comprehensive overview with key metrics, volume charts, and trend analysis
- Advanced Analytics (
/analytics): Detailed breakdown of performance, split trends, stroke rate, consistency score, and training adherence with a uniform legend (teal dot = individual sessions, orange line = 10-session moving average) and synchronized global smoothing controls - Insights View (
/insights): Dedicated page for browsing, filtering, archiving, and giving feedback (helpful/not_helpful/action_taken) on AI-generated performance insights, with full-text search across the archive - Interactive Chart Explanations: Click "Explain" on any chart (analytics page AND session details) to get AI-powered analysis—explanations are cached server-side via
ChartExplanation, displayed in tooltips for quick reference, and offer "Back to chart" navigation - Time-Range Aware Explanations: Analytics chart explanations are cached per time range—switching between "Last 7 days" and "Last 30 days" yields separate, context-appropriate AI analyses
- Structured AI Explanations: Chart explanations follow a clear format: "Why This Chart Matters" (practical value), "What I See In Your Data" (patterns/trends), and "What This Means For You" (actionable insights)
- Performance Correlations: Scatter plots showing relationships between power/pace, stroke rate/pace, duration/distance, energy/duration, with Split Time always-on as a permanent correlation chart
- Sessions List: Browse, filter, and sort all your rowing sessions with advanced search
- Session Details: Deep dive into individual workout metrics with interactive charts and AI explanations across all analysis modules:
- Overview: Power & Stroke Rate
- Performance Graphs: Pace Analysis, Work per Stroke, Stroke Length, Heart Rate
- Segments: Segment Analysis (100m/500m), Rolling Power Average, Rolling Split Average
- Deep Analysis: Power Distribution, Rhythm Distribution, Rate vs Power, Rate vs Split
- Stroke-by-Stroke Analysis: Stroke data parsed from SmartRow detailed CSVs is persisted with each session, unlocking power/rhythm distributions, stroke-length consistency, technique maps, and a pre-computed
consistencyScoreper session - Personal Records: Automatic tracking of best times and performances across all distances
- Secure Authentication: Email/password login with email verification (double opt-in)
- Magic Link Login: Passwordless authentication via email
- Google OAuth: Optional sign-in with Google (when configured)
- User Profiles: Manage your account, change password, and update profile information
- Data Isolation: Each user's data is completely isolated and private
- Automated SmartRow Sync: One-click sync from
smartrow.fitdirectly inside/sync. A server-side Playwright session logs in with stored credentials, exports the workouts list (CSV) and the detailed-stroke archive (ZIP), and imports both in a single pass - CSV Drag-and-Drop: Manual upload of any SmartRow CSV (sessions list or detailed stroke export)
- ZIP Batch Import: Upload the SmartRow archive ZIP to import many detailed-stroke sessions at once with progress reporting
- Duplicate-Safe Imports: Sessions are deduplicated by
(userId, timestamp, distance); existing sessions are updated, not duplicated - Lean Session Loading: Session lists use
/api/sessions/listwithout stroke rows and includestrokeDataCount; detail pages fetch full stroke data on demand through/api/sessions?id=<sessionId> - Last-Sync Timestamp: SmartRow credentials and the most recent sync time are stored in
UserSettings.smartRowSettings
- PostgreSQL Database: Robust, scalable data storage with full ACID compliance via Prisma
- Local Development: Docker-based PostgreSQL plus Mailpit for SMTP
- Cloud Ready: Supabase (pooler + direct URL) supported for production
- Memory System: Upload and store PDFs and images for AI analysis; files are kept on disk (or Vercel Blob in deployed environments) and referenced by
MemoryDocument.filePath - Mocap Storage: Recorded video and the
PoseFrameStreambinary blob are stored side-by-side on the same backend (localstorage/directory or Vercel Blob), referenced byMocapSession.videoStoragePathandMocapSession.poseStreamPath - Session Cache Invalidation: Rowing-session mutations bump
UserSettings.sessionsRevision; client caches use the revision plus row count to reuse lean lists safely and hydrate detail data only when needed - Data Privacy: Workout data, mocap video, and pose data are scoped by user ID; AI keys are encrypted at rest
- Dark Theme: Modern, easy-on-the-eyes interface
- Responsive Design: Works seamlessly on desktop, tablet, and mobile
- Offline-First: All features work offline except AI-powered capabilities
- Rich Tooltips: AI explanations display in beautifully formatted tooltips with markdown rendering, responsive sizing, and smooth scrolling
- Smart Navigation: "Back to chart" links from chat explanations navigate directly to the source chart (both analytics and session detail pages) with automatic scroll-to-view
- Dynamic Awards System: Earn achievements for session milestones (First Splash, Century Club, Year of Rowing), total distance (Million Meter Club), streaks, duration, power output, pace improvements, and more
- Improvement Awards: Track percentage gains in power (up to +100% Double Power) and pace compared to a baseline computed from the first 3 valid sessions
- Streak Milestones: Notifications for 7-, 14-, 21-, 45-, 60-, and 100-day streaks
- AI Award Suggestions: The AI proposes custom awards (
AIAwardSuggestion) with structured criteria; once approved they auto-evaluate against your data - Generated Achievement Stories & Art: Each unlocked award gets an AI-written story and optional generated image (
GeneratedAchievement), with a configurablecolorPalettefor the artwork - Live Award Notifications: Animated overlays the moment a new achievement unlocks
- High-Tier Stretch Goals: Long-term achievements including 750k meters, 1 Million meters, 100 hours rowing, 300W power, and sub-1:35/500m pace
- Browser-Based Capture (
/mocap): Single-webcam recording with in-browser MediaPipe Pose Landmarker running in a Web Worker. Zero install, no cloud upload of video by default. - Multi-Camera Sidecar Capture: Optional freemocap sidecar on localhost (default port
8765) providessidecar-3dcapture withworld-mm-3dkeypoints,cameraCount,calibrationId, andreprojectionErrorMmquality data. - Capture Perspectives: Browser sessions use
side-leftorside-right; sidecar sessions usesidecar-3d. Metrics that need multi-camera geometry are marked asrequires-multi-camon side-view captures instead of being estimated. - Record-Only Capture: Browser sessions can persist video without a pose stream. Record-only sessions finalize with
analysisMode: "record-only"and cannot be linked or reanalyzed until pose data exists. - Per-Session Calibration: Browser capture stores catch and finish reference frames on the
MocapSession. Sidecar capture stores the sidecar's CharucocalibrationIdfor traceability. - Pose Frame Stream: A versioned binary
PoseFrameStreamblob is appended in chunks viaPOST /api/mocap/sessions/:id/pose-stream. v1 stores normalized 2D{x, y, confidence}frames; v2 stores sidecarworld-mm-3d{x, y, z, confidence}frames. - Guarded Lifecycle:
MocapSession.statusmoves throughcapturing,analyzing, andready. Finalize, link, unlink, and reanalysis routes use guarded transitions and return normalized{ ok, analysisMode, strokeMetricCount, faultCount }responses on success. - Stroke Segmentation:
pose-segmentedboundaries are computed from the pose stream. Linking aMocapSessionto aRowingSessionre-segments atomically tocsv-alignedusing SmartRowStrokeData. - Posture Fault Catalog: Side-view rules emit
rounded_back_at_catch,early_arm_bend,back_opens_before_legs_drive,excessive_layback, andslow_recovery_ratio. Sidecar 3D metrics surface lateral asymmetry, knee-track deviation, and shin-angle data where available. - Configurable Thresholds: Conservative hand-coded defaults (
postureThresholdsV1) with auto-migration on version bumps; user customization stored inUserSettings.postureThresholdsis preserved (userOverridden: true). - Live Coaching Cues: Post-stroke cues (≤ 1 s after the stroke completes) via the
LiveCoachingEngine, with optional spoken audio (speakCue) and configurable verbosity inUserSettings.mocapPreferences. - Auto-Link on CSV Import: When a CSV import produces a
RowingSessionwhose timestamp overlaps aMocapSessioncapture window by ±2 minutes, the user is prompted to link them. Linking is bidirectional, exclusive, and reversible (/unlinkendpoint). - Re-Analysis Endpoint:
POST /api/mocap/sessions/:id/reanalyzere-runs the segmenter, metrics calculator, and fault detector with current rules so old sessions use current thresholds. - Cloud-AI Payload Tiers: AI gets a
PostureFaultsummary by default; per-stroke metrics are opt-in viaUserSettings.mocapDetailedAIShare; raw frames never cross to cloud (see ADR-0004).
- Framework: Next.js 16 (App Router, React 19)
- Language: TypeScript 5
- Styling: TailwindCSS 4
- Components: shadcn/ui (Radix UI primitives)
- Charts: Recharts 3
- Animations: Framer Motion
- Markdown: react-markdown + remark-gfm + Shiki for code highlighting
- AI Integration: OpenAI API (chat, image generation, condensation prompts)
- Authentication: NextAuth.js v4 (Credentials, Email magic link, optional Google OAuth)
- Database: PostgreSQL with Prisma v7 and
@prisma/adapter-pg - State Management: Zustand with persist middleware
- Storage:
- PostgreSQL for user data, sessions, plans, achievements, mocap rows
- File system (or Vercel Blob via
@vercel/blob) for award images, memory documents, mocap video, and pose stream blobs
- Pose Estimation:
@mediapipe/tasks-visionPose Landmarker, Web Worker, WASM; optional freemocap sidecar via localhost HTTP/WebSocket contract - CSV / ZIP Parsing:
papaparse,jszip - PDF Extraction:
unpdf - SmartRow Automation:
playwright(server-side login + export download for/api/smartrow/sync) - Email: Nodemailer with Mailpit (local) or SMTP (production)
- Rate Limiting: Upstash Redis for API protection
- Validation: Zod
- Development: Docker Compose for local services
- Node.js 22.12+
- npm 10.8+
- Docker & Docker Compose (for local development)
- OpenAI API Key (optional, for AI features)
- PostgreSQL database (local via Docker or Supabase)
-
Clone the repository
git clone https://github.com/rupertgermann/rowing-tracker cd rowing-tracker -
Install dependencies
npm install
-
Set up environment variables
cp .env.example .env
Edit
.envand configure:DATABASE_URL- PostgreSQL connection stringNEXTAUTH_SECRET- Generate withopenssl rand -base64 32NEXTAUTH_URL- Your app URL (http://localhost:3000 for local)EMAIL_SERVER- SMTP server for emails (smtp://localhost:1025 for local)EMAIL_FROM- Sender email addressUPSTASH_REDIS_REST_URL- Upstash Redis URL (optional, for rate limiting)UPSTASH_REDIS_REST_TOKEN- Upstash Redis token (optional, for rate limiting)
-
Start local services (PostgreSQL + Mailpit)
npm run db:start
This starts:
- PostgreSQL on port 5432
- Mailpit SMTP on port 1025
- Mailpit Web UI on http://localhost:9025
-
Run database migrations
npm run db:migrate
-
Run the development server
npm run dev
-
Open your browser Navigate to http://localhost:3000
-
Create an account
- Click "Register" to create your account
- Check Mailpit (http://localhost:9025) for verification email
- Click the verification link
- Sign in with your credentials
-
Configure AI (Optional)
- Go to Settings → AI Coach
- Enter your OpenAI API Key to enable Chat, Insights, and Training Plans
- Add your Personal Context to inform the AI about medical conditions, preferences, or goals
- Customize the AI prompts (base, chat, training plan, insights) in Advanced Configuration with one-click "reset to default"
-
Configure SmartRow Auto-Sync (Optional)
- Go to Settings → SmartRow
- Enter your
smartrow.fitemail and password (stored per-user inUserSettings.smartRowSettings) - Visit
/syncand click Sync Now to pull all workouts in one pass
-
Promote a User to Admin (Optional)
npm run admin:promote -- <email>
Admin users see the Admin Panel entry in the user menu and can access
/adminto manage other users.
The /sync page offers three import paths. All three deduplicate against existing sessions and update changed records in place.
- Save your SmartRow credentials in Settings → SmartRow.
- Open
/syncand click Sync Now. - The server runs a Playwright session against
https://smartrow.fit/my-workouts/, downloads the workouts CSV and the detailed-stroke ZIP, and imports both. ThelastSynctimestamp is updated on completion.
Drag-and-drop a SmartRow CSV onto /sync or click to browse. Both the workouts-list CSV and individual detailed-stroke CSVs are accepted.
Drop the SmartRow archive ZIP onto /sync to import many detailed-stroke sessions at once with progress reporting (ZipProcessProgress).
SmartRow CSV exports use:
- Delimiter: Semicolon (
;) - Decimal Format: Comma (
,) — European format - Timestamp:
YYYY-MM-DD HH:MM:SS.mmm(UTC) - Time Field: Seconds
- Time stamp (UTC)
- Distance (m)
- Time (seconds)
- Energy (kCal)
- Stroke count (#)
- Average power (W)
- Maximum power (W)
- Average split (s) — per 500m
- Minimum split (s)
- Average stroke rate (SPM)
- Maximum stroke rate (SPM)
Detailed-stroke files (one per session, contained in the SmartRow ZIP) are parsed by src/lib/strokeParser.ts into StrokeData rows. They drive stroke-by-stroke analysis and the precomputed consistencyScore on RowingSession.
-
Create a Supabase project
- Go to supabase.com
- Create a new project
- Note your project URL and database password
-
Configure environment variables
# In your .env or deployment platform DATABASE_URL="postgresql://postgres.[project-ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres?pgbouncer=true" DIRECT_URL="postgresql://postgres.[project-ref]:[password]@aws-0-[region].pooler.supabase.com:5432/postgres" NEXTAUTH_SECRET="your-production-secret" NEXTAUTH_URL="https://yourdomain.com" EMAIL_SERVER="smtp://your-smtp-server:587" EMAIL_FROM="noreply@yourdomain.com" # Rate Limiting (recommended for production) UPSTASH_REDIS_REST_URL="https://your-redis.upstash.io" UPSTASH_REDIS_REST_TOKEN="your-upstash-token"
-
Run migrations
npm run db:migrate
-
Deploy to Vercel/Netlify
- Connect your repository
- Add environment variables
- Deploy!
Available npm scripts:
# Start local Docker services (PostgreSQL + Mailpit)
npm run db:start
# Stop local Docker services
npm run db:stop
# Generate Prisma client
npm run db:generate
# Run migrations (development)
npm run db:migrate
# Run migrations (production)
npm run db:migrate:deploy
# Reset database (WARNING: deletes all data)
npm run db:reset
# Seed database (where a seed is configured)
npm run db:seed
# Open Prisma Studio (database GUI)
npm run db:studio
# Push schema without migration (dev only)
npm run db:push
# Promote a user to admin
npm run admin:promote -- <email>
# Backfill consistencyScore for existing sessions (one-off)
npx tsx scripts/backfill-consistency.tsrowing-tracker/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── dashboard/ # Main dashboard
│ │ ├── analytics/ # Advanced analytics page
│ │ ├── sessions/ # Session list and detail pages
│ │ ├── prs/ # Personal records & achievements
│ │ ├── plans/ # AI training plans
│ │ ├── chat/ # AI Coach chat
│ │ ├── insights/ # AI insights archive & feedback
│ │ ├── mocap/ # Webcam capture + posture replay
│ │ ├── sync/ # SmartRow sync, CSV/ZIP upload
│ │ ├── profile/ # User profile
│ │ ├── settings/ # App settings (AI, SmartRow, posture, ...)
│ │ ├── admin/ # Admin user management (admin role only)
│ │ ├── auth/ # Login, register, verify-email, reset
│ │ ├── api/ # API routes (NextAuth, sessions, mocap, smartrow, ...)
│ │ ├── layout.tsx
│ │ └── globals.css
│ ├── components/ # Reusable UI components (shadcn/ui + custom)
│ ├── hooks/ # Custom React hooks
│ ├── lib/ # Utilities & services
│ │ ├── auth.ts # NextAuth configuration
│ │ ├── db/prisma.ts # Prisma client singleton
│ │ ├── services/ # Service singletons
│ │ ├── mocap/ # Pose source, frame stream, analysis pipeline, coaching
│ │ │ ├── browserPoseSource.ts
│ │ │ ├── poseFrameStream.ts
│ │ │ ├── poseWorker.ts
│ │ │ ├── analysis/ # Pure functions: segmenter, metrics, fault detector, thresholds
│ │ │ └── coaching/ # LiveCoachingEngine, CoachingAdvisor, cue audio
│ │ ├── csvParser.ts
│ │ ├── strokeParser.ts
│ │ ├── zipParser.ts
│ │ ├── awards.ts
│ │ └── ...
│ ├── types/ # TypeScript type definitions
│ └── middleware.ts # Auth + admin route guards
├── prisma/ # Database schema & migrations
│ └── schema.prisma
├── scripts/ # One-off operational scripts
│ ├── promote-admin.ts
│ └── backfill-consistency.ts
├── tests/ # Unit + Playwright e2e tests
│ ├── *.test.ts # tsx --test
│ ├── fixtures/mocap/ # Pose-frame fixtures
│ └── e2e/ # Playwright specs
├── docs/
│ ├── DATABASE_SCHEMA.md
│ ├── design-system.md
│ ├── prd.md
│ ├── prd-mocap-posture.md # Mocap PRD + locked decisions
│ ├── sidecar-local-setup.md # Local freemocap sidecar setup and mock
│ ├── adr/ # Architecture Decision Records (0001–0005)
│ ├── agents/ # Agent docs (issue tracker, triage labels, domain)
│ └── csvs/ # Sample SmartRow CSVs
├── CONTEXT.md # Domain glossary
├── AGENTS.md # Engineering rules
└── docker-compose.yml # Local PostgreSQL & Mailpit
- Authentication: User registers/logs in → NextAuth validates (Credentials / Email magic link / Google OAuth) → JWT session created with
idandrole - Import:
- Sync:
/api/smartrow/syncruns Playwright against smartrow.fit → returns CSV + base64 ZIP → client parses with papaparse / jszip → saved to PostgreSQL - Manual: User drops CSV/ZIP → client-side validation → saved to PostgreSQL
- Sync:
- Mocap Capture: Browser webcam or freemocap sidecar → chunked HTTP uploads of video and
PoseFrameStream→ guarded finalize → pose-segmented metrics and faults for analyzable captures, orrecord-onlyfor video-only captures - Storage:
- User data, sessions, plans, mocap rows → PostgreSQL via Prisma
- Award images, memory documents, mocap video, pose stream blobs → file system or Vercel Blob
- Client rowing-session state → Zustand store for local UI state; RowingSession persistence runs through API/service helpers
- Display: Session lists use
/api/sessions/listwithstrokeDataCount; detail views hydrate fullStrokeDatathrough/api/sessions?id=<sessionId>; cache busts viaUserSettings.sessionsRevision/insightsRevision - Analysis: Real-time PR calculations, trend analysis, consistency score, posture metrics, fault counts
- AI Features: Context (sessions, achievements, memory documents, posture summary) retrieved from database → personal context injected from
UserSettings.userProfileContext→ sent to OpenAI → response streamed - Linking: When a
RowingSessionoverlaps aMocapSessioncapture window by ±2 minutes, the user is prompted to link; linking triggers atomic re-segmentation tocsv-aligned - Data Isolation: All queries filtered by authenticated user ID; admin endpoints additionally gated via
src/lib/adminAuth.ts
Development:
npm run dev— Start development servernpm run build— Build for productionnpm run start— Start production servernpm run lint— Run ESLint
Database:
npm run db:start— Start PostgreSQL & Mailpitnpm run db:stop— Stop Docker servicesnpm run db:generate— Generate Prisma clientnpm run db:migrate— Run migrations (dev)npm run db:migrate:deploy— Apply migrations (prod)npm run db:studio— Open Prisma Studionpm run db:push— Push schema without migration (dev only)npm run db:seed— Seed database (when configured)npm run db:reset— Reset database (destructive)
Tests:
npx tsc --noEmit— Run TypeScript validationnpm run lint— Run ESLint across the projectnpm test— Run unit tests viatsx --test tests/*.test.tsnpm run test:e2e— Run Playwright end-to-end tests intests/e2e/npx tsx --test tests/rowingSessionPersistence.test.ts— Focused RowingSession list/detail hydration testsnpx tsx --test tests/sidecarTracer.test.ts— Sidecar v2 blob, projection, and sidecar-fault testsnpx tsx --test tests/sidecarMockContract.test.ts— In-process sidecar contract test without hardwarenpm run test:e2e -- tests/e2e/session-detail-mocap-link.spec.ts— Session detail, stroke-data hydration, and mocap-link e2e coveragenpm run test:e2e -- tests/e2e/mocap-capture.spec.ts— Browser and sidecar mocap capture e2e coverageenv PLAYWRIGHT_SKIP_SERVER=1 npm run test:e2e -- <spec>— Reuse an already-running dev server for a focused Playwright spec
Operations:
npm run admin:promote -- <email>— Promote a user to adminnpx tsx scripts/backfill-consistency.ts— BackfillconsistencyScorefor existing sessions and bumpsessionsRevisionfor affected users
- App Router: Uses Next.js 16's App Router for file-based routing
- Server Components: Default for better performance
- Client Components: Mark with
'use client'when needed - State Management: Zustand with DB persistence
- Styling: TailwindCSS with dark theme support
# Add shadcn/ui components
npx shadcn@latest add button card table badge
# Components are added to your components/ directoryPostgreSQL with Prisma ORM. Key models:
User Management:
User— User accounts with authentication androle(user/admin)Account— OAuth provider accountsAuthSession— Active sessionsVerificationToken— Email verification + magic-link tokensPasswordResetToken— Password reset flowUserSettings— Preferences, AI config, SmartRow credentials, posture thresholds, mocap preferences, dashboard/sessions/analytics view state, cache-busting revisionsUserApiKey— Encrypted per-provider API keys (e.g. OpenAI)
Rowing Data:
RowingSession— Workout sessions with metrics and pre-computedconsistencyScoreStrokeData— Stroke-by-stroke analysis data (with optionalstrokeLength)PersonalRecord— Best performance per distance
Mocap (Posture Analysis):
MocapSession— One capture per session with video + pose stream paths,source,capturePerspective, calibration frames orcalibrationId,cameraCount,qualityScore, and guardedcapturing/analyzing/readystatusStrokePostureMetric— Per-stroke posture metrics withsegmentationSource(pose-segmentedorcsv-aligned)PostureFault— Detected faults withfaultType,severity,phase,evidenceJson
Achievements:
EarnedAward— Unlocked achievementsAIAwardSuggestion— AI-suggested custom awards with structured criteriaGeneratedAchievement— AI-generated story + optional image withcolorPalette
Training:
TrainingPlan— Multi-week training programs with adherence trackingTrainingWeek— Weekly structureTrainingSession— Individual planned workouts with target zonesTrainingSessionLink— Links planned sessions to actualRowingSessionrows
AI & Memory:
ChatSession— AI coach conversations grouped bycategory(chat,explanation,plan_analysis,insight_discussion)ChatMessage— Individual messages with optional attachmentsAIInsight— Generated performance insights withpriority,confidence,evidence,feedbackMemoryDocument— Uploaded PDFs / images / training plans / notes referenced byfilePathChartExplanation— Cached chart explanations keyed by(userId, chartId)
See prisma/schema.prisma for the complete, authoritative schema, and docs/DATABASE_SCHEMA.md for the annotated reference.
- User Isolation: Each user's data is completely isolated in the database
- Secure Authentication: Passwords hashed with bcrypt, JWT sessions
- Email Verification: Double opt-in ensures account ownership
- Data Encryption: Sensitive data encrypted at rest (database level)
- Privacy First: Your workout data is never shared with other users
- GDPR Ready: User data can be exported or deleted on request
- API Keys: OpenAI keys encrypted in database, never exposed to client
The app includes built-in rate limiting using Upstash Redis to protect against abuse and ensure fair usage.
| Endpoint Type | Limit | Description |
|---|---|---|
| General API | 100 req/min | Standard API endpoints |
| Authentication | 10 req/min | Login, register, password reset |
| AI/Chat | 20 req/min | AI coach, chat endpoints |
| Upload | 30 req/min | CSV file uploads |
| Sensitive | 5 req/min | Account deletion, data export |
Rate limiting is optional but recommended for production. To enable:
- Create an Upstash account at upstash.com
- Create a Redis database (free tier available)
- Add environment variables:
UPSTASH_REDIS_REST_URL="https://your-redis.upstash.io" UPSTASH_REDIS_REST_TOKEN="your-token"
- When configured: Requests exceeding limits receive a
429 Too Many Requestsresponse withRetry-Afterheader - When not configured: Rate limiting is disabled (graceful degradation for development)
- Rate limit headers: Responses include
X-RateLimit-Limit,X-RateLimit-Remaining, andX-RateLimit-Reset
Rate limiting is applied to:
/api/auth/register— Prevents registration spam/api/auth/forgot-password,/api/auth/reset-password— Limits password-reset abuse/api/chat(POST) — Controls AI usage costs/api/smartrow/sync— Limits Playwright-driven SmartRow scrapes/api/mocap/sessions/*(upload + finalize) — Caps mocap capture write volume/api/user/delete— Protects account deletion/api/user/export— Prevents data export abuse
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
If you encounter any issues:
- Check that your CSV file matches the required format
- Ensure all required columns are present
- Verify Docker services are running (
npm run db:start) - Open an issue on GitHub with details about your problem
Built with ❤️ for the rowing community