Skip to content

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.

poetry install --with analytics,integrations,docs

Import workout logs

Place one workout per archive file under the configured FITNESS_TRACKER_DATA_DIR (the default is data/). For example:

data/log_archive/YML/2024/January/training_log_2024-01-07.yml

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:

poetry run python -m src.crud.insert --datatype real --dates 2024-01-07 --file_format yml

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:

poetry run python -m src.crud.insert --datatype real --dates 2024-01-07 --workout_number 2

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:

poetry run python -m src.utils.set_db_and_table --migrate-ids --year 2024

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:

cp .env.example .env

With Docker Compose

docker compose up --build
  • 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:

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:

E2E_BASE_URL=http://127.0.0.1:3000 npm --prefix frontend run test:e2e

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

python3 src/utils/get_breath_holding.py

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.