Files
mimic/backend/app/__init__.py
Knacky 006c4c2c5f feat(backend): sprint 2 — simulations + MITRE ATT&CK
- Simulation model with full field set (redteam + SOC sides) and cascade delete
- Alembic migration 0002 for simulations table
- simulation_workflow service: PATCH RBAC field-level + auto-transition pending→in_progress + state machine
- mitre service: STIX bundle loader (boot-safe) + ranked search (exact-id > prefix-id > name)
- 7 new API endpoints: list/create/get/patch/delete simulations, transition, MITRE autocomplete
- serialize_simulation added to serializers.py
- Makefile update-mitre target with real curl + optional docker restart
- Dockerfile updated to copy backend/data/ into image
- MITRE enterprise-attack.json bundle committed (~45 MB)
- 67 new tests (total 130 passing), ruff clean, mypy introduces no new errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 10:59:14 +02:00

76 lines
2.6 KiB
Python

"""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("/<path:path>")
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