from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse from contextlib import asynccontextmanager import logging import os from .config import get_settings from .database import init_db, SessionLocal from .routes import router from .seed import seed_database settings = get_settings() logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): # Startup: initialize database init_db() # Seed test data in development mode if settings.is_development: logger.info(f"Running in {settings.env} mode - checking for seed data") db = SessionLocal() try: seed_database(db) finally: db.close() else: logger.info(f"Running in {settings.env} mode - skipping seed data") yield # Shutdown: cleanup if needed app = FastAPI( title="Orchard", description="Content-Addressable Storage System", version="1.0.0", lifespan=lifespan, ) # Include API routes app.include_router(router) # Serve static files (React build) if the directory exists static_dir = os.path.join(os.path.dirname(__file__), "..", "..", "frontend", "dist") if os.path.exists(static_dir): app.mount("/assets", StaticFiles(directory=os.path.join(static_dir, "assets")), name="assets") @app.get("/") async def serve_spa(): return FileResponse(os.path.join(static_dir, "index.html")) # Catch-all for SPA routing (must be last) @app.get("/{full_path:path}") async def serve_spa_routes(full_path: str): # Don't catch API routes or health endpoint if full_path.startswith("api/") or full_path.startswith("health"): from fastapi import HTTPException raise HTTPException(status_code=404, detail="Not found") # Serve SPA for all other routes (including /project/*) index_path = os.path.join(static_dir, "index.html") if os.path.exists(index_path): return FileResponse(index_path) from fastapi import HTTPException raise HTTPException(status_code=404, detail="Not found")