Skip to content

0jonjo/calcpace_web

Repository files navigation

Calcpace Web

Live: https://calcpace.app

Track your runs and rides. Understand your fitness.

Log activities, upload GPX routes, analyse splits — and use free public tools to calculate pace, predict race times and estimate VO2max. No ads, no tracking, totally free. Your data is yours — export it anytime.

Built to practice modern Rails infrastructure and deployment. This app serves as a production dogfooding environment for the open-source calcpace gem.

Goals

  • Master VPS deployment with Kamal + Docker on DigitalOcean (vs. Heroku/PaaS)
  • Practice TDD with Minitest and CI/CD with GitHub Actions
  • Follow 37signals/Basecamp design principles — thin controllers, rich models, Current attributes

Stack

Layer Technology
Backend Ruby on Rails 8
Frontend ERB + Tailwind CSS
Database PostgreSQL 17
Cache / Jobs Redis + Sidekiq
Email Resend API
Monitoring AppSignal (APM + error tracking)
Deploy Kamal + Docker
Hosting DigitalOcean (VPS)
File Storage Cloudflare R2 (Active Storage — S3-compatible)
Maps Leaflet.js + OpenStreetMap (no API key required)
Bot Protection Cloudflare Turnstile + Rack::Attack

Requirements

  • Ruby 3.4.4 (via asdf)
  • Docker + Docker Compose
  • PostgreSQL client (libpq)

Local Setup

# 1. Clone and install dependencies
git clone https://github.com/0jonjo/calcpace_web.git
cd calcpace_web
bundle install

# 2. Start PostgreSQL and Redis via Docker
docker compose up -d

# 3. Create and migrate the database
bin/rails db:create db:migrate

# 4. Start the development server (Rails + Tailwind watcher)
bin/dev

Open http://localhost:3000.

Note: A system Redis on port 6379 will conflict. The compose file maps Redis to port 6380 to avoid it.

Running Tests

bin/rails test

Public Tools

The calculator and converter are publicly accessible at calcpace.app — no login required:

Calculator (/calculator)

Compute running and cycling metrics:

  • Pace — given distance and total time
  • Total Time — given distance and pace
  • Distance — given total time and pace
  • Race Times — finish time for standard distances at a given pace
  • Race Predictor — estimate finish time for standard race distances (5K, 10K, half marathon, marathon) from a known result, using Riegel or Cameron formula
  • VO2max — estimate aerobic capacity from a race result (Daniels & Gilbert formula)

Supports both metric (km) and imperial (mi) unit systems.

Converter (/converter)

Convert between common sports units:

  • Distance — km ↔ mi
  • Speed — km/h ↔ mph
  • Pace — min/km ↔ min/mi

All calculations are powered by the calcpace gem.

Guest Account

A guest account with sample activities (a run and a bike ride) is available to explore the app:

  • Email: guest@calcpace.app
  • Password: set via GUEST_PASSWORD env var in production

The guest profile is automatically reset every Monday at 3am by the GuestResetJob.

Registration

Registration is open. Visit /registration/new or click "Sign up" in the nav.

After registering, a verification email is sent via Resend. The account is locked until the email is confirmed (link expires in 48 hours). Unverified users are redirected to the verification page on login.

Registration is protected by Cloudflare Turnstile (bot challenge) and Rack::Attack (rate limiting).

Transactional Emails

Handled by Resend (free tier: 3,000 emails/month). All emails are queued via Sidekiq.

Event Email
Account created Email verification link (expires in 48h) + welcome email
Password reset requested Reset link (expires in 15 min)
Password changed Security notification

Activity Features

GPS / GPX Upload

Activities support an optional .gpx file upload (stored in Cloudflare R2). When a GPX file is attached:

  1. GpxParseJob runs asynchronously via Sidekiq
  2. The job parses trackpoints from the file using the gpx gem
  3. Trackpoints are bulk-inserted into the trackpoints table
  4. Distance and elevation gain are calculated via the calcpace gem (TrackCalculator module — Haversine formula) and stored on the activity

The activity show page (both logged-in and public) renders an interactive OpenStreetMap map via Leaflet.js and a per-km splits table, calculated on-the-fly from the stored trackpoints.

Public Profile

Users can opt in to a public profile at /:username. Each activity can individually be set to public or private. Public activities are listed on the profile page and have a dedicated public detail page at /:username/activities/:id.

Image Uploads

Profile avatars and activity photos are stored in Cloudflare R2 via Active Storage. Supported formats: JPEG, PNG, WebP (max 5 MB). GPX files: XML format (max 20 MB).

Background Jobs

Sidekiq processes the job queue using the Redis accessory. A separate worker container runs alongside the web container in production (see config/deploy.yml).

Job Trigger Description
GuestResetJob Every Monday at 3am (sidekiq-cron) Wipes guest user activities and recreates the profile
GpxParseJob On GPX file upload Parses trackpoints, calculates distance/elevation, bulk-inserts into DB

CI/CD

GitHub Actions runs the full test suite on every push to main. Deploys are triggered by GitHub Releases. See .github/workflows/.

Deployment

Deployment is handled by Kamal targeting a DigitalOcean VPS. See config/deploy.yml.

About

A sports activity tracker for runners and cyclists with a public calculators and unit converter. This app serves a dogfooding for the open-source calcpace gem

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages