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:
JSC
2025-08-16 21:16:02 +02:00
parent 5e6cc04ad2
commit a947fd830b
14 changed files with 1005 additions and 13 deletions

View File

@@ -11,7 +11,9 @@ from app.models.credit_action import CreditActionType
from app.models.sound import Sound
from app.models.user import User
from app.repositories.sound import SortOrder, SoundRepository, SoundSortField
from app.schemas.sound import SoundResponse, SoundsListResponse
from app.services.credit import CreditService, InsufficientCreditsError
from app.services.favorite import FavoriteService
from app.services.vlc_player import VLCPlayerService, get_vlc_player_service
router = APIRouter(prefix="/sounds", tags=["sounds"])
@@ -27,6 +29,11 @@ def get_credit_service() -> CreditService:
return CreditService(get_session_factory())
def get_favorite_service() -> FavoriteService:
"""Get the favorite service."""
return FavoriteService(get_session_factory())
async def get_sound_repository(
session: Annotated[AsyncSession, Depends(get_db)],
) -> SoundRepository:
@@ -34,10 +41,11 @@ async def get_sound_repository(
return SoundRepository(session)
@router.get("/")
@router.get("/", response_model=SoundsListResponse)
async def get_sounds( # noqa: PLR0913
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
current_user: Annotated[User, Depends(get_current_active_user_flexible)],
sound_repo: Annotated[SoundRepository, Depends(get_sound_repository)],
favorite_service: Annotated[FavoriteService, Depends(get_favorite_service)],
types: Annotated[
list[str] | None,
Query(description="Filter by sound types (e.g., SDB, TTS, EXT)"),
@@ -62,7 +70,7 @@ async def get_sounds( # noqa: PLR0913
int,
Query(description="Number of results to skip", ge=0),
] = 0,
) -> dict[str, list[Sound]]:
) -> SoundsListResponse:
"""Get sounds with optional search, filtering, and sorting."""
try:
sounds = await sound_repo.search_and_sort(
@@ -73,13 +81,22 @@ async def get_sounds( # noqa: PLR0913
limit=limit,
offset=offset,
)
# Add favorite indicators for each sound
sound_responses = []
for sound in sounds:
is_favorited = await favorite_service.is_sound_favorited(current_user.id, sound.id)
favorite_count = await favorite_service.get_sound_favorite_count(sound.id)
sound_response = SoundResponse.from_sound(sound, is_favorited, favorite_count)
sound_responses.append(sound_response)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to get sounds: {e!s}",
) from e
else:
return {"sounds": sounds}
return SoundsListResponse(sounds=sound_responses)
# VLC PLAYER