"""API dependencies.""" from typing import Annotated, Optional from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.services.auth import AuthService from app.models.user import User # Security scheme security = HTTPBearer() security_optional = HTTPBearer(auto_error=False) async def get_current_user( credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)], db: Annotated[AsyncSession, Depends(get_db)], ) -> User: """Get current authenticated user from JWT token.""" credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) token = credentials.credentials payload = AuthService.decode_token(token) if payload is None: raise credentials_exception user_id_str = payload.get("sub") if user_id_str is None: raise credentials_exception try: user_id = int(user_id_str) except (ValueError, TypeError): raise credentials_exception user = await AuthService.get_user_by_id(db, user_id) if user is None: raise credentials_exception if not user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="User account is disabled", ) return user async def get_current_active_user( current_user: Annotated[User, Depends(get_current_user)], ) -> User: """Ensure user is active.""" if not current_user.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user", ) return current_user async def get_current_user_optional( credentials: Annotated[Optional[HTTPAuthorizationCredentials], Depends(security_optional)], db: Annotated[AsyncSession, Depends(get_db)], ) -> Optional[User]: """Get current user if authenticated, otherwise return None. This allows endpoints to work for both authenticated and anonymous users, potentially showing different content based on auth status. """ if credentials is None: return None token = credentials.credentials payload = AuthService.decode_token(token) if payload is None: return None user_id_str = payload.get("sub") if user_id_str is None: return None try: user_id = int(user_id_str) except (ValueError, TypeError): return None user = await AuthService.get_user_by_id(db, user_id) if user is None or not user.is_active: return None return user # Type aliases for cleaner annotations CurrentUser = Annotated[User, Depends(get_current_user)] ActiveUser = Annotated[User, Depends(get_current_active_user)] OptionalUser = Annotated[Optional[User], Depends(get_current_user_optional)] Database = Annotated[AsyncSession, Depends(get_db)]