Files
delphi-database/app/main.py
HotSwapp 7e9bfcec5e feat: Rebuild complete CSV import system for legacy data migration
PROBLEM SOLVED:
- Completely removed broken import functionality
- Built new robust, modular CSV import system from scratch
- Provides reliable data migration path for legacy .sc files

NEW IMPORT SYSTEM FEATURES:
 Modular CSV parsers for all 5 tables (ROLODEX, PHONE, FILES, LEDGER, QDROS)
 RESTful API endpoints with background processing (/api/admin/import/*)
 Admin web interface at /admin/import for file uploads
 Comprehensive validation and error handling
 Real-time progress tracking and status monitoring
 Detailed logging with import session tracking
 Transaction rollback on failures
 Batch import with dependency ordering
 Foreign key validation and duplicate detection

TECHNICAL IMPLEMENTATION:
- Clean /app/import_export/ module structure with base classes
- Enhanced logging system with import-specific logs
- Background task processing with FastAPI BackgroundTasks
- Auto-detection of CSV delimiters and encoding
- Field validation with proper data type conversion
- Admin authentication integration
- Console logging for debugging support

IMPORT WORKFLOW:
1. Admin selects table type and uploads CSV file
2. System validates headers and data structure
3. Background processing with real-time status updates
4. Detailed error reporting and success metrics
5. Import logs stored in logs/imports/ directory

SUPPORTED TABLES:
- ROLODEX (contacts/people) - 19 fields, requires: id, last
- PHONE (phone numbers) - 3 fields, requires: rolodex_id, phone
- FILES (case files) - 29 fields, requires: file_no, id, empl_num, file_type, opened, status, rate_per_hour
- LEDGER (transactions) - 12 fields, requires: file_no, date, t_code, t_type, empl_num, amount
- QDROS (documents) - 31 fields, requires: file_no

REMOVED FILES:
- app/api/unified_import_api.py
- app/services/unified_import.py
- app/api/flexible.py
- app/models/flexible.py
- templates/unified_import.html
- templates/flexible.html
- static/js/flexible.js
- All legacy import routes and references

TESTING COMPLETED:
 Schema validation for all table types
 CSV header validation
 Single file import functionality
 Multi-table dependency validation
 Error handling and logging
 API endpoint integration

READY FOR PRODUCTION: System tested and validated with sample data.
Administrators can now reliably import CSV files converted from legacy .sc files.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-21 20:54:46 -05:00

331 lines
12 KiB
Python

"""
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)