"""Shared API dependencies: DB session, the authenticated user, and the mailer.""" from typing import Annotated from fastapi import Depends, HTTPException, Request, status from sqlalchemy.ext.asyncio import AsyncSession from app.core.config import get_settings from app.core.db import get_session from app.integrations.mailer.base import Mailer from app.integrations.mailer.console import ConsoleMailer from app.integrations.mailer.smtp import SMTPMailer from app.integrations.models.base import EmbeddingProvider, LLMProvider from app.integrations.models.null import NullEmbeddingProvider, NullLLMProvider from app.integrations.objectstore.base import ObjectStore from app.integrations.objectstore.s3 import S3ObjectStore from app.models.user import User from app.services import auth_service SessionDep = Annotated[AsyncSession, Depends(get_session)] def extract_session_token(request: Request) -> str | None: """Bearer header (API clients) takes precedence over the session cookie (browser).""" authorization = request.headers.get("authorization") if authorization and authorization.lower().startswith("bearer "): return authorization[7:].strip() return request.cookies.get(get_settings().cookie_name) async def get_current_user(request: Request, session: SessionDep) -> User: raw_token = extract_session_token(request) if raw_token is None: raise HTTPException(status.HTTP_401_UNAUTHORIZED, "authentication required") user = await auth_service.resolve_session_user(session, raw_token=raw_token) if user is None: raise HTTPException(status.HTTP_401_UNAUTHORIZED, "invalid or expired session") return user CurrentUser = Annotated[User, Depends(get_current_user)] async def get_current_user_or_none(request: Request, session: SessionDep) -> User | None: """Optional auth for public read endpoints — never raises. Returns the user when a valid session is present, else None (anonymous viewer).""" raw_token = extract_session_token(request) if raw_token is None: return None return await auth_service.resolve_session_user(session, raw_token=raw_token) CurrentUserOrNone = Annotated[User | None, Depends(get_current_user_or_none)] def get_mailer() -> Mailer: settings = get_settings() if settings.mailer == "smtp" and settings.smtp_host: return SMTPMailer(settings) return ConsoleMailer() MailerDep = Annotated[Mailer, Depends(get_mailer)] def get_objectstore() -> ObjectStore: return S3ObjectStore(get_settings()) ObjectStoreDep = Annotated[ObjectStore, Depends(get_objectstore)] def get_llm_provider() -> LLMProvider: settings = get_settings() if settings.model_provider == "anthropic" and settings.anthropic_api_key: from app.integrations.models.anthropic_provider import AnthropicLLMProvider return AnthropicLLMProvider( api_key=settings.anthropic_api_key, model=settings.llm_model, max_tokens=settings.llm_max_tokens, ) return NullLLMProvider() LLMProviderDep = Annotated[LLMProvider, Depends(get_llm_provider)] def get_embedding_provider() -> EmbeddingProvider: # Only the null provider exists today; concrete embedders (Ollama/Voyage) # implement the same interface and are selected here by settings.embedding_provider. return NullEmbeddingProvider() EmbeddingProviderDep = Annotated[EmbeddingProvider, Depends(get_embedding_provider)]