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:
+14
-12
@@ -3,7 +3,8 @@
|
||||
import logging
|
||||
|
||||
import psycopg2.errors
|
||||
from flask import Flask, g, jsonify, request
|
||||
from flask import Flask, Response, g, jsonify, request
|
||||
from flask.typing import ResponseReturnValue
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
import auth
|
||||
@@ -28,18 +29,19 @@ def create_app() -> Flask:
|
||||
app.register_blueprint(notes.bp)
|
||||
|
||||
@app.get("/api/health")
|
||||
def health():
|
||||
@auth.allow_anonymous
|
||||
def health() -> ResponseReturnValue:
|
||||
return {"status": "ok"}
|
||||
|
||||
@app.before_request
|
||||
def require_json_for_mutations():
|
||||
def require_json_for_mutations() -> None:
|
||||
if request.method in MUTATING_METHODS and not request.is_json:
|
||||
raise ApiError(
|
||||
400, "Request body must be JSON with Content-Type: application/json"
|
||||
)
|
||||
|
||||
@app.after_request
|
||||
def log_request(response):
|
||||
def log_request(response: Response) -> Response:
|
||||
log.info(
|
||||
"%s %s -> %s user=%s",
|
||||
request.method,
|
||||
@@ -50,18 +52,20 @@ def create_app() -> Flask:
|
||||
return response
|
||||
|
||||
@app.errorhandler(ApiError)
|
||||
def handle_api_error(error: ApiError):
|
||||
def handle_api_error(error: ApiError) -> ResponseReturnValue:
|
||||
body = {"error": error.message}
|
||||
if error.field:
|
||||
body["field"] = error.field
|
||||
return jsonify(body), error.status
|
||||
|
||||
@app.errorhandler(HTTPException)
|
||||
def handle_http_error(error: HTTPException):
|
||||
return jsonify(error=error.description), error.code
|
||||
def handle_http_error(error: HTTPException) -> ResponseReturnValue:
|
||||
return jsonify(error=error.description), error.code or 500
|
||||
|
||||
@app.errorhandler(psycopg2.errors.CheckViolation)
|
||||
def handle_check_violation(error: psycopg2.errors.CheckViolation):
|
||||
def handle_check_violation(
|
||||
error: psycopg2.errors.CheckViolation,
|
||||
) -> ResponseReturnValue:
|
||||
return (
|
||||
jsonify(
|
||||
error=f"Value breaks data rule '{error.diag.constraint_name}'; correct it and retry"
|
||||
@@ -70,14 +74,12 @@ def create_app() -> Flask:
|
||||
)
|
||||
|
||||
@app.errorhandler(Exception)
|
||||
def handle_unexpected(_error: Exception):
|
||||
def handle_unexpected(_error: Exception) -> ResponseReturnValue:
|
||||
log.exception("Unhandled error on %s %s", request.method, request.path)
|
||||
return jsonify(error="Unexpected server error"), 500
|
||||
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="127.0.0.1", port=5000)
|
||||
create_app().run(host="127.0.0.1", port=5000)
|
||||
|
||||
Reference in New Issue
Block a user