"""FastAPI dependency injection wiring. All service and repository instances are created here. No business logic. """ from __future__ import annotations from typing import Optional import structlog from fastapi import Depends, Header, HTTPException from config import settings from export.validator import ExportValidator from export.scanner import IDExportScanner from repositories.user_repository import UserRepository from repositories.conversion_repository import ConversionRepository from services.scan_service import ScanService from services.conversion_service import ConversionService from services.user_service import UserService from services.payment_service import PaymentService log = structlog.get_logger() # ── Singletons (stateless services safe to share) ───────────────────────────── _validator = ExportValidator() _scanner = IDExportScanner() _scan_service = ScanService(_validator, _scanner) _user_repo = UserRepository() _conversion_repo = ConversionRepository() _user_service = UserService(_user_repo) _payment_service = PaymentService() _conversion_service = ConversionService(_scan_service, _conversion_repo) # ── Service getters ─────────────────────────────────────────────────────────── def get_scan_service() -> ScanService: return _scan_service def get_conversion_service() -> ConversionService: return _conversion_service def get_user_service() -> UserService: return _user_service def get_payment_service() -> PaymentService: return _payment_service # ── Admin token ─────────────────────────────────────────────────────────────── def get_admin_token() -> str: """Return the PocketBase admin token from settings. This token is used for server-side writes (creating conversion/purchase records) that must bypass normal user permission rules. """ return getattr(settings, 'pocketbase_admin_token', '') # ── Auth dependencies ───────────────────────────────────────────────────────── async def get_optional_user( authorization: Optional[str] = Header(None), ) -> Optional[dict]: """Extract and validate the Bearer token. Returns user dict or None. Used on endpoints where auth is optional (e.g. scan — no credits needed). """ if not authorization or not authorization.startswith('Bearer '): return None token = authorization.split(' ', 1)[1] try: user = await _user_repo.get_by_token(token) return user except Exception: return None async def get_current_user_or_401( authorization: Optional[str] = Header(None), ) -> dict: """Extract and validate the Bearer token. Raises 401 if missing or invalid. Used on endpoints that require authentication (convert, users/me, payments). """ if not authorization or not authorization.startswith('Bearer '): raise HTTPException(401, 'Authentication required') token = authorization.split(' ', 1)[1] try: return await _user_repo.get_by_token(token) except Exception: raise HTTPException(401, 'Invalid or expired token')