import os from pathlib import Path from fastapi import FastAPI, APIRouter, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse import logging from contextlib import asynccontextmanager from database.database import engine from models import Base from api.routes import router as root_router from api.user_routes import router as user_router from api.job_routes import router as job_router from api.assessment_routes import router as assessment_router from api.application_routes import router as application_router from config import settings from logging_config import get_logger from db_seeder import seed_database # Create logger for this module logging.basicConfig(level=settings.log_level) logger = logging.getLogger(__name__) logger = get_logger(__name__) PORT = int(os.environ.get("PORT", 7860)) HOST = os.environ.get("HOST", "0.0.0.0") DEBUG = os.getenv("DEBUG", str(settings.debug)).lower() == "true" @asynccontextmanager async def lifespan(app: FastAPI): """Handle startup and shutdown events""" # Startup logger.info(f"Starting {settings.app_name} v{settings.app_version}") logger.info(f"Database URL: {settings.database_url}") # Seed the database if it's empty logger.info("Checking if database needs seeding...") seed_database() logger.info("Database seeding check completed") logger.info("Application started successfully") yield # Shutdown logger.info("Application shutting down") # Initialize FastAPI app with settings app = FastAPI( title=settings.app_name, description=settings.app_description, version=settings.app_version ) # ---------------------------- # Create database tables on startup # ---------------------------- @app.on_event("startup") def startup_event(): Base.metadata.create_all(bind=engine) logger.info(f"Database tables created/verified at {settings.database_url}") # ---------------------------- # Configure CORS # ---------------------------- origins = os.getenv("CORS_ORIGINS", "http://localhost:5173,http://localhost:3000").split(",") app.add_middleware( CORSMiddleware, allow_origins=[o.strip() for o in origins if o.strip()], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ---------------------------- # API routers under /api # ---------------------------- api_router = APIRouter(prefix="/api") api_router.include_router(root_router) api_router.include_router(user_router) api_router.include_router(job_router) api_router.include_router(assessment_router) api_router.include_router(application_router) app.include_router(api_router) react_build_path = Path("static") if react_build_path.exists(): # Serve actual static files (JS/CSS/images) under /static app.mount("/static", StaticFiles(directory=react_build_path / "assets"), name="static_assets") else: logger.warning("React build not found in /static. Make sure to build frontend.") # ---------------------------- # SPA fallback for non-API routes (this will handle all non-API routes) # NOTE: This must be defined LAST to avoid catching other routes # ---------------------------- @app.get("/{full_path:path}") async def serve_spa(full_path: str, request: Request): """ Serve React SPA for any non-API path that doesn't match a real file. This handles client-side routing for React Router. """ # If it's an API route, return 404 since we already handled API routes separately if request.url.path.startswith("/api"): from fastapi.responses import JSONResponse return JSONResponse({"detail": "API route not found."}, status_code=404) # For all other routes, serve the SPA index.html so React Router can handle it index_file = react_build_path / "index.html" if index_file.exists(): return FileResponse(index_file) from fastapi.responses import JSONResponse return JSONResponse({"detail": "SPA not found, build your frontend first."}, status_code=500) logger.info("Application routes registered") # ---------------------------- # Run server (local/dev) # ---------------------------- if __name__ == "__main__": import uvicorn logger.info(f"Starting server on {HOST}:{PORT}") uvicorn.run( "main:app", host=HOST, port=PORT, log_level="info", workers=1, loop="asyncio", )