update readme

This commit is contained in:
SowinskiBraeden committed 2026-06-24 19:59:12 -07:00
1 parent 6da4c3e379
commit f078034789
1 file changed
+163 -107
+163 -107
View File
@@ -1,132 +1,188 @@
# Poker Portal # Boker
A small Flask app I put together for tracking our low-stakes hold'em nights without having to keep a spreadsheet open all the time. A Flask web app for tracking low-stakes poker nights. Multiple leagues, per-player stats, session history, leaderboards, and a full double-entry ledger — without keeping a spreadsheet open.
The idea is pretty simple: public pages for stats and session history, plus a small admin area for recording buy-ins, gross cashout results, actual paid-out cash, and notes. The data sits in a CSV audit log, so everything is append-only and easy to follow later. ## Features
## What it does - User accounts with registration and login
- Create and manage multiple leagues
- all-time leaderboard - Invite league members by email with role-based access (owner / manager / viewer)
- per-session pages - Public or private league visibility
- per-player stat pages - Per-session event ledger (buy-ins, cashouts, fronts, rollovers, payouts)
- admin login for recording events - All-time leaderboard with rank tracking and eligibility thresholds
- open / closed session tracking - Per-player stat pages with session history and charts
- actual cash payout tracking with `paid_out` events - Open / closed session tracking
- debt repayments and write-offs with `debt_repayment` / `writeoff` events - Debt tracking: fronts, repayments, and write-offs
- session and player charts - CSV export of any league ledger
- CSV import / export from the admin page - CSV import for migrating historical data
- append-only `entries.csv` ledger instead of overwriting old rows - Rate limiting and CSRF protection
## Stack ## Stack
Built with a pretty lightweight setup: - Python 3 / Flask
- SQLAlchemy + Flask-Migrate (SQLite for local dev, PostgreSQL in production)
- Python - Jinja2 templates
- Flask
- Jinja templates
- Chart.js - Chart.js
- plain CSS - Plain CSS
- CSV event log for storage
## Setup
### 1. Clone and create a virtual environment
```bash
git clone https://github.com/SowinskiBraeden/boker
cd boker
python -m venv .venv
source .venv/bin/activate
```
### 2. Install dependencies
```bash
pip install -r requirements.txt
```
### 3. Configure environment
```bash
cp .env.example .env
```
Open `.env` and set at minimum:
```env
SECRET_KEY=replace-this-with-a-long-random-string
```
The other values can be left as defaults for local development. See the [Environment variables](#environment-variables) section for the full list.
### 4. Initialize the database
```bash
flask --app app db upgrade
```
This creates `data/boker-dev.sqlite3` and applies all migrations. Run this again whenever you pull new migrations.
### 5. Run the development server
```bash
flask --app app run
```
Then open `http://127.0.0.1:5000` and register an account.
## Environment variables
All variables are read from `.env` at startup. Copy `.env.example` as a starting point.
| Variable | Required | Default | Description |
|---|---|---|---|
| `SECRET_KEY` | Yes | `change-this-before-deploying` | Flask session signing key. Use a long random string in production. |
| `DATABASE_URL` | No | `sqlite:///data/boker-dev.sqlite3` | SQLAlchemy connection URL. For production use a `postgresql+psycopg://` URL. |
| `FLASK_ENV` | No | _(unset)_ | Set to `production` to enable secure cookie flags. |
| `APP_BASE_URL` | No | `http://localhost:5000` | Base URL used when generating links in email (invite, password reset). |
| `MAIL_SERVER` | No | _(empty)_ | SMTP server hostname. Email features are disabled if left blank. |
| `MAIL_PORT` | No | `587` | SMTP port. |
| `MAIL_USE_TLS` | No | `true` | Set to `false` to disable STARTTLS. |
| `MAIL_USERNAME` | No | _(empty)_ | SMTP username / API key. |
| `MAIL_PASSWORD` | No | _(empty)_ | SMTP password / API key secret. |
| `MAIL_DEFAULT_SENDER` | No | `noreply@myboker.org` | From address on outgoing mail. |
| `FLASK_DEBUG` | No | `0` | Set to `1` to enable the Flask reloader and debugger. |
## Running tests
```bash
python -m pytest tests/
```
Or with the standard library runner:
```bash
python -m unittest discover tests/
```
## Production deployment
### Database
Set `DATABASE_URL` to a PostgreSQL connection string:
```env
DATABASE_URL=postgresql+psycopg://user:password@host/dbname
```
`psycopg` (v3) is already in `requirements.txt`.
After deploying, run migrations:
```bash
flask --app app db upgrade
```
### Environment
Set `FLASK_ENV=production` to enable:
- `Secure` flag on the session cookie
- `SameSite=Strict` cookie policy
- HTTPS preferred URL scheme
Generate a strong secret key:
```bash
python -c "import secrets; print(secrets.token_hex(32))"
```
### WSGI
Run with a production WSGI server (gunicorn, uWSGI, etc.) rather than the Flask dev server:
```bash
gunicorn "app:app"
```
## Ledger model ## Ledger model
The app treats `data/entries.csv` as the source of truth. Each event in the ledger is an append-only row. Nothing is edited or deleted — corrections are new rows. This keeps the full history readable.
Each row is an event, not a final snapshot. Instead of editing an old row, I append another one. That keeps rebuys, corrections, payouts, and session state changes visible in the log instead of hiding them behind edits. ### Event types
Current event types: | Event | Description |
|---|---|
| `buyin` | Player buys chips. Counts as poker investment and real cash in. |
| `front` | House fronts chips to a player. Counts as poker investment; cash is owed back. |
| `cashout` | Gross chip result at end of session. Not cash movement — sets the payout claim. |
| `paid_out` | Cash physically paid out to a player. Settles the payout claim. |
| `rollover_out` | Player carries winnings into the next session instead of being paid. Settles source session without cash movement. |
| `rollover_in` | Carried funds enter play in the destination session. Counts as investment without new cash in. |
| `payout_carry_in` | Prior-session credit applied to a later payout. Increases the payout due without counting as investment. |
| `debt_repayment` | Player repays a front outside of poker. Counts as real cash in; reduces the receivable. |
| `writeoff` | Front is forgiven. Resolves the receivable without cash. |
| `note` | Free-text bookkeeping note. No financial effect. |
| `session_open` | Marks a session as live. |
| `session_close` | Marks a session as closed. |
- `buyin` Legacy ledgers may contain `paid`, `front_collected`, or `front_writeoff` — the app reads these as aliases for `paid_out`, `debt_repayment`, and `writeoff`.
- `front`
- `debt_repayment`
- `writeoff`
- `cashout`
- `paid_out`
- `rollover_in`
- `payout_carry_in`
- `rollover_out`
- `note`
- `session_open`
- `session_close`
A few examples: ### Accounting summary
- another `buyin` for a rebuy - **Poker investment** = `buyin + front + rollover_in`
- another `cashout` if chip counts are corrected later - **Poker net** = `cashout investment`
- a `paid_out` event when someone is actually settled up - **Real cash in** = `buyin + debt_repayment`
- a `debt_repayment` event when a front is repaid outside poker - **Real cash out** = `paid_out`
- a `writeoff` event when a front will not be collected - **Rollover-out** settles a session without cash leaving the book
- a `note` event for bookkeeping context - **Writeoff** resolves a receivable without cash coming in
- `session_open` / `session_close` to mark whether a game night is still live
Accounting in the app keeps poker results separate from banker cashflow:
- poker investment is `buyin + front + rollover_in`
- poker net is `cashout - poker investment`
- real cash in is `buyin + debt_repayment`
- real cash out is `paid_out`
- `rollover_out` settles the source session without counting as cash out
- `rollover_in` enters play in the destination session without counting as cash in
- `payout_carry_in` records prior-session value carried into a later payout; it increases the destination payout due without counting as poker investment or cash in
- `writeoff` resolves a receivable without counting as cash in
Older ledgers may still contain `paid`, `front_collected`, or `front_writeoff`.
The app reads those historical names as aliases for `paid_out`,
`debt_repayment`, and `writeoff`.
It is still just a small side project, but I wanted the event model to stay clean enough that the numbers are easy to trust and the history is easy to read back through.
## CSV format ## CSV format
Main file: Each league's ledger can be exported and re-imported as CSV.
`data/entries.csv`
Header: Header:
```csv ```
id,created_at,session_id,session_date,player_name,event_type,amount_cents,note,actor id,created_at,session_id,session_date,player_name,event_type,amount_cents,note,actor
``` ```
Amounts are stored in cents to avoid floating-point issues. Amounts are stored in cents (integer) to avoid floating-point rounding.
## Running it locally Import validates headers, event types, session references, and player names before writing anything. Rows with a matching `id` are skipped so re-importing a previous export is safe.
```bash
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
python app.py
```
Then open:
`http://127.0.0.1:8000`
## Environment values
The app reads these from `.env`:
- `SECRET_KEY`
- `ADMIN_USERNAME`
- `ADMIN_PASSWORD`
Example:
```env
SECRET_KEY=change-this
ADMIN_USERNAME=admin
ADMIN_PASSWORD=change-me
```
## Notes
A few choices here were deliberate:
- no database for now
- no user accounts, just one admin login
- public-facing stats pages, admin-only controls
- CSV backup before importing a replacement ledger
If I ever decide to take it further, the first real upgrade would probably be moving the storage layer to SQLite while keeping the rest of the app roughly the same.