Move application code into package

This commit is contained in:
SowinskiBraeden committed 2026-06-28 17:41:11 -07:00
1 parent 83c9fec489
commit e376b3628a
25 files changed
+285 -269

No files matched your search

+8
View File
@@ -23,6 +23,14 @@ The current app is account-based and league-based. League owners/managers record
- Chart.js - Chart.js
- plain CSS - plain CSS
## Project Layout
- `app.py` is the deployment entrypoint for `flask --app app` and Gunicorn.
- `boker/` contains the Flask application package, routes, models, services, repositories, and config.
- `templates/` and `static/` contain Jinja views and public assets.
- `migrations/` contains Alembic migrations for database deploys.
- `tests/` contains the committed regression suite.
## Ledger Model ## Ledger Model
Each ledger row is an event, not a final snapshot. Corrections are made by appending or voiding events, so the history remains auditable. Each ledger row is an event, not a final snapshot. Corrections are made by appending or voiding events, so the history remains auditable.
+2 -117
View File
@@ -1,125 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations from __future__ import annotations
import click from boker.app import app, create_app
import os
from flask import Flask, render_template
from auth import current_user_id, is_logged_in, is_site_admin, normalize_email __all__ = ["app", "create_app"]
from config import DEFAULT_SECRET_KEY, Config, ProductionConfig
from db import database_extensions_available, db, init_database
from extensions import csrf, limiter, mail
from routes.account import account_bp
from routes.internal import internal_bp
from routes.leagues import leagues_bp
from routes.public import public_bp
from storage import ensure_data_file
from utils import cents_to_dollars, safe_date_label
def _config_for_environment():
app_env = os.getenv("APP_ENV", os.getenv("FLASK_ENV", "")).lower()
if app_env in {"prod", "production"}:
return ProductionConfig
return Config
def create_app(config_overrides: dict | None = None) -> Flask:
app = Flask(__name__)
app.config.from_object(_config_for_environment())
if config_overrides:
app.config.update(config_overrides)
if app.config["SESSION_COOKIE_SECURE"] and app.config["SECRET_KEY"] == DEFAULT_SECRET_KEY:
raise RuntimeError("Set SECRET_KEY before running in production.")
ensure_data_file(app.config["DATA_PATH"])
init_database(app)
csrf.init_app(app)
limiter.init_app(app)
mail.init_app(app)
app.jinja_env.filters["money"] = cents_to_dollars
app.jinja_env.filters["pretty_date"] = safe_date_label
@app.context_processor
def inject_globals() -> dict:
return {
"app_version": app.config["APP_VERSION"],
"current_user_id": current_user_id(),
"is_logged_in": is_logged_in(),
"current_user_is_site_admin": is_site_admin(),
}
app.register_blueprint(public_bp)
app.register_blueprint(account_bp)
app.register_blueprint(internal_bp)
app.register_blueprint(leagues_bp)
@app.errorhandler(404)
def not_found(e):
return render_template("404.html"), 404
@app.errorhandler(403)
def forbidden(e):
return render_template("403.html"), 403
@app.cli.command("init-db")
def init_db_command() -> None:
if not database_extensions_available() or db is None:
raise click.ClickException(
"Database dependencies are not installed. Run pip install -r requirements.txt."
)
with app.app_context():
db.create_all()
click.echo("Initialized database tables.")
@app.cli.command("grant-site-admin")
@click.argument("email")
def grant_site_admin_command(email: str) -> None:
if not database_extensions_available() or db is None:
raise click.ClickException(
"Database dependencies are not installed. Run pip install -r requirements.txt."
)
from db_models import User
normalized_email = normalize_email(email)
with app.app_context():
user = User.query.filter_by(email=normalized_email, disabled_at=None).one_or_none()
if user is None:
raise click.ClickException(f"No active user found for {normalized_email}.")
user.is_site_admin = True
db.session.commit()
click.echo(f"Granted site admin access to {normalized_email}.")
@app.cli.command("revoke-site-admin")
@click.argument("email")
def revoke_site_admin_command(email: str) -> None:
if not database_extensions_available() or db is None:
raise click.ClickException(
"Database dependencies are not installed. Run pip install -r requirements.txt."
)
from db_models import User
normalized_email = normalize_email(email)
with app.app_context():
user = User.query.filter_by(email=normalized_email).one_or_none()
if user is None:
raise click.ClickException(f"No user found for {normalized_email}.")
user.is_site_admin = False
db.session.commit()
click.echo(f"Revoked site admin access from {normalized_email}.")
return app
app = create_app()
if __name__ == "__main__": if __name__ == "__main__":
app.run(debug=True) app.run(debug=True)
+1
View File
@@ -0,0 +1 @@
"""Application package for myboker.org."""
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env python3
from __future__ import annotations
import click
import os
from flask import Flask, render_template
from boker.auth import current_user_id, is_logged_in, is_site_admin, normalize_email
from boker.config import DEFAULT_SECRET_KEY, Config, ProductionConfig
from boker.db import database_extensions_available, db, init_database
from boker.extensions import csrf, limiter, mail
from boker.routes.account import account_bp
from boker.routes.internal import internal_bp
from boker.routes.leagues import leagues_bp
from boker.routes.public import public_bp
from boker.storage import ensure_data_file
from boker.utils import cents_to_dollars, safe_date_label
def _config_for_environment():
app_env = os.getenv("APP_ENV", os.getenv("FLASK_ENV", "")).lower()
if app_env in {"prod", "production"}:
return ProductionConfig
return Config
def create_app(config_overrides: dict | None = None) -> Flask:
app = Flask(__name__, template_folder="../templates", static_folder="../static")
app.config.from_object(_config_for_environment())
if config_overrides:
app.config.update(config_overrides)
if app.config["SESSION_COOKIE_SECURE"] and app.config["SECRET_KEY"] == DEFAULT_SECRET_KEY:
raise RuntimeError("Set SECRET_KEY before running in production.")
ensure_data_file(app.config["DATA_PATH"])
init_database(app)
csrf.init_app(app)
limiter.init_app(app)
mail.init_app(app)
app.jinja_env.filters["money"] = cents_to_dollars
app.jinja_env.filters["pretty_date"] = safe_date_label
@app.context_processor
def inject_globals() -> dict:
return {
"app_version": app.config["APP_VERSION"],
"current_user_id": current_user_id(),
"is_logged_in": is_logged_in(),
"current_user_is_site_admin": is_site_admin(),
}
app.register_blueprint(public_bp)
app.register_blueprint(account_bp)
app.register_blueprint(internal_bp)
app.register_blueprint(leagues_bp)
@app.errorhandler(404)
def not_found(e):
return render_template("404.html"), 404
@app.errorhandler(403)
def forbidden(e):
return render_template("403.html"), 403
@app.cli.command("init-db")
def init_db_command() -> None:
if not database_extensions_available() or db is None:
raise click.ClickException(
"Database dependencies are not installed. Run pip install -r requirements.txt."
)
with app.app_context():
db.create_all()
click.echo("Initialized database tables.")
@app.cli.command("grant-site-admin")
@click.argument("email")
def grant_site_admin_command(email: str) -> None:
if not database_extensions_available() or db is None:
raise click.ClickException(
"Database dependencies are not installed. Run pip install -r requirements.txt."
)
from boker.db_models import User
normalized_email = normalize_email(email)
with app.app_context():
user = User.query.filter_by(email=normalized_email, disabled_at=None).one_or_none()
if user is None:
raise click.ClickException(f"No active user found for {normalized_email}.")
user.is_site_admin = True
db.session.commit()
click.echo(f"Granted site admin access to {normalized_email}.")
@app.cli.command("revoke-site-admin")
@click.argument("email")
def revoke_site_admin_command(email: str) -> None:
if not database_extensions_available() or db is None:
raise click.ClickException(
"Database dependencies are not installed. Run pip install -r requirements.txt."
)
from boker.db_models import User
normalized_email = normalize_email(email)
with app.app_context():
user = User.query.filter_by(email=normalized_email).one_or_none()
if user is None:
raise click.ClickException(f"No user found for {normalized_email}.")
user.is_site_admin = False
db.session.commit()
click.echo(f"Revoked site admin access from {normalized_email}.")
return app
app = create_app()
+2 -2
View File
@@ -72,12 +72,12 @@ def is_site_admin() -> bool:
if not user_id: if not user_id:
return False return False
from db import database_extensions_available from boker.db import database_extensions_available
if not database_extensions_available(): if not database_extensions_available():
return False return False
from db_models import User from boker.db_models import User
user = User.query.filter_by(id=user_id, disabled_at=None).one_or_none() user = User.query.filter_by(id=user_id, disabled_at=None).one_or_none()
return bool(user and user.is_site_admin) return bool(user and user.is_site_admin)
+2 -2
View File
@@ -4,8 +4,8 @@ from __future__ import annotations
from typing import Any from typing import Any
from models import SessionEntry, SessionSummary from boker.models import SessionEntry, SessionSummary
from utils import net_result_bucket, session_chart_label, session_sort_key from boker.utils import net_result_bucket, session_chart_label, session_sort_key
PLAYER_PALETTE = [ PLAYER_PALETTE = [
"#9b8cf0", # --line-1 violet "#9b8cf0", # --line-1 violet
+1 -1
View File
@@ -4,7 +4,7 @@ from __future__ import annotations
import os import os
from pathlib import Path from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent BASE_DIR = Path(__file__).resolve().parent.parent
DATA_PATH = BASE_DIR / "data" / "entries.csv" DATA_PATH = BASE_DIR / "data" / "entries.csv"
DEFAULT_DATABASE_URL = f"sqlite:///{BASE_DIR / 'data' / 'boker-dev.sqlite3'}" DEFAULT_DATABASE_URL = f"sqlite:///{BASE_DIR / 'data' / 'boker-dev.sqlite3'}"
DEFAULT_SECRET_KEY = "change-this-before-deploying" DEFAULT_SECRET_KEY = "change-this-before-deploying"
+1 -1
View File
@@ -24,7 +24,7 @@ def init_database(app: Any) -> bool:
app.config["DATABASE_EXTENSIONS_AVAILABLE"] = False app.config["DATABASE_EXTENSIONS_AVAILABLE"] = False
return False return False
import db_models # noqa: F401 - registers SQLAlchemy models with metadata. import boker.db_models # noqa: F401 - registers SQLAlchemy models with metadata.
db.init_app(app) db.init_app(app)
migrate.init_app(app, db) migrate.init_app(app, db)
+1 -1
View File
@@ -4,7 +4,7 @@ from __future__ import annotations
import uuid import uuid
from datetime import date, datetime, timezone from datetime import date, datetime, timezone
from db import db from boker.db import db
if db is None: # pragma: no cover - imported only when DB dependencies exist. if db is None: # pragma: no cover - imported only when DB dependencies exist.
raise RuntimeError("Database dependencies are not installed.") raise RuntimeError("Database dependencies are not installed.")
+1 -1
View File
@@ -7,7 +7,7 @@ import socket
from flask import current_app from flask import current_app
from flask_mail import Message from flask_mail import Message
from extensions import mail from boker.extensions import mail
def _mail_send_suppressed() -> bool: def _mail_send_suppressed() -> bool:
File renamed without changes.
@@ -5,13 +5,13 @@ import secrets
import string import string
from datetime import date from datetime import date
from auth import hash_password, normalize_email from boker.auth import hash_password, normalize_email
from db import db from boker.db import db
if db is None: # pragma: no cover - imported only when DB dependencies exist. if db is None: # pragma: no cover - imported only when DB dependencies exist.
raise RuntimeError("Database dependencies are not installed.") raise RuntimeError("Database dependencies are not installed.")
from db_models import ( # noqa: E402 from boker.db_models import ( # noqa: E402
LedgerEvent, LedgerEvent,
League, League,
LeagueMembership, LeagueMembership,
@@ -24,7 +24,7 @@ from db_models import ( # noqa: E402
make_user, make_user,
normalize_lookup, normalize_lookup,
) )
from utils import slugify from boker.utils import slugify
PUBLIC_KEY_ALPHABET = string.ascii_lowercase + string.digits PUBLIC_KEY_ALPHABET = string.ascii_lowercase + string.digits
@@ -223,7 +223,7 @@ def set_session_status(session: PokerSession, status: str) -> PokerSession:
if status not in {"open", "closed"}: if status not in {"open", "closed"}:
raise ValueError(f"Unsupported session status: {status}") raise ValueError(f"Unsupported session status: {status}")
from db_models import utc_now from boker.db_models import utc_now
session.status = status session.status = status
if status == "open": if status == "open":
@@ -323,7 +323,7 @@ def add_league_member(
def remove_league_member(league_id: str, user_id: str) -> None: def remove_league_member(league_id: str, user_id: str) -> None:
from db_models import utc_now from boker.db_models import utc_now
membership = LeagueMembership.query.filter_by( membership = LeagueMembership.query.filter_by(
league_id=league_id, league_id=league_id,
@@ -421,7 +421,7 @@ def delete_season(season: Season) -> None:
def delete_league(league: League) -> None: def delete_league(league: League) -> None:
from db_models import LedgerEvent, LeagueMembership, Player, PokerSession, Season from boker.db_models import LedgerEvent, LeagueMembership, Player, PokerSession, Season
LedgerEvent.query.filter_by(league_id=league.id).delete(synchronize_session=False) LedgerEvent.query.filter_by(league_id=league.id).delete(synchronize_session=False)
PokerSession.query.filter_by(league_id=league.id).delete(synchronize_session=False) PokerSession.query.filter_by(league_id=league.id).delete(synchronize_session=False)
Player.query.filter_by(league_id=league.id).delete(synchronize_session=False) Player.query.filter_by(league_id=league.id).delete(synchronize_session=False)
@@ -1,12 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations from __future__ import annotations
from db import db from boker.db import db
if db is None: # pragma: no cover - imported only when DB dependencies exist. if db is None: # pragma: no cover - imported only when DB dependencies exist.
raise RuntimeError("Database dependencies are not installed.") raise RuntimeError("Database dependencies are not installed.")
from db_models import ( # noqa: E402 from boker.db_models import ( # noqa: E402
CANONICAL_EVENT_TYPES, CANONICAL_EVENT_TYPES,
LedgerEvent, LedgerEvent,
Player, Player,
@@ -14,7 +14,7 @@ from db_models import ( # noqa: E402
User, User,
canonical_event_type, canonical_event_type,
) )
from storage import EventRow # noqa: E402 from boker.storage import EventRow # noqa: E402
def session_event_ref(session: PokerSession) -> str: def session_event_ref(session: PokerSession) -> str:
View File
File renamed without changes.
File renamed without changes.
+21 -21
View File
@@ -7,7 +7,7 @@ from urllib.parse import urlsplit
from flask import Blueprint, flash, redirect, render_template, request, session as flask_session, url_for from flask import Blueprint, flash, redirect, render_template, request, session as flask_session, url_for
from auth import ( from boker.auth import (
current_user_id, current_user_id,
generate_invite_token, generate_invite_token,
generate_reset_token, generate_reset_token,
@@ -20,8 +20,8 @@ from auth import (
verify_password, verify_password,
verify_reset_token, verify_reset_token,
) )
from db import database_extensions_available, db from boker.db import database_extensions_available, db
from extensions import limiter from boker.extensions import limiter
account_bp = Blueprint("account", __name__, url_prefix="/account") account_bp = Blueprint("account", __name__, url_prefix="/account")
EMAIL_VERIFICATION_TTL = timedelta(minutes=15) EMAIL_VERIFICATION_TTL = timedelta(minutes=15)
@@ -64,7 +64,7 @@ def _new_verification_code() -> str:
def _issue_verification_code(user) -> str: def _issue_verification_code(user) -> str:
from db_models import utc_now from boker.db_models import utc_now
code = _new_verification_code() code = _new_verification_code()
user.email_verification_code_hash = hash_password(code) user.email_verification_code_hash = hash_password(code)
@@ -73,7 +73,7 @@ def _issue_verification_code(user) -> str:
def _send_verification_code(user) -> None: def _send_verification_code(user) -> None:
from emails import send_email_verification_code from boker.emails import send_email_verification_code
code = _issue_verification_code(user) code = _issue_verification_code(user)
db.session.commit() db.session.commit()
@@ -112,7 +112,7 @@ def register():
} }
if request.method == "POST": if request.method == "POST":
from league_repositories import create_user, find_user_by_email from boker.league_repositories import create_user, find_user_by_email
email = normalize_email(form["email"]) email = normalize_email(form["email"])
password = request.form.get("password", "") password = request.form.get("password", "")
@@ -158,7 +158,7 @@ def login():
} }
if request.method == "POST": if request.method == "POST":
from league_repositories import find_user_by_email from boker.league_repositories import find_user_by_email
user = find_user_by_email(form["email"]) user = find_user_by_email(form["email"])
password = request.form.get("password", "") password = request.form.get("password", "")
@@ -195,7 +195,7 @@ def verify_email():
flash("Account database is not available.", "error") flash("Account database is not available.", "error")
return redirect(url_for("account.login")) return redirect(url_for("account.login"))
from db_models import User, utc_now from boker.db_models import User, utc_now
user_id = flask_session.get("pending_verification_user_id") user_id = flask_session.get("pending_verification_user_id")
user = db.session.get(User, user_id) if user_id else None user = db.session.get(User, user_id) if user_id else None
@@ -245,7 +245,7 @@ def resend_verification_code():
flash("Account database is not available.", "error") flash("Account database is not available.", "error")
return redirect(url_for("account.login")) return redirect(url_for("account.login"))
from db_models import User from boker.db_models import User
user_id = flask_session.get("pending_verification_user_id") user_id = flask_session.get("pending_verification_user_id")
user = db.session.get(User, user_id) if user_id else None user = db.session.get(User, user_id) if user_id else None
@@ -275,7 +275,7 @@ def settings():
flash("Account database is not available.", "error") flash("Account database is not available.", "error")
return redirect(url_for("leagues.index")) return redirect(url_for("leagues.index"))
from db_models import User from boker.db_models import User
user = db.session.get(User, current_user_id()) user = db.session.get(User, current_user_id())
if user is None: if user is None:
@@ -292,7 +292,7 @@ def update_email():
flash("Account database is not available.", "error") flash("Account database is not available.", "error")
return redirect(url_for("account.settings")) return redirect(url_for("account.settings"))
from db_models import User from boker.db_models import User
user = db.session.get(User, current_user_id()) user = db.session.get(User, current_user_id())
if user is None: if user is None:
@@ -307,7 +307,7 @@ def update_email():
elif not verify_password(user.password_hash, current_password): elif not verify_password(user.password_hash, current_password):
flash("Current password is incorrect.", "error") flash("Current password is incorrect.", "error")
else: else:
from league_repositories import find_user_by_email from boker.league_repositories import find_user_by_email
existing = find_user_by_email(new_email) existing = find_user_by_email(new_email)
if existing and existing.id != user.id: if existing and existing.id != user.id:
@@ -327,7 +327,7 @@ def update_password():
flash("Account database is not available.", "error") flash("Account database is not available.", "error")
return redirect(url_for("account.settings")) return redirect(url_for("account.settings"))
from db_models import User from boker.db_models import User
user = db.session.get(User, current_user_id()) user = db.session.get(User, current_user_id())
if user is None: if user is None:
@@ -359,7 +359,7 @@ def disable_account():
flash("Account database is not available.", "error") flash("Account database is not available.", "error")
return redirect(url_for("account.settings")) return redirect(url_for("account.settings"))
from db_models import League, User, utc_now from boker.db_models import League, User, utc_now
user = db.session.get(User, current_user_id()) user = db.session.get(User, current_user_id())
if user is None: if user is None:
@@ -395,8 +395,8 @@ def delete_account():
flash("Account database is not available.", "error") flash("Account database is not available.", "error")
return redirect(url_for("account.settings")) return redirect(url_for("account.settings"))
from db_models import League, LeagueMembership, User, utc_now from boker.db_models import League, LeagueMembership, User, utc_now
from league_repositories import delete_league from boker.league_repositories import delete_league
user = db.session.get(User, current_user_id()) user = db.session.get(User, current_user_id())
if user is None: if user is None:
@@ -436,9 +436,9 @@ def forgot_password():
flash("Account database is not available.", "error") flash("Account database is not available.", "error")
return redirect(url_for("account.forgot_password")) return redirect(url_for("account.forgot_password"))
from emails import send_password_reset from boker.emails import send_password_reset
from flask import current_app from flask import current_app
from league_repositories import find_user_by_email from boker.league_repositories import find_user_by_email
email = normalize_email(request.form.get("email", "")) email = normalize_email(request.form.get("email", ""))
user = find_user_by_email(email) user = find_user_by_email(email)
@@ -472,7 +472,7 @@ def reset_password(token):
flash("Account database is not available.", "error") flash("Account database is not available.", "error")
return redirect(url_for("account.login")) return redirect(url_for("account.login"))
from db_models import User from boker.db_models import User
user = db.session.get(User, user_id) user = db.session.get(User, user_id)
if user is None or user.disabled_at is not None: if user is None or user.disabled_at is not None:
@@ -507,8 +507,8 @@ def accept_invite(token):
flash("Account database is not available.", "error") flash("Account database is not available.", "error")
return redirect(url_for("leagues.index")) return redirect(url_for("leagues.index"))
from db_models import User from boker.db_models import User
from league_repositories import add_league_member, find_league_by_id, find_membership, find_user_by_email from boker.league_repositories import add_league_member, find_league_by_id, find_membership, find_user_by_email
invite_email = normalize_email(data.get("email", "")) invite_email = normalize_email(data.get("email", ""))
invite_next = url_for("account.accept_invite", token=token) invite_next = url_for("account.accept_invite", token=token)
@@ -9,17 +9,17 @@ from flask import Blueprint, current_app, flash, redirect, render_template, requ
from sqlalchemy import or_ from sqlalchemy import or_
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from auth import ( from boker.auth import (
current_user_id, current_user_id,
generate_reset_token, generate_reset_token,
hash_password, hash_password,
normalize_email, normalize_email,
site_admin_required, site_admin_required,
) )
from db import db from boker.db import db
from db_models import LedgerEvent, League, LeagueMembership, Player, PokerSession, User, utc_now from boker.db_models import LedgerEvent, League, LeagueMembership, Player, PokerSession, User, utc_now
from league_repositories import delete_league, transfer_league_ownership from boker.league_repositories import delete_league, transfer_league_ownership
from utils import slugify from boker.utils import slugify
internal_bp = Blueprint("internal", __name__, url_prefix="/internal") internal_bp = Blueprint("internal", __name__, url_prefix="/internal")
@@ -415,7 +415,7 @@ def send_user_password_reset(user_id: str):
flash("Disabled users cannot receive password resets.", "error") flash("Disabled users cannot receive password resets.", "error")
return redirect(url_for("internal.user_detail", user_id=user.id)) return redirect(url_for("internal.user_detail", user_id=user.id))
from emails import send_password_reset from boker.emails import send_password_reset
token = generate_reset_token(user.id) token = generate_reset_token(user.id)
base_url = current_app.config.get("APP_BASE_URL", "").rstrip("/") base_url = current_app.config.get("APP_BASE_URL", "").rstrip("/")
@@ -436,7 +436,7 @@ def create_user_temporary_password(user_id: str):
flash("Disabled users cannot receive temporary passwords.", "error") flash("Disabled users cannot receive temporary passwords.", "error")
return redirect(url_for("internal.user_detail", user_id=user.id)) return redirect(url_for("internal.user_detail", user_id=user.id))
from emails import send_temporary_password from boker.emails import send_temporary_password
temporary_password = _temporary_password() temporary_password = _temporary_password()
user.password_hash = hash_password(temporary_password) user.password_hash = hash_password(temporary_password)
+74 -74
View File
@@ -9,12 +9,12 @@ from datetime import datetime, timezone
from flask import Blueprint, abort, flash, make_response, redirect, render_template, request, url_for from flask import Blueprint, abort, flash, make_response, redirect, render_template, request, url_for
from auth import current_user_id, login_required, normalize_email from boker.auth import current_user_id, login_required, normalize_email
from charts import cumulative_profit_series, player_session_series from boker.charts import cumulative_profit_series, player_session_series
from db import database_extensions_available, db from boker.db import database_extensions_available, db
from models import SessionSummary from boker.models import SessionSummary
from services import apply_rank_changes, build_leaderboard, build_session_summaries, session_events from boker.services import apply_rank_changes, build_leaderboard, build_session_summaries, session_events
from utils import cents_to_dollars, session_label, session_sort_key from boker.utils import cents_to_dollars, session_label, session_sort_key
leagues_bp = Blueprint("leagues", __name__) leagues_bp = Blueprint("leagues", __name__)
@@ -48,7 +48,7 @@ def split_league_ref(league_ref: str) -> tuple[str, str]:
def require_league(league_ref: str, allowed_roles: set[str]): def require_league(league_ref: str, allowed_roles: set[str]):
from league_repositories import find_league_by_public_key, user_has_league_role from boker.league_repositories import find_league_by_public_key, user_has_league_role
_slug, public_key = split_league_ref(league_ref) _slug, public_key = split_league_ref(league_ref)
league = find_league_by_public_key(public_key) league = find_league_by_public_key(public_key)
@@ -64,7 +64,7 @@ def require_league(league_ref: str, allowed_roles: set[str]):
def get_league_with_visibility_gate(league_ref: str): def get_league_with_visibility_gate(league_ref: str):
"""Load league; if private, enforce login + membership. Returns (league, None) or (None, redirect).""" """Load league; if private, enforce login + membership. Returns (league, None) or (None, redirect)."""
from league_repositories import find_league_by_public_key, user_has_league_role from boker.league_repositories import find_league_by_public_key, user_has_league_role
_slug, public_key = split_league_ref(league_ref) _slug, public_key = split_league_ref(league_ref)
league = find_league_by_public_key(public_key) league = find_league_by_public_key(public_key)
@@ -99,8 +99,8 @@ def empty_session_summary(session) -> SessionSummary:
def session_ref_map(league_id: str) -> dict[str, str]: def session_ref_map(league_id: str) -> dict[str, str]:
from ledger_repositories import session_event_ref from boker.ledger_repositories import session_event_ref
from league_repositories import list_sessions_for_league from boker.league_repositories import list_sessions_for_league
return {session_event_ref(session): session.id for session in list_sessions_for_league(league_id)} return {session_event_ref(session): session.id for session in list_sessions_for_league(league_id)}
@@ -112,8 +112,8 @@ def index():
flash("League database is not available.", "error") flash("League database is not available.", "error")
return render_template("leagues_index.html", leagues=[]) return render_template("leagues_index.html", leagues=[])
from league_repositories import list_leagues_for_user from boker.league_repositories import list_leagues_for_user
from ledger_repositories import list_event_rows_for_league from boker.ledger_repositories import list_event_rows_for_league
memberships = list_leagues_for_user(current_user_id() or "") memberships = list_leagues_for_user(current_user_id() or "")
league_summaries = [] league_summaries = []
@@ -190,8 +190,8 @@ def new():
} }
if request.method == "POST": if request.method == "POST":
from db_models import User from boker.db_models import User
from league_repositories import create_league, unique_league_slug from boker.league_repositories import create_league, unique_league_slug
owner = db.session.get(User, current_user_id()) owner = db.session.get(User, current_user_id())
if owner is None: if owner is None:
@@ -221,8 +221,8 @@ def dashboard(league_ref: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from league_repositories import league_counts, user_has_league_role from boker.league_repositories import league_counts, user_has_league_role
from ledger_repositories import list_event_rows_for_league from boker.ledger_repositories import list_event_rows_for_league
league, resp = get_league_with_visibility_gate(league_ref) league, resp = get_league_with_visibility_gate(league_ref)
if resp: if resp:
@@ -289,7 +289,7 @@ def legacy_dashboard_redirect(league_id: str, league_slug: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from league_repositories import find_league_by_id, user_has_league_role from boker.league_repositories import find_league_by_id, user_has_league_role
league = find_league_by_id(league_id) league = find_league_by_id(league_id)
if league is None: if league is None:
@@ -306,8 +306,8 @@ def leaderboard(league_ref: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from ledger_repositories import list_event_rows_for_league from boker.ledger_repositories import list_event_rows_for_league
from league_repositories import ( from boker.league_repositories import (
list_players_for_league, list_players_for_league,
list_seasons_for_league, list_seasons_for_league,
user_has_league_role, user_has_league_role,
@@ -328,10 +328,10 @@ def leaderboard(league_ref: str):
selected_season_id = request.args.get("season", "").strip() selected_season_id = request.args.get("season", "").strip()
selected_season = None selected_season = None
if selected_season_id: if selected_season_id:
from league_repositories import find_season, list_sessions_for_league from boker.league_repositories import find_season, list_sessions_for_league
selected_season = find_season(league.id, selected_season_id) selected_season = find_season(league.id, selected_season_id)
if selected_season: if selected_season:
from ledger_repositories import session_event_ref from boker.ledger_repositories import session_event_ref
season_db_sessions = list_sessions_for_league(league.id) season_db_sessions = list_sessions_for_league(league.id)
season_refs = { season_refs = {
session_event_ref(s) session_event_ref(s)
@@ -442,12 +442,12 @@ def ledger(league_ref: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from ledger_repositories import ( from boker.ledger_repositories import (
append_ledger_event, append_ledger_event,
list_all_event_rows_for_league, list_all_event_rows_for_league,
list_event_rows_for_league, list_event_rows_for_league,
) )
from league_repositories import ( from boker.league_repositories import (
find_player_for_league, find_player_for_league,
find_session_for_league, find_session_for_league,
list_players_for_league, list_players_for_league,
@@ -579,7 +579,7 @@ def void_event(league_ref: str, event_id: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from ledger_repositories import void_ledger_event from boker.ledger_repositories import void_ledger_event
league = require_league(league_ref, {"owner"}) league = require_league(league_ref, {"owner"})
reason = request.form.get("void_reason", "").strip() reason = request.form.get("void_reason", "").strip()
@@ -602,8 +602,8 @@ def player_detail(league_ref: str, player_id: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from ledger_repositories import list_event_rows_for_league from boker.ledger_repositories import list_event_rows_for_league
from league_repositories import find_player_for_league, user_has_league_role from boker.league_repositories import find_player_for_league, user_has_league_role
league, resp = get_league_with_visibility_gate(league_ref) league, resp = get_league_with_visibility_gate(league_ref)
if resp: if resp:
@@ -661,7 +661,7 @@ def players(league_ref: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from league_repositories import ( from boker.league_repositories import (
create_player, create_player,
list_players_for_league, list_players_for_league,
player_name_exists, player_name_exists,
@@ -700,7 +700,7 @@ def players(league_ref: str):
flash("Player added.", "success") flash("Player added.", "success")
return redirect(url_for("leagues.players", **league_url_values(league))) return redirect(url_for("leagues.players", **league_url_values(league)))
from ledger_repositories import list_event_rows_for_league from boker.ledger_repositories import list_event_rows_for_league
all_sessions = build_session_summaries(list_event_rows_for_league(league.id)) all_sessions = build_session_summaries(list_event_rows_for_league(league.id))
board = build_leaderboard(all_sessions, league.break_even_cents) board = build_leaderboard(all_sessions, league.break_even_cents)
@@ -734,7 +734,7 @@ def update_player_status(league_ref: str, player_id: str, status: str, message:
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from league_repositories import find_player_for_league, set_player_status from boker.league_repositories import find_player_for_league, set_player_status
league = require_league(league_ref, {"owner", "manager"}) league = require_league(league_ref, {"owner", "manager"})
player = find_player_for_league(league.id, player_id) player = find_player_for_league(league.id, player_id)
@@ -754,7 +754,7 @@ def edit_player(league_ref: str, player_id: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from league_repositories import find_player_for_league, player_name_taken, update_player from boker.league_repositories import find_player_for_league, player_name_taken, update_player
league = require_league(league_ref, {"owner", "manager"}) league = require_league(league_ref, {"owner", "manager"})
player = find_player_for_league(league.id, player_id) player = find_player_for_league(league.id, player_id)
@@ -782,8 +782,8 @@ def sessions(league_ref: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from ledger_repositories import append_ledger_event from boker.ledger_repositories import append_ledger_event
from league_repositories import ( from boker.league_repositories import (
create_poker_session, create_poker_session,
list_seasons_for_league, list_seasons_for_league,
list_sessions_for_league, list_sessions_for_league,
@@ -816,10 +816,10 @@ def sessions(league_ref: str):
status = "closed" if form["status"] == "closed" else "open" status = "closed" if form["status"] == "closed" else "open"
season_id = form["season_id"] or None season_id = form["season_id"] or None
if season_id is None: if season_id is None:
from league_repositories import auto_assign_sessions_to_seasons as _auto from boker.league_repositories import auto_assign_sessions_to_seasons as _auto
# try auto-assign: create the session first, then let the # try auto-assign: create the session first, then let the
# function match it; we pass a temporary session date check inline # function match it; we pass a temporary session date check inline
from league_repositories import list_seasons_for_league as _ls from boker.league_repositories import list_seasons_for_league as _ls
eligible = [ eligible = [
s for s in _ls(league.id, include_archived=False) s for s in _ls(league.id, include_archived=False)
if s.start_date and s.end_date if s.start_date and s.end_date
@@ -836,7 +836,7 @@ def sessions(league_ref: str):
session.label = form["label"] or None session.label = form["label"] or None
session.notes = form["notes"] or None session.notes = form["notes"] or None
if status == "open": if status == "open":
from league_repositories import set_session_status from boker.league_repositories import set_session_status
set_session_status(session, "open") set_session_status(session, "open")
@@ -854,8 +854,8 @@ def sessions(league_ref: str):
flash(f"Created {session.display_label}.", "success") flash(f"Created {session.display_label}.", "success")
return redirect(url_for("leagues.sessions", **league_url_values(league))) return redirect(url_for("leagues.sessions", **league_url_values(league)))
from db_models import LedgerEvent from boker.db_models import LedgerEvent
from ledger_repositories import list_event_rows_for_league from boker.ledger_repositories import list_event_rows_for_league
all_sessions = list_sessions_for_league(league.id) all_sessions = list_sessions_for_league(league.id)
summaries = build_session_summaries(list_event_rows_for_league(league.id)) summaries = build_session_summaries(list_event_rows_for_league(league.id))
@@ -908,8 +908,8 @@ def prune_empty_sessions(league_ref: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from db_models import LedgerEvent, PokerSession from boker.db_models import LedgerEvent, PokerSession
from league_repositories import list_sessions_for_league from boker.league_repositories import list_sessions_for_league
league = require_league(league_ref, {"owner", "manager"}) league = require_league(league_ref, {"owner", "manager"})
@@ -944,7 +944,7 @@ def delete_session(league_ref: str, session_id: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from league_repositories import find_session_for_league from boker.league_repositories import find_session_for_league
league = require_league(league_ref, {"owner"}) league = require_league(league_ref, {"owner"})
session = find_session_for_league(league.id, session_id) session = find_session_for_league(league.id, session_id)
@@ -964,7 +964,7 @@ def edit_session(league_ref: str, session_id: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from league_repositories import find_session_for_league from boker.league_repositories import find_session_for_league
league = require_league(league_ref, {"owner", "manager"}) league = require_league(league_ref, {"owner", "manager"})
session = find_session_for_league(league.id, session_id) session = find_session_for_league(league.id, session_id)
@@ -983,7 +983,7 @@ def edit_session(league_ref: str, session_id: str):
return redirect(url_for("leagues.session_detail", league_ref=league.url_ref, session_id=session_id)) return redirect(url_for("leagues.session_detail", league_ref=league.url_ref, session_id=session_id))
if new_date != session.session_date: if new_date != session.session_date:
from db_models import PokerSession as _PS from boker.db_models import PokerSession as _PS
max_seq = db.session.query(db.func.max(_PS.sequence_on_date)).filter( max_seq = db.session.query(db.func.max(_PS.sequence_on_date)).filter(
_PS.league_id == league.id, _PS.league_id == league.id,
_PS.session_date == new_date, _PS.session_date == new_date,
@@ -993,7 +993,7 @@ def edit_session(league_ref: str, session_id: str):
session.session_date = new_date session.session_date = new_date
if new_season_id is not None: if new_season_id is not None:
from league_repositories import find_season from boker.league_repositories import find_season
valid = find_season(league.id, new_season_id) valid = find_season(league.id, new_season_id)
new_season_id = valid.id if valid else None new_season_id = valid.id if valid else None
@@ -1012,8 +1012,8 @@ def session_detail(league_ref: str, session_id: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from ledger_repositories import append_ledger_event, list_all_event_rows_for_session, list_event_rows_for_session from boker.ledger_repositories import append_ledger_event, list_all_event_rows_for_session, list_event_rows_for_session
from league_repositories import ( from boker.league_repositories import (
find_session_for_league, find_session_for_league,
list_players_for_league, list_players_for_league,
list_seasons_for_league, list_seasons_for_league,
@@ -1101,9 +1101,9 @@ def session_public_view(league_ref: str, session_id: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from charts import session_breakdown_series from boker.charts import session_breakdown_series
from ledger_repositories import list_event_rows_for_league, list_event_rows_for_session from boker.ledger_repositories import list_event_rows_for_league, list_event_rows_for_session
from league_repositories import find_league_by_public_key, find_season, find_session_for_league, list_sessions_for_league, user_has_league_role from boker.league_repositories import find_league_by_public_key, find_season, find_session_for_league, list_sessions_for_league, user_has_league_role
_slug, public_key = split_league_ref(league_ref) _slug, public_key = split_league_ref(league_ref)
league = find_league_by_public_key(public_key) league = find_league_by_public_key(public_key)
@@ -1182,8 +1182,8 @@ def update_session_status(league_ref: str, session_id: str, status: str, message
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from ledger_repositories import append_ledger_event from boker.ledger_repositories import append_ledger_event
from league_repositories import find_session_for_league, set_session_status from boker.league_repositories import find_session_for_league, set_session_status
league = require_league(league_ref, {"owner", "manager"}) league = require_league(league_ref, {"owner", "manager"})
session = find_session_for_league(league.id, session_id) session = find_session_for_league(league.id, session_id)
@@ -1214,7 +1214,7 @@ def seasons(league_ref: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from league_repositories import ( from boker.league_repositories import (
create_season, create_season,
list_seasons_for_league, list_seasons_for_league,
user_has_league_role, user_has_league_role,
@@ -1283,7 +1283,7 @@ def auto_assign_seasons(league_ref: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from league_repositories import auto_assign_sessions_to_seasons from boker.league_repositories import auto_assign_sessions_to_seasons
league = require_league(league_ref, {"owner", "manager"}) league = require_league(league_ref, {"owner", "manager"})
count = auto_assign_sessions_to_seasons(league.id) count = auto_assign_sessions_to_seasons(league.id)
@@ -1302,7 +1302,7 @@ def update_season(league_ref: str, season_id: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from league_repositories import find_season, update_season as repo_update_season from boker.league_repositories import find_season, update_season as repo_update_season
league = require_league(league_ref, {"owner", "manager"}) league = require_league(league_ref, {"owner", "manager"})
season = find_season(league.id, season_id) season = find_season(league.id, season_id)
@@ -1338,7 +1338,7 @@ def archive_season(league_ref: str, season_id: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from league_repositories import archive_season as repo_archive, find_season from boker.league_repositories import archive_season as repo_archive, find_season
league = require_league(league_ref, {"owner", "manager"}) league = require_league(league_ref, {"owner", "manager"})
season = find_season(league.id, season_id) season = find_season(league.id, season_id)
@@ -1359,7 +1359,7 @@ def unarchive_season(league_ref: str, season_id: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from league_repositories import find_season, unarchive_season as repo_unarchive from boker.league_repositories import find_season, unarchive_season as repo_unarchive
league = require_league(league_ref, {"owner", "manager"}) league = require_league(league_ref, {"owner", "manager"})
season = find_season(league.id, season_id) season = find_season(league.id, season_id)
@@ -1380,7 +1380,7 @@ def delete_season(league_ref: str, season_id: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from league_repositories import delete_season as repo_delete, find_season from boker.league_repositories import delete_season as repo_delete, find_season
league = require_league(league_ref, {"owner", "manager"}) league = require_league(league_ref, {"owner", "manager"})
season = find_season(league.id, season_id) season = find_season(league.id, season_id)
@@ -1437,7 +1437,7 @@ def league_settings(league_ref: str):
elif break_even_cents < 0 or break_even_cents > 10000: elif break_even_cents < 0 or break_even_cents > 10000:
flash("Break-even threshold must be between $0.00 and $100.00.", "error") flash("Break-even threshold must be between $0.00 and $100.00.", "error")
else: else:
from utils import slugify from boker.utils import slugify
league.name = form["name"] league.name = form["name"]
league.slug = slugify(form["name"]) league.slug = slugify(form["name"])
@@ -1449,7 +1449,7 @@ def league_settings(league_ref: str):
flash("League settings saved.", "success") flash("League settings saved.", "success")
return redirect(url_for("leagues.league_settings", league_ref=league.url_ref)) return redirect(url_for("leagues.league_settings", league_ref=league.url_ref))
from league_repositories import list_members_for_league from boker.league_repositories import list_members_for_league
members = list_members_for_league(league.id) members = list_members_for_league(league.id)
return render_template("league_settings.html", league=league, form=form, is_owner=True, can_manage=True, members=members) return render_template("league_settings.html", league=league, form=form, is_owner=True, can_manage=True, members=members)
@@ -1462,11 +1462,11 @@ def invite_member(league_ref: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from auth import generate_invite_token from boker.auth import generate_invite_token
from emails import send_league_invite from boker.emails import send_league_invite
from flask import current_app from flask import current_app
from db_models import User from boker.db_models import User
from league_repositories import find_membership, find_user_by_email from boker.league_repositories import find_membership, find_user_by_email
league = require_league(league_ref, {"owner"}) league = require_league(league_ref, {"owner"})
email = normalize_email(request.form.get("email", "")) email = normalize_email(request.form.get("email", ""))
@@ -1509,7 +1509,7 @@ def remove_member(league_ref: str, user_id: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from league_repositories import remove_league_member from boker.league_repositories import remove_league_member
league = require_league(league_ref, {"owner"}) league = require_league(league_ref, {"owner"})
@@ -1530,7 +1530,7 @@ def update_member_role(league_ref: str, user_id: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from league_repositories import set_league_member_role from boker.league_repositories import set_league_member_role
league = require_league(league_ref, {"owner"}) league = require_league(league_ref, {"owner"})
role = request.form.get("role", "").strip() role = request.form.get("role", "").strip()
@@ -1560,7 +1560,7 @@ def transfer_ownership(league_ref: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from league_repositories import transfer_league_ownership from boker.league_repositories import transfer_league_ownership
league = require_league(league_ref, {"owner"}) league = require_league(league_ref, {"owner"})
new_owner_user_id = request.form.get("new_owner_user_id", "").strip() new_owner_user_id = request.form.get("new_owner_user_id", "").strip()
@@ -1588,7 +1588,7 @@ def archive_league(league_ref: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from db_models import utc_now from boker.db_models import utc_now
league = require_league(league_ref, {"owner"}) league = require_league(league_ref, {"owner"})
confirm_name = request.form.get("confirm_name", "").strip() confirm_name = request.form.get("confirm_name", "").strip()
@@ -1610,7 +1610,7 @@ def delete_league_route(league_ref: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from league_repositories import delete_league from boker.league_repositories import delete_league
league = require_league(league_ref, {"owner"}) league = require_league(league_ref, {"owner"})
confirm_name = request.form.get("confirm_name", "").strip() confirm_name = request.form.get("confirm_name", "").strip()
@@ -1633,8 +1633,8 @@ def export_ledger_csv(league_ref: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from ledger_repositories import list_event_rows_for_league from boker.ledger_repositories import list_event_rows_for_league
from storage import CSV_HEADERS from boker.storage import CSV_HEADERS
league = require_league(league_ref, {"owner", "manager"}) league = require_league(league_ref, {"owner", "manager"})
rows = list_event_rows_for_league(league.id) rows = list_event_rows_for_league(league.id)
@@ -1659,14 +1659,14 @@ def import_ledger_csv(league_ref: str):
flash("League database is not available.", "error") flash("League database is not available.", "error")
return redirect(url_for("public.home")) return redirect(url_for("public.home"))
from db_models import CANONICAL_EVENT_TYPES, canonical_event_type from boker.db_models import CANONICAL_EVENT_TYPES, canonical_event_type
from ledger_repositories import ( from boker.ledger_repositories import (
append_ledger_event, append_ledger_event,
list_ledger_events_for_league, list_ledger_events_for_league,
session_event_ref, session_event_ref,
) )
from league_repositories import create_player, list_players_for_league, list_sessions_for_league, unique_player_slug from boker.league_repositories import create_player, list_players_for_league, list_sessions_for_league, unique_player_slug
from storage import CSV_HEADERS from boker.storage import CSV_HEADERS
league = require_league(league_ref, {"owner", "manager"}) league = require_league(league_ref, {"owner", "manager"})
@@ -1700,7 +1700,7 @@ def import_ledger_csv(league_ref: str):
flash(f"CSV header mismatch. Expected: {', '.join(CSV_HEADERS)}", "error") flash(f"CSV header mismatch. Expected: {', '.join(CSV_HEADERS)}", "error")
return redirect(url_for("leagues.ledger", **league_url_values(league))) return redirect(url_for("leagues.ledger", **league_url_values(league)))
from db_models import PokerSession, make_session from boker.db_models import PokerSession, make_session
sessions = list_sessions_for_league(league.id) sessions = list_sessions_for_league(league.id)
session_by_ref = {session_event_ref(s): s for s in sessions} session_by_ref = {session_event_ref(s): s for s in sessions}
+8 -8
View File
@@ -4,16 +4,16 @@ from __future__ import annotations
from flask import Blueprint, flash, redirect, render_template, request, url_for from flask import Blueprint, flash, redirect, render_template, request, url_for
from flask import current_app from flask import current_app
from charts import cumulative_profit_series, player_session_series, session_breakdown_series from boker.charts import cumulative_profit_series, player_session_series, session_breakdown_series
from config import ELIGIBLE_MIN_SESSIONS from boker.config import ELIGIBLE_MIN_SESSIONS
from services import ( from boker.services import (
apply_rank_changes, apply_rank_changes,
build_leaderboard, build_leaderboard,
build_session_summaries, build_session_summaries,
session_events, session_events,
) )
from storage import load_events from boker.storage import load_events
from utils import session_label, session_sort_key from boker.utils import session_label, session_sort_key
public_bp = Blueprint("public", __name__) public_bp = Blueprint("public", __name__)
@@ -40,9 +40,9 @@ def terms():
@public_bp.get("/explore") @public_bp.get("/explore")
def explore(): def explore():
from auth import is_logged_in from boker.auth import is_logged_in
from db import database_extensions_available from boker.db import database_extensions_available
from league_repositories import league_counts, list_public_leagues from boker.league_repositories import league_counts, list_public_leagues
q = request.args.get("q", "").strip() q = request.args.get("q", "").strip()
leagues = list_public_leagues(q) if database_extensions_available() else [] leagues = list_public_leagues(q) if database_extensions_available() else []
+3 -3
View File
@@ -4,9 +4,9 @@ from __future__ import annotations
from collections import defaultdict from collections import defaultdict
from models import PlayerStats, SessionEntry, SessionSummary from boker.models import PlayerStats, SessionEntry, SessionSummary
from storage import EventRow from boker.storage import EventRow
from utils import entry_sort_key, net_result_bucket, session_sort_key from boker.utils import entry_sort_key, net_result_bucket, session_sort_key
SESSION_MARKER_TYPES = {"session_open", "session_close"} SESSION_MARKER_TYPES = {"session_open", "session_close"}
View File
File renamed without changes.
+1 -1
View File
@@ -7,7 +7,7 @@ import re
from markupsafe import Markup from markupsafe import Markup
from models import SessionEntry, SessionSummary from boker.models import SessionEntry, SessionSummary
BREAK_EVEN_TOLERANCE_CENTS = 100 BREAK_EVEN_TOLERANCE_CENTS = 100
+3 -3
View File
@@ -1,13 +1,13 @@
import unittest import unittest
from charts import player_session_series from boker.charts import player_session_series
from services import ( from boker.services import (
build_leaderboard, build_leaderboard,
build_session_summaries, build_session_summaries,
pending_payout_carry_items, pending_payout_carry_items,
prunable_empty_session_ids, prunable_empty_session_ids,
) )
from utils import session_sort_key from boker.utils import session_sort_key
def cents(amount: float) -> int: def cents(amount: float) -> int:
+15 -15
View File
@@ -4,8 +4,8 @@ from datetime import datetime, timedelta, timezone
from pathlib import Path from pathlib import Path
from app import create_app from app import create_app
from auth import hash_password from boker.auth import hash_password
from db import db from boker.db import db
class InternalAdminAccessTests(unittest.TestCase): class InternalAdminAccessTests(unittest.TestCase):
@@ -22,7 +22,7 @@ class InternalAdminAccessTests(unittest.TestCase):
self.client = self.app.test_client() self.client = self.app.test_client()
with self.app.app_context(): with self.app.app_context():
from db_models import League, LeagueMembership, User from boker.db_models import League, LeagueMembership, User
db.create_all() db.create_all()
self.user = User( self.user = User(
@@ -118,7 +118,7 @@ class InternalAdminAccessTests(unittest.TestCase):
def test_admin_users_page_is_paginated(self): def test_admin_users_page_is_paginated(self):
with self.app.app_context(): with self.app.app_context():
from db_models import User from boker.db_models import User
users = [ users = [
User(email=f"bulk{i:02d}@example.com", password_hash=hash_password("password123")) User(email=f"bulk{i:02d}@example.com", password_hash=hash_password("password123"))
@@ -136,7 +136,7 @@ class InternalAdminAccessTests(unittest.TestCase):
def test_admin_leagues_page_is_paginated(self): def test_admin_leagues_page_is_paginated(self):
with self.app.app_context(): with self.app.app_context():
from db_models import League from boker.db_models import League
leagues = [ leagues = [
League( League(
@@ -160,8 +160,8 @@ class InternalAdminAccessTests(unittest.TestCase):
def test_session_growth_uses_session_date_not_created_at(self): def test_session_growth_uses_session_date_not_created_at(self):
with self.app.app_context(): with self.app.app_context():
from db_models import PokerSession from boker.db_models import PokerSession
from routes.internal import _growth_data from boker.routes.internal import _growth_data
now = datetime.now(timezone.utc) now = datetime.now(timezone.utc)
old_session = PokerSession( old_session = PokerSession(
@@ -196,7 +196,7 @@ class InternalAdminAccessTests(unittest.TestCase):
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
with self.app.app_context(): with self.app.app_context():
from db_models import User from boker.db_models import User
user = db.session.get(User, self.user_id) user = db.session.get(User, self.user_id)
self.assertEqual(user.email, "viewer@example.com") self.assertEqual(user.email, "viewer@example.com")
@@ -212,7 +212,7 @@ class InternalAdminAccessTests(unittest.TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
with self.app.app_context(): with self.app.app_context():
from db_models import User from boker.db_models import User
user = db.session.get(User, self.user_id) user = db.session.get(User, self.user_id)
self.assertEqual(user.email, "changed@example.com") self.assertEqual(user.email, "changed@example.com")
@@ -220,7 +220,7 @@ class InternalAdminAccessTests(unittest.TestCase):
def test_admin_delete_user_blocks_owned_leagues(self): def test_admin_delete_user_blocks_owned_leagues(self):
self.login_as(self.admin_id) self.login_as(self.admin_id)
with self.app.app_context(): with self.app.app_context():
from db_models import League, LeagueMembership from boker.db_models import League, LeagueMembership
owned_league = League( owned_league = League(
name="Manager Owned Poker", name="Manager Owned Poker",
@@ -241,7 +241,7 @@ class InternalAdminAccessTests(unittest.TestCase):
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
with self.app.app_context(): with self.app.app_context():
from db_models import User from boker.db_models import User
self.assertIsNotNone(db.session.get(User, self.manager_id)) self.assertIsNotNone(db.session.get(User, self.manager_id))
@@ -251,7 +251,7 @@ class InternalAdminAccessTests(unittest.TestCase):
archive_response = self.client.post(f"/internal/leagues/{self.league_id}/archive") archive_response = self.client.post(f"/internal/leagues/{self.league_id}/archive")
self.assertEqual(archive_response.status_code, 302) self.assertEqual(archive_response.status_code, 302)
with self.app.app_context(): with self.app.app_context():
from db_models import League from boker.db_models import League
league = db.session.get(League, self.league_id) league = db.session.get(League, self.league_id)
self.assertIsNotNone(league.archived_at) self.assertIsNotNone(league.archived_at)
@@ -259,7 +259,7 @@ class InternalAdminAccessTests(unittest.TestCase):
restore_response = self.client.post(f"/internal/leagues/{self.league_id}/restore") restore_response = self.client.post(f"/internal/leagues/{self.league_id}/restore")
self.assertEqual(restore_response.status_code, 302) self.assertEqual(restore_response.status_code, 302)
with self.app.app_context(): with self.app.app_context():
from db_models import League from boker.db_models import League
league = db.session.get(League, self.league_id) league = db.session.get(League, self.league_id)
self.assertIsNone(league.archived_at) self.assertIsNone(league.archived_at)
@@ -280,7 +280,7 @@ class InternalAdminAccessTests(unittest.TestCase):
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
with self.app.app_context(): with self.app.app_context():
from db_models import League from boker.db_models import League
league = db.session.get(League, self.league_id) league = db.session.get(League, self.league_id)
self.assertEqual(league.name, "Saturday Poker") self.assertEqual(league.name, "Saturday Poker")
@@ -300,7 +300,7 @@ class InternalAdminAccessTests(unittest.TestCase):
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
with self.app.app_context(): with self.app.app_context():
from db_models import League, LeagueMembership from boker.db_models import League, LeagueMembership
league = db.session.get(League, self.league_id) league = db.session.get(League, self.league_id)
self.assertEqual(league.created_by_user_id, self.manager_id) self.assertEqual(league.created_by_user_id, self.manager_id)
+2 -2
View File
@@ -1,7 +1,7 @@
import unittest import unittest
from models import SessionEntry, SessionSummary from boker.models import SessionEntry, SessionSummary
from services import build_leaderboard from boker.services import build_leaderboard
class CashInAccountingTest(unittest.TestCase): class CashInAccountingTest(unittest.TestCase):