f1tness_parser/CLAUDE.md

6.9 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

F1tness Parser is a FastAPI-based web application that parses fitness training data from two note formats:

  • Obsidian Notes: Markdown tables with # DD.MM.YYYY (trainer-session) headers
  • Apple Notes: Bold markdown tables with **DD.MM.YYYY (trainer-session)** headers

The project is split into two applications in one repository:

  • backend/ - FastAPI JSON API (parsers, models, services, PostgreSQL)
  • frontend/ - static site on plain HTML + CSS + JS (ES modules, no build step), served by nginx, which also proxies /api/ to the backend

Development Commands

The project is developed and run only in Docker: there is no local Python setup. Everything below runs inside the fastapi-app container (backend code lives in backend/, inside the container it is /app) (.env must have POSTGRES_DATABASE_HOST=postgres, see .env.example).

Docker Development

# Start backend (:8000) + frontend (:8080) + PostgreSQL (applies migrations on start, hot reload for backend/app/)
# Open http://localhost:8080
docker compose up --build

# Apply migrations manually (Alembic; the app itself does NOT migrate on startup)
docker compose exec fastapi-app alembic upgrade head

# Create a migration after changing tables in app/core/database/tables.py
# (-u keeps the generated file owned by you instead of root)
docker compose exec -u "$(id -u):$(id -g)" fastapi-app alembic revision --autogenerate -m "describe change"

# Import all Obsidian + Apple notes into the database
docker compose exec fastapi-app python -m app.cli import [--replace]

# Stop (keeps the data) / stop and drop the database volume
docker compose down
docker compose down -v

Rebuild (--build) is needed only after changing requirements*.txt, Dockerfile or pyproject.toml.

The backend Dockerfile has two targets: dev (default in compose: pytest, mypy, tests, notes) and prod (runtime dependencies and app code only). Production images are built with docker build --target prod backend; the prod image has no data/, so the dev-only GET /api/v1/trainings/obsidian/ doesn't work there.

Testing

Tests that need a database use a separate <POSTGRES_DATABASE_DB>_test database (TEST_DATABASE_URL, created automatically). Without it they are skipped.

# Run all tests
docker compose exec fastapi-app pytest

# Run specific test file
docker compose exec fastapi-app pytest tests/test_core/test_obsidian_parser.py

# Without a running stack (starts postgres, runs once, removes the container)
docker compose run --rm fastapi-app pytest

Type Checking

docker compose exec fastapi-app mypy app/

Architecture

Core Components

Parser Architecture: The system uses a base parser class (backend/app/core/parsers/base.py) with format-specific implementations:

  • ObsidianNotesParser - handles Obsidian markdown format
  • AppleNotesParser - handles Apple notes format
  • Each parser includes exercise name mapping for normalization

Data Models: Located in backend/app/core/dto/training.py:

  • Training - represents a workout session
  • Exercise - individual exercise with multiple approaches
  • Approach - single set with weight and reps

API Structure (backend, JSON only):

  • /api/v1/auth/ (login, refresh, logout, me), /api/v1/trainings/ (GET list, GET/PUT/DELETE {id}/; PUT replaces the whole training), /api/v1/exercises/, /api/v1/exercises/progress/?name=, /api/v1/users/ (POST, GET list with ?role=, GET/PATCH/DELETE {id}/; role is trainer or trainee, passwords are hashed with scrypt, no login yet), /api/v1/parser/parse/, /api/v1/health/

Frontend (frontend/):

  • index.html, exercises.html, parse.html, users.html, login.html - pages; js/auth.js (tokens, refresh, logout) and js/page.js (every page imports it first: requires a login, fills the sidebar, exports user); js/common.js (API + DOM helpers), js/table.js (DataTables 3 wrapper), one module per page, js/modal.js (Bootstrap modal shell), js/edit-dialog.js + js/edit-form.js (training edit modal), js/user-dialog.js (user create/edit modal)
  • vendor/ - Bootstrap (CSS + JS bundle), Chart.js and DataTables 3 (jQuery-free): the only third-party code
  • nginx.conf - serves static files and proxies /api/ to fastapi-app:8000 (same origin, no CORS)

Authentication

Access token: JWT (HS256, 15 min, JWT_SECRET in .env), sent as Authorization: Bearer. Refresh token: random opaque string, 30 days, only its hash is stored (refresh_tokens); it works once (rotation), reusing a used one revokes the whole chain. POST /auth/login/ with client: "web" puts the refresh token only into an httpOnly SameSite=Strict cookie (path /api/v1/auth, Secure when COOKIE_SECURE=true, set it in production with HTTPS); client: "native" (iOS) returns it in the body for the Keychain. The web frontend keeps the access token in sessionStorage.

There is no registration: only a superuser creates users (/api/v1/users/). The first login: docker compose exec fastapi-app python -m app.cli set-password EMAIL [--superuser]. Access: a superuser sees and changes everything; a trainee sees and changes their trainings; a trainer only sees the trainings where they are the trainer. Exercise statistics are about the user's own trainings (?trainee_id= narrows to one trainee).

Database

Trainings belong to a trainee (trainings.trainee_id, required) and optionally have a trainer (trainings.trainer_id, NULL = solo); both are users (role trainee / trainer). A user with trainings can't be deleted. python -m app.cli import takes the trainer from the note header and matches it with a trainer user by "First Last".

The backend uses PostgreSQL through async SQLAlchemy (asyncpg). tables are described with SQLAlchemy Core (no ORM) in backend/app/core/database/tables.py, TrainingRepository (backend/app/core/database/repository.py) is the data access layer, and a session is injected into endpoints with Depends(get_session). Schema migrations are handled by Alembic (alembic.ini, migrations/); the URL is taken from app/config.py.

Configuration is managed through backend/app/config.py using Pydantic settings with .env file support.

Key Files

  • backend/app/main.py - FastAPI application setup with lifespan management
  • backend/app/config.py - Application configuration and database settings
  • backend/run.py - Standalone uvicorn runner (unused: Docker starts uvicorn from compose.yaml)
  • backend/migrations/ - Alembic environment and versions
  • compose.yaml - Docker Compose setup: backend, frontend, PostgreSQL

Environment Setup

Copy .env.example to .env (repo root) and configure:

  • PostgreSQL connection settings
  • Debug mode
  • PYTHONPATH (for Docker)

The application will run database migrations automatically on startup through the FastAPI lifespan event.