"""FastAPI dependencies for Mantimon TCG API. This module provides dependency injection functions for authentication and database access in API endpoints. Usage: from app.api.deps import get_current_user, get_db @router.get("/me") async def get_me( user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): return user Dependencies: - get_db: Async database session - get_current_user: Authenticated user from JWT (required) - get_optional_user: Authenticated user or None - get_current_premium_user: User with active premium """ from typing import Annotated from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from sqlalchemy.ext.asyncio import AsyncSession from app.db import get_session from app.db.models import User from app.services.jwt_service import verify_access_token from app.services.user_service import user_service # OAuth2 scheme for extracting Bearer token from Authorization header # tokenUrl is for OpenAPI docs - points to where tokens are obtained oauth2_scheme = OAuth2PasswordBearer( tokenUrl="/api/auth/token", # For OpenAPI docs auto_error=True, # Raise 401 if no token ) oauth2_scheme_optional = OAuth2PasswordBearer( tokenUrl="/api/auth/token", auto_error=False, # Return None if no token ) async def get_db() -> AsyncSession: """Get async database session. Yields: AsyncSession for database operations. Example: @router.get("/items") async def get_items(db: AsyncSession = Depends(get_db)): ... """ async with get_session() as session: yield session async def get_current_user( token: Annotated[str, Depends(oauth2_scheme)], db: Annotated[AsyncSession, Depends(get_db)], ) -> User: """Get the current authenticated user from JWT token. Validates the access token and fetches the user from database. Args: token: JWT access token from Authorization header. db: Database session. Returns: The authenticated User. Raises: HTTPException: 401 if token is invalid or user not found. Example: @router.get("/me") async def get_me(user: User = Depends(get_current_user)): return {"email": user.email} """ credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) # Verify token and extract user ID user_id = verify_access_token(token) if user_id is None: raise credentials_exception # Fetch user from database user = await user_service.get_by_id(db, user_id) if user is None: raise credentials_exception return user async def get_optional_user( token: Annotated[str | None, Depends(oauth2_scheme_optional)], db: Annotated[AsyncSession, Depends(get_db)], ) -> User | None: """Get the current user if authenticated, or None. Useful for endpoints that work both with and without authentication, but may provide additional features for authenticated users. Args: token: JWT access token or None. db: Database session. Returns: The authenticated User, or None if not authenticated. Example: @router.get("/cards") async def get_cards(user: User | None = Depends(get_optional_user)): if user: # Show user's collection else: # Show public cards """ if token is None: return None user_id = verify_access_token(token) if user_id is None: return None return await user_service.get_by_id(db, user_id) async def get_current_premium_user( user: Annotated[User, Depends(get_current_user)], ) -> User: """Get the current user and verify they have premium. Args: user: The authenticated user. Returns: The authenticated User with active premium. Raises: HTTPException: 403 if user doesn't have active premium. Example: @router.post("/decks") async def create_unlimited_decks( user: User = Depends(get_current_premium_user) ): # Only premium users can have unlimited decks ... """ if not user.has_active_premium: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Premium subscription required", ) return user # Type aliases for cleaner endpoint signatures CurrentUser = Annotated[User, Depends(get_current_user)] OptionalUser = Annotated[User | None, Depends(get_optional_user)] PremiumUser = Annotated[User, Depends(get_current_premium_user)] DbSession = Annotated[AsyncSession, Depends(get_db)]