changes
This commit is contained in:
120
app/main.py
120
app/main.py
@@ -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)
|
||||
Reference in New Issue
Block a user