|
|
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 |
|
|
|
|
|
|
|
|
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""" |
|
|
|
|
|
logger.info(f"Starting {settings.app_name} v{settings.app_version}") |
|
|
logger.info(f"Database URL: {settings.database_url}") |
|
|
|
|
|
|
|
|
logger.info("Checking if database needs seeding...") |
|
|
seed_database() |
|
|
logger.info("Database seeding check completed") |
|
|
|
|
|
logger.info("Application started successfully") |
|
|
|
|
|
yield |
|
|
|
|
|
logger.info("Application shutting down") |
|
|
|
|
|
|
|
|
app = FastAPI( |
|
|
title=settings.app_name, |
|
|
description=settings.app_description, |
|
|
version=settings.app_version |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.on_event("startup") |
|
|
def startup_event(): |
|
|
Base.metadata.create_all(bind=engine) |
|
|
logger.info(f"Database tables created/verified at {settings.database_url}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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_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(): |
|
|
|
|
|
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.") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@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 request.url.path.startswith("/api"): |
|
|
from fastapi.responses import JSONResponse |
|
|
return JSONResponse({"detail": "API route not found."}, status_code=404) |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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", |
|
|
) |
|
|
|