Files
mimic/backend/app/__init__.py
Knacky 1f327e9aa8 feat(backend): sprint 5 — SimulationTemplate CRUD + instantiation
- SimulationTemplate model + migration 0005 (CREATE TABLE + name index)
- 5 CRUD endpoints under /api/templates (admin|redteam only, SOC 403)
- POST /api/engagements/<eid>/simulations extended with optional template_id
- serialize_template() reusing _enrich_techniques/_enrich_tactics helpers
- IntegrityError → 409 for duplicate name on both POST and PATCH
- 28 new tests (CRUD, RBAC, dedup, instantiation, migration round-trip)
- 221 tests pass; ruff clean; mypy clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 06:25:19 +02:00

77 lines
2.7 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, templates_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)
app.register_blueprint(templates_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