This commit is contained in:
HotSwapp
2025-08-18 20:20:04 -05:00
parent 89b2bc0aa2
commit bac8cc4bd5
114 changed files with 30258 additions and 1341 deletions

View File

@@ -1,7 +1,7 @@
"""
Delphi Consulting Group Database System - Main FastAPI Application
"""
from fastapi import FastAPI, Request, Depends
from fastapi import FastAPI, Request, Depends, Response
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse, RedirectResponse
@@ -9,6 +9,7 @@ from fastapi.middleware.cors import CORSMiddleware
from app.config import settings
from app.database.base import engine
from sqlalchemy import text
from app.database.fts import ensure_rolodex_fts, ensure_files_fts, ensure_ledger_fts, ensure_qdros_fts
from app.database.indexes import ensure_secondary_indexes
from app.database.schema_updates import ensure_schema_updates
@@ -18,6 +19,17 @@ from app.auth.security import get_admin_user
from app.core.logging import setup_logging, get_logger
from app.middleware.logging import LoggingMiddleware
from app.middleware.errors import register_exception_handlers
from app.middleware.rate_limiting import RateLimitMiddleware, AuthenticatedRateLimitMiddleware
from app.middleware.security_headers import (
SecurityHeadersMiddleware,
RequestSizeLimitMiddleware,
CSRFMiddleware
)
from app.middleware.session_middleware import (
SessionManagementMiddleware,
SessionSecurityMiddleware,
SessionCookieMiddleware
)
# Initialize logging
setup_logging()
@@ -50,8 +62,51 @@ app = FastAPI(
description="Modern Python web application for Delphi Consulting Group",
)
# Add logging middleware
logger.info("Adding request logging middleware")
# Initialize WebSocket pool on startup
@app.on_event("startup")
async def startup_event():
"""Initialize WebSocket pool and other startup tasks"""
from app.services.websocket_pool import initialize_websocket_pool
logger.info("Initializing WebSocket connection pool")
await initialize_websocket_pool(
cleanup_interval=60,
connection_timeout=300,
heartbeat_interval=30,
max_connections_per_topic=1000,
max_total_connections=10000
)
logger.info("WebSocket pool initialized successfully")
@app.on_event("shutdown")
async def shutdown_event():
"""Cleanup WebSocket pool and other shutdown tasks"""
from app.services.websocket_pool import shutdown_websocket_pool
logger.info("Shutting down WebSocket connection pool")
await shutdown_websocket_pool()
logger.info("WebSocket pool shutdown complete")
# Add security middleware (order matters - first added is outermost)
logger.info("Adding security middleware")
# Security headers
app.add_middleware(SecurityHeadersMiddleware)
# Request size limiting
app.add_middleware(RequestSizeLimitMiddleware, max_size=100 * 1024 * 1024) # 100MB
# CSRF protection
app.add_middleware(CSRFMiddleware)
# Session management (before rate limiting so request.state.user is available)
app.add_middleware(SessionManagementMiddleware, cleanup_interval=3600)
app.add_middleware(SessionSecurityMiddleware)
app.add_middleware(SessionCookieMiddleware, secure=not settings.debug)
# Rate limiting: first apply authenticated, then fallback IP-based
app.add_middleware(AuthenticatedRateLimitMiddleware)
app.add_middleware(RateLimitMiddleware)
# Request logging (after security checks)
app.add_middleware(LoggingMiddleware, log_requests=True, log_responses=settings.debug)
# Register global exception handlers
@@ -60,12 +115,30 @@ register_exception_handlers(app)
# Configure CORS
logger.info("Configuring CORS middleware")
# Parse CORS origins from settings (comma-separated string to list)
cors_origins = []
if settings.cors_origins:
cors_origins = [origin.strip() for origin in settings.cors_origins.split(",")]
else:
# Default to localhost for development only
cors_origins = [
"http://localhost:8000",
"http://127.0.0.1:8000",
"https://localhost:8000",
"https://127.0.0.1:8000"
]
if settings.debug:
logger.warning("Using default localhost CORS origins. Set CORS_ORIGINS environment variable for production.")
logger.info(f"CORS origins configured: {cors_origins}")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Configure appropriately for production
allow_origins=cors_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", "Authorization", "X-Requested-With"],
)
# Mount static files
@@ -78,6 +151,7 @@ logger.info("Initializing Jinja2 templates")
templates = Jinja2Templates(directory="templates")
# Include routers
from app.api.advanced_variables import router as advanced_variables_router
from app.api.auth import router as auth_router
from app.api.customers import router as customers_router
from app.api.files import router as files_router
@@ -92,13 +166,22 @@ from app.api.support import router as support_router
from app.api.settings import router as settings_router
from app.api.mortality import router as mortality_router
from app.api.pensions import router as pensions_router
from app.api.pension_valuation import router as pension_valuation_router
from app.api.templates import router as templates_router
from app.api.qdros import router as qdros_router
from app.api.timers import router as timers_router
from app.api.labels import router as labels_router
from app.api.file_management import router as file_management_router
from app.api.deadlines import router as deadlines_router
from app.api.document_workflows import router as document_workflows_router
from app.api.session_management import router as session_management_router
from app.api.advanced_templates import router as advanced_templates_router
from app.api.jobs import router as jobs_router
logger.info("Including API routers")
app.include_router(advanced_variables_router, prefix="/api/variables", tags=["advanced-variables"])
app.include_router(auth_router, prefix="/api/auth", tags=["authentication"])
app.include_router(session_management_router, tags=["session-management"])
app.include_router(customers_router, prefix="/api/customers", tags=["customers"])
app.include_router(files_router, prefix="/api/files", tags=["files"])
app.include_router(financial_router, prefix="/api/financial", tags=["financial"])
@@ -112,10 +195,16 @@ app.include_router(settings_router, prefix="/api/settings", tags=["settings"])
app.include_router(flexible_router, prefix="/api")
app.include_router(mortality_router, prefix="/api/mortality", tags=["mortality"])
app.include_router(pensions_router, prefix="/api/pensions", tags=["pensions"])
app.include_router(pension_valuation_router, prefix="/api/pensions", tags=["pensions-valuation"])
app.include_router(templates_router, prefix="/api/templates", tags=["templates"])
app.include_router(advanced_templates_router, prefix="/api/templates", tags=["advanced-templates"])
app.include_router(qdros_router, prefix="/api", tags=["qdros"])
app.include_router(timers_router, prefix="/api/timers", tags=["timers"])
app.include_router(file_management_router, prefix="/api/file-management", tags=["file-management"])
app.include_router(deadlines_router, prefix="/api/deadlines", tags=["deadlines"])
app.include_router(document_workflows_router, prefix="/api/workflows", tags=["document-workflows"])
app.include_router(labels_router, prefix="/api/labels", tags=["labels"])
app.include_router(jobs_router, prefix="/api/jobs", tags=["jobs"])
@app.get("/", response_class=HTMLResponse)
@@ -223,6 +312,25 @@ async def health_check():
return {"status": "healthy", "version": settings.app_version}
@app.get("/ready")
async def readiness_check():
"""Readiness check that verifies database connectivity."""
try:
with engine.connect() as connection:
connection.execute(text("SELECT 1"))
return {"status": "ready", "version": settings.app_version}
except Exception as e:
return {"status": "degraded", "error": str(e), "version": settings.app_version}
@app.get("/metrics")
async def metrics_endpoint() -> Response:
"""Prometheus metrics endpoint."""
from app.core.logging import export_metrics
payload, content_type = export_metrics()
return Response(content=payload, media_type=content_type)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000, reload=settings.debug)