Files
SYNC_API/sync_api/main.py

276 lines
8.7 KiB
Python

from fastapi import FastAPI, HTTPException, Request, Depends
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from fastapi.responses import JSONResponse
from fastapi.security import HTTPBasic, HTTPBasicCredentials
import logging
import sys
import secrets
from datetime import datetime
from contextlib import asynccontextmanager
# Importar routers
from routers import nodes, health
from config.settings import settings
# Configurar logging
def setup_logging():
"""Configurar sistema de logging"""
log_level = getattr(logging, settings.log_level.upper(), logging.INFO)
logging.basicConfig(
level=log_level,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
# Opcionalmente agregar FileHandler para logs en archivo
# logging.FileHandler('sync_api.log')
]
)
# Reducir logs de uvicorn y otras librerías si no estamos en debug
if not settings.debug_mode:
logging.getLogger("uvicorn").setLevel(logging.WARNING)
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Gestión del ciclo de vida de la aplicación"""
# Startup
logger = logging.getLogger(__name__)
logger.info("🚀 Iniciando API de Monitoreo de Sincronización...")
# Probar conexión a la base de datos
try:
from database.connection import db_connection
test_result = db_connection.test_connection()
if test_result.get("connection_successful", False):
logger.info("✅ Conexión a base de datos SQL Server exitosa")
logger.info(f"📊 Base de datos: {test_result.get('database_name', 'N/A')}")
else:
logger.warning("⚠️ No se pudo conectar a la base de datos al iniciar")
except Exception as e:
logger.error(f"❌ Error probando conexión inicial: {str(e)}")
logger.info(f"🌐 API disponible en: http://{settings.api_host}:{settings.api_port}")
logger.info(f"📖 Documentación en: http://{settings.api_host}:{settings.api_port}/docs")
yield # La aplicación está ejecutándose
# Shutdown
logger.info("🛑 Cerrando API de Monitoreo de Sincronización...")
# Configurar logging antes de crear la app
setup_logging()
# Crear la aplicación FastAPI
app = FastAPI(
title="API de Monitoreo de Sincronización",
description="""
API REST para monitoreo de sincronización de bases de datos SQL Server.
""",
version="1.0.0",
contact={
"name": "Sistema de Monitoreo Aduanasoft",
"url": "https://cpanel-a24.aduanasoft.com/ASDATABASES/",
},
license_info={
"name": "AduanaSoft",
},
lifespan=lifespan
)
# Configurar CORS para integración con PHP
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost",
"http://localhost:80",
"http://localhost:8080",
"http://127.0.0.1",
"http://127.0.0.1:80",
"http://127.0.0.1:8080",
# Dominios de producción Aduanasoft
"https://cpanel-a24.aduanasoft.com",
"http://cpanel-a24.aduanasoft.com",
"https://cpanel-a24.aduanasoft.com:8000",
"http://cpanel-a24.aduanasoft.com:8000",
],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],
)
# Middleware de seguridad para hosts confiables (opcional)
if not settings.debug_mode:
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=[
"localhost",
"127.0.0.1",
"*.aduanasoft.com",
"cpanel-a24.aduanasoft.com"
]
)
# Logger para la aplicación
logger = logging.getLogger(__name__)
# Configurar seguridad HTTP Basic Auth
security = HTTPBasic()
def authenticate_user(credentials: HTTPBasicCredentials = Depends(security)):
"""Autenticación básica HTTP"""
correct_username = secrets.compare_digest(credentials.username, settings.api_username)
correct_password = secrets.compare_digest(credentials.password, settings.api_password)
if not (correct_username and correct_password):
raise HTTPException(
status_code=401,
detail="Credenciales incorrectas",
headers={"WWW-Authenticate": "Basic"},
)
return credentials.username
# Manejador de excepciones global
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
"""Manejador global de excepciones no capturadas"""
logger.error(f"Error no capturado en {request.url}: {str(exc)}")
logger.error(f"Tipo de error: {type(exc).__name__}")
return JSONResponse(
status_code=500,
content={
"error": "Error interno del servidor",
"detail": "Ha ocurrido un error inesperado. Consulte los logs para más detalles.",
"timestamp": datetime.now().isoformat(),
"path": str(request.url)
}
)
# Middleware para logging de requests
@app.middleware("http")
async def log_requests(request: Request, call_next):
"""Middleware para registrar todas las peticiones"""
start_time = datetime.now()
# Registrar petición entrante
logger.info(f"📨 {request.method} {request.url}")
# Procesar petición
response = await call_next(request)
# Calcular tiempo de procesamiento
process_time = (datetime.now() - start_time).total_seconds()
# Registrar respuesta
logger.info(f"📤 {request.method} {request.url} - {response.status_code} - {process_time:.3f}s")
# Agregar header con tiempo de procesamiento
response.headers["X-Process-Time"] = str(process_time)
return response
# Registrar routers
app.include_router(nodes.router)
app.include_router(health.router)
# Endpoint raíz
@app.get("/",
summary="Información de la API",
description="Endpoint raíz que proporciona información básica de la API")
async def root():
"""
Endpoint raíz con información básica de la API.
Útil para verificar que la API está funcionando correctamente.
"""
return {
"message": "API de Monitoreo de Sincronización - Aduanasoft",
"version": "1.0.0",
"status": "running",
"timestamp": datetime.now().isoformat(),
"endpoints": {
"health": "/api/health",
"nodes_list": "/api/nodes",
"node_status": "/api/nodes/{node_name}/status",
"node_backup": "/api/nodes/{node_name}/backup-info",
"documentation": "/docs",
"openapi_spec": "/openapi.json"
},
"features": [
"Monitoreo de estado de nodos",
"Información de sincronización en tiempo real",
"Health checks del sistema",
"Información de backups",
"Integración AJAX/PHP ready"
]
}
# Endpoint para obtener información del servidor
@app.get("/info",
summary="Información del servidor",
description="Información técnica del servidor y configuración")
async def server_info():
"""
Información técnica del servidor y configuración actual.
Útil para debugging y verificación de configuración.
"""
return {
"server_config": {
"host": settings.api_host,
"port": settings.api_port,
"debug_mode": settings.debug_mode,
"log_level": settings.log_level
},
"database_config": {
"server": settings.database_server,
"database": settings.database_name,
"driver": settings.database_driver,
"port": settings.database_port
},
"cache_config": {
"enabled": settings.enable_cache,
"ttl": settings.cache_ttl
},
"timestamp": datetime.now().isoformat()
}
if __name__ == "__main__":
import uvicorn
logger.info("🚀 Iniciando servidor HTTPS en puerto 9443...")
logger.info("🔐 Configurando SSL...")
# Configuración específica para puerto 9443 con SSL
try:
uvicorn.run(
"main:app",
host="0.0.0.0",
port=9443,
ssl_keyfile="ssl/private.key",
ssl_certfile="ssl/certificate.crt",
reload=False,
log_level="info",
access_log=True
)
except Exception as e:
logger.error(f"❌ Error con SSL: {e}")
logger.info("🔧 Iniciando sin SSL como fallback...")
uvicorn.run(
"main:app",
host="0.0.0.0",
port=9443,
reload=False,
log_level="info"
)