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 typing import Annotated, Any
from fastapi import APIRouter, Depends, HTTPException, status 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.dependencies import get_current_active_user_flexible
from app.core.logging import get_logger from app.core.logging import get_logger
from app.models.user import User 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__) logger = get_logger(__name__)
router = APIRouter(prefix="/player", tags=["player"]) 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") @router.post("/play")
async def play( async def play(
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001 current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
@@ -143,7 +125,7 @@ async def previous_track(
@router.post("/seek") @router.post("/seek")
async def seek( async def seek(
request: SeekRequest, request: PlayerSeekRequest,
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001 current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
) -> dict[str, str]: ) -> dict[str, str]:
"""Seek to specific position in current track.""" """Seek to specific position in current track."""
@@ -161,7 +143,7 @@ async def seek(
@router.post("/volume") @router.post("/volume")
async def set_volume( async def set_volume(
request: VolumeRequest, request: PlayerVolumeRequest,
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001 current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
) -> dict[str, str]: ) -> dict[str, str]:
"""Set playback volume.""" """Set playback volume."""
@@ -179,7 +161,7 @@ async def set_volume(
@router.post("/mode") @router.post("/mode")
async def set_mode( async def set_mode(
request: ModeRequest, request: PlayerModeRequest,
current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001 current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001
) -> dict[str, str]: ) -> dict[str, str]:
"""Set playback mode.""" """Set playback mode."""

View File

@@ -3,113 +3,25 @@
from typing import Annotated from typing import Annotated
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from pydantic import BaseModel
from sqlmodel.ext.asyncio.session import AsyncSession from sqlmodel.ext.asyncio.session import AsyncSession
from app.core.database import get_db from app.core.database import get_db
from app.core.dependencies import get_current_active_user_flexible 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.models.user import User
from app.schemas.playlist import (
PlaylistAddSoundRequest,
PlaylistCreateRequest,
PlaylistReorderRequest,
PlaylistResponse,
PlaylistSoundResponse,
PlaylistStatsResponse,
PlaylistUpdateRequest,
)
from app.services.playlist import PlaylistService from app.services.playlist import PlaylistService
router = APIRouter(prefix="/playlists", tags=["playlists"]) 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( async def get_playlist_service(
session: Annotated[AsyncSession, Depends(get_db)], session: Annotated[AsyncSession, Depends(get_db)],
) -> PlaylistService: ) -> PlaylistService:
@@ -241,16 +153,16 @@ async def get_playlist_sounds(
playlist_id: int, playlist_id: int,
current_user: Annotated[User, Depends(get_current_active_user_flexible)], current_user: Annotated[User, Depends(get_current_active_user_flexible)],
playlist_service: Annotated[PlaylistService, Depends(get_playlist_service)], playlist_service: Annotated[PlaylistService, Depends(get_playlist_service)],
) -> list[SoundResponse]: ) -> list[PlaylistSoundResponse]:
"""Get all sounds in a playlist.""" """Get all sounds in a playlist."""
sounds = await playlist_service.get_playlist_sounds(playlist_id) 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") @router.post("/{playlist_id}/sounds")
async def add_sound_to_playlist( async def add_sound_to_playlist(
playlist_id: int, playlist_id: int,
request: AddSoundRequest, request: PlaylistAddSoundRequest,
current_user: Annotated[User, Depends(get_current_active_user_flexible)], current_user: Annotated[User, Depends(get_current_active_user_flexible)],
playlist_service: Annotated[PlaylistService, Depends(get_playlist_service)], playlist_service: Annotated[PlaylistService, Depends(get_playlist_service)],
) -> dict[str, str]: ) -> dict[str, str]:
@@ -283,7 +195,7 @@ async def remove_sound_from_playlist(
@router.put("/{playlist_id}/sounds/reorder") @router.put("/{playlist_id}/sounds/reorder")
async def reorder_playlist_sounds( async def reorder_playlist_sounds(
playlist_id: int, playlist_id: int,
request: ReorderRequest, request: PlaylistReorderRequest,
current_user: Annotated[User, Depends(get_current_active_user_flexible)], current_user: Annotated[User, Depends(get_current_active_user_flexible)],
playlist_service: Annotated[PlaylistService, Depends(get_playlist_service)], playlist_service: Annotated[PlaylistService, Depends(get_playlist_service)],
) -> dict[str, str]: ) -> dict[str, str]:

View File

@@ -1 +1,46 @@
"""Schemas package.""" """Schemas package."""
from .auth import (
ApiTokenRequest,
ApiTokenResponse,
ApiTokenStatusResponse,
AuthResponse,
TokenResponse,
UserLoginRequest,
UserRegisterRequest,
UserResponse,
)
from .player import PlayerModeRequest, PlayerSeekRequest, PlayerVolumeRequest
from .playlist import (
PlaylistAddSoundRequest,
PlaylistCreateRequest,
PlaylistReorderRequest,
PlaylistResponse,
PlaylistSoundResponse,
PlaylistStatsResponse,
PlaylistUpdateRequest,
)
__all__ = [
# Auth schemas
"ApiTokenRequest",
"ApiTokenResponse",
"ApiTokenStatusResponse",
"AuthResponse",
"TokenResponse",
"UserLoginRequest",
"UserRegisterRequest",
"UserResponse",
# Player schemas
"PlayerModeRequest",
"PlayerSeekRequest",
"PlayerVolumeRequest",
# Playlist schemas
"PlaylistAddSoundRequest",
"PlaylistCreateRequest",
"PlaylistReorderRequest",
"PlaylistResponse",
"PlaylistSoundResponse",
"PlaylistStatsResponse",
"PlaylistUpdateRequest",
]

23
app/schemas/player.py Normal file
View File

@@ -0,0 +1,23 @@
"""Player schemas."""
from pydantic import BaseModel, Field
from app.services.player import PlayerMode
class PlayerSeekRequest(BaseModel):
"""Request model for seek operation."""
position_ms: int = Field(ge=0, description="Position in milliseconds")
class PlayerVolumeRequest(BaseModel):
"""Request model for volume control."""
volume: int = Field(ge=0, le=100, description="Volume level (0-100)")
class PlayerModeRequest(BaseModel):
"""Request model for mode change."""
mode: PlayerMode = Field(description="Playback mode")

104
app/schemas/playlist.py Normal file
View File

@@ -0,0 +1,104 @@
"""Playlist schemas."""
from pydantic import BaseModel
from app.models.playlist import Playlist
from app.models.sound import Sound
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."""
if playlist.id is None:
raise ValueError("Playlist ID cannot be None")
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 PlaylistSoundResponse(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) -> "PlaylistSoundResponse":
"""Create response from sound model."""
if sound.id is None:
raise ValueError("Sound ID cannot be None")
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 PlaylistAddSoundRequest(BaseModel):
"""Request model for adding a sound to a playlist."""
sound_id: int
position: int | None = None
class PlaylistReorderRequest(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