"""Flask application factory.""" from __future__ import annotations from datetime import timedelta from flask import Flask, jsonify from flask.typing import ResponseReturnValue from mimic.api import register_blueprints from mimic.auth.identity import load_user from mimic.config import Settings, get_settings from mimic.extensions import db, login_manager, migrate, socketio from mimic.logging import configure_logging def create_app(settings: Settings | None = None) -> Flask: settings = settings or get_settings() configure_logging(settings.log_level, as_json=settings.log_json) app = Flask(__name__) app.config.update( SECRET_KEY=settings.secret_key.get_secret_value(), SQLALCHEMY_DATABASE_URI=settings.database_url, SQLALCHEMY_TRACK_MODIFICATIONS=False, SESSION_COOKIE_SECURE=settings.session_cookie_secure, SESSION_COOKIE_SAMESITE=settings.session_cookie_samesite, SESSION_COOKIE_HTTPONLY=True, PERMANENT_SESSION_LIFETIME=timedelta(minutes=settings.session_lifetime_minutes), MIMIC_SETTINGS=settings, ) db.init_app(app) migrate.init_app(app, db, directory="src/mimic/db/migrations") login_manager.init_app(app) login_manager.user_loader(load_user) @login_manager.unauthorized_handler # type: ignore[untyped-decorator] def _unauthorized() -> ResponseReturnValue: # API returns JSON; never redirect to a login page. return ( jsonify({"error": "not_authenticated", "message": "no active session"}), 401, ) socketio.init_app( app, cors_allowed_origins=settings.cors_origins or "*", async_mode="gevent", ) _enable_cors_in_dev(app, settings) register_blueprints(app) @app.get("/healthz") def healthz() -> ResponseReturnValue: return jsonify(status="ok"), 200 return app def _enable_cors_in_dev(app: Flask, settings: Settings) -> None: """Dev-only CORS for the Vite frontend on http://localhost:5173. In production, the reverse proxy (Caddy + same-origin) terminates this concern; enabling CORS there would expand the CSRF surface for no benefit. """ if settings.env != "development": return if not settings.cors_origins: return from flask_cors import CORS # noqa: PLC0415 — keeps the prod import path lean CORS( app, resources={r"/api/*": {"origins": settings.cors_origins}}, supports_credentials=True, methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"], allow_headers=["Content-Type", "X-Requested-With"], max_age=600, )