95 lines
3.5 KiB
Python
95 lines
3.5 KiB
Python
"""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')
|