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
This commit is contained in:
318
app/services/favorite.py
Normal file
318
app/services/favorite.py
Normal file
@@ -0,0 +1,318 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user