from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, desc from typing import List from datetime import datetime import json from app.db.session import get_db from app.models.models import AuditReport, Finding, RiskScore, Executive, AuthorizedAsset, FindingStatus from app.schemas.schemas import AuditReportOut, AuditReportCreate from app.core.security import require_admin router = APIRouter(prefix="/audit-reports", tags=["audit-reports"]) @router.get("", response_model=List[AuditReportOut]) async def list_reports( tenant_id: str = Query(...), payload: dict = Depends(require_admin), db: AsyncSession = Depends(get_db), ): result = await db.execute( select(AuditReport) .where(AuditReport.tenant_id == tenant_id) .order_by(desc(AuditReport.report_date)) ) return result.scalars().all() @router.post("/generate", response_model=AuditReportOut, status_code=201) async def generate_audit_report( tenant_id: str = Query(...), body: AuditReportCreate = ..., payload: dict = Depends(require_admin), db: AsyncSession = Depends(get_db), ): """Generate a Vault Audit Report snapshot for a tenant.""" # Get current score score_result = await db.execute( select(RiskScore) .where(RiskScore.tenant_id == tenant_id) .order_by(desc(RiskScore.score_date)) .limit(1) ) latest_score = score_result.scalar_one_or_none() # Snapshot top findings findings_result = await db.execute( select(Finding) .where( Finding.tenant_id == tenant_id, Finding.status.in_([FindingStatus.open, FindingStatus.in_progress]) ) .order_by(Finding.created_at) .limit(20) ) top_findings = findings_result.scalars().all() findings_snapshot = [ { "id": f.id, "title": f.title, "severity": f.severity.value, "category": f.category.value, "ai_summary": f.ai_summary, "ai_impact_level": f.ai_impact_level, } for f in top_findings ] report = AuditReport( tenant_id=tenant_id, title=body.title, report_date=datetime.utcnow(), baseline_score=latest_score.overall_score if latest_score else None, executive_summary=body.executive_summary, scope_description=body.scope_description, key_findings_json=json.dumps(findings_snapshot), is_baseline=True, generated_by=payload.get("sub"), ) db.add(report) await db.commit() await db.refresh(report) # Kick off PDF generation in background from app.services.report_generator import generate_pdf_for_report import asyncio asyncio.create_task(generate_pdf_for_report(report.id)) return report @router.get("/{report_id}", response_model=AuditReportOut) async def get_report( report_id: str, payload: dict = Depends(require_admin), db: AsyncSession = Depends(get_db), ): result = await db.execute(select(AuditReport).where(AuditReport.id == report_id)) report = result.scalar_one_or_none() if not report: raise HTTPException(status_code=404, detail="Report not found") return report