https://github.com/0jonjo/calcpace_web
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
https://github.com/0jonjo/calcpace_web
bike calcpace calculator cycling kamal ruby-on-rails running
Last synced: about 2 months ago
JSON representation
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
- Host: GitHub
- URL: https://github.com/0jonjo/calcpace_web
- Owner: 0jonjo
- Created: 2026-03-22T13:21:44.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-04-19T22:57:52.000Z (3 months ago)
- Last Synced: 2026-04-20T00:37:08.722Z (3 months ago)
- Topics: bike, calcpace, calculator, cycling, kamal, ruby-on-rails, running
- Language: HTML
- Homepage: https://calcpace.app/
- Size: 578 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Calcpace Web
**Live:** [https://calcpace.app](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`](https://github.com/0jonjo/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](https://github.com/basecamp) — 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](https://asdf-vm.com/))
- Docker + Docker Compose
- PostgreSQL client (`libpq`)
## Local Setup
```bash
# 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](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
```bash
bin/rails test
```
## Public Tools
The calculator and converter are publicly accessible at [calcpace.app](https://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`](https://github.com/0jonjo/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](https://developers.cloudflare.com/turnstile/) (bot challenge) and Rack::Attack (rate limiting).
## Transactional Emails
Handled by [Resend](https://resend.com) (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`](https://github.com/dougfales/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/`](.github/workflows/).
## Deployment
Deployment is handled by [Kamal](https://kamal-deploy.org/) targeting a DigitalOcean VPS. See `config/deploy.yml`.