Files
sdb2-backend/app/services/favorite.py
JSC a947fd830b feat: Implement favorites management API; add endpoints for adding, removing, and retrieving favorites for sounds and playlists
feat: Create Favorite model and repository for managing user favorites in the database
feat: Add FavoriteService to handle business logic for favorites management
feat: Enhance Playlist and Sound response schemas to include favorite indicators and counts
refactor: Update API routes to include favorites functionality in playlists and sounds
2025-08-16 21:16:02 +02:00

319 lines
10 KiB
Python

"""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)