crowdata / app /main.py
YOSOYYONOSOYOTRO's picture
Upload folder using huggingface_hub
c581b0d verified
Raw
History Blame Contribute Delete
7.7 kB
import asyncio
import sys
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from contextlib import asynccontextmanager
import logging
import uuid
# Fix crítico para Windows: Playwright necesita ProactorEventLoop para subprocesses.
# SelectorEventLoop causa NotImplementedError en _make_subprocess_transport.
# Python 3.8+ usa WindowsProactorEventLoopPolicy por defecto — NO sobreescribir.
if sys.platform == "win32":
pass # Keep default ProactorEventLoop — Playwright requires it for subprocesses
from app.config import get_settings
from app.database import init_db
from app.auth.router import router as auth_router
from app.utils.logging_structured import CorrelationMiddleware, set_correlation_id, get_correlation_id
settings = get_settings()
# Use structured JSON logging in production, human-readable in dev
if settings.debug:
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
)
else:
# Production: JSON structured logging
import logging.handlers
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(message)s'))
root_logger = logging.getLogger()
root_logger.handlers = [handler]
root_logger.setLevel(logging.INFO)
logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
# Validar settings críticos al iniciar
if not settings.secret_key:
logger.critical("SECRET_KEY no configurado en .env — la aplicación NO puede iniciar sin una clave secreta")
raise SystemExit(1)
if settings.mp_access_token and "TEST" in settings.mp_access_token:
logger.warning("MercadoPago usando access token de TEST — no procesará pagos reales")
logger.info("🚀 CrowData API iniciando...")
await init_db()
logger.info("✅ Base de datos inicializada")
# Iniciar Celery Beat para tareas programadas (monitoring, cleanup, etc.)
# En producción: celery -A app.tasks.celery_app beat -l info
# En desarrollo: usamos daemon simple
# Iniciar daemon de monitoring en desarrollo (sin Celery)
if settings.environment != "production":
try:
from app.tasks.monitoring import start_monitoring_daemon
monitor_task = asyncio.create_task(start_monitoring_daemon())
except (ImportError, AttributeError) as e:
logger.warning(f"⚠️ Monitoring daemon no disponible (Celery no instalado o función faltante): {e}")
yield
logger.info("🛑 Cancelando tareas de fondo...")
# Celery workers se gestionan externamente
logger.info("🛑 CrowData API cerrando...")
app = FastAPI(
title="CrowData API",
description="""
## API de Consulta de Datos Públicos Argentinos
CrowData consulta múltiples fuentes oficiales argentinas para generar informes completos de personas, empresas, vehículos y propiedades.
### Fuentes de datos
- **ARCA/AFIP** — Situación fiscal, IVA, Monotributo
- **BCRA** — Situación crediticia, cheques rechazados
- **IGJ** — Sociedades, directivos, sede social
- **Boletín Oficial** — Publicaciones oficiales
- **DNRPA** — Vehículos registrados
- **ANSES** — Aportes previsionales, obra social
- **INPI** — Marcas y patentes comerciales
- **Poder Judicial** — Causas judiciales federales y provinciales
- **Redes Sociales** — Perfiles públicos OSINT
### Autenticación
Todos los endpoints requieren JWT token. Obtener token via `POST /api/auth/jwt/login`.
### Rate Limits
- **Free**: 10 requests/min, 5 informes/día
- **Basic**: 60 requests/min, 50 informes/día
- **Pro**: 200 requests/min, ilimitado
""",
version="1.0.0",
lifespan=lifespan,
docs_url="/api/docs",
redoc_url="/api/redoc",
)
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
"""Captura errores no manejados y retorna respuesta segura."""
logger.error(f"Unhandled exception: {exc}", exc_info=True)
return JSONResponse(
status_code=500,
content={
"detail": f"Error: {type(exc).__name__}: {str(exc)}",
"type": "internal_error",
},
)
# CORS — producción: solo dominios permitidos
ALLOWED_ORIGINS = [
"http://localhost:3000",
"http://localhost:5173",
"http://localhost:8080",
"https://crowdata.ar",
"https://www.crowdata.ar",
"https://crowdata.netlify.app",
"https://tomasdelpico-crowdata-api.hf.space",
"https://yosoyyonosoyotro-crowdata.hf.space",
]
import os
env_origins = os.getenv("ALLOWED_ORIGINS")
if env_origins:
ALLOWED_ORIGINS.extend([origin.strip() for origin in env_origins.split(",") if origin.strip()])
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS,
allow_origin_regex=r"^https://(.*\.)?(crowdata\.ar|netlify\.app|hf\.space)$",
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Correlation ID Middleware - must be after CORS but before other middleware
from app.utils.logging_structured import CorrelationMiddleware
app.add_middleware(CorrelationMiddleware)
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
"""Agrega headers de seguridad a todas las respuestas."""
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Permissions-Policy"] = "camera=(), microphone=(), geolocation=()"
# HSTS solo en producción (HTTPS)
if settings.environment == "production":
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
return response
app.add_middleware(SecurityHeadersMiddleware)
# Rate Limiting
from app.middleware.rate_limit import RateLimitMiddleware
app.add_middleware(RateLimitMiddleware)
# Login History
from app.middleware.login_history import LoginHistoryMiddleware
app.add_middleware(LoginHistoryMiddleware)
# CSRF Protection (Double-submit cookie)
from app.middleware.csrf import get_csrf_middleware
from app.config import get_settings
settings = get_settings()
app.add_middleware(
get_csrf_middleware(
cookie_secure=not settings.debug,
cookie_samesite="lax",
excluded_paths=["/api/auth/jwt/login", "/api/auth/jwt/logout", "/api/auth/register", "/api/auth/forgot-password", "/api/auth/reset-password"],
)
)
from app.reports.router import router as reports_router
from app.reports.vehiculo_router import router as vehiculo_router
from app.payments.router import router as payments_router
from app.admin.router import router as admin_router
# Routers
app.include_router(auth_router, prefix="/api")
app.include_router(reports_router, prefix="/api")
app.include_router(vehiculo_router, prefix="/api")
app.include_router(payments_router, prefix="/api")
app.include_router(admin_router, prefix="/api")
@app.get("/api/health", tags=["system"])
async def health_check():
return {"status": "ok", "service": "CrowData API", "version": "1.0.0"}
@app.get("/api", tags=["system"])
async def root():
return {
"service": "CrowData API",
"docs": "/api/docs",
"endpoints": {
"auth": "/api/auth",
"reports": "/api/reports",
}
}