2026-06-24 19:59:12 -07:00
2026-06-24 19:53:19 -07:00
2026-06-23 21:38:33 -07:00
2026-06-24 19:53:19 -07:00
2026-06-24 19:08:33 -07:00
2026-06-22 14:17:10 -07:00
2026-06-24 19:53:19 -07:00
2026-06-24 19:59:12 -07:00
2026-06-23 21:38:33 -07:00

Boker

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.

Features

  • User accounts with registration and login
  • Create and manage multiple leagues
  • Invite league members by email with role-based access (owner / manager / viewer)
  • Public or private league visibility
  • Per-session event ledger (buy-ins, cashouts, fronts, rollovers, payouts)
  • All-time leaderboard with rank tracking and eligibility thresholds
  • Per-player stat pages with session history and charts
  • Open / closed session tracking
  • Debt tracking: fronts, repayments, and write-offs
  • CSV export of any league ledger
  • CSV import for migrating historical data
  • Rate limiting and CSRF protection

Stack

  • Python 3 / Flask
  • SQLAlchemy + Flask-Migrate (SQLite for local dev, PostgreSQL in production)
  • Jinja2 templates
  • Chart.js
  • Plain CSS

Setup

1. Clone and create a virtual environment

git clone https://github.com/SowinskiBraeden/boker
cd boker
python -m venv .venv
source .venv/bin/activate

2. Install dependencies

pip install -r requirements.txt

3. Configure environment

cp .env.example .env

Open .env and set at minimum:

SECRET_KEY=replace-this-with-a-long-random-string

The other values can be left as defaults for local development. See the Environment variables section for the full list.

4. Initialize the database

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

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

python -m pytest tests/

Or with the standard library runner:

python -m unittest discover tests/

Production deployment

Database

Set DATABASE_URL to a PostgreSQL connection string:

DATABASE_URL=postgresql+psycopg://user:password@host/dbname

psycopg (v3) is already in requirements.txt.

After deploying, run migrations:

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:

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:

gunicorn "app:app"

Ledger model

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.

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.

Legacy ledgers may contain paid, front_collected, or front_writeoff — the app reads these as aliases for paid_out, debt_repayment, and writeoff.

Accounting summary

  • Poker investment = buyin + front + rollover_in
  • Poker net = cashout investment
  • Real cash in = buyin + debt_repayment
  • Real cash out = paid_out
  • Rollover-out settles a session without cash leaving the book
  • Writeoff resolves a receivable without cash coming in

CSV format

Each league's ledger can be exported and re-imported as CSV.

Header:

id,created_at,session_id,session_date,player_name,event_type,amount_cents,note,actor

Amounts are stored in cents (integer) to avoid floating-point rounding.

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.

S
Description
Poker Tracking, buy ins, cash outs, player statistics over time. etc.
Readme
1.1 MiB
0 Stars 1 Watchers 0 Forks
Languages
HTML 41.4%
Python 35.1%
CSS 23.4%