""" Delphi Consulting Group Database System - Main FastAPI Application """ from fastapi import FastAPI, Request, Depends, Response from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from fastapi.responses import HTMLResponse, RedirectResponse 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 from app.models import BaseModel from app.models.user import User 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() logger = get_logger("main") # Create database tables logger.info("Creating database tables") BaseModel.metadata.create_all(bind=engine) # Initialize SQLite FTS (if available) logger.info("Initializing FTS (if available)") ensure_rolodex_fts(engine) ensure_files_fts(engine) ensure_ledger_fts(engine) ensure_qdros_fts(engine) # Ensure helpful secondary indexes logger.info("Ensuring secondary indexes (status, type, employee, etc.)") ensure_secondary_indexes(engine) # Ensure idempotent schema updates for added columns logger.info("Ensuring schema updates (new columns)") ensure_schema_updates(engine) # Initialize FastAPI app logger.info("Initializing FastAPI application", version=settings.app_version, debug=settings.debug) app = FastAPI( title=settings.app_name, version=settings.app_version, description="Modern Python web application for Delphi Consulting Group", ) # 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 logger.info("Registering global exception handlers") 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=cors_origins, allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], allow_headers=["Content-Type", "Authorization", "X-Requested-With"], ) # Mount static files logger.info("Mounting static file directories") app.mount("/static", StaticFiles(directory="static"), name="static") app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads") # Templates 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 from app.api.financial import router as financial_router from app.api.documents import router as documents_router from app.api.billing import router as billing_router from app.api.search import router as search_router from app.api.admin import router as admin_router 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 from app.api.import_csv import router as import_csv_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"]) app.include_router(billing_router, prefix="/api/billing", tags=["billing"]) app.include_router(documents_router, prefix="/api/documents", tags=["documents"]) app.include_router(search_router, prefix="/api/search", tags=["search"]) app.include_router(admin_router, prefix="/api/admin", tags=["admin"]) app.include_router(support_router, prefix="/api/support", tags=["support"]) app.include_router(settings_router, prefix="/api/settings", tags=["settings"]) 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.include_router(import_csv_router, prefix="/api/admin/import", tags=["import"]) @app.get("/", response_class=HTMLResponse) async def root(request: Request): """Dashboard as the main landing page. Client-side JS handles auth redirect.""" return templates.TemplateResponse( "dashboard.html", {"request": request, "title": "Dashboard - " + settings.app_name} ) @app.get("/login", response_class=HTMLResponse) async def login_page(request: Request): """Login page""" return templates.TemplateResponse( "login.html", {"request": request, "title": "Login - " + settings.app_name} ) @app.get("/customers", response_class=HTMLResponse) async def customers_page(request: Request): """Customer management page""" return templates.TemplateResponse( "customers.html", {"request": request, "title": "Customers - " + settings.app_name} ) @app.get("/files", response_class=HTMLResponse) async def files_page(request: Request): """File cabinet management page""" return templates.TemplateResponse( "files.html", {"request": request, "title": "File Cabinet - " + settings.app_name} ) @app.get("/financial", response_class=HTMLResponse) async def financial_page(request: Request): """Financial/Ledger management page""" return templates.TemplateResponse( "financial.html", {"request": request, "title": "Financial/Ledger - " + settings.app_name} ) @app.get("/billing", response_class=HTMLResponse) async def billing_page(request: Request): """Billing Statements page""" return templates.TemplateResponse( "billing.html", {"request": request, "title": "Billing Statements - " + settings.app_name} ) @app.get("/documents", response_class=HTMLResponse) async def documents_page(request: Request): """Document management page""" return templates.TemplateResponse( "documents.html", {"request": request, "title": "Document Management - " + settings.app_name} ) @app.get("/search", response_class=HTMLResponse) async def search_page(request: Request): """Advanced search page""" return templates.TemplateResponse( "search.html", {"request": request, "title": "Advanced Search - " + settings.app_name} ) @app.get("/admin", response_class=HTMLResponse) async def admin_page(request: Request): """System administration page (admin only)""" return templates.TemplateResponse( "admin.html", {"request": request, "title": "System Administration - " + settings.app_name} ) @app.get("/admin/import", response_class=HTMLResponse) async def admin_import_page(request: Request): """CSV Import page (admin only)""" return templates.TemplateResponse( "admin_import.html", {"request": request, "title": "CSV Import - " + settings.app_name} ) @app.get("/health") async def health_check(): """Health check endpoint""" 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)