Files
chat/chat/web/middleware.py
T
2026-04-26 14:33:28 -04:00

62 lines
2.1 KiB
Python

from __future__ import annotations
from fastapi import Request
from fastapi.responses import RedirectResponse
from starlette.middleware.base import BaseHTTPMiddleware
from chat.db.connection import open_db
from chat.state.entities import get_you, list_bots
class FirstRunRedirectMiddleware(BaseHTTPMiddleware):
"""Redirect users through the first-run flow (per requirements §16.2).
Behavior on GET requests to landing routes (``/`` and ``/chats``):
- No ``you_entity`` → ``/settings``
- ``you_entity`` exists but no bots → ``/bots/new``
- Otherwise pass through to the underlying handler.
The middleware is a no-op for:
- Non-GET requests (POST/PUT writes proceed and surface their own errors).
- Static assets, health checks, and any path under ``/settings``,
``/bots``, ``/api``, ``/health``, ``/favicon`` — so the user can
actually complete setup once redirected.
- Sub-paths of ``/chats`` (e.g. ``/chats/<id>``, ``/chats/<id>/drawer``);
only the bare landing pages get the redirect treatment. Sub-resources
either 404 cleanly or are HTMX partials that should not page-redirect.
"""
SKIP_PREFIXES = (
"/static",
"/settings",
"/bots",
"/health",
"/favicon",
"/api",
)
async def dispatch(self, request: Request, call_next):
if request.method != "GET":
return await call_next(request)
path = request.url.path
if any(path.startswith(p) for p in self.SKIP_PREFIXES):
return await call_next(request)
# Only fire on the landing routes themselves.
if path != "/" and path != "/chats":
return await call_next(request)
settings = request.app.state.settings
with open_db(settings.db_path) as conn:
you = get_you(conn)
bots = list_bots(conn)
if you is None:
return RedirectResponse(url="/settings", status_code=303)
if not bots:
return RedirectResponse(url="/bots/new", status_code=303)
return await call_next(request)