276 lines
8.7 KiB
Python
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"
|
|
)
|