docs: replace implementation plans with architecture and API reference docs

Remove phase implementation plans, design notes, and source spreadsheet
that are no longer needed. Add architecture.md, API_REFERENCE.md, and
database_schema.md for ongoing development and debugging reference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 14:19:49 -06:00
parent fd979aa2da
commit 53e62f694f
7 changed files with 706 additions and 159 deletions

197
docs/architecture.md Normal file
View File

@@ -0,0 +1,197 @@
# Architecture
## Overview
SneakySwole is a workout tracking and programming app built with FastAPI, HTMX, and SQLite. It follows a layered architecture with strict separation of concerns: routes handle HTTP, services handle business logic and data access, and models define the schema.
All user interactions return HTML (full pages or HTMX partials) — there are no JSON APIs except the health check.
---
## Technology Stack
| Layer | Technology | Notes |
|-------|-----------|-------|
| Web framework | FastAPI + Uvicorn | Async, Python 3.12 |
| Frontend | Jinja2 + HTMX + Pico CSS | Dark theme, no JS framework |
| Database | SQLite3 + SQLModel ORM | Single file at `data/sneakyswole.db` |
| Migrations | Alembic | Schema versioning, auto-generated DDL |
| Auth | bcrypt + itsdangerous | Hashed passwords, signed session cookies |
| Logging | structlog | Structured JSON logging |
| Config | pydantic-settings | Typed `.env` loader with validation |
| Container | Docker + docker-compose | Single service, port 8000, named volume |
| Testing | pytest | 30+ test modules |
| Dependencies | uv + requirements.txt | Pinned versions |
---
## Application Layers
### 1. Routes (`app/routes/`)
HTTP handlers that parse requests, call services, and return rendered templates. Each file covers one domain.
| File | Prefix | Purpose |
|------|--------|---------|
| `auth.py` | `/login`, `/logout` | Login form, credential verification, session cookies |
| `pages.py` | `/` | Home page |
| `profiles.py` | `/profiles` | Profile list, switch, edit |
| `workouts.py` | `/workouts` | Workout day list and detail viewer |
| `exercises.py` | `/exercises` | Exercise browser with HTMX search |
| `logging.py` | `/log` | Inline set logging (create/edit/delete) |
| `history.py` | `/history` | Past session list and detail |
| `dashboard.py` | `/dashboard` | Progress stats, volume charts, per-exercise progress |
| `schedule.py` | `/schedule` | 4-week calendar view |
| `health.py` | `/health` | JSON health check |
**Rules:**
- Routes never query the database directly — all data access goes through services
- All routes (except `/login`, `/logout`, `/health`) require admin authentication via `@Depends(get_current_admin_user)`
- Routes return `TemplateResponse` (HTML), not JSON
### 2. Services (`app/services/`)
Business logic and all database access. Each service is instantiated with a SQLModel `Session`.
| Service | File | Responsibility |
|---------|------|---------------|
| `AuthService` | `auth_service.py` | bcrypt password verification, session token creation/validation (itsdangerous) |
| `UserService` | `user_service.py` | User CRUD, list profiles, update stats |
| `ExerciseService` | `exercise_service.py` | Exercise queries (by day, muscle group), warmup listing, workout day listing |
| `LogService` | `log_service.py` | Workout log CRUD (create/read/update/delete sets) |
| `WorkoutSessionService` | `workout_session_service.py` | Session management (get_or_create, list, lookup) |
| `ProgressionService` | `progression_service.py` | Auto-progression suggestions (+reps/+weight/deload) |
| `AnalyticsService` | `analytics_service.py` | User stats, volume by day, per-exercise progress data |
| `SeedService` | `seed_service.py` | YAML-driven database initialization from `config/` files |
### 3. Models (`app/models/`)
SQLModel ORM classes defining 8 database tables. See `docs/database_schema.md` for full details.
### 4. Utils (`app/utils/`)
Shared utilities — currently just auth dependencies:
- **`auth.py`** — `get_current_admin_user()` (FastAPI dependency), `get_active_profile_id()`, `NotAuthenticatedError`, `SESSION_COOKIE_NAME`
### 5. Templates (`app/templates/`)
Jinja2 templates split into full pages and reusable HTMX partials.
- **`base.html`** — Master layout with Pico CSS dark theme, HTMX script, nav bar
- **`pages/`** — 12 full-page templates (login, home, dashboard, workout_day, etc.)
- **`partials/`** — 13 HTMX fragment templates (exercise_card, log_form, nav, stats_card, etc.)
### 6. Configuration
- **`app/config.py`** — Typed `Settings` class (pydantic-settings), singleton via `@lru_cache`
- **`.env`** — Runtime secrets (gitignored), referenced by docker-compose
- **`.env.example`** — Template documenting required variables
- **`config/exercises.yaml`** — Exercise library (name, muscle group, sets, tempo, form cues)
- **`config/user_programs.yaml`** — Per-user programming targets (week 1/4 reps and weights)
---
## Key Patterns
### Authentication Flow
1. Admin submits credentials via `POST /login`
2. `AuthService.authenticate()` verifies bcrypt hash
3. On success, `AuthService.create_session_token()` creates a signed token (itsdangerous)
4. Token stored in httponly cookie (`session`), samesite=lax, 24h TTL
5. `get_current_admin_user()` dependency validates token on every protected route
6. Invalid/missing token raises `NotAuthenticatedError` → 302 redirect to `/login`
### Profile Switching
- Admin selects active profile via `POST /profiles/switch`
- Profile ID stored in separate httponly cookie (`active_profile_id`)
- `get_active_profile_id()` extracts it from the request
- Workout logging happens under the active profile
### NavContextMiddleware
Starlette middleware (`app/main.py:NavContextMiddleware`) runs on every request:
1. Reads session cookie and validates token
2. If valid admin: loads `admin`, `profiles` list, and `active_profile` into `request.state`
3. Templates read from `request.state` to render the nav bar (profile switcher, etc.)
### HTMX Partial Pattern
All dynamic updates use HTMX with HTML fragment responses:
- Filter dropdowns trigger `hx-get` to `/exercises/search` → returns `partials/exercise_list.html`
- Log form submits `hx-post` to `/log` → returns `partials/log_entry.html`
- No JSON APIs, no fetch calls, no vanilla JS
### Database Startup
1. `create_app()` creates SQLModel engine and calls `SQLModel.metadata.create_all()`
2. `@app.on_event("startup")` triggers `SeedService.seed_all()`
3. Seed service reads `config/exercises.yaml` + `config/user_programs.yaml`
4. Inserts exercises, warmups, workout days, user profiles, and programming targets
5. Creates admin user from `.env` credentials (bcrypt-hashed)
6. Skips seeding if data already exists
---
## Request Lifecycle
```
Client Request
NavContextMiddleware (injects admin/profiles/active_profile into request.state)
FastAPI Router (matches route)
Auth Dependency (get_current_admin_user — validates session cookie)
Route Handler (parses request, calls service(s))
Service Layer (business logic, DB queries via SQLModel Session)
Jinja2 TemplateResponse (renders full page or HTMX partial)
Client Response (HTML)
```
---
## Docker Setup
- **`Dockerfile`** — Slim Python 3.12 base, copies app + config, installs deps, runs Uvicorn
- **`docker-compose.yaml`** — Single `app` service, port 8000, named volume for `data/`, `.env` file
- Static files and templates are baked into the image
- SQLite DB persists via Docker volume mount at `data/`
---
## Directory Structure
```
SneakySwole/
├── app/
│ ├── __init__.py
│ ├── config.py # Typed settings (pydantic-settings)
│ ├── database.py # Engine factory + session dependency
│ ├── logging_config.py # structlog setup
│ ├── main.py # App factory, middleware, router registration
│ ├── models/ # SQLModel ORM (8 tables)
│ ├── routes/ # FastAPI handlers (10 modules)
│ ├── services/ # Business logic + DB access (8 services)
│ ├── static/css/ # Pico CSS overrides
│ ├── templates/ # Jinja2 (pages/ + partials/)
│ └── utils/ # Auth dependencies
├── config/ # YAML seed data
│ ├── exercises.yaml
│ └── user_programs.yaml
├── alembic/ # Database migrations
├── tests/ # pytest test suite
├── data/ # SQLite DB (gitignored, Docker volume)
├── docs/ # Documentation
├── Dockerfile
├── docker-compose.yaml
├── pyproject.toml
├── requirements.txt
└── .env.example
```