feat: auth by default, theme toggle, CoderPad startup, review fixes
- Require a session on every route; public routes opt out with @allow_anonymous - Split password hashing and pepper loading into passwords.py - Add a system/light/dark theme toggle backed by light-dark() colors - Ignore stale 401s from an earlier session, PATCH only changed book fields, and block overlapping journal-entry saves - Add bin/start and CoderPad Vite server settings for the pad's start/restart - Rewrite README as a mise onboarding guide; expand .gitignore - Include review-round fixes and tests
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
"""Request validation shared by every slice. Errors carry a status, a fix-it message, and the field."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from flask import request
|
||||
|
||||
JsonObject = dict[str, Any]
|
||||
|
||||
|
||||
class ApiError(Exception):
|
||||
def __init__(self, status: int, message: str, field: str | None = None):
|
||||
@@ -11,7 +15,7 @@ class ApiError(Exception):
|
||||
self.field = field
|
||||
|
||||
|
||||
def json_body(allowed: set[str]) -> dict:
|
||||
def json_body(allowed: set[str]) -> JsonObject:
|
||||
body = request.get_json(silent=True)
|
||||
if not isinstance(body, dict):
|
||||
raise ApiError(400, "Request body must be a JSON object")
|
||||
@@ -36,7 +40,7 @@ def require_utf8(value: str, field: str) -> None:
|
||||
) from None
|
||||
|
||||
|
||||
def text(body: dict, field: str, max_len: int) -> str:
|
||||
def text(body: JsonObject, field: str, max_len: int) -> str:
|
||||
value = body.get(field)
|
||||
if not isinstance(value, str) or not value.strip():
|
||||
raise ApiError(400, f"{field} is required and must be non-blank text", field)
|
||||
@@ -49,7 +53,7 @@ def text(body: dict, field: str, max_len: int) -> str:
|
||||
return value
|
||||
|
||||
|
||||
def integer(body: dict, field: str, low: int, high: int) -> int:
|
||||
def integer(body: JsonObject, field: str, low: int, high: int) -> int:
|
||||
value = body.get(field)
|
||||
if isinstance(value, bool) or not isinstance(value, int):
|
||||
raise ApiError(400, f"{field} must be a whole number", field)
|
||||
|
||||
Reference in New Issue
Block a user