refactor: Organize and implement player and playlist schemas
This commit is contained in:
@@ -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."""
|
||||||
|
|||||||
@@ -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]:
|
||||||
|
|||||||
@@ -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
23
app/schemas/player.py
Normal 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
104
app/schemas/playlist.py
Normal 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
|
||||||
Reference in New Issue
Block a user