major file restrucutre

This commit is contained in:
SowinskiBraeden committed 2026-06-24 19:53:19 -07:00
1 parent ac278065d2
commit 6da4c3e379
53 files changed
+188 -180

No files matched your search

+2 -60
View File
@@ -1,66 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations from boker import create_app
import os
import click
from flask import Flask
from auth import current_user_id, is_logged_in
from config import 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.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 create_app(config_overrides: dict | None = None) -> Flask:
app = Flask(__name__)
cfg = ProductionConfig if os.getenv("FLASK_ENV") == "production" else Config
app.config.from_object(cfg)
if config_overrides:
app.config.update(config_overrides)
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(),
}
app.register_blueprint(public_bp)
app.register_blueprint(account_bp)
app.register_blueprint(leagues_bp)
@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.")
return app
app = create_app() app = create_app()
if __name__ == "__main__": if __name__ == "__main__":
import os
app.run(debug=os.getenv("FLASK_DEBUG", "0") == "1") app.run(debug=os.getenv("FLASK_DEBUG", "0") == "1")
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env python3
from __future__ import annotations
import os
import click
from flask import Flask
from .auth import current_user_id, is_logged_in
from .config import 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.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 create_app(config_overrides: dict | None = None) -> Flask:
app = Flask(__name__)
cfg = ProductionConfig if os.getenv("FLASK_ENV") == "production" else Config
app.config.from_object(cfg)
if config_overrides:
app.config.update(config_overrides)
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(),
}
app.register_blueprint(public_bp)
app.register_blueprint(account_bp)
app.register_blueprint(leagues_bp)
@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.")
return app
View File
File renamed without changes.
+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 .models import SessionEntry, SessionSummary
from utils import net_result_bucket, session_chart_label, session_sort_key from .utils import net_result_bucket, session_chart_label, session_sort_key
PLAYER_PALETTE = [ PLAYER_PALETTE = [
"#9b8cf0", # --line-1 violet "#9b8cf0", # --line-1 violet
+2 -2
View File
@@ -4,12 +4,12 @@ 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'}"
ELIGIBLE_MIN_SESSIONS = 3 ELIGIBLE_MIN_SESSIONS = 3
APP_VERSION = "2.3.0" APP_VERSION = "2.3.1"
def load_local_env(env_path: Path) -> None: def load_local_env(env_path: Path) -> None:
+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. from . import 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 .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
@@ -4,7 +4,7 @@ from __future__ import annotations
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 .extensions import mail
def send_password_reset(to_email: str, reset_url: str) -> None: def send_password_reset(to_email: str, reset_url: str) -> None:
File renamed without changes.
View File
File renamed without changes.
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 ..auth import hash_password, normalize_email
from db import db from ..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 ..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 ..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 ..db_models import utc_now
session.status = status session.status = status
if status == "open": if status == "open":
@@ -313,7 +313,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 ..db_models import utc_now
membership = LeagueMembership.query.filter_by( membership = LeagueMembership.query.filter_by(
league_id=league_id, league_id=league_id,
@@ -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 ..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 ..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 ..storage import EventRow # noqa: E402
def session_event_ref(session: PokerSession) -> str: def session_event_ref(session: PokerSession) -> str:
+1
View File
@@ -0,0 +1 @@
+16 -15
View File
@@ -5,7 +5,7 @@ from urllib.parse import urlparse
from flask import Blueprint, flash, redirect, render_template, request, url_for from flask import Blueprint, flash, redirect, render_template, request, url_for
from auth import ( from ..auth import (
current_user_id, current_user_id,
generate_invite_token, generate_invite_token,
generate_reset_token, generate_reset_token,
@@ -18,8 +18,8 @@ from auth import (
verify_password, verify_password,
verify_reset_token, verify_reset_token,
) )
from db import database_extensions_available, db from ..db import database_extensions_available, db
from extensions import limiter from ..extensions import limiter
account_bp = Blueprint("account", __name__, url_prefix="/account") account_bp = Blueprint("account", __name__, url_prefix="/account")
@@ -49,7 +49,7 @@ def register():
} }
if request.method == "POST": if request.method == "POST":
from league_repositories import create_user, find_user_by_email from ..repositories.leagues 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", "")
@@ -88,7 +88,7 @@ def login():
} }
if request.method == "POST": if request.method == "POST":
from league_repositories import find_user_by_email from ..repositories.leagues 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", "")
@@ -122,7 +122,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 ..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:
@@ -139,7 +139,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 ..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:
@@ -154,7 +154,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 ..repositories.leagues 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:
@@ -174,7 +174,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 ..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:
@@ -206,7 +206,7 @@ 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, User, utc_now from ..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:
@@ -246,9 +246,10 @@ 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 flask import current_app from flask import current_app
from league_repositories import find_user_by_email
from ..emails import send_password_reset
from ..repositories.leagues 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)
@@ -282,7 +283,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 ..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:
@@ -320,8 +321,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 ..db_models import User
from league_repositories import add_league_member, find_league_by_id, find_membership from ..repositories.leagues import add_league_member, find_league_by_id, find_membership
user = db.session.get(User, current_user_id()) user = db.session.get(User, current_user_id())
if user is None or user.email != data.get("email"): if user is None or user.email != data.get("email"):
+4 -4
View File
@@ -16,16 +16,16 @@ from flask import (
url_for, url_for,
) )
from auth import is_admin from ..auth import is_admin
from services import ( from ..services import (
build_session_summaries, build_session_summaries,
next_session_id, next_session_id,
pending_payout_carry_items, pending_payout_carry_items,
prunable_empty_session_ids, prunable_empty_session_ids,
unique_player_names, unique_player_names,
) )
from storage import CSV_HEADERS, append_event, load_events, write_events from ..storage import CSV_HEADERS, append_event, load_events, write_events
from utils import cents_to_dollars, session_label from ..utils import cents_to_dollars, session_label
admin_bp = Blueprint("admin", __name__) admin_bp = Blueprint("admin", __name__)
+59 -58
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 ..auth import current_user_id, login_required, normalize_email
from charts import cumulative_profit_series, player_session_series from ..charts import cumulative_profit_series, player_session_series
from db import database_extensions_available, db from ..db import database_extensions_available, db
from models import SessionSummary from ..models import SessionSummary
from services import apply_rank_changes, build_leaderboard, build_session_summaries, session_events from ..services import apply_rank_changes, build_leaderboard, build_session_summaries, session_events
from utils import cents_to_dollars, session_label, session_sort_key from ..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 ..repositories.leagues 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 ..repositories.leagues 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 ..repositories.ledger import session_event_ref
from league_repositories import list_sessions_for_league from ..repositories.leagues 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 ..repositories.leagues import list_leagues_for_user
from ledger_repositories import list_event_rows_for_league from ..repositories.ledger 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 ..db_models import User
from league_repositories import create_league, unique_league_slug from ..repositories.leagues 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,7 +221,7 @@ 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 ..repositories.leagues import league_counts, 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:
@@ -242,7 +242,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 ..repositories.leagues 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:
@@ -259,9 +259,9 @@ 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 config import ELIGIBLE_MIN_SESSIONS from ..config import ELIGIBLE_MIN_SESSIONS
from ledger_repositories import list_event_rows_for_league from ..repositories.ledger import list_event_rows_for_league
from league_repositories import list_players_for_league, user_has_league_role from ..repositories.leagues import list_players_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:
@@ -366,12 +366,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 ..repositories.ledger 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 ..repositories.leagues 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,
@@ -506,7 +506,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 ..repositories.ledger 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()
@@ -529,8 +529,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 ..repositories.ledger import list_event_rows_for_league
from league_repositories import find_player_for_league, user_has_league_role from ..repositories.leagues 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:
@@ -588,7 +588,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 ..repositories.leagues import (
create_player, create_player,
list_players_for_league, list_players_for_league,
player_name_exists, player_name_exists,
@@ -627,7 +627,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 ..repositories.ledger 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) board = build_leaderboard(all_sessions)
@@ -661,7 +661,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 ..repositories.leagues 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)
@@ -681,7 +681,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 ..repositories.leagues 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)
@@ -709,8 +709,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 ..repositories.ledger import append_ledger_event
from league_repositories import ( from ..repositories.leagues import (
create_poker_session, create_poker_session,
list_sessions_for_league, list_sessions_for_league,
user_has_league_role, user_has_league_role,
@@ -747,7 +747,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 ..repositories.leagues import set_session_status
set_session_status(session, "open") set_session_status(session, "open")
@@ -765,8 +765,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 ..db_models import LedgerEvent
from ledger_repositories import list_event_rows_for_league from ..repositories.ledger 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))
@@ -803,8 +803,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 ..db_models import LedgerEvent, PokerSession
from league_repositories import list_sessions_for_league from ..repositories.leagues import list_sessions_for_league
league = require_league(league_ref, {"owner", "manager"}) league = require_league(league_ref, {"owner", "manager"})
@@ -839,7 +839,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 ..repositories.leagues 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)
@@ -859,7 +859,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 ..repositories.leagues 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)
@@ -877,7 +877,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 ..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,
@@ -900,8 +900,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 ..repositories.ledger import append_ledger_event, list_all_event_rows_for_session, list_event_rows_for_session
from league_repositories import ( from ..repositories.leagues import (
find_session_for_league, find_session_for_league,
list_players_for_league, list_players_for_league,
user_has_league_role, user_has_league_role,
@@ -989,9 +989,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 ..charts import session_breakdown_series
from ledger_repositories import list_event_rows_for_league, list_event_rows_for_session from ..repositories.ledger import list_event_rows_for_league, list_event_rows_for_session
from league_repositories import find_league_by_public_key, find_session_for_league, list_sessions_for_league, user_has_league_role from ..repositories.leagues import find_league_by_public_key, 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)
@@ -1062,8 +1062,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 ..repositories.ledger import append_ledger_event
from league_repositories import find_session_for_league, set_session_status from ..repositories.leagues 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)
@@ -1114,7 +1114,7 @@ def league_settings(league_ref: str):
elif form["visibility"] not in ("private", "public"): elif form["visibility"] not in ("private", "public"):
flash("Invalid visibility value.", "error") flash("Invalid visibility value.", "error")
else: else:
from utils import slugify from ..utils import slugify
league.name = form["name"] league.name = form["name"]
league.slug = slugify(form["name"]) league.slug = slugify(form["name"])
@@ -1124,7 +1124,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 ..repositories.leagues 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, members=members) return render_template("league_settings.html", league=league, form=form, is_owner=True, members=members)
@@ -1137,10 +1137,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 emails import send_league_invite
from flask import current_app from flask import current_app
from league_repositories import find_membership, find_user_by_email
from ..auth import generate_invite_token
from ..emails import send_league_invite
from ..repositories.leagues 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", ""))
@@ -1181,7 +1182,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 ..repositories.leagues import remove_league_member
league = require_league(league_ref, {"owner"}) league = require_league(league_ref, {"owner"})
@@ -1202,7 +1203,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 ..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()
@@ -1224,8 +1225,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 ..repositories.ledger import list_event_rows_for_league
from storage import CSV_HEADERS from ..storage import CSV_HEADERS
league = require_league(league_ref, {"owner", "manager", "viewer"}) league = require_league(league_ref, {"owner", "manager", "viewer"})
rows = list_event_rows_for_league(league.id) rows = list_event_rows_for_league(league.id)
@@ -1250,14 +1251,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 ..db_models import CANONICAL_EVENT_TYPES, canonical_event_type
from ledger_repositories import ( from ..repositories.ledger 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 list_players_for_league, list_sessions_for_league from ..repositories.leagues import list_players_for_league, list_sessions_for_league
from storage import CSV_HEADERS from ..storage import CSV_HEADERS
league = require_league(league_ref, {"owner", "manager"}) league = require_league(league_ref, {"owner", "manager"})
+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 ..charts import cumulative_profit_series, player_session_series, session_breakdown_series
from config import ELIGIBLE_MIN_SESSIONS from ..config import ELIGIBLE_MIN_SESSIONS
from services import ( from ..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 ..storage import load_events
from utils import session_label, session_sort_key from ..utils import session_label, session_sort_key
public_bp = Blueprint("public", __name__) public_bp = Blueprint("public", __name__)
@@ -30,9 +30,9 @@ def help():
@public_bp.get("/explore") @public_bp.get("/explore")
def explore(): def explore():
from auth import is_logged_in from ..auth import is_logged_in
from db import database_extensions_available from ..db import database_extensions_available
from league_repositories import league_counts, list_public_leagues from ..repositories.leagues 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 .models import PlayerStats, SessionEntry, SessionSummary
from storage import EventRow from .storage import EventRow
from utils import entry_sort_key, net_result_bucket, session_sort_key from .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"}
File renamed without changes.
File renamed without changes.
File renamed without changes.
View File
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
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 .models import SessionEntry, SessionSummary
BREAK_EVEN_TOLERANCE_CENTS = 100 BREAK_EVEN_TOLERANCE_CENTS = 100
+15 -13
View File
@@ -2,15 +2,17 @@ from pathlib import Path
import tempfile import tempfile
import unittest import unittest
import app as app_module from boker import create_app
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,
) )
from storage import load_events, write_events from boker.storage import load_events, write_events
from utils import session_sort_key from boker.utils import session_sort_key
_app = create_app()
def cents(amount: float) -> int: def cents(amount: float) -> int:
@@ -203,8 +205,8 @@ class AccountingTests(unittest.TestCase):
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
data_path = Path(tmpdir) / "entries.csv" data_path = Path(tmpdir) / "entries.csv"
write_events(data_path, rows) write_events(data_path, rows)
original_path = app_module.app.config["DATA_PATH"] original_path = _app.config["DATA_PATH"]
app_module.app.config["DATA_PATH"] = data_path _app.config["DATA_PATH"] = data_path
try: try:
sessions = build_session_summaries(load_events(data_path)) sessions = build_session_summaries(load_events(data_path))
@@ -213,7 +215,7 @@ class AccountingTests(unittest.TestCase):
[{"player_name": "A", "amount_cents": cents(5)}], [{"player_name": "A", "amount_cents": cents(5)}],
) )
with app_module.app.test_client() as client: with _app.test_client() as client:
with client.session_transaction() as flask_session: with client.session_transaction() as flask_session:
flask_session["is_admin"] = True flask_session["is_admin"] = True
@@ -222,7 +224,7 @@ class AccountingTests(unittest.TestCase):
data={"session_id": "s2", "player_name": "A"}, data={"session_id": "s2", "player_name": "A"},
) )
finally: finally:
app_module.app.config["DATA_PATH"] = original_path _app.config["DATA_PATH"] = original_path
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
updated_sessions = build_session_summaries(load_events(data_path)) updated_sessions = build_session_summaries(load_events(data_path))
@@ -261,17 +263,17 @@ class AccountingTests(unittest.TestCase):
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
data_path = Path(tmpdir) / "entries.csv" data_path = Path(tmpdir) / "entries.csv"
write_events(data_path, rows) write_events(data_path, rows)
original_path = app_module.app.config["DATA_PATH"] original_path = _app.config["DATA_PATH"]
app_module.app.config["DATA_PATH"] = data_path _app.config["DATA_PATH"] = data_path
try: try:
with app_module.app.test_client() as client: with _app.test_client() as client:
with client.session_transaction() as flask_session: with client.session_transaction() as flask_session:
flask_session["is_admin"] = True flask_session["is_admin"] = True
response = client.post("/admin/prune-empty-sessions") response = client.post("/admin/prune-empty-sessions")
finally: finally:
app_module.app.config["DATA_PATH"] = original_path _app.config["DATA_PATH"] = original_path
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
remaining = load_events(data_path) remaining = load_events(data_path)
+3 -2
View File
@@ -1,6 +1,7 @@
import unittest import unittest
from stats import SessionEntry, SessionSummary, build_leaderboard from boker.models import SessionEntry, SessionSummary
from boker.services import build_leaderboard
class CashInAccountingTest(unittest.TestCase): class CashInAccountingTest(unittest.TestCase):
@@ -78,7 +79,7 @@ class CashInAccountingTest(unittest.TestCase):
self.assertEqual(player.total_buy_in_cents, 2500) self.assertEqual(player.total_buy_in_cents, 2500)
self.assertEqual(player.total_front_collected_cents, 0) self.assertEqual(player.total_front_collected_cents, 0)
self.assertEqual(player.total_cash_in_cents, 3000) self.assertEqual(player.total_real_cash_in_cents, 2500)
if __name__ == "__main__": if __name__ == "__main__":