refactor: decompose monolith — app.py 10,600→143 lines, 30+ modules
Fase 0 complete: extract all business logic, auth, database, and 176 endpoints from monolithic app.py into dedicated modules. Structure: - app.py: FastAPI hub (CORS, routers, startup/shutdown) - models.py: 13 Pydantic request models - utils.py: shared utilities (embed status, upload validation, process registries) - config.py: constants, env vars, model catalogs - auth/: crypto, jwt_auth, oidc, rate_limit - database/: db(), init_db(), schema DDL - services/: genai, compliance, chat, terraform, embeddings, cis_reports, mcp - routes/: 13 APIRouter modules (auth, users, oci_config, oci_explorer, genai, mcp, adb, embeddings, reports, chat, terraform, settings, cis_engine) Also: README updated with OCIR auth instructions for manual docker run. 86 tests passing.
This commit is contained in:
60
backend/utils.py
Normal file
60
backend/utils.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""Shared utilities — upload validation, embed status tracking, global process registries."""
|
||||
import json
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from fastapi import HTTPException, UploadFile
|
||||
from config import _EMBED_STATUS_DIR, _MAX_UPLOAD_BYTES
|
||||
|
||||
# ── Global process registries (used by routes and shutdown handler) ───────────
|
||||
running_reports: dict[str, asyncio.subprocess.Process] = {}
|
||||
running_terraform: dict[str, asyncio.subprocess.Process] = {}
|
||||
|
||||
|
||||
# ── Embed status (file-based) ────────────────────────────────────────────────
|
||||
def set_embed_status(task_id: str, data: dict):
|
||||
(_EMBED_STATUS_DIR / f"{task_id}.json").write_text(json.dumps(data), encoding="utf-8")
|
||||
|
||||
|
||||
def get_embed_status(task_id: str) -> dict | None:
|
||||
p = _EMBED_STATUS_DIR / f"{task_id}.json"
|
||||
if p.exists():
|
||||
try:
|
||||
return json.loads(p.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def update_embed_status(task_id: str, updates: dict):
|
||||
current = get_embed_status(task_id) or {}
|
||||
current.update(updates)
|
||||
set_embed_status(task_id, current)
|
||||
|
||||
|
||||
# ── File upload validation ────────────────────────────────────────────────────
|
||||
async def validate_upload(file: UploadFile, allowed_exts: set[str] | None = None, max_bytes: int = _MAX_UPLOAD_BYTES):
|
||||
"""Validate uploaded file size and extension. Returns file data bytes."""
|
||||
data = await file.read()
|
||||
if len(data) > max_bytes:
|
||||
raise HTTPException(400, f"Arquivo excede o limite de {max_bytes // (1024*1024)}MB")
|
||||
if allowed_exts and file.filename:
|
||||
ext = Path(file.filename).suffix.lower()
|
||||
if ext not in allowed_exts:
|
||||
raise HTTPException(400, f"Extensão '{ext}' não permitida. Aceitas: {', '.join(sorted(allowed_exts))}")
|
||||
return data
|
||||
|
||||
|
||||
# ── Report file classification ────────────────────────────────────────────────
|
||||
def classify_report_file(fname: str) -> str:
|
||||
"""Classify a report file into a category based on its filename."""
|
||||
fl = fname.lower()
|
||||
if "summary_report" in fl: return "summary"
|
||||
if "error_report" in fl or "error" in fl and fl.endswith(".csv"): return "error"
|
||||
if fl.startswith("obp_") and "findings" in fl: return "obp_finding"
|
||||
if fl.startswith("obp_") and "best_practices" in fl: return "obp_best_practice"
|
||||
if fl.startswith("obp_"): return "obp_finding"
|
||||
if fl.startswith("raw_data_"): return "raw_data"
|
||||
if fl.startswith("cis_"): return "cis_finding"
|
||||
if "consolidated_report" in fl: return "consolidated"
|
||||
if fl.endswith(".png"): return "diagram"
|
||||
return "other"
|
||||
Reference in New Issue
Block a user