"""Service for managing user favorites.""" from collections.abc import Callable from sqlmodel.ext.asyncio.session import AsyncSession from app.core.logging import get_logger from app.models.favorite import Favorite from app.models.playlist import Playlist from app.models.sound import Sound from app.models.user import User from app.repositories.favorite import FavoriteRepository from app.repositories.playlist import PlaylistRepository from app.repositories.sound import SoundRepository from app.repositories.user import UserRepository logger = get_logger(__name__) class FavoriteService: """Service for managing user favorites.""" def __init__(self, db_session_factory: Callable[[], AsyncSession]) -> None: """Initialize the favorite service. Args: db_session_factory: Factory function to create database sessions """ self.db_session_factory = db_session_factory async def add_sound_favorite(self, user_id: int, sound_id: int) -> Favorite: """Add a sound to user's favorites. Args: user_id: The user ID sound_id: The sound ID Returns: The created favorite Raises: ValueError: If user or sound not found, or already favorited """ async with self.db_session_factory() as session: favorite_repo = FavoriteRepository(session) user_repo = UserRepository(session) sound_repo = SoundRepository(session) # Verify user exists user = await user_repo.get_by_id(user_id) if not user: raise ValueError(f"User with ID {user_id} not found") # Verify sound exists sound = await sound_repo.get_by_id(sound_id) if not sound: raise ValueError(f"Sound with ID {sound_id} not found") # Check if already favorited existing = await favorite_repo.get_by_user_and_sound(user_id, sound_id) if existing: raise ValueError( f"Sound {sound_id} is already favorited by user {user_id}" ) # Create favorite favorite_data = { "user_id": user_id, "sound_id": sound_id, "playlist_id": None, } favorite = await favorite_repo.create(favorite_data) logger.info("User %s favorited sound %s", user_id, sound_id) return favorite async def add_playlist_favorite(self, user_id: int, playlist_id: int) -> Favorite: """Add a playlist to user's favorites. Args: user_id: The user ID playlist_id: The playlist ID Returns: The created favorite Raises: ValueError: If user or playlist not found, or already favorited """ async with self.db_session_factory() as session: favorite_repo = FavoriteRepository(session) user_repo = UserRepository(session) playlist_repo = PlaylistRepository(session) # Verify user exists user = await user_repo.get_by_id(user_id) if not user: raise ValueError(f"User with ID {user_id} not found") # Verify playlist exists playlist = await playlist_repo.get_by_id(playlist_id) if not playlist: raise ValueError(f"Playlist with ID {playlist_id} not found") # Check if already favorited existing = await favorite_repo.get_by_user_and_playlist( user_id, playlist_id ) if existing: raise ValueError( f"Playlist {playlist_id} is already favorited by user {user_id}" ) # Create favorite favorite_data = { "user_id": user_id, "sound_id": None, "playlist_id": playlist_id, } favorite = await favorite_repo.create(favorite_data) logger.info("User %s favorited playlist %s", user_id, playlist_id) return favorite async def remove_sound_favorite(self, user_id: int, sound_id: int) -> None: """Remove a sound from user's favorites. Args: user_id: The user ID sound_id: The sound ID Raises: ValueError: If favorite not found """ async with self.db_session_factory() as session: favorite_repo = FavoriteRepository(session) favorite = await favorite_repo.get_by_user_and_sound(user_id, sound_id) if not favorite: raise ValueError(f"Sound {sound_id} is not favorited by user {user_id}") await favorite_repo.delete(favorite) logger.info("User %s removed sound %s from favorites", user_id, sound_id) async def remove_playlist_favorite(self, user_id: int, playlist_id: int) -> None: """Remove a playlist from user's favorites. Args: user_id: The user ID playlist_id: The playlist ID Raises: ValueError: If favorite not found """ async with self.db_session_factory() as session: favorite_repo = FavoriteRepository(session) favorite = await favorite_repo.get_by_user_and_playlist( user_id, playlist_id ) if not favorite: raise ValueError( f"Playlist {playlist_id} is not favorited by user {user_id}" ) await favorite_repo.delete(favorite) logger.info( "User %s removed playlist %s from favorites", user_id, playlist_id ) async def get_user_favorites( self, user_id: int, limit: int = 100, offset: int = 0, ) -> list[Favorite]: """Get all favorites for a user. Args: user_id: The user ID limit: Maximum number of favorites to return offset: Number of favorites to skip Returns: List of user favorites """ async with self.db_session_factory() as session: favorite_repo = FavoriteRepository(session) return await favorite_repo.get_user_favorites(user_id, limit, offset) async def get_user_sound_favorites( self, user_id: int, limit: int = 100, offset: int = 0, ) -> list[Favorite]: """Get sound favorites for a user. Args: user_id: The user ID limit: Maximum number of favorites to return offset: Number of favorites to skip Returns: List of user sound favorites """ async with self.db_session_factory() as session: favorite_repo = FavoriteRepository(session) return await favorite_repo.get_user_sound_favorites(user_id, limit, offset) async def get_user_playlist_favorites( self, user_id: int, limit: int = 100, offset: int = 0, ) -> list[Favorite]: """Get playlist favorites for a user. Args: user_id: The user ID limit: Maximum number of favorites to return offset: Number of favorites to skip Returns: List of user playlist favorites """ async with self.db_session_factory() as session: favorite_repo = FavoriteRepository(session) return await favorite_repo.get_user_playlist_favorites( user_id, limit, offset ) async def is_sound_favorited(self, user_id: int, sound_id: int) -> bool: """Check if a sound is favorited by a user. Args: user_id: The user ID sound_id: The sound ID Returns: True if the sound is favorited, False otherwise """ async with self.db_session_factory() as session: favorite_repo = FavoriteRepository(session) return await favorite_repo.is_sound_favorited(user_id, sound_id) async def is_playlist_favorited(self, user_id: int, playlist_id: int) -> bool: """Check if a playlist is favorited by a user. Args: user_id: The user ID playlist_id: The playlist ID Returns: True if the playlist is favorited, False otherwise """ async with self.db_session_factory() as session: favorite_repo = FavoriteRepository(session) return await favorite_repo.is_playlist_favorited(user_id, playlist_id) async def get_favorite_counts(self, user_id: int) -> dict[str, int]: """Get favorite counts for a user. Args: user_id: The user ID Returns: Dictionary with favorite counts """ async with self.db_session_factory() as session: favorite_repo = FavoriteRepository(session) total = await favorite_repo.count_user_favorites(user_id) sounds = len(await favorite_repo.get_user_sound_favorites(user_id)) playlists = len(await favorite_repo.get_user_playlist_favorites(user_id)) return { "total": total, "sounds": sounds, "playlists": playlists, } async def get_sound_favorite_count(self, sound_id: int) -> int: """Get the number of users who have favorited a sound. Args: sound_id: The sound ID Returns: Number of users who favorited this sound """ async with self.db_session_factory() as session: favorite_repo = FavoriteRepository(session) return await favorite_repo.count_sound_favorites(sound_id) async def get_playlist_favorite_count(self, playlist_id: int) -> int: """Get the number of users who have favorited a playlist. Args: playlist_id: The playlist ID Returns: Number of users who favorited this playlist """ async with self.db_session_factory() as session: favorite_repo = FavoriteRepository(session) return await favorite_repo.count_playlist_favorites(playlist_id)