""" Shared Dependencies Provides dependency injection for FastAPI routes. All authentication, database access, and common dependencies are defined here. """ import sys from pathlib import Path from typing import Dict, Optional, List from datetime import datetime from fastapi import Depends, HTTPException, Query, Request, WebSocket from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials # Add parent directory to path sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent)) from modules.universal_logger import get_logger logger = get_logger('API') # Security security = HTTPBearer(auto_error=False) # ============================================================================ # GLOBAL STATE (imported from main app) # ============================================================================ class AppState: """Global application state - singleton pattern""" _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._initialized = False return cls._instance def __init__(self): if self._initialized: return self.db = None self.config: Dict = {} self.scheduler = None self.websocket_manager = None # ConnectionManager instance for broadcasts self.scraper_event_emitter = None # ScraperEventEmitter for real-time scraping monitor self.active_scraper_sessions: Dict = {} # Track active scraping sessions for monitor page self.running_platform_downloads: Dict = {} # Track running platform downloads {platform: process} self.download_tasks: Dict = {} self.auth = None self.settings = None self.face_recognition_semaphore = None self.indexing_running: bool = False self.indexing_start_time = None self.review_rescan_running: bool = False self.review_rescan_progress: Dict = {"current": 0, "total": 0, "current_file": ""} self._initialized = True def get_app_state() -> AppState: """Get the global application state""" return AppState() # ============================================================================ # AUTHENTICATION DEPENDENCIES # ============================================================================ async def get_current_user( request: Request, credentials: Optional[HTTPAuthorizationCredentials] = Depends(security) ) -> Dict: """ Dependency to get current authenticated user from JWT token. Supports both Authorization header and cookie-based authentication. """ app_state = get_app_state() auth_token = None # Try Authorization header first if credentials: auth_token = credentials.credentials # Try cookie second elif 'auth_token' in request.cookies: auth_token = request.cookies.get('auth_token') if not auth_token: raise HTTPException(status_code=401, detail="Not authenticated") if not app_state.auth: raise HTTPException(status_code=500, detail="Authentication not initialized") payload = app_state.auth.verify_session(auth_token) if not payload: raise HTTPException(status_code=401, detail="Invalid or expired token") return payload async def get_current_user_optional( request: Request, credentials: Optional[HTTPAuthorizationCredentials] = Depends(security) ) -> Optional[Dict]: """Optional authentication dependency - returns None if not authenticated""" try: return await get_current_user(request, credentials) except HTTPException: return None async def get_current_user_media( request: Request, credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), token: Optional[str] = Query(None) ) -> Dict: """ Authentication for media endpoints. Supports header, cookie, and query parameter tokens (for img/video tags). Priority: 1) Authorization header, 2) Cookie, 3) Query parameter """ app_state = get_app_state() auth_token = None # Try to get token from Authorization header first (preferred) if credentials: auth_token = credentials.credentials # Try cookie second (for and