fix: harden API, add rate limiting, disable debug by default

- Add input validation and error handling across all API endpoints
  (malformed JSON, missing fields, invalid types return proper 4xx)
- Add per-IP rate limiting (10/min new games, 60/min other requests)
- Add session rollback on unhandled exceptions
- Default debug=False, add --debug CLI flag to opt in
- Frontend: add fetch error handling, fix feedback timeout stacking,
  disable buttons during requests, allow clicking found cells for
  overlapping word selection
- Expand seed data to 7 categories with 25-35 words each

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mathew Sir Guest the best
2026-05-07 00:59:47 -06:00
co-authored by Claude Opus 4.6
parent 4d681a92ca
commit cd1dc9200b
11 changed files with 708 additions and 110 deletions
+6 -4
View File
@@ -21,12 +21,13 @@ from handlers.api import (
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
def make_app(session_factory=None):
def make_app(session_factory=None, debug=False):
"""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).
Returns:
A configured ``tornado.web.Application`` instance.
@@ -45,18 +46,19 @@ def make_app(session_factory=None):
template_path=os.path.join(BASE_DIR, "templates"),
static_path=os.path.join(BASE_DIR, "static"),
session_factory=session_factory,
debug=True,
debug=debug,
)
@click.command()
@click.option("--port", default=8888, type=int, help="Port to listen on.")
def main(port):
@click.option("--debug", is_flag=True, help="Enable debug mode.")
def main(port, debug):
"""Start the web server."""
engine = get_engine()
init_db(engine)
session_factory = get_session_factory(engine)
app = make_app(session_factory=session_factory)
app = make_app(session_factory=session_factory, debug=debug)
app.listen(port)
print(f"Server started at http://localhost:{port}")
tornado.ioloop.IOLoop.current().start()