Files
trustos/backend/app/api/routes/reports.py
drjones 463dff883b feat: initial TrustOS platform scaffold
- FastAPI backend: auth, findings, dashboard, attack paths, footprint, AI translator, risk calculator, PDF report generator
- Next.js frontend: Vault dashboard, login, findings table, finding detail with AI coach, digital footprint, reports
- PostgreSQL data model: tenants, users, assets, findings, risk scores, audit reports, attack paths
- Docker Compose + Dockerfiles for all services
- Demo seed data: Acme Corp with 6 findings and 90-day risk score history
- AI Risk Translator (OpenAI/Anthropic) with plain-English business impact
- Role-based access: executive / it_admin / trustos_admin
- Scope-lock engine: authorization required before any assessment

Stage 1-8 complete: Phase 1 Vault Audit product ready
2026-07-05 09:46:08 +00:00

104 lines
3.2 KiB
Python

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