feat(api): implement complete API layer with services and endpoints

Complete API implementation (BE-006 to BE-010):

BE-006: API Dependencies & Configuration
- Add core/config.py with Settings and environment variables
- Add core/exceptions.py with AppException hierarchy
- Add api/deps.py with get_db() and get_running_scenario() dependencies
- Add pydantic-settings dependency

BE-007: Services Layer
- Add services/pii_detector.py: PIIDetector with email/SSN/credit card patterns
- Add services/cost_calculator.py: AWS cost calculation (SQS, Lambda, Bedrock)
- Add services/ingest_service.py: Log processing with hash, PII detection, metrics

BE-008: Scenarios API Endpoints
- POST /api/v1/scenarios - Create scenario
- GET /api/v1/scenarios - List with filters and pagination
- GET /api/v1/scenarios/{id} - Get single scenario
- PUT /api/v1/scenarios/{id} - Update scenario
- DELETE /api/v1/scenarios/{id} - Delete scenario
- POST /api/v1/scenarios/{id}/start - Start (draft->running)
- POST /api/v1/scenarios/{id}/stop - Stop (running->completed)
- POST /api/v1/scenarios/{id}/archive - Archive (completed->archived)

BE-009: Ingest API
- POST /ingest with X-Scenario-ID header validation
- Depends on get_running_scenario() for status check
- Returns LogResponse with processed metrics
- POST /flush for backward compatibility

BE-010: Metrics API
- GET /api/v1/scenarios/{id}/metrics - Full metrics endpoint
- Aggregates data from scenario_logs
- Calculates costs using CostCalculator
- Returns cost breakdown (SQS/Lambda/Bedrock)
- Returns timeseries data grouped by hour

Refactored main.py:
- Simplified to use api_router
- Added exception handlers
- Added health check endpoint

All endpoints tested and working.

Tasks: BE-006, BE-007, BE-008, BE-009, BE-010 complete
This commit is contained in:
Luca Sacchi Ricciardi
2026-04-07 14:35:50 +02:00
parent ebefc323c3
commit b18728f0f9
16 changed files with 1757 additions and 117 deletions
+32
View File
@@ -0,0 +1,32 @@
"""Application configuration."""
from functools import lru_cache
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
"""Application settings from environment variables."""
# Database
database_url: str = "postgresql+asyncpg://app:changeme@localhost:5432/mockupaws"
# Application
app_name: str = "mockupAWS"
debug: bool = False
# Pagination
default_page_size: int = 20
max_page_size: int = 100
class Config:
env_file = ".env"
case_sensitive = False
@lru_cache()
def get_settings() -> Settings:
"""Get cached settings instance."""
return Settings()
settings = get_settings()
+65
View File
@@ -0,0 +1,65 @@
"""Custom exceptions for the application."""
from fastapi import HTTPException, status
class AppException(Exception):
"""Base application exception."""
status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR
code: str = "internal_error"
def __init__(self, message: str = None):
self.message = message or "An internal error occurred"
super().__init__(self.message)
class NotFoundException(AppException):
"""Resource not found exception."""
status_code = status.HTTP_404_NOT_FOUND
code = "not_found"
def __init__(self, resource: str = "Resource"):
super().__init__(f"{resource} not found")
class ValidationException(AppException):
"""Validation error exception."""
status_code = status.HTTP_400_BAD_REQUEST
code = "validation_error"
class ConflictException(AppException):
"""Conflict error exception."""
status_code = status.HTTP_409_CONFLICT
code = "conflict"
class ScenarioNotRunningException(AppException):
"""Scenario is not in running state."""
status_code = status.HTTP_400_BAD_REQUEST
code = "scenario_not_running"
def __init__(self):
super().__init__("Scenario is not in 'running' state. Cannot ingest logs.")
def setup_exception_handlers(app):
"""Setup exception handlers for FastAPI app."""
from fastapi import Request
from fastapi.responses import JSONResponse
@app.exception_handler(AppException)
async def app_exception_handler(request: Request, exc: AppException):
return JSONResponse(
status_code=exc.status_code,
content={
"error": exc.code,
"message": exc.message,
"status_code": exc.status_code,
},
)