diff --git a/app/api/v1/player.py b/app/api/v1/player.py index 7e03f97..ccb365c 100644 --- a/app/api/v1/player.py +++ b/app/api/v1/player.py @@ -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.""" diff --git a/app/api/v1/playlists.py b/app/api/v1/playlists.py index 5d611f8..24b1ccf 100644 --- a/app/api/v1/playlists.py +++ b/app/api/v1/playlists.py @@ -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]: diff --git a/app/schemas/__init__.py b/app/schemas/__init__.py index 3a8b2f5..bbc7905 100644 --- a/app/schemas/__init__.py +++ b/app/schemas/__init__.py @@ -1 +1,46 @@ """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", +] diff --git a/app/schemas/player.py b/app/schemas/player.py new file mode 100644 index 0000000..06602fb --- /dev/null +++ b/app/schemas/player.py @@ -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") diff --git a/app/schemas/playlist.py b/app/schemas/playlist.py new file mode 100644 index 0000000..7fb1064 --- /dev/null +++ b/app/schemas/playlist.py @@ -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