CP_EHRENAMT/app/__init__.py
2026-06-25 18:58:44 +02:00

124 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""App-Factory für die CheckPoint-Ehrenamt-App.
Baut die Flask-App zusammen, initialisiert die Datenbank (SQLite in instance/)
und registriert die Blueprints. Mandantenfähigkeit wird über das Team-Scoping
(app/team_scope.py) vorbereitet die echte Logik folgt in späteren Sessions.
"""
import os
from datetime import timezone
from zoneinfo import ZoneInfo
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import event
from sqlalchemy.engine import Engine
_BERLIN = ZoneInfo("Europe/Berlin")
@event.listens_for(Engine, "connect")
def _sqlite_pragmas(dbapi_connection, connection_record):
"""Nur für SQLite: WAL + busy_timeout machen parallele gunicorn-Worker
verträglich (statt „database is locked"), Fremdschlüssel werden erzwungen.
Bei anderen Engines (z. B. späteres PostgreSQL) ein No-op."""
import sqlite3
if isinstance(dbapi_connection, sqlite3.Connection):
cur = dbapi_connection.cursor()
cur.execute("PRAGMA journal_mode=WAL")
cur.execute("PRAGMA busy_timeout=5000")
cur.execute("PRAGMA foreign_keys=ON")
cur.close()
def _lokalzeit(dt, fmt="%d.%m.%Y %H:%M"):
"""Jinja-Filter: UTC-Zeitstempel (in SQLite naiv) → Europe/Berlin formatiert."""
if dt is None:
return ""
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return dt.astimezone(_BERLIN).strftime(fmt)
# Eine zentrale DB-Instanz, die die Modelle (Session 2) importieren.
db = SQLAlchemy()
def create_app(test_config=None):
# instance_relative_config: Pfade wie die SQLite-DB liegen in instance/.
app = Flask(__name__, instance_relative_config=True)
# CP_ENV=production schaltet die Härtung scharf (sichere Cookies, SECRET_KEY Pflicht).
ist_produktion = os.environ.get("CP_ENV") == "production"
secret = os.environ.get("SECRET_KEY")
if ist_produktion and not secret:
raise RuntimeError(
"SECRET_KEY muss in Produktion gesetzt sein (CP_ENV=production)."
)
app.config.from_mapping(
# In Produktion über Umgebungsvariable setzen, nie hart im Code.
SECRET_KEY=secret or "dev-only-change-me",
SQLALCHEMY_DATABASE_URI="sqlite:///"
+ os.path.join(app.instance_path, "checkpoint.sqlite"),
SQLALCHEMY_TRACK_MODIFICATIONS=False,
# Dokument-Uploads (Session 10): außerhalb von static/, max. 10 MB.
UPLOAD_FOLDER=os.path.join(app.instance_path, "uploads"),
MAX_CONTENT_LENGTH=10 * 1024 * 1024,
# Cookie-Härtung. Secure nur in Produktion (sonst bricht http-Login lokal).
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE="Lax",
SESSION_COOKIE_SECURE=ist_produktion,
)
if test_config is not None:
app.config.from_mapping(test_config)
# instance/ + Upload-Ordner sicherstellen (für DB + Dokumente).
os.makedirs(app.instance_path, exist_ok=True)
os.makedirs(app.config["UPLOAD_FOLDER"], exist_ok=True)
# Hinter nginx: X-Forwarded-Proto/-For/-Host auswerten (korrektes https-Schema).
if ist_produktion:
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)
db.init_app(app)
# CSRF-Schutz für alle POST-Formulare.
from .csrf import init_csrf
init_csrf(app)
# Lokalzeit-Filter (einheitliche Anzeige in Europe/Berlin).
app.jinja_env.filters["lokalzeit"] = _lokalzeit
# Modelle importieren, damit sie bei create_all() bekannt sind.
# (Noch leer die Tabellen kommen in Session 2.)
from . import models # noqa: F401
with app.app_context():
db.create_all()
# Blueprints registrieren.
from .routes import main_bp
from .routes.planung import bp as planung_bp
from .routes.dienste import bp as dienste_bp
from .routes.dokumente import bp as dokumente_bp
from .routes.chat import bp as chat_bp
from .routes.profil import bp as profil_bp
from .auth import bp as auth_bp, register_cli
app.register_blueprint(main_bp)
app.register_blueprint(auth_bp)
app.register_blueprint(planung_bp)
app.register_blueprint(dienste_bp)
app.register_blueprint(dokumente_bp)
app.register_blueprint(chat_bp)
app.register_blueprint(profil_bp)
# CLI-Befehle (z. B. flask create-admin).
register_cli(app)
return app