"""FastAPI app entry: mount routes, static, templates, Sentry.""" from contextlib import asynccontextmanager import sentry_sdk from fastapi import FastAPI, Request from fastapi.responses import RedirectResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from pathlib import Path from app.core.auth import ( Base as AuthBase, RequireLogin, auth_backend, fastapi_users, get_async_engine, google_oauth_client, ) from app.core.config import get_settings from app.routes import health, invite, events, admin, index, auth @asynccontextmanager async def lifespan(app: FastAPI): # Create auth tables (auth_user, oauth_account) if they don't exist async with get_async_engine().begin() as conn: await conn.run_sync(AuthBase.metadata.create_all) yield app = FastAPI( title="Slack Data Collector", description="Self-hosted Slack onboarding and workspace data sync service", lifespan=lifespan, ) # ── Exception handler: redirect to login when admin access is attempted ──── @app.exception_handler(RequireLogin) async def require_login_handler(request: Request, exc: RequireLogin): return RedirectResponse("/auth/login", status_code=302) # ── Templates and static ─────────────────────────────────────────────────── BASE_DIR = Path(__file__).resolve().parent app.state.templates = Jinja2Templates(directory=str(BASE_DIR / "templates")) app.state.templates.env.globals["site_title"] = get_settings().SITE_TITLE app.state.templates.env.globals["site_description"] = get_settings().SITE_DESCRIPTION app.state.templates.env.globals["admin_email"] = get_settings().ADMIN_EMAIL or "" app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static") # ── Routers ──────────────────────────────────────────────────────────────── settings = get_settings() app.include_router(health.router, tags=["health"]) app.include_router(invite.router, tags=["invite"]) app.include_router(events.router, tags=["events"]) app.include_router(admin.router, prefix="/admin", tags=["admin"]) app.include_router(auth.router, tags=["auth"]) app.include_router(index.router, tags=["index"]) # Google OAuth routes: GET /auth/authorize GET /auth/callback redirect_url = f"{settings.BASE_URL}/auth/callback" if settings.BASE_URL else None app.include_router( fastapi_users.get_oauth_router( google_oauth_client, auth_backend, settings.SESSION_SECRET, redirect_url=redirect_url, associate_by_email=True, is_verified_by_default=True, ), prefix="/auth", tags=["auth"], ) sentry_sdk.init(dsn=settings.SENTRY_DSN, environment=settings.ENVIRONMENT)