mirror of
https://github.com/Rarebuffalo/securelens-backend.git
synced 2026-06-19 07:00:30 +00:00
117 lines
3.9 KiB
Python
117 lines
3.9 KiB
Python
import csv
|
|
import io
|
|
from datetime import datetime
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from fastapi.responses import StreamingResponse
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
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.user import User
|
|
|
|
router = APIRouter(prefix="/scans", tags=["report"])
|
|
|
|
|
|
def _generate_csv(scan: ScanResult) -> io.StringIO:
|
|
output = io.StringIO()
|
|
writer = csv.writer(output)
|
|
|
|
writer.writerow(["SecureLens AI Scan Report"])
|
|
writer.writerow(["URL", scan.url])
|
|
writer.writerow(["Date", scan.created_at.strftime("%Y-%m-%d %H:%M:%S")])
|
|
writer.writerow(["Security Score", scan.security_score])
|
|
writer.writerow([])
|
|
|
|
writer.writerow(["Issue", "Severity", "Layer", "Fix", "Contextual Severity", "Explanation"])
|
|
for i in scan.issues:
|
|
writer.writerow([
|
|
i.get("issue"),
|
|
i.get("severity"),
|
|
i.get("layer"),
|
|
i.get("fix"),
|
|
i.get("contextual_severity", ""),
|
|
i.get("explanation", ""),
|
|
])
|
|
|
|
output.seek(0)
|
|
return output
|
|
|
|
|
|
def _generate_pdf(scan: ScanResult) -> io.BytesIO:
|
|
pdf = FPDF()
|
|
pdf.add_page()
|
|
|
|
pdf.set_font("helvetica", "B", 16)
|
|
pdf.cell(0, 10, "SecureLens AI Scan Report", new_x="LMARGIN", new_y="NEXT", align="C")
|
|
|
|
pdf.set_font("helvetica", "", 12)
|
|
pdf.cell(0, 10, f"URL: {scan.url}", new_x="LMARGIN", new_y="NEXT")
|
|
pdf.cell(0, 10, f"Date: {scan.created_at.strftime('%Y-%m-%d %H:%M:%S')}", new_x="LMARGIN", new_y="NEXT")
|
|
pdf.cell(0, 10, f"Security Score: {scan.security_score}/100", new_x="LMARGIN", new_y="NEXT")
|
|
|
|
pdf.ln(5)
|
|
pdf.set_font("helvetica", "B", 14)
|
|
pdf.cell(0, 10, "Discovered Issues", new_x="LMARGIN", new_y="NEXT")
|
|
|
|
for i in scan.issues:
|
|
pdf.set_font("helvetica", "B", 12)
|
|
pdf.cell(0, 8, f"Issue: {i.get('issue')} [{i.get('severity')}]", new_x="LMARGIN", new_y="NEXT")
|
|
|
|
pdf.set_font("helvetica", "", 10)
|
|
pdf.multi_cell(0, 6, f"Layer: {i.get('layer')}")
|
|
pdf.multi_cell(0, 6, f"Fix: {i.get('fix')}")
|
|
|
|
if i.get("explanation"):
|
|
pdf.multi_cell(0, 6, f"AI Context: {i.get('explanation')}")
|
|
pdf.ln(4)
|
|
|
|
pdf_bytes = pdf.output()
|
|
return io.BytesIO(pdf_bytes)
|
|
|
|
|
|
@router.get("/{scan_id}/export/csv")
|
|
async def export_csv(
|
|
scan_id: str,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
result = await db.execute(
|
|
select(ScanResult).where(ScanResult.id == scan_id, ScanResult.user_id == current_user.id)
|
|
)
|
|
scan = result.scalar_one_or_none()
|
|
|
|
if not scan:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Scan not found")
|
|
|
|
csv_data = _generate_csv(scan)
|
|
response = StreamingResponse(iter([csv_data.getvalue()]), media_type="text/csv")
|
|
response.headers["Content-Disposition"] = f"attachment; filename=scan_{scan_id}.csv"
|
|
return response
|
|
|
|
|
|
@router.get("/{scan_id}/export/pdf")
|
|
async def export_pdf(
|
|
scan_id: str,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
result = await db.execute(
|
|
select(ScanResult).where(ScanResult.id == scan_id, ScanResult.user_id == current_user.id)
|
|
)
|
|
scan = result.scalar_one_or_none()
|
|
|
|
if not scan:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Scan not found")
|
|
|
|
try:
|
|
pdf_data = _generate_pdf(scan)
|
|
response = StreamingResponse(pdf_data, media_type="application/pdf")
|
|
response.headers["Content-Disposition"] = f"attachment; filename=scan_{scan_id}.pdf"
|
|
return response
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"PDF Generation failed: {str(e)}")
|