UMBRA/backend/app/main.py
Kyle Pope ad102c24ed Apply QA suggestions and update all documentation
Code changes (S-01, S-02, S-05):
- DRY nginx proxy blocks via shared proxy-params.conf include
- Add ENVIRONMENT and CORS_ORIGINS to .env.example
- Remove unused X-Requested-With from CORS allow_headers

Documentation updates:
- README.md: reflect auth upgrade, security hardening, production
  deployment guide with secret generation commands, updated architecture
  diagram, current project structure and feature list
- CLAUDE.md: codify established dev workflow (branch → implement →
  test → QA → merge), update auth/infra/stack sections, add authority
  links for progress.md and ntfy.md
- progress.md: add Phase 11 (auth upgrade) and Phase 12 (pentest
  remediation), update file inventory, fix outstanding items
- ui_refresh.md: update current status line

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:36:12 +08:00

81 lines
3.0 KiB
Python

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
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
@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.get("/")
async def root():
return {"message": "UMBRA API is running"}
@app.get("/health")
async def health_check():
return {"status": "healthy"}