84 lines
2.3 KiB
Python
84 lines
2.3 KiB
Python
"""Application entry point: wires slices, cross-cutting request rules, and JSON error handling."""
|
|
|
|
import logging
|
|
|
|
import psycopg2.errors
|
|
from flask import Flask, g, jsonify, request
|
|
from werkzeug.exceptions import HTTPException
|
|
|
|
import auth
|
|
import books
|
|
import db
|
|
import notes
|
|
from validation import ApiError
|
|
|
|
log = logging.getLogger(__name__)
|
|
MUTATING_METHODS = {"POST", "PUT", "PATCH", "DELETE"}
|
|
|
|
|
|
def create_app() -> Flask:
|
|
logging.basicConfig(
|
|
level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s"
|
|
)
|
|
app = Flask(__name__)
|
|
app.config["MAX_CONTENT_LENGTH"] = 1024 * 1024
|
|
db.init_app(app)
|
|
auth.init_app(app)
|
|
app.register_blueprint(books.bp)
|
|
app.register_blueprint(notes.bp)
|
|
|
|
@app.get("/api/health")
|
|
def health():
|
|
return {"status": "ok"}
|
|
|
|
@app.before_request
|
|
def require_json_for_mutations():
|
|
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):
|
|
log.info(
|
|
"%s %s -> %s user=%s",
|
|
request.method,
|
|
request.path,
|
|
response.status_code,
|
|
g.get("user_id"),
|
|
)
|
|
return response
|
|
|
|
@app.errorhandler(ApiError)
|
|
def handle_api_error(error: ApiError):
|
|
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
|
|
|
|
@app.errorhandler(psycopg2.errors.CheckViolation)
|
|
def handle_check_violation(error: psycopg2.errors.CheckViolation):
|
|
return (
|
|
jsonify(
|
|
error=f"Value breaks data rule '{error.diag.constraint_name}'; correct it and retry"
|
|
),
|
|
400,
|
|
)
|
|
|
|
@app.errorhandler(Exception)
|
|
def handle_unexpected(_error: Exception):
|
|
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)
|