{"id":47670464,"url":"https://github.com/0jonjo/calcpace_web","last_synced_at":"2026-05-16T03:26:20.611Z","repository":{"id":346149165,"uuid":"1188703509","full_name":"0jonjo/calcpace_web","owner":"0jonjo","description":"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","archived":false,"fork":false,"pushed_at":"2026-04-19T22:57:52.000Z","size":592,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-20T00:37:08.722Z","etag":null,"topics":["bike","calcpace","calculator","cycling","kamal","ruby-on-rails","running"],"latest_commit_sha":null,"homepage":"https://calcpace.app/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/0jonjo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-22T13:21:44.000Z","updated_at":"2026-04-19T22:57:02.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/0jonjo/calcpace_web","commit_stats":null,"previous_names":["0jonjo/calcpace_web"],"tags_count":29,"template":false,"template_full_name":null,"purl":"pkg:github/0jonjo/calcpace_web","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0jonjo%2Fcalcpace_web","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0jonjo%2Fcalcpace_web/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0jonjo%2Fcalcpace_web/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0jonjo%2Fcalcpace_web/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/0jonjo","download_url":"https://codeload.github.com/0jonjo/calcpace_web/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0jonjo%2Fcalcpace_web/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32263182,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-25T09:15:33.318Z","status":"ssl_error","status_checked_at":"2026-04-25T09:15:31.997Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bike","calcpace","calculator","cycling","kamal","ruby-on-rails","running"],"created_at":"2026-04-02T12:41:28.049Z","updated_at":"2026-04-25T13:20:25.236Z","avatar_url":"https://github.com/0jonjo.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Calcpace Web\n\n**Live:** [https://calcpace.app](https://calcpace.app)\n\nTrack your runs and rides. Understand your fitness.\n\nLog 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.\n\nBuilt 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.\n\n## Goals\n\n- Master VPS deployment with **Kamal + Docker** on DigitalOcean (vs. Heroku/PaaS)\n- Practice **TDD with Minitest** and **CI/CD with GitHub Actions**\n- Follow [37signals/Basecamp design principles](https://github.com/basecamp) — thin controllers, rich models, Current attributes\n\n## Stack\n\n| Layer | Technology |\n|---|---|\n| Backend | Ruby on Rails 8 |\n| Frontend | ERB + Tailwind CSS |\n| Database | PostgreSQL 17 |\n| Cache / Jobs | Redis + Sidekiq |\n| Email | Resend API |\n| Monitoring | AppSignal (APM + error tracking) |\n| Deploy | Kamal + Docker |\n| Hosting | DigitalOcean (VPS) |\n| File Storage | Cloudflare R2 (Active Storage — S3-compatible) |\n| Maps | Leaflet.js + OpenStreetMap (no API key required) |\n| Bot Protection | Cloudflare Turnstile + Rack::Attack |\n\n## Requirements\n\n- Ruby 3.4.4 (via [asdf](https://asdf-vm.com/))\n- Docker + Docker Compose\n- PostgreSQL client (`libpq`)\n\n## Local Setup\n\n```bash\n# 1. Clone and install dependencies\ngit clone https://github.com/0jonjo/calcpace_web.git\ncd calcpace_web\nbundle install\n\n# 2. Start PostgreSQL and Redis via Docker\ndocker compose up -d\n\n# 3. Create and migrate the database\nbin/rails db:create db:migrate\n\n# 4. Start the development server (Rails + Tailwind watcher)\nbin/dev\n```\n\nOpen [http://localhost:3000](http://localhost:3000).\n\n\u003e **Note:** A system Redis on port 6379 will conflict. The compose file maps Redis to port **6380** to avoid it.\n\n## Running Tests\n\n```bash\nbin/rails test\n```\n\n## Public Tools\n\nThe calculator and converter are publicly accessible at [calcpace.app](https://calcpace.app) — no login required:\n\n### Calculator (`/calculator`)\nCompute running and cycling metrics:\n- **Pace** — given distance and total time\n- **Total Time** — given distance and pace\n- **Distance** — given total time and pace\n- **Race Times** — finish time for standard distances at a given pace\n- **Race Predictor** — estimate finish time for standard race distances (5K, 10K, half marathon, marathon) from a known result, using Riegel or Cameron formula\n- **VO2max** — estimate aerobic capacity from a race result (Daniels \u0026 Gilbert formula)\n\nSupports both **metric (km)** and **imperial (mi)** unit systems.\n\n### Converter (`/converter`)\nConvert between common sports units:\n- **Distance** — km ↔ mi\n- **Speed** — km/h ↔ mph\n- **Pace** — min/km ↔ min/mi\n\nAll calculations are powered by the [`calcpace`](https://github.com/0jonjo/calcpace) gem.\n\n## Guest Account\n\nA guest account with sample activities (a run and a bike ride) is available to explore the app:\n\n- **Email:** `guest@calcpace.app`\n- **Password:** set via `GUEST_PASSWORD` env var in production\n\nThe guest profile is automatically reset every Monday at 3am by the `GuestResetJob`.\n\n## Registration\n\nRegistration is open. Visit `/registration/new` or click \"Sign up\" in the nav.\n\nAfter 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.\n\nRegistration is protected by [Cloudflare Turnstile](https://developers.cloudflare.com/turnstile/) (bot challenge) and Rack::Attack (rate limiting).\n\n## Transactional Emails\n\nHandled by [Resend](https://resend.com) (free tier: 3,000 emails/month). All emails are queued via Sidekiq.\n\n| Event | Email |\n|---|---|\n| Account created | Email verification link (expires in 48h) + welcome email |\n| Password reset requested | Reset link (expires in 15 min) |\n| Password changed | Security notification |\n\n## Activity Features\n\n### GPS / GPX Upload\n\nActivities support an optional `.gpx` file upload (stored in Cloudflare R2). When a GPX file is attached:\n\n1. `GpxParseJob` runs asynchronously via Sidekiq\n2. The job parses trackpoints from the file using the [`gpx`](https://github.com/dougfales/gpx) gem\n3. Trackpoints are bulk-inserted into the `trackpoints` table\n4. Distance and elevation gain are calculated via the `calcpace` gem (`TrackCalculator` module — Haversine formula) and stored on the activity\n\nThe 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.\n\n### Public Profile\n\nUsers 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`.\n\n### Image Uploads\n\nProfile 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).\n\n## Background Jobs\n\nSidekiq processes the job queue using the Redis accessory. A separate worker container runs alongside the web container in production (see `config/deploy.yml`).\n\n| Job | Trigger | Description |\n|---|---|---|\n| `GuestResetJob` | Every Monday at 3am (sidekiq-cron) | Wipes guest user activities and recreates the profile |\n| `GpxParseJob` | On GPX file upload | Parses trackpoints, calculates distance/elevation, bulk-inserts into DB |\n\n## CI/CD\n\nGitHub Actions runs the full test suite on every push to `main`. Deploys are triggered by GitHub Releases. See [`.github/workflows/`](.github/workflows/).\n\n## Deployment\n\nDeployment is handled by [Kamal](https://kamal-deploy.org/) targeting a DigitalOcean VPS. See `config/deploy.yml`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0jonjo%2Fcalcpace_web","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F0jonjo%2Fcalcpace_web","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0jonjo%2Fcalcpace_web/lists"}