"""FastAPI application entrypoint."""
from pathlib import Path
from fastapi import FastAPI
from fastapi.exceptions import RequestValidationError
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from sqlalchemy import text
from gnexus_creds.api import admin_router
from gnexus_creds.api import router as api_router
from gnexus_creds.config import get_settings
from gnexus_creds.db import SessionLocal
from gnexus_creds.errors import AppError, app_error_handler, validation_error_handler
from gnexus_creds.logging import configure_logging
from gnexus_creds.mcp import router as mcp_router
from gnexus_creds.oauth import router as auth_router
from gnexus_creds.oauth import webhook_router
PROJECT_ROOT = Path(__file__).resolve().parent.parent
FRONTEND_DIST = PROJECT_ROOT / "frontend" / "dist"
UI_KIT_ASSETS = PROJECT_ROOT / "frontend" / "node_modules" / "gnexus-ui-kit" / "dist" / "assets"
def create_app() -> FastAPI:
configure_logging()
settings = get_settings()
app = FastAPI(title=settings.app_name, version="0.1.0")
app.add_exception_handler(AppError, app_error_handler)
app.add_exception_handler(RequestValidationError, validation_error_handler)
app.include_router(auth_router)
app.include_router(webhook_router)
app.include_router(api_router)
app.include_router(admin_router)
app.include_router(mcp_router)
@app.get("/health", tags=["system"])
async def health() -> dict[str, str]:
return {"status": "ok"}
@app.get("/ready", tags=["system"])
async def ready() -> dict[str, str]:
with SessionLocal() as db:
db.execute(text("select 1"))
return {"status": "ok"}
if FRONTEND_DIST.exists():
ui_kit_fonts_dir = UI_KIT_ASSETS / "fonts"
if ui_kit_fonts_dir.exists():
app.mount("/assets/fonts", StaticFiles(directory=ui_kit_fonts_dir), name="ui-kit-fonts")
assets_dir = FRONTEND_DIST / "assets"
if assets_dir.exists():
app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
@app.get("/", include_in_schema=False)
async def index() -> FileResponse:
return FileResponse(FRONTEND_DIST / "index.html")
@app.get("/{path:path}", include_in_schema=False)
async def spa_fallback(path: str) -> FileResponse:
if path.startswith(("api/", "auth/", "mcp/", "webhooks/")):
raise AppError("not_found", "Not found.", status_code=404)
return FileResponse(FRONTEND_DIST / "index.html")
return app
app = create_app()