Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion backend/app/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import os
import time
from datetime import datetime, timezone
from sqlalchemy import create_engine, Column, Integer, String, DateTime, JSON, Text
from sqlalchemy import create_engine, Column, Integer, String, DateTime, JSON, Text, inspect, text
from sqlalchemy.orm import declarative_base, sessionmaker

DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:////app/reports/open_easm_dev.sqlite3")
Expand Down Expand Up @@ -34,6 +34,7 @@ class AuditRecord(Base):
excel_filename = Column(String(255), nullable=True)
json_filename = Column(String(255), nullable=True)
pdf_filename = Column(String(255), nullable=True)
html_filename = Column(String(255), nullable=True)
audit_json = Column(JSON, nullable=False)

class FindingRecord(Base):
Expand All @@ -50,11 +51,24 @@ class FindingRecord(Base):
fingerprint = Column(String(512), index=True, nullable=False)
finding_json = Column(JSON, nullable=False)

def _ensure_audit_html_column() -> None:
"""Add html_filename to existing databases without breaking older audits."""
inspector = inspect(engine)
if "audits" not in inspector.get_table_names():
return
columns = {column["name"] for column in inspector.get_columns("audits")}
if "html_filename" in columns:
return
with engine.begin() as connection:
connection.execute(text("ALTER TABLE audits ADD COLUMN html_filename VARCHAR(255)"))


def init_db_with_retry(retries: int = 30, delay: float = 2.0):
last_error = None
for _ in range(retries):
try:
Base.metadata.create_all(bind=engine)
_ensure_audit_html_column()
return
except Exception as exc:
last_error = exc
Expand Down
47 changes: 47 additions & 0 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from app.reports.excel_report import generate_excel_report
from app.reports.json_report import generate_json_report
from app.reports.pdf_report import generate_pdf_report
from app.reports.html_report import generate_html_report
from app.database import init_db_with_retry, get_db
from app.services.storage import save_audit, list_audits, get_audit_record, dashboard_stats, compare_latest, delete_audit, delete_all_audits, delete_domain_audits
from app.services.domain_verification import start_verification, check_verification, get_domain_status, list_verified_domains, delete_verification, serialize_verification
Expand Down Expand Up @@ -211,6 +212,7 @@ async def create_audit(payload: AuditRequest, request: Request, db=Depends(get_d
},
"report_filename": None,
"json_filename": None,
"html_filename": None,
}

audit["attack_graph"] = build_attack_graph(audit)
Expand All @@ -234,6 +236,12 @@ async def create_audit(payload: AuditRequest, request: Request, db=Depends(get_d
except Exception as exc:
audit["report_errors"].append(f"PDF: {exc}")

try:
html_filename = generate_html_report(audit)
audit["html_filename"] = html_filename
except Exception as exc:
audit["report_errors"].append(f"HTML: {exc}")

AUDITS[audit_id] = audit
save_audit(db, audit)

Expand Down Expand Up @@ -305,6 +313,8 @@ async def create_audit(payload: AuditRequest, request: Request, db=Depends(get_d
"report_url": f"/api/reports/{audit_id}/excel",
"json_url": f"/api/reports/{audit_id}/json",
"pdf_url": f"/api/reports/{audit_id}/pdf",
"html_filename": audit.get("html_filename"),
"html_url": f"/api/reports/{audit_id}/html",
"graph_url": f"/api/audits/{audit_id}/graph",
"attack_graph": audit.get("attack_graph", {}),
"report_errors": audit.get("report_errors", []),
Expand Down Expand Up @@ -409,12 +419,15 @@ async def api_reports_center(limit: int = 100, domain: str | None = None, db=Dep
"excel_filename": r.excel_filename,
"json_filename": r.json_filename,
"pdf_filename": r.pdf_filename,
"html_filename": r.html_filename or (r.audit_json or {}).get("html_filename"),
"excel_exists": bool(r.excel_filename and (REPORT_DIR / r.excel_filename).exists()),
"json_exists": bool(r.json_filename and (REPORT_DIR / r.json_filename).exists()),
"pdf_exists": bool(r.pdf_filename and (REPORT_DIR / r.pdf_filename).exists()),
"html_exists": bool((r.html_filename or (r.audit_json or {}).get("html_filename")) and (REPORT_DIR / (r.html_filename or (r.audit_json or {}).get("html_filename"))).exists()),
"excel_url": f"/api/reports/{r.id}/excel",
"json_url": f"/api/reports/{r.id}/json",
"pdf_url": f"/api/reports/{r.id}/pdf",
"html_url": f"/api/reports/{r.id}/html",
})
return {
"count": len(items),
Expand Down Expand Up @@ -548,6 +561,40 @@ async def download_json_report(audit_id: str, db=Depends(get_db)):
filename=filename,
)


@app.get("/api/reports/{audit_id}/html")
async def download_html_report(audit_id: str, db=Depends(get_db)):
audit = AUDITS.get(audit_id)
record = None
if not audit:
record = get_audit_record(db, audit_id)
if record:
audit = record.audit_json
else:
raise HTTPException(status_code=404, detail="Audit introuvable.")

filename = audit.get("html_filename") or (record.html_filename if record else None)
path = REPORT_DIR / filename if filename else None
if not filename or not path or not path.exists():
try:
filename = generate_html_report(audit)
audit["html_filename"] = filename
if audit_id in AUDITS:
AUDITS[audit_id] = audit
save_audit(db, audit)
except Exception as exc:
raise HTTPException(status_code=500, detail=f"Impossible de générer le rapport HTML: {exc}")
path = REPORT_DIR / filename

if not path.exists():
raise HTTPException(status_code=404, detail="Fichier HTML introuvable.")

return FileResponse(
path,
media_type="text/html",
filename=filename,
)

@app.get("/api/reports/{audit_id}/pdf")
async def download_pdf_report(audit_id: str, db=Depends(get_db)):
audit = AUDITS.get(audit_id)
Expand Down
Loading
Loading