diff --git a/app/api/v1/main.py b/app/api/v1/main.py index 11a037d..785bd29 100644 --- a/app/api/v1/main.py +++ b/app/api/v1/main.py @@ -3,6 +3,7 @@ from fastapi import APIRouter from app.core.logging import get_logger +from app.schemas.common import HealthResponse router = APIRouter() @@ -10,7 +11,7 @@ logger = get_logger(__name__) @router.get("/") -def health() -> dict[str, str]: +def health() -> HealthResponse: """Health check endpoint.""" logger.info("Health check endpoint accessed") - return {"status": "healthy"} + return HealthResponse(status="healthy") diff --git a/app/api/v1/player.py b/app/api/v1/player.py index ccb365c..a498d9b 100644 --- a/app/api/v1/player.py +++ b/app/api/v1/player.py @@ -7,7 +7,13 @@ from fastapi import APIRouter, Depends, HTTPException, status 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.schemas.player import PlayerModeRequest, PlayerSeekRequest, PlayerVolumeRequest +from app.schemas.common import MessageResponse +from app.schemas.player import ( + PlayerModeRequest, + PlayerSeekRequest, + PlayerStateResponse, + PlayerVolumeRequest, +) from app.services.player import get_player_service logger = get_logger(__name__) @@ -18,12 +24,12 @@ router = APIRouter(prefix="/player", tags=["player"]) @router.post("/play") async def play( current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001 -) -> dict[str, str]: +) -> MessageResponse: """Play current sound.""" try: player = get_player_service() await player.play() - return {"message": "Playback started"} + return MessageResponse(message="Playback started") except Exception as e: logger.exception("Error starting playback") raise HTTPException( @@ -36,12 +42,12 @@ async def play( async def play_at_index( index: int, current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001 -) -> dict[str, str]: +) -> MessageResponse: """Play sound at specific index.""" try: player = get_player_service() await player.play(index) - return {"message": f"Playing sound at index {index}"} + return MessageResponse(message=f"Playing sound at index {index}") except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -58,12 +64,12 @@ async def play_at_index( @router.post("/pause") async def pause( current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001 -) -> dict[str, str]: +) -> MessageResponse: """Pause playback.""" try: player = get_player_service() await player.pause() - return {"message": "Playback paused"} + return MessageResponse(message="Playback paused") except Exception as e: logger.exception("Error pausing playback") raise HTTPException( @@ -75,12 +81,12 @@ async def pause( @router.post("/stop") async def stop( current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001 -) -> dict[str, str]: +) -> MessageResponse: """Stop playback.""" try: player = get_player_service() await player.stop_playback() - return {"message": "Playback stopped"} + return MessageResponse(message="Playback stopped") except Exception as e: logger.exception("Error stopping playback") raise HTTPException( @@ -92,12 +98,12 @@ async def stop( @router.post("/next") async def next_track( current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001 -) -> dict[str, str]: +) -> MessageResponse: """Skip to next track.""" try: player = get_player_service() await player.next() - return {"message": "Skipped to next track"} + return MessageResponse(message="Skipped to next track") except Exception as e: logger.exception("Error skipping to next track") raise HTTPException( @@ -109,12 +115,12 @@ async def next_track( @router.post("/previous") async def previous_track( current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001 -) -> dict[str, str]: +) -> MessageResponse: """Go to previous track.""" try: player = get_player_service() await player.previous() - return {"message": "Went to previous track"} + return MessageResponse(message="Went to previous track") except Exception as e: logger.exception("Error going to previous track") raise HTTPException( @@ -127,12 +133,12 @@ async def previous_track( async def seek( request: PlayerSeekRequest, current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001 -) -> dict[str, str]: +) -> MessageResponse: """Seek to specific position in current track.""" try: player = get_player_service() await player.seek(request.position_ms) - return {"message": f"Seeked to position {request.position_ms}ms"} + return MessageResponse(message=f"Seeked to position {request.position_ms}ms") except Exception as e: logger.exception("Error seeking to position %s", request.position_ms) raise HTTPException( @@ -145,12 +151,12 @@ async def seek( async def set_volume( request: PlayerVolumeRequest, current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001 -) -> dict[str, str]: +) -> MessageResponse: """Set playback volume.""" try: player = get_player_service() await player.set_volume(request.volume) - return {"message": f"Volume set to {request.volume}"} + return MessageResponse(message=f"Volume set to {request.volume}") except Exception as e: logger.exception("Error setting volume to %s", request.volume) raise HTTPException( @@ -163,12 +169,12 @@ async def set_volume( async def set_mode( request: PlayerModeRequest, current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001 -) -> dict[str, str]: +) -> MessageResponse: """Set playback mode.""" try: player = get_player_service() await player.set_mode(request.mode) - return {"message": f"Mode set to {request.mode.value}"} + return MessageResponse(message=f"Mode set to {request.mode.value}") except Exception as e: logger.exception("Error setting mode to %s", request.mode) raise HTTPException( @@ -180,12 +186,12 @@ async def set_mode( @router.post("/reload-playlist") async def reload_playlist( current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001 -) -> dict[str, str]: +) -> MessageResponse: """Reload current playlist.""" try: player = get_player_service() await player.reload_playlist() - return {"message": "Playlist reloaded"} + return MessageResponse(message="Playlist reloaded") except Exception as e: logger.exception("Error reloading playlist") raise HTTPException( @@ -197,11 +203,12 @@ async def reload_playlist( @router.get("/state") async def get_state( current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001 -) -> dict[str, Any]: +) -> PlayerStateResponse: """Get current player state.""" try: player = get_player_service() - return player.get_state() + state = player.get_state() + return PlayerStateResponse(**state) except Exception as e: logger.exception("Error getting player state") raise HTTPException( diff --git a/app/api/v1/playlists.py b/app/api/v1/playlists.py index 24b1ccf..2f1ce6e 100644 --- a/app/api/v1/playlists.py +++ b/app/api/v1/playlists.py @@ -8,6 +8,7 @@ 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.user import User +from app.schemas.common import MessageResponse from app.schemas.playlist import ( PlaylistAddSoundRequest, PlaylistCreateRequest, @@ -120,10 +121,10 @@ async def delete_playlist( playlist_id: int, current_user: Annotated[User, Depends(get_current_active_user_flexible)], playlist_service: Annotated[PlaylistService, Depends(get_playlist_service)], -) -> dict[str, str]: +) -> MessageResponse: """Delete a playlist.""" await playlist_service.delete_playlist(playlist_id, current_user.id) - return {"message": "Playlist deleted successfully"} + return MessageResponse(message="Playlist deleted successfully") @router.get("/search/{query}") @@ -165,7 +166,7 @@ async def add_sound_to_playlist( request: PlaylistAddSoundRequest, current_user: Annotated[User, Depends(get_current_active_user_flexible)], playlist_service: Annotated[PlaylistService, Depends(get_playlist_service)], -) -> dict[str, str]: +) -> MessageResponse: """Add a sound to a playlist.""" await playlist_service.add_sound_to_playlist( playlist_id=playlist_id, @@ -173,7 +174,7 @@ async def add_sound_to_playlist( user_id=current_user.id, position=request.position, ) - return {"message": "Sound added to playlist successfully"} + return MessageResponse(message="Sound added to playlist successfully") @router.delete("/{playlist_id}/sounds/{sound_id}") @@ -182,14 +183,14 @@ async def remove_sound_from_playlist( sound_id: int, current_user: Annotated[User, Depends(get_current_active_user_flexible)], playlist_service: Annotated[PlaylistService, Depends(get_playlist_service)], -) -> dict[str, str]: +) -> MessageResponse: """Remove a sound from a playlist.""" await playlist_service.remove_sound_from_playlist( playlist_id=playlist_id, sound_id=sound_id, user_id=current_user.id, ) - return {"message": "Sound removed from playlist successfully"} + return MessageResponse(message="Sound removed from playlist successfully") @router.put("/{playlist_id}/sounds/reorder") @@ -198,14 +199,14 @@ async def reorder_playlist_sounds( request: PlaylistReorderRequest, current_user: Annotated[User, Depends(get_current_active_user_flexible)], playlist_service: Annotated[PlaylistService, Depends(get_playlist_service)], -) -> dict[str, str]: +) -> MessageResponse: """Reorder sounds in a playlist.""" await playlist_service.reorder_playlist_sounds( playlist_id=playlist_id, user_id=current_user.id, sound_positions=request.sound_positions, ) - return {"message": "Playlist sounds reordered successfully"} + return MessageResponse(message="Playlist sounds reordered successfully") @router.put("/{playlist_id}/set-current") @@ -223,10 +224,10 @@ async def set_current_playlist( async def unset_current_playlist( current_user: Annotated[User, Depends(get_current_active_user_flexible)], playlist_service: Annotated[PlaylistService, Depends(get_playlist_service)], -) -> dict[str, str]: +) -> MessageResponse: """Unset the current playlist.""" await playlist_service.unset_current_playlist(current_user.id) - return {"message": "Current playlist unset successfully"} + return MessageResponse(message="Current playlist unset successfully") @router.get("/{playlist_id}/stats") diff --git a/app/schemas/__init__.py b/app/schemas/__init__.py index bbc7905..6e403c0 100644 --- a/app/schemas/__init__.py +++ b/app/schemas/__init__.py @@ -10,7 +10,13 @@ from .auth import ( UserRegisterRequest, UserResponse, ) -from .player import PlayerModeRequest, PlayerSeekRequest, PlayerVolumeRequest +from .common import HealthResponse, MessageResponse, StatusResponse +from .player import ( + PlayerModeRequest, + PlayerSeekRequest, + PlayerStateResponse, + PlayerVolumeRequest, +) from .playlist import ( PlaylistAddSoundRequest, PlaylistCreateRequest, @@ -31,9 +37,14 @@ __all__ = [ "UserLoginRequest", "UserRegisterRequest", "UserResponse", + # Common schemas + "HealthResponse", + "MessageResponse", + "StatusResponse", # Player schemas "PlayerModeRequest", "PlayerSeekRequest", + "PlayerStateResponse", "PlayerVolumeRequest", # Playlist schemas "PlaylistAddSoundRequest", diff --git a/app/schemas/common.py b/app/schemas/common.py new file mode 100644 index 0000000..6762b2a --- /dev/null +++ b/app/schemas/common.py @@ -0,0 +1,21 @@ +"""Common response schemas.""" + +from pydantic import BaseModel, Field + + +class MessageResponse(BaseModel): + """Generic message response.""" + + message: str = Field(description="Response message") + + +class StatusResponse(BaseModel): + """Generic status response.""" + + status: str = Field(description="Status message") + + +class HealthResponse(BaseModel): + """Health check response.""" + + status: str = Field(description="Health status") \ No newline at end of file diff --git a/app/schemas/player.py b/app/schemas/player.py index 06602fb..2c2f868 100644 --- a/app/schemas/player.py +++ b/app/schemas/player.py @@ -1,5 +1,7 @@ """Player schemas.""" +from typing import Any + from pydantic import BaseModel, Field from app.services.player import PlayerMode @@ -21,3 +23,24 @@ class PlayerModeRequest(BaseModel): """Request model for mode change.""" mode: PlayerMode = Field(description="Playback mode") + + +class PlayerStateResponse(BaseModel): + """Response model for player state.""" + + status: str = Field(description="Player status (playing, paused, stopped)") + current_sound: dict[str, Any] | None = Field( + None, description="Current sound information" + ) + playlist: dict[str, Any] | None = Field( + None, description="Current playlist information" + ) + position_ms: int = Field(description="Current position in milliseconds") + duration_ms: int | None = Field( + None, description="Total duration in milliseconds", + ) + volume: int = Field(description="Current volume (0-100)") + mode: str = Field(description="Current playback mode") + index: int | None = Field( + None, description="Current track index in playlist", + ) diff --git a/app/schemas/playlist.py b/app/schemas/playlist.py index 7fb1064..7a28351 100644 --- a/app/schemas/playlist.py +++ b/app/schemas/playlist.py @@ -1,6 +1,6 @@ """Playlist schemas.""" -from pydantic import BaseModel +from pydantic import BaseModel, Field from app.models.playlist import Playlist from app.models.sound import Sound