Prompt 15: Component Build - API Layer
Context
You are a senior software engineer implementing the API Layer for CODITECT-COMPLIANCE. This component provides REST and GraphQL APIs for all compliance operations.
Output Specification
Generate complete, production-ready Python code for the API layer. Output should be 1,500-2,500 lines of code.
Implementation Requirements
Technology Stack
- Python 3.12+
- FastAPI for REST
- Strawberry for GraphQL
- Pydantic for validation
- OAuth 2.0 / JWT authentication
Component Specifications
1. FastAPI Application
# File: src/api/main.py
from fastapi import FastAPI, Depends, HTTPException
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI(
title="CODITECT-COMPLIANCE API",
version="1.0.0",
description="Compliance automation platform API"
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Mount routers
app.include_router(frameworks_router, prefix="/api/v1/frameworks", tags=["frameworks"])
app.include_router(controls_router, prefix="/api/v1/controls", tags=["controls"])
app.include_router(evidence_router, prefix="/api/v1/evidence", tags=["evidence"])
app.include_router(integrations_router, prefix="/api/v1/integrations", tags=["integrations"])
app.include_router(agents_router, prefix="/api/v1/agents", tags=["agents"])
app.include_router(audits_router, prefix="/api/v1/audits", tags=["audits"])
2. Authentication Middleware
# File: src/api/auth.py
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
security = HTTPBearer()
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security)
) -> User:
token = credentials.credentials
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["RS256"])
user = await user_repo.get(payload["sub"])
set_tenant_context(TenantContext(
organization_id=user.organization_id,
user_id=user.id,
roles=user.roles,
permissions=expand_permissions(user.roles)
))
return user
except JWTError:
raise HTTPException(status_code=401, detail="Invalid token")
def require_permission(permission: str):
async def checker(user: User = Depends(get_current_user)):
if permission not in get_tenant_context().permissions:
raise HTTPException(status_code=403, detail="Permission denied")
return user
return checker
3. Framework Endpoints
# File: src/api/routes/frameworks.py
router = APIRouter()
@router.get("/", response_model=PaginatedResponse[FrameworkResponse])
async def list_frameworks(
status: Optional[str] = None,
page: int = Query(default=1, ge=1),
per_page: int = Query(default=20, le=100),
user: User = Depends(get_current_user),
service: ControlGraphService = Depends()
):
"""List all compliance frameworks."""
frameworks, total = await service.list_frameworks(
status=status,
offset=(page - 1) * per_page,
limit=per_page
)
return PaginatedResponse(
items=[FrameworkResponse.from_domain(f) for f in frameworks],
total=total,
page=page,
per_page=per_page
)
@router.post("/import", response_model=FrameworkResponse)
async def import_framework(
request: ImportFrameworkRequest,
user: User = Depends(require_permission("frameworks:write")),
service: ControlGraphService = Depends()
):
"""Import a standard compliance framework."""
framework = await service.import_framework(request.code, request.version)
return FrameworkResponse.from_domain(framework)
@router.get("/{framework_id}/posture", response_model=PostureScoreResponse)
async def get_posture_score(
framework_id: str,
user: User = Depends(get_current_user),
service: ControlGraphService = Depends()
):
"""Get compliance posture score for a framework."""
posture = await service.calculate_posture_score(framework_id)
return PostureScoreResponse.from_domain(posture)
4. GraphQL Schema
# File: src/api/graphql/schema.py
import strawberry
from strawberry.fastapi import GraphQLRouter
@strawberry.type
class Framework:
id: str
code: str
name: str
version: str
control_count: int
posture_score: float
@strawberry.type
class Control:
id: str
control_id: str
title: str
category: str
implementation_status: str
evidence_count: int
@strawberry.type
class Query:
@strawberry.field
async def frameworks(self, info) -> List[Framework]:
service = info.context["control_graph_service"]
return await service.list_frameworks()
@strawberry.field
async def framework(self, info, id: str) -> Optional[Framework]:
service = info.context["control_graph_service"]
return await service.get_framework(id)
@strawberry.field
async def gap_analysis(self, info, framework_id: str) -> GapAnalysis:
service = info.context["control_graph_service"]
return await service.get_gap_analysis(framework_id)
@strawberry.type
class Mutation:
@strawberry.mutation
async def import_framework(self, info, code: str, version: str) -> Framework:
service = info.context["control_graph_service"]
return await service.import_framework(code, version)
@strawberry.mutation
async def trigger_evidence_collection(
self, info, integration_id: str
) -> CollectionJob:
service = info.context["evidence_service"]
return await service.trigger_collection(integration_id)
@strawberry.type
class Subscription:
@strawberry.subscription
async def posture_updates(self, info, framework_id: str) -> AsyncIterator[PostureUpdate]:
async for update in info.context["posture_stream"].subscribe(framework_id):
yield update
schema = strawberry.Schema(query=Query, mutation=Mutation, subscription=Subscription)
graphql_router = GraphQLRouter(schema)
5. Request/Response Models
# File: src/api/models.py
from pydantic import BaseModel, Field
from typing import List, Optional, Generic, TypeVar
T = TypeVar('T')
class PaginatedResponse(BaseModel, Generic[T]):
items: List[T]
total: int
page: int
per_page: int
class FrameworkResponse(BaseModel):
id: str
code: str
name: str
version: str
authority: str
control_count: int
status: str
posture_score: Optional[float] = None
@classmethod
def from_domain(cls, framework: Framework) -> "FrameworkResponse":
return cls(**framework.model_dump())
class ImportFrameworkRequest(BaseModel):
code: str = Field(..., pattern="^[A-Z0-9_-]+$")
version: str
class TriggerCollectionRequest(BaseModel):
integration_id: str
control_ids: Optional[List[str]] = None
priority: str = Field(default="normal", pattern="^(high|normal|low)$")
File Structure
src/api/
├── __init__.py
├── main.py # FastAPI app
├── auth.py # Authentication
├── dependencies.py # Dependency injection
├── middleware.py # Custom middleware
├── models.py # Pydantic models
├── routes/
│ ├── __init__.py
│ ├── frameworks.py
│ ├── controls.py
│ ├── evidence.py
│ ├── integrations.py
│ ├── agents.py
│ └── audits.py
├── graphql/
│ ├── __init__.py
│ ├── schema.py
│ ├── types.py
│ └── resolvers.py
└── tests/
Acceptance Criteria
- REST API: Complete CRUD for all resources
- GraphQL: Query, Mutation, Subscription support
- Authentication: JWT with RBAC
- Pagination: Cursor-based pagination
- Validation: Pydantic request validation
- Documentation: OpenAPI auto-generation
Token Budget
- Target: 15,000-22,000 tokens
Dependencies
- Input: SDD API design
- Input: All service components
- Output: Consumed by UI, external clients