An open API service indexing awesome lists of open source software.

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

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`.