anderson-ufrj
fix: disable TrustedHostMiddleware for HuggingFace Spaces compatibility
05ab472
raw
history blame
14.3 kB
"""
Module: api.app
Description: FastAPI application for Cidadão.AI transparency platform
Author: Anderson H. Silva
Date: 2025-01-24
License: Proprietary - All rights reserved
"""
import asyncio
from contextlib import asynccontextmanager
from typing import Dict, Any
from fastapi import FastAPI, HTTPException, Depends, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
# from fastapi.middleware.trustedhost import TrustedHostMiddleware # Disabled for HuggingFace
from fastapi.responses import JSONResponse
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.openapi.utils import get_openapi
from src.core import get_logger, settings
from src.core.exceptions import CidadaoAIError, create_error_response
from src.core.audit import audit_logger, AuditEventType, AuditSeverity, AuditContext
from src.api.routes import investigations, analysis, reports, health, auth, oauth, audit, chat, websocket_chat
from src.api.middleware.rate_limiting import RateLimitMiddleware
from src.api.middleware.authentication import AuthenticationMiddleware
from src.api.middleware.logging_middleware import LoggingMiddleware
from src.api.middleware.security import SecurityMiddleware
from src.api.middleware.compression import CompressionMiddleware
logger = get_logger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifespan manager with enhanced audit logging."""
# Startup
logger.info("cidadao_ai_api_starting")
# Log startup event
await audit_logger.log_event(
event_type=AuditEventType.SYSTEM_STARTUP,
message=f"Cidadão.AI API started (env: {settings.app_env})",
severity=AuditSeverity.LOW,
details={
"version": "1.0.0",
"environment": settings.app_env,
"debug": settings.debug,
"security_enabled": True
}
)
# Initialize global resources here
# - Database connections
# - Background tasks
# - Cache connections
yield
# Shutdown
logger.info("cidadao_ai_api_shutting_down")
# Log shutdown event
await audit_logger.log_event(
event_type=AuditEventType.SYSTEM_SHUTDOWN,
message="Cidadão.AI API shutting down",
severity=AuditSeverity.LOW
)
# Cleanup resources here
# - Close database connections
# - Stop background tasks
# - Clean up cache
# Create FastAPI application
app = FastAPI(
title="Cidadão.AI API",
description="""
**Plataforma de Transparência Pública com IA**
API para investigação inteligente de dados públicos brasileiros.
## Funcionalidades
* **Investigação** - Detecção de anomalias e irregularidades
* **Análise** - Padrões e correlações em dados públicos
* **Relatórios** - Geração de relatórios em linguagem natural
* **Transparência** - Acesso democrático a informações governamentais
## Agentes Especializados
* **InvestigatorAgent** - Detecção de anomalias com IA explicável
* **AnalystAgent** - Análise de padrões e correlações
* **ReporterAgent** - Geração de relatórios inteligentes
## Fontes de Dados
* Portal da Transparência do Governo Federal
* Contratos, despesas, licitações e convênios públicos
* Dados de servidores e empresas sancionadas
""",
version="1.0.0",
contact={
"name": "Cidadão.AI",
"url": "https://github.com/anderson-ufrj/cidadao.ai",
"email": "[email protected]",
},
license_info={
"name": "Proprietary",
"url": "https://github.com/anderson-ufrj/cidadao.ai/blob/main/LICENSE",
},
lifespan=lifespan,
docs_url=None, # Disable default docs
redoc_url=None, # Disable redoc
)
# Add security middleware (order matters!)
app.add_middleware(SecurityMiddleware)
app.add_middleware(LoggingMiddleware)
app.add_middleware(RateLimitMiddleware)
# Add compression middleware for mobile optimization
app.add_middleware(
CompressionMiddleware,
minimum_size=1024, # Compress responses > 1KB
compression_level=6 # Balanced compression
)
# Add trusted host middleware for production
# DISABLED for HuggingFace Spaces - causes issues with proxy headers
# if settings.app_env == "production":
# app.add_middleware(
# TrustedHostMiddleware,
# allowed_hosts=["api.cidadao.ai", "*.cidadao.ai", "*.hf.space", "*.huggingface.co"]
# )
# else:
# app.add_middleware(
# TrustedHostMiddleware,
# allowed_hosts=["localhost", "127.0.0.1", "*.cidadao.ai", "testserver"]
# )
# CORS middleware with secure configuration
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=settings.cors_allow_credentials,
allow_methods=settings.cors_allow_methods,
allow_headers=settings.cors_allow_headers,
expose_headers=["X-RateLimit-Limit", "X-RateLimit-Remaining"]
)
# Custom OpenAPI schema
def custom_openapi():
"""Generate custom OpenAPI schema."""
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title=app.title,
version=app.version,
description=app.description,
routes=app.routes,
)
# Add custom API info
openapi_schema["info"]["x-logo"] = {
"url": "https://cidadao.ai/logo.png"
}
# Add servers
openapi_schema["servers"] = [
{"url": "http://localhost:8000", "description": "Development server"},
{"url": "https://api.cidadao.ai", "description": "Production server"},
]
# Add security schemes
openapi_schema["components"]["securitySchemes"] = {
"ApiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key"
},
"BearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
}
}
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
# Custom documentation endpoint
@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
"""Custom Swagger UI with branding."""
return get_swagger_ui_html(
openapi_url=app.openapi_url,
title=f"{app.title} - Documentação",
swagger_js_url="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js",
swagger_css_url="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css",
swagger_favicon_url="https://cidadao.ai/favicon.ico",
)
# Include routers with security
app.include_router(
health.router,
prefix="/health",
tags=["Health Check"]
)
app.include_router(
auth.router,
prefix="/auth",
tags=["Authentication"]
)
app.include_router(
oauth.router,
prefix="/auth/oauth",
tags=["OAuth2"]
)
app.include_router(
audit.router,
prefix="/audit",
tags=["Audit & Security"]
)
app.include_router(
investigations.router,
prefix="/api/v1/investigations",
tags=["Investigations"]
)
app.include_router(
analysis.router,
prefix="/api/v1/analysis",
tags=["Analysis"]
)
app.include_router(
reports.router,
prefix="/api/v1/reports",
tags=["Reports"]
)
app.include_router(
chat.router,
prefix="/api/v1/chat",
tags=["Chat"]
)
app.include_router(
websocket_chat.router,
prefix="/api/v1",
tags=["WebSocket"]
)
# Global exception handler
@app.exception_handler(CidadaoAIError)
async def cidadao_ai_exception_handler(request, exc: CidadaoAIError):
"""Handle CidadãoAI custom exceptions."""
logger.error(
"api_exception_occurred",
error_type=type(exc).__name__,
error_message=exc.message,
error_details=exc.details,
path=request.url.path,
method=request.method,
)
# Map exception types to HTTP status codes
status_code_map = {
"ValidationError": 400,
"DataNotFoundError": 404,
"AuthenticationError": 401,
"UnauthorizedError": 403,
"RateLimitError": 429,
"LLMError": 503,
"TransparencyAPIError": 502,
"AgentExecutionError": 500,
}
status_code = status_code_map.get(exc.error_code, 500)
error_response = create_error_response(exc, status_code)
return JSONResponse(
status_code=status_code,
content=error_response
)
@app.exception_handler(HTTPException)
async def http_exception_handler(request, exc: HTTPException):
"""Enhanced HTTP exception handler with audit logging."""
# Create audit context
context = AuditContext(
ip_address=request.client.host if request.client else "unknown",
user_agent=request.headers.get("user-agent"),
host=request.headers.get("host")
)
# Log security-related errors
if exc.status_code in [401, 403, 429]:
await audit_logger.log_event(
event_type=AuditEventType.UNAUTHORIZED_ACCESS,
message=f"HTTP {exc.status_code}: {exc.detail}",
severity=AuditSeverity.MEDIUM if exc.status_code != 429 else AuditSeverity.HIGH,
success=False,
error_code=str(exc.status_code),
error_message=exc.detail,
context=context
)
logger.warning(
"http_exception_occurred",
status_code=exc.status_code,
detail=exc.detail,
path=request.url.path,
method=request.method,
)
return JSONResponse(
status_code=exc.status_code,
content={
"status": "error",
"status_code": exc.status_code,
"error": {
"error": "HTTPException",
"message": exc.detail,
"details": {}
}
}
)
@app.exception_handler(Exception)
async def general_exception_handler(request, exc: Exception):
"""Enhanced general exception handler with audit logging."""
# Log unexpected errors with audit
context = AuditContext(
ip_address=request.client.host if request.client else "unknown",
user_agent=request.headers.get("user-agent"),
host=request.headers.get("host")
)
await audit_logger.log_event(
event_type=AuditEventType.API_ERROR,
message=f"Unhandled exception: {str(exc)}",
severity=AuditSeverity.HIGH,
success=False,
error_message=str(exc),
details={"error_type": type(exc).__name__},
context=context
)
logger.error(
"unexpected_exception_occurred",
error_type=type(exc).__name__,
error_message=str(exc),
path=request.url.path,
method=request.method,
)
# Don't expose internal errors in production
if settings.app_env == "production":
return JSONResponse(
status_code=500,
content={
"status": "error",
"status_code": 500,
"error": {
"error": "InternalServerError",
"message": "An unexpected error occurred",
"details": {}
}
}
)
else:
return JSONResponse(
status_code=500,
content={
"status": "error",
"status_code": 500,
"error": {
"error": "InternalServerError",
"message": f"An unexpected error occurred: {str(exc)}",
"details": {"error_type": type(exc).__name__}
}
}
)
# Root endpoint
@app.get("/", include_in_schema=False)
async def root():
"""Root endpoint with API information."""
return {
"message": "Cidadão.AI - Plataforma de Transparência Pública",
"version": "1.0.0",
"description": "API para investigação inteligente de dados públicos brasileiros",
"documentation": "/docs",
"health": "/health",
"status": "operational"
}
# API info endpoint
@app.get("/api/v1/info", tags=["General"])
async def api_info():
"""Get API information and capabilities."""
return {
"api": {
"name": "Cidadão.AI API",
"version": "1.0.0",
"description": "Plataforma de transparência pública com IA",
},
"agents": {
"investigator": {
"description": "Detecção de anomalias e irregularidades",
"capabilities": [
"Anomalias de preço",
"Concentração de fornecedores",
"Padrões temporais suspeitos",
"Contratos duplicados",
"Irregularidades de pagamento"
]
},
"analyst": {
"description": "Análise de padrões e correlações",
"capabilities": [
"Tendências de gastos",
"Padrões organizacionais",
"Comportamento de fornecedores",
"Análise sazonal",
"Métricas de eficiência"
]
},
"reporter": {
"description": "Geração de relatórios inteligentes",
"capabilities": [
"Relatórios executivos",
"Análise detalhada",
"Múltiplos formatos",
"Linguagem natural"
]
}
},
"data_sources": [
"Portal da Transparência",
"Contratos públicos",
"Despesas governamentais",
"Licitações",
"Convênios",
"Servidores públicos"
],
"formats": [
"JSON",
"Markdown",
"HTML",
"PDF (planned)"
]
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"src.api.app:app",
host=settings.host,
port=settings.port,
reload=settings.debug,
workers=settings.workers if not settings.debug else 1,
log_level=settings.log_level.lower(),
)