refactor: Organize and implement player and playlist schemas

This commit is contained in:
JSC
2025-07-31 10:23:46 +02:00
parent 3feff2e0f1
commit dc372b961e
5 changed files with 190 additions and 124 deletions

View File

@@ -3,36 +3,18 @@
from typing import Annotated, Any
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field
from app.core.dependencies import get_current_active_user_flexible
from app.core.logging import get_logger
from app.models.user import User
from app.services.player import PlayerMode, get_player_service
from app.schemas.player import PlayerModeRequest, PlayerSeekRequest, PlayerVolumeRequest
from app.services.player import get_player_service
logger = get_logger(__name__)
router = APIRouter(prefix="/player", tags=["player"])
class SeekRequest(BaseModel):
"""Request model for seek operation."""
position_ms: int = Field(ge=0, description="Position in milliseconds")
class VolumeRequest(BaseModel):
"""Request model for volume control."""
volume: int = Field(ge=0, le=100, description="Volume level (0-100)")
class ModeRequest(BaseModel):
"""Request model for mode change."""
mode: PlayerMode = Field(description="Playback mode")
@router.post("/play")
async def play(
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
@@ -143,7 +125,7 @@ async def previous_track(
@router.post("/seek")
async def seek(
request: SeekRequest,
request: PlayerSeekRequest,
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
) -> dict[str, str]:
"""Seek to specific position in current track."""
@@ -161,7 +143,7 @@ async def seek(
@router.post("/volume")
async def set_volume(
request: VolumeRequest,
request: PlayerVolumeRequest,
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
) -> dict[str, str]:
"""Set playback volume."""
@@ -179,7 +161,7 @@ async def set_volume(
@router.post("/mode")
async def set_mode(
request: ModeRequest,
request: PlayerModeRequest,
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
) -> dict[str, str]:
"""Set playback mode."""

View File

@@ -3,113 +3,25 @@
from typing import Annotated
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from sqlmodel.ext.asyncio.session import AsyncSession
from app.core.database import get_db
from app.core.dependencies import get_current_active_user_flexible
from app.models.playlist import Playlist
from app.models.sound import Sound
from app.models.user import User
from app.schemas.playlist import (
PlaylistAddSoundRequest,
PlaylistCreateRequest,
PlaylistReorderRequest,
PlaylistResponse,
PlaylistSoundResponse,
PlaylistStatsResponse,
PlaylistUpdateRequest,
)
from app.services.playlist import PlaylistService
router = APIRouter(prefix="/playlists", tags=["playlists"])
class PlaylistCreateRequest(BaseModel):
"""Request model for creating a playlist."""
name: str
description: str | None = None
genre: str | None = None
class PlaylistUpdateRequest(BaseModel):
"""Request model for updating a playlist."""
name: str | None = None
description: str | None = None
genre: str | None = None
is_current: bool | None = None
class PlaylistResponse(BaseModel):
"""Response model for playlist data."""
id: int
name: str
description: str | None
genre: str | None
is_main: bool
is_current: bool
is_deletable: bool
created_at: str
updated_at: str | None
@classmethod
def from_playlist(cls, playlist: Playlist) -> "PlaylistResponse":
"""Create response from playlist model."""
return cls(
id=playlist.id,
name=playlist.name,
description=playlist.description,
genre=playlist.genre,
is_main=playlist.is_main,
is_current=playlist.is_current,
is_deletable=playlist.is_deletable,
created_at=playlist.created_at.isoformat(),
updated_at=playlist.updated_at.isoformat() if playlist.updated_at else None,
)
class SoundResponse(BaseModel):
"""Response model for sound data in playlists."""
id: int
name: str
filename: str
type: str
duration: int | None
size: int | None
play_count: int
created_at: str
@classmethod
def from_sound(cls, sound: Sound) -> "SoundResponse":
"""Create response from sound model."""
return cls(
id=sound.id,
name=sound.name,
filename=sound.filename,
type=sound.type,
duration=sound.duration,
size=sound.size,
play_count=sound.play_count,
created_at=sound.created_at.isoformat(),
)
class AddSoundRequest(BaseModel):
"""Request model for adding a sound to a playlist."""
sound_id: int
position: int | None = None
class ReorderRequest(BaseModel):
"""Request model for reordering sounds in a playlist."""
sound_positions: list[tuple[int, int]]
class PlaylistStatsResponse(BaseModel):
"""Response model for playlist statistics."""
sound_count: int
total_duration_ms: int
total_play_count: int
async def get_playlist_service(
session: Annotated[AsyncSession, Depends(get_db)],
) -> PlaylistService:
@@ -241,16 +153,16 @@ async def get_playlist_sounds(
playlist_id: int,
current_user: Annotated[User, Depends(get_current_active_user_flexible)],
playlist_service: Annotated[PlaylistService, Depends(get_playlist_service)],
) -> list[SoundResponse]:
) -> list[PlaylistSoundResponse]:
"""Get all sounds in a playlist."""
sounds = await playlist_service.get_playlist_sounds(playlist_id)
return [SoundResponse.from_sound(sound) for sound in sounds]
return [PlaylistSoundResponse.from_sound(sound) for sound in sounds]
@router.post("/{playlist_id}/sounds")
async def add_sound_to_playlist(
playlist_id: int,
request: AddSoundRequest,
request: PlaylistAddSoundRequest,
current_user: Annotated[User, Depends(get_current_active_user_flexible)],
playlist_service: Annotated[PlaylistService, Depends(get_playlist_service)],
) -> dict[str, str]:
@@ -283,7 +195,7 @@ async def remove_sound_from_playlist(
@router.put("/{playlist_id}/sounds/reorder")
async def reorder_playlist_sounds(
playlist_id: int,
request: ReorderRequest,
request: PlaylistReorderRequest,
current_user: Annotated[User, Depends(get_current_active_user_flexible)],
playlist_service: Annotated[PlaylistService, Depends(get_playlist_service)],
) -> dict[str, str]: