"""Ponto de entrada FastAPI do Lernotafiscal.""" from __future__ import annotations import logging from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.sessions import SessionMiddleware from starlette.requests import Request from starlette.responses import RedirectResponse from . import auth, database from .config import get_settings from .routes import ( auth_routes, dashboard_routes, documents_routes, files_routes, upload_routes, ) logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s") logger = logging.getLogger("lernotafiscal") # Caminhos liberados sem sessão (login e assets da própria UI). PUBLIC_PREFIXES = ("/login", "/static/", "/favicon.ico", "/healthz") class AuthGuardMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): path = request.url.path if any(path == p or path.startswith(p) for p in PUBLIC_PREFIXES): return await call_next(request) if not request.session.get("user"): return RedirectResponse("/login", status_code=303) return await call_next(request) @asynccontextmanager async def lifespan(app: FastAPI): settings = get_settings() with database.session() as conn: database.init_db(conn) if settings.secret_key_is_ephemeral: logger.warning("SECRET_KEY não definido: sessões não sobrevivem a reinícios. Defina SECRET_KEY em produção.") note = auth.seed_admin() if note: logger.warning(note) logger.info("OpenAI %s.", "habilitado (" + settings.openai_model + ")" if settings.openai_enabled else "desabilitado — usando OCR/heurística local") yield def create_app() -> FastAPI: settings = get_settings() app = FastAPI(title="Lernotafiscal", lifespan=lifespan) # Ordem importa: SessionMiddleware é adicionado por último para ser o mais # externo e popular request.session ANTES da guarda de autenticação. app.add_middleware(AuthGuardMiddleware) app.add_middleware( SessionMiddleware, secret_key=settings.secret_key, session_cookie=settings.session_cookie, https_only=settings.session_https_only, max_age=settings.session_max_age, same_site="lax", ) static_dir = Path(__file__).resolve().parent / "static" app.mount("/static", StaticFiles(directory=str(static_dir)), name="static") app.include_router(auth_routes.router) app.include_router(dashboard_routes.router) app.include_router(upload_routes.router) app.include_router(documents_routes.router) app.include_router(files_routes.router) @app.get("/healthz") def healthz(): return {"status": "ok"} return app app = create_app()