"""Flask application factory.""" from __future__ import annotations import os from pathlib import Path from flask import Flask, jsonify, send_from_directory from backend.app.api import auth_bp, engagements_bp, simulations_bp, users_bp from backend.app.cli import register_cli from backend.app.config import Config, TestConfig from backend.app.errors import register_error_handlers from backend.app.extensions import db, migrate def create_app(config_object: object | None = None) -> Flask: """Application factory. `config_object` is an *instance* (not a class) of Config or a subclass. If None, picks TestConfig when MIMIC_TESTING=1, otherwise Config. """ static_folder = str(Path(__file__).parent / "static") app = Flask(__name__, static_folder=static_folder, static_url_path="/static") if config_object is None: config_object = TestConfig() if os.environ.get("MIMIC_TESTING") == "1" else Config() app.config.from_object(config_object) db.init_app(app) migrations_dir = str(Path(__file__).parent.parent / "migrations") migrate.init_app(app, db, directory=migrations_dir) # Ensure models are imported so Alembic/metadata see them. from backend.app import models # noqa: F401 app.register_blueprint(auth_bp) app.register_blueprint(users_bp) app.register_blueprint(engagements_bp) app.register_blueprint(simulations_bp) from backend.app.services import mitre as mitre_svc mitre_svc.load_bundle() register_error_handlers(app) register_cli(app) static_root = Path(static_folder) @app.get("/api/health") def health(): return {"status": "ok"}, 200 # Serve the built frontend (Vite output copied into app/static at image build time). @app.get("/") def index(): index_path = static_root / "index.html" if index_path.exists(): return send_from_directory(static_folder, "index.html") return {"status": "ok", "message": "Mimic API running. Frontend not built."}, 200 @app.get("/") def spa_fallback(path: str): # Unknown /api/* paths must stay JSON 404 — never shadowed by index.html. if path.startswith("api/"): return jsonify({"error": "Not found"}), 404 # Serve static assets if present; otherwise hand back index.html for client routing. candidate = static_root / path if candidate.is_file(): return send_from_directory(static_folder, path) index_path = static_root / "index.html" if index_path.exists(): return send_from_directory(static_folder, "index.html") return jsonify({"error": "Not found"}), 404 return app