From 8666fb63115a91d1a85c0ff856067f9a3529b578 Mon Sep 17 00:00:00 2001 From: rarebuffalo Date: Mon, 4 May 2026 09:02:26 +0530 Subject: [PATCH] Updated to get reports in pdf/csv --- app/routers/report.py | 160 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 157 insertions(+), 3 deletions(-) diff --git a/app/routers/report.py b/app/routers/report.py index 8dfc55c..ee6aa4f 100644 --- a/app/routers/report.py +++ b/app/routers/report.py @@ -11,9 +11,10 @@ from fpdf import FPDF from app.database import get_db from app.middleware.auth import get_current_user from app.models.scan import ScanResult +from app.models.code_scan import CodeScanResult from app.models.user import User -router = APIRouter(prefix="/scans", tags=["report"]) +router = APIRouter(tags=["report"]) def _generate_csv(scan: ScanResult) -> io.StringIO: @@ -73,7 +74,111 @@ def _generate_pdf(scan: ScanResult) -> io.BytesIO: return io.BytesIO(pdf_bytes) -@router.get("/{scan_id}/export/csv") +def _generate_code_csv(scan: CodeScanResult) -> io.StringIO: + output = io.StringIO() + writer = csv.writer(output) + + writer.writerow(["SecureLens AI - Repository Security Report"]) + writer.writerow(["Repository", scan.repo_url]) + writer.writerow(["Date", scan.created_at.strftime("%Y-%m-%d %H:%M:%S")]) + writer.writerow([]) + + writer.writerow(["Executive Summary"]) + writer.writerow([scan.summary]) + writer.writerow([]) + + writer.writerow(["File Path", "Severity", "Issue", "Line", "Explanation", "Suggested Fix"]) + for i in scan.issues: + writer.writerow([ + i.get("file_path"), + i.get("severity"), + i.get("issue"), + i.get("line_number", "N/A"), + i.get("explanation"), + i.get("suggested_fix"), + ]) + + output.seek(0) + return output + + +def _generate_code_pdf(scan: CodeScanResult) -> io.BytesIO: + pdf = FPDF() + pdf.add_page() + + # Title + pdf.set_font("helvetica", "B", 18) + pdf.set_text_color(20, 40, 80) + pdf.cell(0, 15, "SecureLens AI - Repository Security Report", new_x="LMARGIN", new_y="NEXT", align="C") + + # Metadata + pdf.set_font("helvetica", "", 11) + pdf.set_text_color(0, 0, 0) + pdf.cell(0, 8, f"Repository: {scan.repo_url}", new_x="LMARGIN", new_y="NEXT") + pdf.cell(0, 8, f"Scan Date: {scan.created_at.strftime('%Y-%m-%d %H:%M:%S')}", new_x="LMARGIN", new_y="NEXT") + pdf.ln(5) + + # Summary Section + pdf.set_font("helvetica", "B", 14) + pdf.set_fill_color(240, 240, 240) + pdf.cell(0, 10, "Executive Summary", new_x="LMARGIN", new_y="NEXT", fill=True) + pdf.set_font("helvetica", "", 11) + pdf.ln(2) + pdf.multi_cell(0, 6, scan.summary) + pdf.ln(10) + + # Issues Section + pdf.set_font("helvetica", "B", 14) + pdf.set_fill_color(240, 240, 240) + pdf.cell(0, 10, "Security Findings", new_x="LMARGIN", new_y="NEXT", fill=True) + pdf.ln(5) + + if not scan.issues: + pdf.set_font("helvetica", "I", 11) + pdf.cell(0, 10, "No security vulnerabilities were identified in the scanned files.", new_x="LMARGIN", new_y="NEXT") + else: + for i in scan.issues: + # Issue Title & Severity + severity = i.get("severity", "Medium") + pdf.set_font("helvetica", "B", 12) + + # Severity color coding + if severity == "Critical": pdf.set_text_color(200, 0, 0) + elif severity == "High": pdf.set_text_color(255, 69, 0) + elif severity == "Medium": pdf.set_text_color(218, 165, 32) + else: pdf.set_text_color(0, 100, 0) + + line_str = f" [Line {i.get('line_number')}]" if i.get('line_number') else "" + pdf.cell(0, 8, f"{severity}: {i.get('issue')}{line_str}", new_x="LMARGIN", new_y="NEXT") + + # Details + pdf.set_text_color(0, 0, 0) + pdf.set_font("helvetica", "B", 10) + pdf.cell(30, 6, "File:", border=0) + pdf.set_font("helvetica", "", 10) + pdf.cell(0, 6, i.get("file_path"), new_x="LMARGIN", new_y="NEXT") + + pdf.set_font("helvetica", "B", 10) + pdf.cell(0, 6, "Explanation:", new_x="LMARGIN", new_y="NEXT") + pdf.set_font("helvetica", "", 10) + pdf.multi_cell(0, 5, i.get("explanation")) + + if i.get("suggested_fix"): + pdf.set_font("helvetica", "B", 10) + pdf.cell(0, 6, "Suggested Fix:", new_x="LMARGIN", new_y="NEXT") + pdf.set_font("courier", "", 9) + pdf.set_fill_color(245, 245, 245) + pdf.multi_cell(0, 5, i.get("suggested_fix"), fill=True) + + pdf.ln(6) + pdf.line(pdf.get_x(), pdf.get_y(), 200, pdf.get_y()) + pdf.ln(4) + + pdf_bytes = pdf.output() + return io.BytesIO(pdf_bytes) + + +@router.get("/scans/{scan_id}/export/csv") async def export_csv( scan_id: str, current_user: User = Depends(get_current_user), @@ -93,7 +198,7 @@ async def export_csv( return response -@router.get("/{scan_id}/export/pdf") +@router.get("/scans/{scan_id}/export/pdf") async def export_pdf( scan_id: str, current_user: User = Depends(get_current_user), @@ -114,3 +219,52 @@ async def export_pdf( return response except Exception as e: raise HTTPException(status_code=500, detail=f"PDF Generation failed: {str(e)}") + + +@router.get("/code-scans/{scan_id}/export/csv") +async def export_code_csv( + scan_id: str, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +): + result = await db.execute( + select(CodeScanResult).where( + CodeScanResult.id == scan_id, + CodeScanResult.user_id == current_user.id + ) + ) + scan = result.scalar_one_or_none() + + if not scan: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Code scan not found") + + csv_data = _generate_code_csv(scan) + response = StreamingResponse(iter([csv_data.getvalue()]), media_type="text/csv") + response.headers["Content-Disposition"] = f"attachment; filename=code_scan_{scan_id}.csv" + return response + + +@router.get("/code-scans/{scan_id}/export/pdf") +async def export_code_pdf( + scan_id: str, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +): + result = await db.execute( + select(CodeScanResult).where( + CodeScanResult.id == scan_id, + CodeScanResult.user_id == current_user.id + ) + ) + scan = result.scalar_one_or_none() + + if not scan: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Code scan not found") + + try: + pdf_data = _generate_code_pdf(scan) + response = StreamingResponse(pdf_data, media_type="application/pdf") + response.headers["Content-Disposition"] = f"attachment; filename=code_scan_{scan_id}.pdf" + return response + except Exception as e: + raise HTTPException(status_code=500, detail=f"PDF Generation failed: {str(e)}")