feat: add scoreboard with player names, per-category leaderboards, and themed tables

Add player_name column to games (migration included), with regex-based
validation and anti-abuse sanitization. New /scoreboard page shows recent
games from localStorage, popular categories with play counts, and top-10
per-category leaderboards sorted by completion time. Also includes
two-click reverse word matching, base URL prefix support for reverse-proxy
hosting, BasePageHandler refactoring, themed table CSS for all 5 themes,
and comprehensive test coverage for player names and scoreboard API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mathew Sir Guest the best
2026-05-07 10:38:19 -06:00
co-authored by Claude Opus 4.6
parent 9319f08875
commit 36c3c712fe
27 changed files with 3038 additions and 24 deletions
+15 -3
View File
@@ -23,13 +23,14 @@ from handlers.api import (
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
def make_app(session_factory=None, debug=False):
def make_app(session_factory=None, debug=False, prefix="/"):
"""Create and configure the Tornado application.
Args:
session_factory: SQLAlchemy session factory. If None, API
endpoints that require a database will not work.
debug: Enable debug mode (auto-reload, stack traces).
prefix: URL prefix for reverse-proxy hosting (e.g. ``/wordsearch/``).
Returns:
A configured ``tornado.web.Application`` instance.
@@ -50,6 +51,7 @@ def make_app(session_factory=None, debug=False):
template_path=os.path.join(BASE_DIR, "templates"),
static_path=os.path.join(BASE_DIR, "static"),
session_factory=session_factory,
base_url=prefix,
debug=debug,
)
@@ -57,12 +59,22 @@ def make_app(session_factory=None, debug=False):
@click.command()
@click.option("--port", default=8888, type=int, help="Port to listen on.")
@click.option("--debug", is_flag=True, help="Enable debug mode.")
def main(port, debug):
@click.option(
"--prefix",
default="/",
help="URL prefix for reverse-proxy hosting (e.g. /wordsearch/).",
)
def main(port, debug, prefix):
"""Start the web server."""
if not prefix.startswith("/"):
prefix = "/" + prefix
if not prefix.endswith("/"):
prefix = prefix + "/"
engine = get_engine()
init_db(engine)
session_factory = get_session_factory(engine)
app = make_app(session_factory=session_factory, debug=debug)
app = make_app(session_factory=session_factory, debug=debug, prefix=prefix)
app.listen(port)
print(f"Server started at http://localhost:{port}")
tornado.ioloop.IOLoop.current().start()