161 lines
7.5 KiB
Markdown
161 lines
7.5 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. Local schema creation/upgrade has been verified on SQLite; PostgreSQL
|
|
runtime validation remains pending because Docker was unavailable locally.
|
|
|
|
### 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.
|
|
- There is no Explore page, public feed, or anonymous access to activity data.
|
|
- Routes are not masked or trimmed. Access control, rather than geographic masking,
|
|
determines who can see the full route, including start and finish.
|
|
|
|
### Bringing in more activities
|
|
|
|
Run the local sync below, then choose **Settings & imports -> Load new downloads**.
|
|
This button imports the cache assigned to your account, without contacting Garmin.
|
|
Any account can also upload an original FIT file through Settings & imports.
|
|
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. Before public
|
|
deployment, finish email verification/recovery, shared production rate-limit
|
|
storage, encrypted token uploads, scheduled jobs and PostgreSQL/Dokku validation.
|
|
The current server binds only to loopback and runs without debug mode.
|
|
|
|
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. Encrypted database credential
|
|
storage belongs to the future web implementation, not this local proof of concept.
|
|
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`.
|
|
|
|
Current scope: local Flask app and sync CLI. Scheduled worker, token-upload web
|
|
endpoint, email verification/recovery, and Dokku deployment remain to be implemented.
|
|
|
|
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.
|