feat: standardize main.py with health checks, structured logging, and API docs

- Add module-level docstring with endpoint documentation
- Add /health endpoint with database connectivity check
- Add /root endpoint redirecting to docs
- Add FastAPI metadata (title, description, version)
- Reorganize imports and add section separators
- Add startup/shutdown event handlers with emoji logging
- Add check_db_connection() helper using SQLAlchemy
This commit is contained in:
2026-02-27 18:32:48 -05:00
parent 3204451a5e
commit 285cac54a3

View File

@@ -1,24 +1,57 @@
"""
eamco_authorize - FastAPI Payment Authorization Microservice.
This microservice provides endpoints for managing payment processing
through Authorize.net, including payment profiles, transactions, and
auto-delivery billing.
Endpoints:
GET /health - Health check with database connectivity status
POST /api/payment/... - Payment processing endpoints
GET /api/transactions/... - Transaction management endpoints
POST /api/auto/... - Auto-delivery billing endpoints
GET /user/... - User verification endpoints
Usage:
# Development
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
# Production (Docker)
docker run -p 8000:8000 eamco_authorize
"""
import logging import logging
import sys import sys
import uuid import uuid
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
from authorizenet import apicontractsv1
from authorizenet.apicontrollers import getCustomerProfileIdsController
from authorizenet.constants import constants
from .database import engine from .database import engine
from . import models from . import models
from .routers import payment from .routers import payment
from .routers.transaction import transaction_router from .routers.transaction import transaction_router
from .routers.auto import auto_router from .routers.auto import auto_router
from .routers.user_check import user_check_router from .routers.user_check import user_check_router
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
from config import load_config from config import load_config
from authorizenet import apicontractsv1 from sqlalchemy import text
from authorizenet.apicontrollers import getCustomerProfileIdsController
from authorizenet.constants import constants
# =============================================================================
# CONFIGURATION
# =============================================================================
ApplicationConfig = load_config() ApplicationConfig = load_config()
# Configure logging # =============================================================================
# LOGGING CONFIGURATION
# =============================================================================
def setup_logging(): def setup_logging():
"""Configure structured logging for the application.""" """Configure structured logging for the application."""
log_level = logging.DEBUG if ApplicationConfig.CURRENT_SETTINGS != 'PRODUCTION' else logging.INFO log_level = logging.DEBUG if ApplicationConfig.CURRENT_SETTINGS != 'PRODUCTION' else logging.INFO
@@ -42,16 +75,48 @@ def setup_logging():
return logging.getLogger('eamco_authorize') return logging.getLogger('eamco_authorize')
logger = setup_logging() logger = setup_logging()
# =============================================================================
# DATABASE SETUP
# =============================================================================
models.Base.metadata.create_all(bind=engine) models.Base.metadata.create_all(bind=engine)
app = FastAPI()
def check_db_connection():
"""
Test database connectivity.
"""
try:
with engine.connect() as conn:
conn.execute(text("SELECT 1"))
return True
except Exception:
return False
# =============================================================================
# FASTAPI APPLICATION
# =============================================================================
app = FastAPI(
title="eamco_authorize",
description="Payment authorization microservice using Authorize.net",
version="1.0.0",
docs_url="/docs",
redoc_url="/redoc",
)
# =============================================================================
# MIDDLEWARE
# =============================================================================
# Request ID middleware for request tracking/correlation
class RequestIDMiddleware(BaseHTTPMiddleware): class RequestIDMiddleware(BaseHTTPMiddleware):
"""Request ID middleware for request tracking/correlation."""
async def dispatch(self, request: Request, call_next): async def dispatch(self, request: Request, call_next):
request_id = request.headers.get("X-Request-ID") or str(uuid.uuid4())[:8] request_id = request.headers.get("X-Request-ID") or str(uuid.uuid4())[:8]
request.state.request_id = request_id request.state.request_id = request_id
@@ -59,6 +124,7 @@ class RequestIDMiddleware(BaseHTTPMiddleware):
response.headers["X-Request-ID"] = request_id response.headers["X-Request-ID"] = request_id
return response return response
app.add_middleware(RequestIDMiddleware) app.add_middleware(RequestIDMiddleware)
app.add_middleware( app.add_middleware(
@@ -69,18 +135,52 @@ app.add_middleware(
allow_headers=["Authorization", "Content-Type", "Accept", "Origin", "X-Requested-With", "X-Request-ID"], allow_headers=["Authorization", "Content-Type", "Accept", "Origin", "X-Requested-With", "X-Request-ID"],
) )
# =============================================================================
# ROUTERS
# =============================================================================
app.include_router(payment.router, prefix="/api", tags=["payment"]) app.include_router(payment.router, prefix="/api", tags=["payment"])
app.include_router(transaction_router, prefix="/api", tags=["transactions"]) app.include_router(transaction_router, prefix="/api", tags=["transactions"])
app.include_router(auto_router, prefix="/api", tags=["auto"]) app.include_router(auto_router, prefix="/api", tags=["auto"])
app.include_router(user_check_router, prefix="/user", tags=["usercheck"]) app.include_router(user_check_router, prefix="/user", tags=["usercheck"])
# =============================================================================
# ENDPOINTS
# =============================================================================
@app.get("/") @app.get("/", include_in_schema=False)
def read_root(): async def root():
return {"message": "Welcome to the HVAC Payment API"} """Root endpoint - redirect to docs."""
return {
"service": "eamco_authorize",
"version": "1.0.0",
"docs": "/docs",
}
@app.get("/health", tags=["Health"])
async def health_check():
"""
Health check endpoint.
Returns service status and database connectivity.
Use this endpoint for container health checks and monitoring.
Returns:
JSON with status and db_connected flag
"""
db_connected = check_db_connection()
return {
"status": "healthy" if db_connected else "degraded",
"db_connected": db_connected,
}
# =============================================================================
# CREDENTIALS VALIDATION
# =============================================================================
def validate_authorize_credentials(): def validate_authorize_credentials():
@@ -140,16 +240,38 @@ def validate_authorize_credentials():
return True return True
# =============================================================================
# STARTUP/SHUTDOWN EVENTS
# =============================================================================
@app.on_event("startup") @app.on_event("startup")
async def startup_event(): async def startup_event():
"""Application startup - validate payment credentials.""" """Application startup - validate payment credentials and test DB connection."""
logger.info("Starting eamco_authorize service...") logger.info("🚀 eamco_authorize STARTING")
logger.info(f"Mode: {ApplicationConfig.CURRENT_SETTINGS}") mode = ApplicationConfig.CURRENT_SETTINGS.upper()
if mode in ['DEVELOPMENT', 'DEV']:
logger.info("🤖🤖🤖🤖🤖 Mode: Development 🤖🤖🤖🤖🤖")
elif mode in ['PRODUCTION', 'PROD']:
logger.info("💀💀💀💀💀💀💀💀💀💀 ⚠️ WARNING PRODUCTION 💀💀💀💀💀💀💀💀💀💀")
logger.info(f"CORS: {len(ApplicationConfig.origins)} origins configured")
# Test database connection
if check_db_connection():
logger.info("DB Connection: ✅ OK")
else:
logger.warning("DB Connection: ❌ FAILED")
# Validate Authorize.net credentials
try: try:
validate_authorize_credentials() validate_authorize_credentials()
logger.info("Authorize.net credentials: VALID") logger.info("Authorize.net credentials: VALID")
except ValueError as e: except ValueError as e:
logger.critical(f"PAYMENT CREDENTIALS INVALID: {e}") logger.critical(f"PAYMENT CREDENTIALS INVALID: {e}")
logger.critical("Service will start but ALL PAYMENT OPERATIONS WILL FAIL") logger.critical("Service will start but ALL PAYMENT OPERATIONS WILL FAIL")
# In production, you might want to sys.exit(1) here instead
@app.on_event("shutdown")
async def shutdown_event():
"""Application shutdown - cleanup."""
logger.info("🛑 eamco_authorize SHUTTING DOWN")