from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from contextlib import asynccontextmanager from sqlalchemy import text import asyncio from app.core.database import engine, AsyncSessionLocal, Base from app.core.config import settings from app.api import auth, activities, routes, health, records, upload async def init_db(): """Create tables then seed admin, with retries for slow DB startup.""" for attempt in range(10): try: # Step 1: create all tables (separate connection so it commits cleanly) async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) break except Exception as e: if attempt == 9: raise print(f"DB not ready yet (attempt {attempt + 1}/10): {e}") await asyncio.sleep(3) # Step 2: try to enable TimescaleDB hypertable (separate connection, # failure here is non-fatal - falls back to plain Postgres) try: async with engine.begin() as conn: await conn.execute(text( "SELECT create_hypertable('activity_data_points', 'timestamp', " "if_not_exists => TRUE, migrate_data => TRUE)" )) except Exception as e: print(f"TimescaleDB hypertable skipped: {e}") # Step 3: seed admin user from sqlalchemy import select from app.models.user import User from app.core.security import hash_password async with AsyncSessionLocal() as db: result = await db.execute( select(User).where(User.username == settings.admin_username) ) if not result.scalar_one_or_none(): admin = User( username=settings.admin_username, hashed_password=hash_password(settings.admin_password), is_admin=True, ) db.add(admin) await db.commit() print(f"Admin user '{settings.admin_username}' created") @asynccontextmanager async def lifespan(app: FastAPI): await init_db() yield app = FastAPI( title="MileVault", version="1.0.0", lifespan=lifespan, ) app.add_middleware( CORSMiddleware, allow_origins=["*"] if settings.environment == "development" else [], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.include_router(auth.router, prefix="/api/auth", tags=["auth"]) app.include_router(activities.router, prefix="/api/activities", tags=["activities"]) app.include_router(routes.router, prefix="/api/routes", tags=["routes"]) app.include_router(health.router, prefix="/api/health-metrics", tags=["health"]) app.include_router(records.router, prefix="/api/records", tags=["records"]) app.include_router(upload.router, prefix="/api/upload", tags=["upload"]) @app.get("/health") async def healthcheck(): return {"status": "ok"}