No description
  • Rust 92.7%
  • Nix 5.4%
  • Shell 1.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Claude 64f5e0b725 sync: allow syncing athletes other than the current session
require_athlete_access rejected any /sync/{athlete_id} request where
athlete_id didn't match the session's own Intervals.icu athlete id.
That's a local, unconditional block that has nothing to do with
whether the session's access token actually has access to that
athlete's data on Intervals.icu -- for example a coach account whose
API key can see multiple athletes' activities.

require_athlete_access is replaced by require_authenticated_session,
which only checks that the request carries a valid session; the
session's own access token is then used to call Intervals.icu for
whichever athlete_id was requested, and Intervals.icu's own API is
what actually authorizes (or 401s/403s) that per-athlete access.

Import status and the 'last synced' default were previously looked
up using the *session's* local athlete id even when browsing a
different athlete_id -- i.e. a coach browsing an athlete's
activities would see their own import history instead of that
athlete's. Both /sync/{athlete_id} and its import-visible handler
now resolve the local athlete row for the athlete_id being browsed
(local_athlete_id) and use that instead. If that athlete has never
logged in locally, everything is treated as not-yet-imported; actually
importing then fails with the existing 'no local athlete for
Intervals.icu athlete' error rather than silently doing the wrong
thing.

process_activity_with_client (session-derived client) is now used
instead of process_activity (which looks up the target athlete's own
stored token) for both single-activity and bulk import, since the
token doing the request is the session's, not necessarily the target
athlete's own.
2026-08-13 11:39:49 +02:00
migrations add map and clicks 2026-08-12 17:57:25 +02:00
scripts state 2026-08-12 17:49:27 +02:00
src sync: allow syncing athletes other than the current session 2026-08-13 11:39:49 +02:00
.env.example chatgpt 2026-08-12 13:24:01 +02:00
.gitignore extend .gitignore 2026-08-13 05:15:43 +02:00
Cargo.toml web: use Form and Query from axum_extra 2026-08-13 05:07:38 +02:00
flake.lock fixup! fix nix develop psql setup 2026-08-12 13:37:37 +02:00
flake.nix dev api key 2026-08-12 14:02:12 +02:00
README.md add map and clicks 2026-08-12 17:57:25 +02:00
rust-toolchain.toml chatgpt 2026-08-12 13:24:01 +02:00

County Sprints frontend/leaderboard update

Replace these files in the project:

  • src/main.rs
  • src/model.rs
  • src/db.rs
  • src/leaderboard.rs
  • src/web.rs
  • migrations/0001_initial.sql

No change is required to the working src/intervals.rs.

The existing src/webhook.rs can keep calling:

crate::process_activity(state_clone, athlete_id, activity_id.clone()).await

process_activity() now loads the athlete's OAuth access token itself. The development sync uses the explicit API-key client, so the personal development key is not written to the database.

Database

The migration is intentionally a complete replacement because the database can be reset during development.

The new schema adds:

  • leaderboard_groups
  • leaderboard_group_members
  • indexes for activity/crossing queries

Leaderboard location matching

Leaderboard rows are grouped by:

  • 10-minute time bucket
  • source county
  • destination county
  • spatial cluster with a 10 metre DBSCAN radius

The crossing geometry is transformed to EPSG:3857 before the 10 metre clustering calculation so the distance is measured in metres rather than degrees.

Frontend

The frontend now provides:

  • Leaflet map for each leaderboard group
  • small map for every individual crossing
  • activity detail page at /activity/{activity_id}
  • activity map containing all crossings
  • browser-local timestamp formatting using Intl.DateTimeFormat
  • leaderboard group management at /groups
  • group selection on the main leaderboard

The map uses Leaflet and OpenStreetMap tiles. Leaflet's current stable 1.x documentation describes the same map/tile-layer APIs used here. citeturn0search2turn0search9