Examples
Getting started
First, clone the project:
git clone https://github.com/TheNewThinkTank/fitness-tracker.git
Run the commands below from the repository root. Use Python 3.11 or 3.13, Poetry 2.2.1, and Node.js 24 or newer for the frontend.
Import workout logs
Place one workout per archive file under the configured FITNESS_TRACKER_DATA_DIR
(the default is data/). For example:
The file can contain:
date: '2024-01-07'
start_time: '09:00'
end_time: '10:00'
timezone: CET
split: push
exercises: {bench_press: [{set_number: 1, reps: 8, weight: '40 kg'}]}
Dates must be real calendar dates in YYYY-MM-DD format and match the requested
archive date. Each exercise set requires integer set_number and reps fields
and a string weight. Additional workout and set metadata is preserved. Omit
id for a new workout; configured storage assigns a permanent UUID when writing.
Import the log with:
This writes to data/2024_workouts.yml, not the current calendar year's file.
To import across years, first prepare both archive files, including
data/log_archive/YML/2022/February/training_log_2022-02-08.yml, then run:
poetry run python -m src.crud.insert --datatype real --dates 2022-02-08,2024-01-07 --file_format yml
The importer selects a separate database for each date's year. For a second
workout on the same day, use a filename such as training_log_2024-01-07_2.yml:
JSON logs use the same record structure under log_archive/JSON/<YEAR>/<Month>/
with a .json extension and --file_format json. Imports append records;
repeating an import can create another workout. Use a separate data directory
when experimenting with the examples.
Migrate existing workout IDs
The bundled year archives include permanent IDs while retaining their existing
API URLs. For an external legacy data directory, back up its
files and stop other writers before migrating. Set FITNESS_TRACKER_DATA_DIR
to that directory, then preview and apply the migration:
poetry run python -m src.utils.set_db_and_table --migrate-ids --dry-run
poetry run python -m src.utils.set_db_and_table --migrate-ids
Limit the operation to one year when needed:
Use --datatype simulated to migrate the configured simulated database. The
migration preserves existing fields and legacy URLs; running it again makes no
changes once IDs are present. Preserve these IDs when moving or editing records.
Unmigrated files remain readable through the legacy ID fallback, but migrate them before editing or renumbering their TinyDB documents. API reads never backfill files, and Compose mounts workout data read-only. Run migrations from a writable environment with a single writer: atomic YAML replacement is not a multi-process transaction mechanism.
Running locally
The bundled data/ directory works without environment variables. Copy the
template only when using another data directory or an API token:
With Docker Compose
- Backend API:
http://localhost:8000 - API docs:
http://localhost:8000/docs - Frontend:
http://localhost:3000
Without containers
# Start the backend
FITNESS_TRACKER_DATA_DIR=data poetry run uvicorn src.main:app --reload
# Start the frontend (separate terminal)
npm --prefix frontend ci
npm --prefix frontend run dev
The development frontend is available at http://localhost:5173 and proxies API
requests to port 8000.
Log workouts through the frontend
The default server is read-only. Follow the frontend setup guide to provision an athlete password and enable writes, then sign in and select Log workout in the header. The editor supports multiple exercises and sets, local dates, overnight time windows, and optional workout metadata.
Saving opens the new workout in Archive and selects its year. Browser-created
workouts live in SQLite state, not in the YAML archive files used by the CLI
examples above. Preserve the Compose state volume when stopping or upgrading
the application; do not use docker compose down --volumes on personal state.
Testing endpoints locally
Overview of endpoints:
- root
- years
- workouts
- Workout detail:
GET /workouts/{workout_id}, using an item'sidfrom/workouts - healthz
- readyz
The list endpoint defaults to the latest available year. A missing year returns
404 without creating a file. Select individual workouts by UUID rather than
date, since two workouts can share a date. When an API token is configured,
direct backend requests need X-API-Key; the same-origin frontend proxy adds
the token server-side.
Browser workflow checks
Install frontend dependencies and Chromium, then run the checks:
npm --prefix frontend ci
npm exec --prefix frontend -- playwright install chromium
npm --prefix frontend run check
npm --prefix frontend test
npm --prefix frontend run test:e2e
Playwright builds the frontend and starts an isolated preview on port 4173; set
E2E_PORT to choose another port. Deterministic API fixtures exercise year
selection, pagination, ordering, duplicate-day details, and error/retry behavior
on desktop and mobile viewports. They also cover progress totals, chart rendering,
permission gating, workout metadata, local dates, input validation, draft retries,
and repeat-submission prevention. Unmocked read-only integration tests are skipped
unless E2E_BASE_URL points to a running application.
For a running Compose stack:
For a local Vite proxy, use its URL instead (normally http://127.0.0.1:5173).
The container CI job is configured to run the browser suite against the built
Compose services and retain failure screenshots and traces in the
browser-test-results artifact. See the
verification status for the
dated native and production-container results.
Authenticated insertion and editing
Use a separate disposable Compose project and state volume, or separate local
data/state directories. Provision only a fixture password in that state and
start its backend with FITNESS_TRACKER_ENABLE_WRITES=true. Do not reuse a
personal password or run these workflows against personal SQLite state.
For an isolated test stack on port 3000:
E2E_BASE_URL=http://127.0.0.1:3000 E2E_WRITE_TESTS=1 npm --prefix frontend run test:e2e -- --grep authenticated
The default fixture password is test-only-browser-password; E2E_PASSWORD
can override it for a different test fixture. The insertion regression checks
the actual POST through the proxy, session/Origin/CSRF protections, optional
metadata, literal note rendering, detail after reload, and exact volume and
overnight-duration totals. It deletes its own inserted workout afterward.
The other authenticated workflow also changes imports, measurements, and program
targets, so treat the entire test volume as disposable.
Against a running deployment, the two preview-only injected-CSP cases are intentionally skipped. The production Nginx response headers must be checked separately; a preview test alone does not establish deployment security.
Example: insert breath holding data in TinyDB
Example: Import workout logs with Oxygen
poetry install --with analytics
poetry run oxygen run -d 2024-03-03 -f json --dry-run
poetry run oxygen run -d 2024-03-03 -f json --no-open
poetry run oxygen insert -d 2024-03-03,2024-03-04 -f json
poetry run oxygen prepare-figures -y 2024 -m March
Oxygen uses the existing archive layout and project environment settings, but validates selected files before writing and assigns stable IDs to new file imports. Repeated dates are deduplicated; separate same-day files remain separate workouts. Image opening is opt-in. The shell workflow remains available for compatibility. See the Oxygen guide for configuration, safe repeat imports, SQLite-aware plots, and cross-platform opening.