2026-04-02 10:26:44 +00:00
|
|
|
from contextlib import asynccontextmanager
|
|
|
|
|
|
2026-02-10 12:39:18 +00:00
|
|
|
from fastapi import FastAPI, HTTPException, Request
|
|
|
|
|
from fastapi.responses import JSONResponse
|
|
|
|
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
|
|
|
|
2026-01-22 10:37:35 +00:00
|
|
|
from dotenv import load_dotenv
|
2026-01-29 17:35:43 +00:00
|
|
|
from pathlib import Path
|
2026-04-02 10:26:44 +00:00
|
|
|
from datetime import datetime, UTC
|
2026-01-29 17:35:43 +00:00
|
|
|
from dateutil import tz
|
2026-01-22 10:37:35 +00:00
|
|
|
|
|
|
|
|
from signing_service.application.settings.container import get_settings
|
2026-01-29 17:35:43 +00:00
|
|
|
from signing_service.application.settings.setup_logger import create_logger
|
|
|
|
|
from signing_service.api.routes.sign_document import router as sign_router
|
2026-02-03 15:23:25 +00:00
|
|
|
from signing_service.application.settings.version import get_package_version
|
2026-01-22 10:37:35 +00:00
|
|
|
|
|
|
|
|
load_dotenv()
|
|
|
|
|
|
|
|
|
|
# 👇 FAIL FAST: load settings at startup
|
2026-01-29 17:35:43 +00:00
|
|
|
settings = get_settings()
|
|
|
|
|
version = get_package_version()
|
2026-04-02 10:26:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_logger():
|
|
|
|
|
"""Crea el logger de aplicación y asegura el directorio de logs."""
|
|
|
|
|
log_dir = Path(settings.state_path)
|
|
|
|
|
log_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
app_logger = create_logger(
|
|
|
|
|
name="factuges-document-signing-service",
|
|
|
|
|
log_path=log_dir / "factuges-document-signing-service.log",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
app_logger.info("")
|
|
|
|
|
app_logger.info(
|
|
|
|
|
"============================================================")
|
|
|
|
|
app_logger.info("FactuGES Document Signing Service - START")
|
|
|
|
|
app_logger.info("Version: %s", version)
|
|
|
|
|
app_logger.info("UTC Now: %s", datetime.now(UTC).isoformat())
|
|
|
|
|
app_logger.info("Environment: %s", settings.app_env)
|
|
|
|
|
app_logger.info("")
|
|
|
|
|
app_logger.info("Log Level: %s", settings.log_level)
|
|
|
|
|
app_logger.info(
|
|
|
|
|
"Log: %s",
|
|
|
|
|
log_dir / "factuges-document-signing-service.log",
|
|
|
|
|
)
|
|
|
|
|
app_logger.info("")
|
|
|
|
|
app_logger.info("Secret Provider: %s", settings.secret_provider)
|
|
|
|
|
app_logger.info("Infisical project ID: %s", settings.infisical_project_id)
|
|
|
|
|
app_logger.info("Infisical environment: %s", settings.infisical_env_slug)
|
|
|
|
|
app_logger.info("")
|
|
|
|
|
|
|
|
|
|
return app_logger
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger = build_logger()
|
2026-01-22 10:37:35 +00:00
|
|
|
|
2026-02-10 12:39:18 +00:00
|
|
|
|
|
|
|
|
async def http_exception_handler(request: Request, exc: HTTPException):
|
2026-04-02 10:26:44 +00:00
|
|
|
"""Registra y devuelve errores HTTP controlados."""
|
2026-02-10 12:39:18 +00:00
|
|
|
logger.error(
|
|
|
|
|
"HTTPException %s %s | status=%s | detail=%s",
|
|
|
|
|
request.method,
|
|
|
|
|
request.url.path,
|
|
|
|
|
exc.status_code,
|
|
|
|
|
exc.detail,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return JSONResponse(
|
|
|
|
|
status_code=exc.status_code,
|
|
|
|
|
content={"detail": exc.detail},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LoggingMiddleware(BaseHTTPMiddleware):
|
2026-04-02 10:26:44 +00:00
|
|
|
"""Registra request y response para trazabilidad básica."""
|
|
|
|
|
|
2026-02-10 12:39:18 +00:00
|
|
|
async def dispatch(self, request: Request, call_next):
|
2026-04-02 10:26:44 +00:00
|
|
|
client_ip = request.client.host if request.client else "unknown"
|
2026-02-10 12:39:18 +00:00
|
|
|
method = request.method
|
|
|
|
|
url = request.url.path
|
|
|
|
|
|
2026-04-02 10:26:44 +00:00
|
|
|
logger.info("Request: %s %s from %s", method, url, client_ip)
|
2026-02-10 12:39:18 +00:00
|
|
|
|
|
|
|
|
response = await call_next(request)
|
|
|
|
|
|
|
|
|
|
logger.info(
|
2026-04-02 10:26:44 +00:00
|
|
|
"Response: %s %s returned %s to %s",
|
|
|
|
|
method,
|
|
|
|
|
url,
|
|
|
|
|
response.status_code,
|
|
|
|
|
client_ip,
|
|
|
|
|
)
|
2026-02-10 12:39:18 +00:00
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
2026-04-02 10:26:44 +00:00
|
|
|
@asynccontextmanager
|
|
|
|
|
async def lifespan(app: FastAPI):
|
|
|
|
|
"""Gestiona el ciclo de vida de la aplicación."""
|
|
|
|
|
logger.info("Starting signing service")
|
|
|
|
|
yield
|
|
|
|
|
logger.info("Stopping signing service")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def healthcheck() -> dict[str, str]:
|
|
|
|
|
"""Endpoint simple de healthcheck."""
|
|
|
|
|
return {"status": "ok"}
|
|
|
|
|
|
2026-02-10 12:39:18 +00:00
|
|
|
|
2026-04-02 10:26:44 +00:00
|
|
|
async def get_version() -> dict[str, str]:
|
|
|
|
|
"""Devuelve la versión publicada del servicio."""
|
|
|
|
|
return {"version": version}
|
2026-02-10 12:39:18 +00:00
|
|
|
|
|
|
|
|
|
2026-04-02 10:26:44 +00:00
|
|
|
def create_app() -> FastAPI:
|
|
|
|
|
app = FastAPI(lifespan=lifespan,
|
|
|
|
|
title="FactuGES Document Signing Service",
|
|
|
|
|
version=version)
|
2026-02-10 12:39:18 +00:00
|
|
|
|
2026-04-02 10:26:44 +00:00
|
|
|
app.add_exception_handler(HTTPException, http_exception_handler)
|
|
|
|
|
app.add_middleware(LoggingMiddleware)
|
|
|
|
|
app.include_router(sign_router)
|
|
|
|
|
logger.info("API routes registered from sign_document router")
|
2026-01-29 17:35:43 +00:00
|
|
|
|
2026-04-02 10:26:44 +00:00
|
|
|
app.add_api_route("/health", healthcheck, methods=["GET"])
|
|
|
|
|
logger.info("Health check endpoint registered at /health")
|
2026-01-29 17:35:43 +00:00
|
|
|
|
2026-04-02 10:26:44 +00:00
|
|
|
app.add_api_route("/version", get_version, methods=["GET"])
|
|
|
|
|
logger.info("Version endpoint registered at /version")
|
|
|
|
|
return app
|
2026-01-22 10:37:35 +00:00
|
|
|
|
2026-02-03 15:23:25 +00:00
|
|
|
|
2026-04-02 10:26:44 +00:00
|
|
|
app = create_app()
|