Update README with full project documentation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-24 19:12:51 -07:00
parent 72b23c18aa
commit 916d638e84

156
README.md
View File

@@ -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 <token>` 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`