from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from contextlib import asynccontextmanager from apscheduler.schedulers.asyncio import AsyncIOScheduler from app.config import settings from app.database import engine from app.routers import auth, todos, events, calendars, reminders, projects, people, locations, settings as settings_router, dashboard, weather, event_templates from app.routers import totp, admin from app.jobs.notifications import run_notification_dispatch # Import models so Alembic's autogenerate can discover them from app.models import user as _user_model # noqa: F401 from app.models import session as _session_model # noqa: F401 from app.models import totp_usage as _totp_usage_model # noqa: F401 from app.models import backup_code as _backup_code_model # noqa: F401 from app.models import system_config as _system_config_model # noqa: F401 from app.models import audit_log as _audit_log_model # noqa: F401 @asynccontextmanager async def lifespan(app: FastAPI): scheduler = AsyncIOScheduler() scheduler.add_job( run_notification_dispatch, "interval", minutes=1, id="ntfy_dispatch", max_instances=1, # prevent overlap if a run takes longer than 60s ) scheduler.start() yield scheduler.shutdown(wait=False) await engine.dispose() _is_dev = settings.ENVIRONMENT == "development" app = FastAPI( title="UMBRA API", description="Backend API for UMBRA application", version="1.0.0", lifespan=lifespan, docs_url="/docs" if _is_dev else None, redoc_url="/redoc" if _is_dev else None, openapi_url="/openapi.json" if _is_dev else None, ) # CORS configuration app.add_middleware( CORSMiddleware, allow_origins=[o.strip() for o in settings.CORS_ORIGINS.split(",") if o.strip()], allow_credentials=True, allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"], allow_headers=["Content-Type", "Authorization", "Cookie"], ) # Include routers with /api prefix app.include_router(auth.router, prefix="/api/auth", tags=["Authentication"]) app.include_router(todos.router, prefix="/api/todos", tags=["Todos"]) app.include_router(events.router, prefix="/api/events", tags=["Calendar Events"]) app.include_router(calendars.router, prefix="/api/calendars", tags=["Calendars"]) app.include_router(reminders.router, prefix="/api/reminders", tags=["Reminders"]) app.include_router(projects.router, prefix="/api/projects", tags=["Projects"]) app.include_router(people.router, prefix="/api/people", tags=["People"]) app.include_router(locations.router, prefix="/api/locations", tags=["Locations"]) app.include_router(settings_router.router, prefix="/api/settings", tags=["Settings"]) app.include_router(dashboard.router, prefix="/api", tags=["Dashboard"]) app.include_router(weather.router, prefix="/api/weather", tags=["Weather"]) app.include_router(event_templates.router, prefix="/api/event-templates", tags=["Event Templates"]) app.include_router(totp.router, prefix="/api/auth", tags=["TOTP MFA"]) app.include_router(admin.router, prefix="/api/admin", tags=["Admin"]) @app.get("/") async def root(): return {"message": "UMBRA API is running"} @app.get("/health") async def health_check(): return {"status": "healthy"}