96 lines
2.9 KiB
Python
96 lines
2.9 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import timedelta
|
|
from logging.handlers import RotatingFileHandler
|
|
from pathlib import Path
|
|
|
|
from flask import Flask
|
|
from flask_sqlalchemy import SQLAlchemy
|
|
from sqlalchemy import event
|
|
from sqlalchemy.engine import Engine
|
|
from sqlalchemy.engine.url import make_url
|
|
|
|
from .services import Office365Service
|
|
from .settings import Settings, load_settings
|
|
|
|
|
|
db = SQLAlchemy()
|
|
|
|
|
|
def _ensure_sqlite_directory(database_url: str) -> None:
|
|
url = make_url(database_url)
|
|
if url.drivername != "sqlite" or not url.database or url.database == ":memory:":
|
|
return
|
|
Path(url.database).parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
def _configure_logging(app: Flask) -> None:
|
|
log_dir = Path(app.root_path).parent / "logs"
|
|
log_dir.mkdir(parents=True, exist_ok=True)
|
|
log_path = log_dir / "office365_self_service.log"
|
|
|
|
root_logger = logging.getLogger()
|
|
formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s - %(message)s")
|
|
|
|
if not any(isinstance(handler, RotatingFileHandler) for handler in root_logger.handlers):
|
|
file_handler = RotatingFileHandler(
|
|
log_path,
|
|
maxBytes=2 * 1024 * 1024,
|
|
backupCount=5,
|
|
encoding="utf-8",
|
|
)
|
|
file_handler.setFormatter(formatter)
|
|
root_logger.addHandler(file_handler)
|
|
|
|
root_logger.setLevel(logging.INFO)
|
|
app.logger.info("Logging initialized at %s", log_path)
|
|
|
|
|
|
@event.listens_for(Engine, "connect")
|
|
def set_sqlite_pragma(dbapi_conn, connection_record):
|
|
if "sqlite" in str(type(dbapi_conn)):
|
|
cursor = dbapi_conn.cursor()
|
|
cursor.execute("PRAGMA foreign_keys=ON")
|
|
cursor.close()
|
|
|
|
|
|
def create_app(
|
|
settings_override: Settings | None = None,
|
|
service_factory=None,
|
|
) -> Flask:
|
|
settings = settings_override or load_settings()
|
|
app = Flask(__name__, template_folder="templates", static_folder="static")
|
|
app.config["SETTINGS"] = settings
|
|
app.config["JSON_AS_ASCII"] = False
|
|
app.config["SECRET_KEY"] = settings.session_secret
|
|
app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(hours=8)
|
|
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
|
|
app.config["SQLALCHEMY_DATABASE_URI"] = settings.database_url
|
|
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
|
|
|
_configure_logging(app)
|
|
_ensure_sqlite_directory(settings.database_url)
|
|
|
|
db.init_app(app)
|
|
|
|
with app.app_context():
|
|
from .models import AuditEvent, RedemptionCode
|
|
db.create_all()
|
|
|
|
if service_factory is None:
|
|
service = Office365Service(settings)
|
|
elif callable(service_factory):
|
|
service = service_factory(settings)
|
|
else:
|
|
service = service_factory
|
|
|
|
app.extensions["office365_service"] = service
|
|
|
|
from .routes import bp_admin, bp_user
|
|
|
|
app.register_blueprint(bp_admin)
|
|
app.register_blueprint(bp_user)
|
|
|
|
return app
|