212 lines
10 KiB
Markdown
212 lines
10 KiB
Markdown
# Stride
|
|
|
|
A multi-user running dashboard built with Flask, Jinja, SQLAlchemy, Leaflet and
|
|
Chart.js. The local version supports accounts, activity imports, dashboards,
|
|
full route maps, charts, and sharing with accepted friends.
|
|
|
|
## Run the app
|
|
|
|
```powershell
|
|
.\scripts\start-app.ps1
|
|
```
|
|
|
|
Or run the steps individually:
|
|
|
|
```powershell
|
|
uv sync --locked
|
|
uv run --locked flask --app app db upgrade
|
|
uv run --locked python run.py
|
|
```
|
|
|
|
Open **http://127.0.0.1:5000**. On first launch, create your account with your name,
|
|
email, and a new Stride password. No username is required. When exactly one local
|
|
Garmin account cache exists, setup attaches its downloaded activities to this
|
|
first account, privately. Setup is only enabled for local requests before an
|
|
account exists. Subsequent accounts start empty.
|
|
|
|
The local app uses SQLite in `instance/dashboard.db`. Set `DATABASE_URL` to a
|
|
PostgreSQL connection string to use PostgreSQL. Database changes use Alembic
|
|
migrations. SQLite and deployed PostgreSQL migrations have been verified; see
|
|
[deployment validation](docs/DEPLOYMENT.md).
|
|
|
|
### Accounts and sharing
|
|
|
|
- Sign up with name, email and password; sign in with email and password.
|
|
- A generated internal profile identifier replaces a user-chosen username.
|
|
- Activity visibility is **Only me** or **Accepted friends**. Imports default to Only me.
|
|
- Add a friend by their registered email under Friends. The recipient must accept
|
|
the in-app request. No invitation email is sent by this local version.
|
|
- Accepted friends see each other's shared activities and full recorded routes.
|
|
- Removing a friend immediately revokes access to shared profiles, pages and JSON.
|
|
- Use the link icon on your run page to copy a public link. Anyone with that link
|
|
can view the run, full route and charts without signing in. Public links are
|
|
separate from friend visibility; disable a link in the run's sharing settings
|
|
to revoke access. There is no Explore page or public feed.
|
|
- Share links use the address you opened the app with. A localhost link only works
|
|
on your own computer; sharing with others requires a reachable app address.
|
|
- Routes are not masked or trimmed. Access control, rather than geographic masking,
|
|
determines who can see the full route, including start and finish.
|
|
|
|
### Exploring running stats
|
|
|
|
Open **Stats** for the query editor with syntax highlighting, contextual
|
|
autocomplete, and live validation from the execution parser. Use **Ctrl+Space**
|
|
for suggestions and **Ctrl/Cmd+Enter** to run. **Format** normalizes a valid query;
|
|
invalid drafts retain the displayed results. Guided controls and the language
|
|
reference remain available below the editor. CodeMirror 5.65.20 and its completion
|
|
addon are vendored locally under the MIT license; the editor needs no CDN access.
|
|
|
|
The DSL supports statistical summaries and percentiles, calculated fields,
|
|
AND/OR filters, run gaps and changes, rolling workload, running streaks, calendar
|
|
and metric-bin groups, group filters/ranking, cumulative and rolling trends,
|
|
scatter plots with correlation, and CSV exports. See the complete
|
|
[DSL reference and examples](docs/STATS_DSL.md) for units and execution semantics.
|
|
|
|
For your ten biggest gaps between recorded runs:
|
|
|
|
```text
|
|
runs
|
|
during all time
|
|
summarize gap_since_previous_run, distance
|
|
by run
|
|
sort by gap_since_previous_run desc
|
|
take 10
|
|
compare none
|
|
```
|
|
|
|
Gaps use complete owner-scoped history before filters and link to the previous
|
|
run. The explorer uses persisted run summaries without loading recording samples.
|
|
Cards show metric definitions and usable-run coverage. Select a chart point or
|
|
group row to inspect contributing runs. Save named views in this browser, or
|
|
bookmark the results URL; relative queries follow the calendar as runs are added.
|
|
|
|
### Planning running routes
|
|
|
|
Open **Routes** to plan loops or point-to-point runs with a target distance,
|
|
hill difficulty and surface preference. Compare route maps, elevation profiles
|
|
and quality coverage; save routes privately and export GPX. Live generation
|
|
requires a server-side `ORS_API_KEY`. See [route planner setup and limitations](docs/ROUTE_PLANNER.md).
|
|
|
|
### Bringing in more activities
|
|
|
|
Use **Settings & imports -> Connect Garmin** to upload an exported session, then
|
|
choose **Sync now** or leave hourly sync enabled. Any account can also upload an
|
|
original FIT file through Settings & imports. Operators can import local caches
|
|
with the command below.
|
|
Original files remain private and are not exposed by web download routes.
|
|
|
|
If multiple Garmin caches existed at setup, an operator can explicitly assign one:
|
|
|
|
```powershell
|
|
uv run --locked flask --app app import-local --email owner@example.com --account-key ACCOUNT_KEY
|
|
```
|
|
|
|
The application uses CSRF protection, hashed passwords, login throttling, and
|
|
server-side authorization on every activity page and data endpoint. Email
|
|
verification/recovery and shared production rate-limit storage remain outstanding.
|
|
Deployment instructions and sync behavior are in docs/DEPLOYMENT.md.
|
|
The local server binds only to loopback; production runs behind the Dokku HTTPS proxy.
|
|
|
|
Users will upload FIT files or connect Garmin by authenticating locally with
|
|
`python-garminconnect` and uploading their exported session tokens. The server
|
|
will import activities and provide profiles, charts, maps, and publishing controls.
|
|
|
|
See [IMPLEMENTATION_PLAN.md](IMPLEMENTATION_PLAN.md) for the architecture,
|
|
phased checklist, acceptance criteria, and deployment plan.
|
|
|
|
## Run the local sync
|
|
|
|
Requires [uv](https://docs.astral.sh/uv/). Python 3.12 and dependencies are selected
|
|
by the project configuration and lockfile.
|
|
|
|
```powershell
|
|
uv sync --locked
|
|
uv run --locked python -m garmin_sync login
|
|
uv run --locked python -m garmin_sync sync --limit 5
|
|
```
|
|
|
|
Run `login` yourself in an interactive terminal. It prompts for your Garmin email,
|
|
hidden password, and any MFA code. Never paste those into chat or command-line flags.
|
|
The login goes directly to Garmin; no web service is running or receiving your password.
|
|
On Windows, `scripts/connect-garmin.ps1` combines login and a five-activity sync.
|
|
|
|
After login, commands resume the saved session without your password:
|
|
|
|
```powershell
|
|
uv run --locked python -m garmin_sync verify
|
|
uv run --locked python -m garmin_sync sync --limit 20 --type running
|
|
```
|
|
|
|
These are completed activities, not planned training workouts. The default
|
|
includes all activity types so the first test can find your most recent recording.
|
|
The limit is how many recent matching activities to examine, including already
|
|
downloaded ones; it is not the number of new downloads to find.
|
|
|
|
## Files and sessions
|
|
|
|
- Session: `instance/garmin/default/garmin_tokens.json` (private, ignored by Git).
|
|
- Activities: `data/garmin/<account-key>/<activity-id>/` (private, ignored by Git).
|
|
- `original.zip` or `original.fit`: unchanged original download.
|
|
- `activity.json`: activity-list metadata returned by Garmin.
|
|
- `activity-N.fit`: FIT members, assigned safe generated filenames.
|
|
- `activity-N.json`: decoded sessions, laps, record samples, counts, and units.
|
|
- `manifest.json`: completion marker and file checksums for safe retries.
|
|
- `last-sync.json`: summary of the last completed sync command.
|
|
|
|
Decoded FIT values retain their reported units (including GPS semicircles), listed
|
|
in the JSON `units` map. Datetimes are serialized as UTC. These outputs contain full routes and private raw data. The web app only
|
|
serves activity data after ownership or accepted-friend authorization.
|
|
|
|
To load an exported token file from another machine:
|
|
|
|
```powershell
|
|
uv run --locked python -m garmin_sync --profile imported import-tokens 'C:\path\garmin_tokens.json'
|
|
uv run --locked python -m garmin_sync --profile imported sync --limit 5
|
|
```
|
|
|
|
Exports must use the pinned `garminconnect==0.3.17` session format. Old garth
|
|
`oauth1_token.json`/`oauth2_token.json` files are unsupported. The importer validates
|
|
the session with Garmin before saving it. Use `--domain garmin.cn` before the
|
|
subcommand for a China account, consistently for login/import and subsequent syncs.
|
|
|
|
Named profiles isolate local token files; provider account identity partitions
|
|
downloads. The same account should have only one actively used session copy:
|
|
avoid concurrently syncing a copied export on multiple machines/profiles.
|
|
|
|
Tokens refresh when supported by Garmin and are saved after sync. Run `login`
|
|
again when a session expires or is rejected. Tokens grant ongoing account access;
|
|
they are not app-specific, read-only permissions. This tool only invokes read
|
|
operations, but possession of its token file provides broader account access.
|
|
|
|
Local credential storage is plain JSON with restricted permissions on POSIX; on
|
|
Windows it relies on your directory ACLs. Use a private Windows user directory or
|
|
restrict this workspace's ACLs on a shared machine. Uploaded web sessions are encrypted in PostgreSQL using TOKEN_ENCRYPTION_KEY.
|
|
No credentials or real activity files should be committed to Git.
|
|
|
|
## Verification
|
|
|
|
```powershell
|
|
uv run --locked pytest -q
|
|
uv run --locked ruff check garmin_sync tests
|
|
uv run --locked ruff format --check garmin_sync tests
|
|
```
|
|
|
|
The offline tests generate a synthetic FIT file; they never authenticate to Garmin.
|
|
A live verification requires running login, syncing at least one activity, then
|
|
running sync again to confirm it skips the previously downloaded files. Record
|
|
the actual outcome in `docs/SYNC_VALIDATION.md`.
|
|
|
|
The web app supports encrypted Garmin session uploads, manual queued sync, hourly scheduled sync, and a Dokku worker. Email verification/recovery remains unimplemented.
|
|
|
|
For an isolated browser test with synthetic data, run
|
|
`uv run python scripts/preview-fixture.py` and visit port 5001. This test process
|
|
uses an in-memory database and auto-login, never reads Garmin files, and must not
|
|
be deployed. The regular app does not have auto-login or seeded test accounts.
|
|
|
|
## Server sync
|
|
|
|
Under Settings & imports, upload your exported Garmin session JSON to connect.
|
|
Use **Sync now** for manual sync; hourly sync is enabled for new connections.
|
|
Both use a separate persistent worker and import the latest 100 activities privately.
|
|
Pause hourly sync or disconnect at any time. See [deployment notes](docs/DEPLOYMENT.md).
|