File size: 4,447 Bytes
6210d3a
e3cd6e2
 
5e44686
35454f8
91299cb
6210d3a
1bf457e
358dfff
 
6210d3a
358dfff
 
 
 
 
 
 
b04c4ca
358dfff
 
6210d3a
 
358dfff
 
2fba0ae
a074596
e3cd6e2
35454f8
1bf457e
358dfff
 
 
 
 
 
1bf457e
b04c4ca
 
 
 
 
358dfff
1bf457e
358dfff
 
 
 
 
 
 
 
6210d3a
358dfff
 
e3cd6e2
 
 
35454f8
 
 
6210d3a
35454f8
e3cd6e2
 
 
 
5e44686
 
 
 
 
 
 
 
e3cd6e2
 
 
40a8d66
 
 
 
 
 
 
358dfff
e3cd6e2
 
33c29a9
e3cd6e2
 
 
91299cb
e3cd6e2
33c29a9
 
e3cd6e2
91299cb
 
e3cd6e2
 
33c29a9
e3cd6e2
33c29a9
2fba0ae
33c29a9
 
2fba0ae
33c29a9
e3cd6e2
91299cb
 
33c29a9
 
 
35454f8
6210d3a
35454f8
e3cd6e2
 
 
358dfff
 
e3cd6e2
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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",
    )