agilentics / boiler
"""Flask entrypoint.

App Engine runs this as `gunicorn -c gunicorn.py main:app`, so `app` must exist
at module scope.
"""
import logging
from datetime import timedelta

from flask import Flask, render_template

import settings
from app import routes
from utils.db_wrapper import db_session

logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.INFO)
logger = logging.getLogger(__name__)

app = Flask(__name__)

# The session cookie is a credential: it is what says a request is signed in.
# Signing it with the shipped development key would let anyone forge one, so a
# deployed environment refuses to start rather than running with a known secret.
# Empty is included because a deploy that forgets to render SECRET_KEY leaves it "".
_WEAK_SECRETS = {"", "dev-only-not-for-production", "changeme", "secret"}
if settings.Config.ENV == "prod" and settings.Config.SECRET_KEY.strip() in _WEAK_SECRETS:
    raise RuntimeError(
        "SECRET_KEY is unset or still a placeholder. Set a real one before "
        "running in prod - it signs the session cookie, and the cookie is what "
        "says a request is authenticated."
    )

app.config["SECRET_KEY"] = settings.Config.SECRET_KEY
app.config.update(
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SAMESITE="Lax",
    SESSION_COOKIE_SECURE=settings.Config.ENV == "prod",
    PERMANENT_SESSION_LIFETIME=timedelta(days=14),
)

routes.init_app(app)


@app.context_processor
def inject_template_globals():
    """`csrf_token` renders into a meta tag on every page; app.js reads it and
    echoes it on state-changing requests (see endpoints/crud.check_csrf).

    `google_enabled` lets the sign-in page show the Google button only when it is
    configured - the same "unset config = feature hidden" stance the rest of the
    app takes. Both are passed as functions, so a template calls them."""
    from app.endpoints.crud import csrf_token
    from app.endpoints.oauth import is_enabled as google_enabled
    return {"csrf_token": csrf_token, "google_enabled": google_enabled}


@app.teardown_appcontext
def shutdown_session(exception=None):
    """Return the request's connection to the pool. Without this, scoped_session
    leaks a connection per request until the pool is exhausted."""
    db_session.remove()


@app.route("/")
def home():
    # Reached signed-in only: "/" is not in PUBLIC_PATHS, so require_session has
    # already redirected a signed-out visitor to /login. If this app should have
    # a public landing page, add "/" to that allowlist and decide here what a
    # stranger sees.
    return render_template("home.html")


# App Engine's frontend swallows /healthz before it reaches the app, so /health
# is the alias that actually works there; both are kept so the same app is
# probeable locally and anywhere else.
@app.route("/health")
@app.route("/healthz")
def healthz():
    return {"status": "ok"}


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=8080, debug=True)