0% complete
Platform Track Easy < 1 hour

Add a Read-Only Endpoint

Create a simple GET endpoint that queries the database and returns aggregated data.

🎯 The Mission

The frontend team needs a quick way to show scan statistics on a dashboard. They want counts of findings, leads, and endpoints for a given scanβ€”all in one API call.

Your job: Add a simple GET /v1/scans/{scan_id}/summary endpoint that returns these counts. This is your first touch of the platform codebase.

Codebase Orientation

πŸ“ Platform Structure
platform/
β”œβ”€β”€ api/
β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   └── v1/
β”‚   β”‚       β”œβ”€β”€ scans.py      ← Add your endpoint here
β”‚   β”‚       β”œβ”€β”€ findings.py
β”‚   β”‚       └── leads.py
β”‚   β”œβ”€β”€ schemas/
β”‚   β”‚   └── v1/
β”‚   β”‚       └── scan.py       ← Add response schema here
β”‚   └── dependencies/
β”‚       └── db.py             ← TenantDB dependency
β”œβ”€β”€ db/
β”‚   └── models/
β”‚       β”œβ”€β”€ scan.py
β”‚       β”œβ”€β”€ finding.py
β”‚       └── lead.py
└── server/
    └── app.py                ← FastAPI app
            

What You'll Learn

Implementation Guide

Step 1: Define the Response Schema

In platform/api/schemas/v1/scan.py, add:

from pydantic import BaseModel

class ScanSummaryResponse(BaseModel):
    scan_id: UUID
    findings_count: int
    leads_count: int
    endpoints_count: int

Step 2: Add the Route

In platform/api/routes/v1/scans.py, add:

from api.schemas.v1.scan import ScanSummaryResponse

@router.get("/v1/scans/{scan_id}/summary", response_model=ScanSummaryResponse)
async def get_scan_summary(
    scan_id: UUID,
    user: ActiveUser = Depends(get_current_user),
    tenant_db: TenantDB = Depends(get_tenant_db),
) -> ScanSummaryResponse:
    """Get summary counts for a scan."""
    
    # Verify scan exists and belongs to tenant
    service = ScanService(tenant_db, encryption_service)
    try:
        scan = await service.get_scan(scan_id)
    except NotFoundError:
        raise HTTPException(status_code=404, detail="Scan not found")
    
    # Count related entities
    # Option 1: Use existing services
    finding_service = FindingService(tenant_db, service)
    lead_service = LeadService(tenant_db)
    endpoint_service = EndpointService(tenant_db)
    
    # Option 2: Direct count queries (more efficient)
    # findings_count = await tenant_db.session.scalar(
    #     select(func.count(Finding.id)).where(Finding.scan_id == scan_id)
    # )
    
    return ScanSummaryResponse(
        scan_id=scan_id,
        findings_count=...,  # Your implementation
        leads_count=...,
        endpoints_count=...,
    )

Step 3: Test Locally

# Start the platform
cd ~/projects/tenzai
mise run platform

# Test your endpoint (replace with actual IDs)
curl http://localhost:8000/v1/scans/{scan_id}/summary \
  -H "Authorization: Bearer $TOKEN"

# Or use the Swagger UI
open http://localhost:8000/docs

Key Patterns to Notice

βœ“ Success Criteria

πŸ“‹ Progress Checklist

Stretch Goals