From 916d638e84b622f37492ad01383bf7aa5d17bf62 Mon Sep 17 00:00:00 2001 From: derekc Date: Tue, 24 Mar 2026 19:12:51 -0700 Subject: [PATCH] Update README with full project documentation Co-Authored-By: Claude Sonnet 4.6 --- README.md | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 155 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9538adc..d979d5a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,156 @@ -# bourbonacci +# Bourbonacci +A personal bourbon tracking web app. Log pours, track your collection, and view stats across users. Built with FastAPI, vanilla JS, and MySQL — fully containerized with Docker Compose. + +## Stack + +| Layer | Technology | +|---|---| +| Backend | FastAPI (Python 3.12), async SQLAlchemy 2.0, Uvicorn | +| Database | MySQL 8 | +| Frontend | Vanilla HTML/CSS/JS (no framework) | +| Reverse Proxy | Nginx (Alpine) | +| Auth | JWT via `python-jose`, passwords hashed with `passlib[bcrypt]` | +| Container | Docker Compose | + +## Project Structure + +``` +bourbonacci/ +├── backend/ +│ ├── app/ +│ │ ├── main.py # FastAPI app entry point, lifespan (DB init) +│ │ ├── config.py # Pydantic settings +│ │ ├── database.py # Async engine, session factory, Base +│ │ ├── dependencies.py # get_db, get_current_user +│ │ ├── models/ +│ │ │ ├── user.py # User ORM model +│ │ │ └── entry.py # Entry ORM model (add/remove types) +│ │ ├── routers/ +│ │ │ ├── auth.py # POST /api/auth/register, /api/auth/login +│ │ │ ├── users.py # GET/PATCH /api/users/me +│ │ │ ├── entries.py # CRUD /api/entries, GET /api/entries/stats +│ │ │ └── public.py # GET /api/public/stats (unauthenticated) +│ │ ├── schemas/ +│ │ │ ├── user.py # Pydantic schemas for user/auth +│ │ │ └── entry.py # Pydantic schemas for entries/stats +│ │ └── utils/ +│ │ └── security.py # JWT creation, password hashing/verification +│ ├── Dockerfile +│ └── requirements.txt +├── frontend/ +│ ├── index.html # Landing / public stats page +│ ├── login.html +│ ├── register.html +│ ├── dashboard.html # Authenticated home with stats +│ ├── log.html # Log a new entry +│ ├── profile.html # User profile / settings +│ ├── css/style.css +│ └── js/ +│ ├── api.js # Fetch wrapper, base URL, token handling +│ └── auth.js # Login/register/logout logic +├── nginx/ +│ └── default.conf # Serves frontend, proxies /api/ to backend:8000 +├── docker-compose.yml +└── .env.example +``` + +## API Endpoints + +### Auth +| Method | Path | Auth | Description | +|---|---|---|---| +| POST | `/api/auth/register` | No | Register, returns JWT | +| POST | `/api/auth/login` | No | Login, returns JWT | + +### Users +| Method | Path | Auth | Description | +|---|---|---|---| +| GET | `/api/users/me` | Yes | Get current user profile | +| PATCH | `/api/users/me` | Yes | Update display name / timezone | + +### Entries +| Method | Path | Auth | Description | +|---|---|---|---| +| GET | `/api/entries` | Yes | List all entries for current user | +| POST | `/api/entries` | Yes | Create an entry (`add` or `remove`) | +| DELETE | `/api/entries/{id}` | Yes | Delete an entry | +| GET | `/api/entries/stats` | Yes | Aggregated stats for current user | + +### Public +| Method | Path | Auth | Description | +|---|---|---|---| +| GET | `/api/public/stats` | No | Aggregated stats for all users | + +Authenticated routes expect `Authorization: Bearer ` header. + +### Entry Schema + +```json +{ + "entry_type": "add" | "remove", + "date": "YYYY-MM-DD", + "bourbon_name": "string (required for add)", + "proof": 90.0, + "amount_shots": 1.0, + "notes": "optional string" +} +``` + +Stats return `current_total_shots` (adds minus removes) and `estimated_proof` (weighted average across add entries). + +## Getting Started + +### Prerequisites + +- Docker + Docker Compose + +### Setup + +```bash +cp .env.example .env +# Edit .env — set real passwords and a secure SECRET_KEY +``` + +Generate a secure secret key: +```bash +python3 -c "import secrets; print(secrets.token_hex(32))" +``` + +### Run + +```bash +docker compose up -d +``` + +The app will be available at `http://localhost:8057`. + +The backend waits for MySQL to pass its healthcheck before starting. Tables are created automatically on first boot via SQLAlchemy's `init_db()`. + +### Stop + +```bash +docker compose down +# To also remove the database volume: +docker compose down -v +``` + +## Environment Variables + +| Variable | Description | Default | +|---|---|---| +| `MYSQL_ROOT_PASSWORD` | MySQL root password | — | +| `MYSQL_DATABASE` | Database name | `bourbonacci` | +| `MYSQL_USER` | App DB user | `bourbonacci` | +| `MYSQL_PASSWORD` | App DB password | — | +| `DATABASE_URL` | SQLAlchemy async DSN | `mysql+aiomysql://...` | +| `SECRET_KEY` | JWT signing secret (keep long & random) | — | +| `ACCESS_TOKEN_EXPIRE_MINUTES` | JWT TTL in minutes | `480` | + +## Data Model + +**users** +- `id`, `email` (unique), `password_hash`, `display_name`, `timezone`, `created_at` + +**entries** +- `id`, `user_id` (FK), `entry_type` (`add`/`remove`), `date`, `bourbon_name`, `proof`, `amount_shots`, `notes`, `created_at`