start project
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
"""Application factory (Chapter 02 / Chapter 04)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from flask import Flask, jsonify, redirect, request, url_for
|
||||
from flask_login import current_user
|
||||
from sqlalchemy import text
|
||||
|
||||
from app.budget import validate_budget_config
|
||||
from app.config import get_config
|
||||
from app.extensions import cache, csrf, db, limiter, login_manager, migrate
|
||||
from app.logging_setup import configure_logging
|
||||
from app.utils.assets import register_asset_helper
|
||||
|
||||
|
||||
def create_app(config_name: str | None = None) -> Flask:
|
||||
"""Build and configure the Flask application instance."""
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(get_config(config_name))
|
||||
validate_budget_config(app) # Chapter 03: fail loudly on out-of-budget config
|
||||
configure_logging(app) # Chapter 02 factor 11 / Chapter 12
|
||||
|
||||
if not app.config.get("SECRET_KEY") and not app.testing:
|
||||
raise RuntimeError("SECRET_KEY must be set via environment variable")
|
||||
|
||||
db.init_app(app)
|
||||
cache.init_app(app)
|
||||
csrf.init_app(app)
|
||||
login_manager.init_app(app)
|
||||
migrate.init_app(app, db)
|
||||
limiter.init_app(app) # Chapter 12: rate limiting
|
||||
|
||||
register_blueprints(app)
|
||||
register_cli(app)
|
||||
register_asset_helper(app)
|
||||
|
||||
@login_manager.unauthorized_handler
|
||||
def handle_unauthorized():
|
||||
"""Chapter 11 envelope compliance: JSON 401 for /api/... paths
|
||||
instead of Flask-Login's default redirect (which would hand back
|
||||
login HTML to a fetch()/HTMX JSON caller). Full-page routes still
|
||||
redirect to the login form as normal.
|
||||
"""
|
||||
if request.path.startswith("/api/"):
|
||||
return jsonify(error={"code": "unauthorized", "message": "Authentication required."}), 401
|
||||
return redirect(url_for("auth.login", next=request.path))
|
||||
|
||||
@app.get("/healthz")
|
||||
def healthz():
|
||||
"""Liveness/readiness check (Chapter 12) — DB reachable, no long-running work."""
|
||||
db.session.execute(text("SELECT 1"))
|
||||
return {"data": {"status": "ok"}, "meta": {}}
|
||||
|
||||
@app.get("/")
|
||||
def index():
|
||||
"""Root URL: authenticated -> Overview, otherwise -> the login page."""
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for("overview.overview"))
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def register_blueprints(app: Flask) -> None:
|
||||
"""Register one blueprint per dashboard section + support area (Ch. 04)."""
|
||||
from app.blueprints.api import bp as api_bp
|
||||
from app.blueprints.auth import bp as auth_bp
|
||||
from app.blueprints.overview import bp as overview_bp
|
||||
from app.blueprints.security import bp as security_bp
|
||||
from app.blueprints.seo import bp as seo_bp
|
||||
from app.blueprints.uploads import bp as uploads_bp
|
||||
|
||||
app.register_blueprint(overview_bp)
|
||||
app.register_blueprint(seo_bp)
|
||||
app.register_blueprint(security_bp)
|
||||
app.register_blueprint(uploads_bp)
|
||||
app.register_blueprint(api_bp)
|
||||
app.register_blueprint(auth_bp)
|
||||
|
||||
|
||||
def register_cli(app: Flask) -> None:
|
||||
"""Attach `flask <command>` admin processes (factor 12)."""
|
||||
from app.cli import register_commands
|
||||
|
||||
register_commands(app)
|
||||
Reference in New Issue
Block a user