Files
bourbonacci/README.md
2026-03-24 19:38:04 -07:00

5.0 KiB

🥃 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

{
  "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

cp .env.example .env
# Edit .env — set real passwords and a secure SECRET_KEY

Generate a secure secret key:

python3 -c "import secrets; print(secrets.token_hex(32))"

Run

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

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