import os from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from sqlalchemy import func, select from app.config import settings from app.database import async_session, engine from app.models import Base, Program, Show, StationConfig, HistoryEntry, TeamMember, CommunityHighlight from app.user_models import User from app.api import programs, events, tiers, auth, station_config, history, team, community, shows async def _ensure_station_config(session): """Ensure the singleton station config row exists (idempotent).""" result = await session.execute( select(StationConfig).where(StationConfig.key == "default") ) if result.scalar_one_or_none() is None: print(" → Station config missing — seeding defaults...") from seed import seed as run_seed # Only seed the station config, not the full dataset from seed import STATION_CONFIG, _upsert_station_config await _upsert_station_config(session, STATION_CONFIG) await session.commit() print(" ✓ Station config seeded") @asynccontextmanager async def lifespan(app: FastAPI): # Create tables on startup async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) # Auto-seed if database is empty async with async_session() as session: result = await session.execute(select(func.count(Show.id))) count = result.scalar() if count == 0: print(" → Database is empty — running seed...") from seed import seed as run_seed await run_seed() return # Full seed already included station config # Ensure station config exists (runs even if DB already had data) async with async_session() as session: await _ensure_station_config(session) # Bootstrap admin user if configured if settings.ADMIN_USERNAME and settings.ADMIN_PASSWORD: async with async_session() as session: result = await session.execute( select(User).where( User.email == f"{settings.ADMIN_USERNAME}@local", User.auth_provider == "local", ) ) if result.scalar_one_or_none() is None: admin_user = User( email=f"{settings.ADMIN_USERNAME}@local", display_name=settings.ADMIN_USERNAME, auth_provider="local", is_admin=True, ) session.add(admin_user) await session.commit() print(f" → Bootstrap admin created: {settings.ADMIN_USERNAME}") # Ensure uploads directory exists uploads_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "uploads") os.makedirs(uploads_dir, exist_ok=True) yield app = FastAPI( title="KMountain Flower Radio API", description="Backend API for the KMountain Flower Radio Station website.", version="1.0.0", lifespan=lifespan, ) app.add_middleware( CORSMiddleware, allow_origins=settings.CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Register routers app.include_router(auth.router, prefix="/api/auth", tags=["auth"]) app.include_router(programs.router, prefix="/api/programs", tags=["programs"]) app.include_router(events.router, prefix="/api/events", tags=["events"]) app.include_router(tiers.router, prefix="/api/tiers", tags=["tiers"]) app.include_router(station_config.router, prefix="/api/station-config", tags=["station-config"]) app.include_router(history.router, prefix="/api/history", tags=["history"]) app.include_router(team.router, prefix="/api/team", tags=["team"]) app.include_router(community.router, prefix="/api/community", tags=["community"]) app.include_router(shows.router, prefix="/api/shows", tags=["shows"]) # Serve uploaded files (dev only — Nginx handles this in production) if settings.ENV != "production": uploads_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "uploads") if os.path.exists(uploads_dir): app.mount("/uploads", StaticFiles(directory=uploads_dir), name="uploads") # Dev-only admin router (disabled in production) if settings.ENV != "production": from app.api import admin app.include_router(admin.router, prefix="/api/admin", tags=["admin"])