feat: Implement pagination for extractions and playlists with total count in responses
This commit is contained in:
@@ -94,14 +94,18 @@ async def get_all_extractions(
|
|||||||
sort_by: Annotated[str, Query(description="Sort by field")] = "created_at",
|
sort_by: Annotated[str, Query(description="Sort by field")] = "created_at",
|
||||||
sort_order: Annotated[str, Query(description="Sort order (asc/desc)")] = "desc",
|
sort_order: Annotated[str, Query(description="Sort order (asc/desc)")] = "desc",
|
||||||
status_filter: Annotated[str | None, Query(description="Filter by status")] = None,
|
status_filter: Annotated[str | None, Query(description="Filter by status")] = None,
|
||||||
) -> dict[str, list[ExtractionInfo]]:
|
page: Annotated[int, Query(description="Page number", ge=1)] = 1,
|
||||||
|
limit: Annotated[int, Query(description="Items per page", ge=1, le=100)] = 50,
|
||||||
|
) -> dict:
|
||||||
"""Get all extractions with optional filtering, search, and sorting."""
|
"""Get all extractions with optional filtering, search, and sorting."""
|
||||||
try:
|
try:
|
||||||
extractions = await extraction_service.get_all_extractions(
|
result = await extraction_service.get_all_extractions(
|
||||||
search=search,
|
search=search,
|
||||||
sort_by=sort_by,
|
sort_by=sort_by,
|
||||||
sort_order=sort_order,
|
sort_order=sort_order,
|
||||||
status_filter=status_filter,
|
status_filter=status_filter,
|
||||||
|
page=page,
|
||||||
|
limit=limit,
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -110,9 +114,7 @@ async def get_all_extractions(
|
|||||||
detail=f"Failed to get extractions: {e!s}",
|
detail=f"Failed to get extractions: {e!s}",
|
||||||
) from e
|
) from e
|
||||||
else:
|
else:
|
||||||
return {
|
return result
|
||||||
"extractions": extractions,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/user")
|
@router.get("/user")
|
||||||
@@ -123,7 +125,9 @@ async def get_user_extractions(
|
|||||||
sort_by: Annotated[str, Query(description="Sort by field")] = "created_at",
|
sort_by: Annotated[str, Query(description="Sort by field")] = "created_at",
|
||||||
sort_order: Annotated[str, Query(description="Sort order (asc/desc)")] = "desc",
|
sort_order: Annotated[str, Query(description="Sort order (asc/desc)")] = "desc",
|
||||||
status_filter: Annotated[str | None, Query(description="Filter by status")] = None,
|
status_filter: Annotated[str | None, Query(description="Filter by status")] = None,
|
||||||
) -> dict[str, list[ExtractionInfo]]:
|
page: Annotated[int, Query(description="Page number", ge=1)] = 1,
|
||||||
|
limit: Annotated[int, Query(description="Items per page", ge=1, le=100)] = 50,
|
||||||
|
) -> dict:
|
||||||
"""Get all extractions for the current user."""
|
"""Get all extractions for the current user."""
|
||||||
try:
|
try:
|
||||||
if current_user.id is None:
|
if current_user.id is None:
|
||||||
@@ -132,12 +136,14 @@ async def get_user_extractions(
|
|||||||
detail="User ID not available",
|
detail="User ID not available",
|
||||||
)
|
)
|
||||||
|
|
||||||
extractions = await extraction_service.get_user_extractions(
|
result = await extraction_service.get_user_extractions(
|
||||||
user_id=current_user.id,
|
user_id=current_user.id,
|
||||||
search=search,
|
search=search,
|
||||||
sort_by=sort_by,
|
sort_by=sort_by,
|
||||||
sort_order=sort_order,
|
sort_order=sort_order,
|
||||||
status_filter=status_filter,
|
status_filter=status_filter,
|
||||||
|
page=page,
|
||||||
|
limit=limit,
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -146,6 +152,4 @@ async def get_user_extractions(
|
|||||||
detail=f"Failed to get extractions: {e!s}",
|
detail=f"Failed to get extractions: {e!s}",
|
||||||
) from e
|
) from e
|
||||||
else:
|
else:
|
||||||
return {
|
return result
|
||||||
"extractions": extractions,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Playlist management API endpoints."""
|
"""Playlist management API endpoints."""
|
||||||
|
|
||||||
from typing import Annotated
|
from typing import Annotated, Any
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
@@ -55,35 +55,29 @@ async def get_all_playlists( # noqa: PLR0913
|
|||||||
SortOrder,
|
SortOrder,
|
||||||
Query(description="Sort order (asc or desc)"),
|
Query(description="Sort order (asc or desc)"),
|
||||||
] = SortOrder.ASC,
|
] = SortOrder.ASC,
|
||||||
limit: Annotated[
|
page: Annotated[int, Query(description="Page number", ge=1)] = 1,
|
||||||
int | None,
|
limit: Annotated[int, Query(description="Items per page", ge=1, le=100)] = 50,
|
||||||
Query(description="Maximum number of results", ge=1, le=1000),
|
|
||||||
] = None,
|
|
||||||
offset: Annotated[
|
|
||||||
int,
|
|
||||||
Query(description="Number of results to skip", ge=0),
|
|
||||||
] = 0,
|
|
||||||
favorites_only: Annotated[
|
favorites_only: Annotated[
|
||||||
bool,
|
bool,
|
||||||
Query(description="Show only favorited playlists"),
|
Query(description="Show only favorited playlists"),
|
||||||
] = False,
|
] = False,
|
||||||
) -> list[PlaylistResponse]:
|
) -> dict[str, Any]:
|
||||||
"""Get all playlists from all users with search and sorting."""
|
"""Get all playlists from all users with search and sorting."""
|
||||||
playlists = await playlist_service.search_and_sort_playlists(
|
result = await playlist_service.search_and_sort_playlists_paginated(
|
||||||
search_query=search,
|
search_query=search,
|
||||||
sort_by=sort_by,
|
sort_by=sort_by,
|
||||||
sort_order=sort_order,
|
sort_order=sort_order,
|
||||||
user_id=None,
|
user_id=None,
|
||||||
include_stats=True,
|
include_stats=True,
|
||||||
|
page=page,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
offset=offset,
|
|
||||||
favorites_only=favorites_only,
|
favorites_only=favorites_only,
|
||||||
current_user_id=current_user.id,
|
current_user_id=current_user.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Convert to PlaylistResponse with favorite indicators
|
# Convert to PlaylistResponse with favorite indicators
|
||||||
playlist_responses = []
|
playlist_responses = []
|
||||||
for playlist_dict in playlists:
|
for playlist_dict in result["playlists"]:
|
||||||
# The playlist service returns dict, need to create playlist object-like structure
|
# The playlist service returns dict, need to create playlist object-like structure
|
||||||
is_favorited = await favorite_service.is_playlist_favorited(current_user.id, playlist_dict["id"])
|
is_favorited = await favorite_service.is_playlist_favorited(current_user.id, playlist_dict["id"])
|
||||||
favorite_count = await favorite_service.get_playlist_favorite_count(playlist_dict["id"])
|
favorite_count = await favorite_service.get_playlist_favorite_count(playlist_dict["id"])
|
||||||
@@ -98,7 +92,13 @@ async def get_all_playlists( # noqa: PLR0913
|
|||||||
}
|
}
|
||||||
playlist_responses.append(playlist_response)
|
playlist_responses.append(playlist_response)
|
||||||
|
|
||||||
return playlist_responses
|
return {
|
||||||
|
"playlists": playlist_responses,
|
||||||
|
"total": result["total"],
|
||||||
|
"page": result["page"],
|
||||||
|
"limit": result["limit"],
|
||||||
|
"total_pages": result["total_pages"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/user")
|
@router.get("/user")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Extraction repository for database operations."""
|
"""Extraction repository for database operations."""
|
||||||
|
|
||||||
from sqlalchemy import asc, desc, or_
|
from sqlalchemy import asc, desc, func, or_
|
||||||
from sqlmodel import select
|
from sqlmodel import select
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
@@ -65,9 +65,11 @@ class ExtractionRepository(BaseRepository[Extraction]):
|
|||||||
sort_by: str = "created_at",
|
sort_by: str = "created_at",
|
||||||
sort_order: str = "desc",
|
sort_order: str = "desc",
|
||||||
status_filter: str | None = None,
|
status_filter: str | None = None,
|
||||||
) -> list[tuple[Extraction, User]]:
|
limit: int = 50,
|
||||||
|
offset: int = 0,
|
||||||
|
) -> tuple[list[tuple[Extraction, User]], int]:
|
||||||
"""Get extractions for a user with filtering, search, and sorting."""
|
"""Get extractions for a user with filtering, search, and sorting."""
|
||||||
query = (
|
base_query = (
|
||||||
select(Extraction, User)
|
select(Extraction, User)
|
||||||
.join(User, Extraction.user_id == User.id)
|
.join(User, Extraction.user_id == User.id)
|
||||||
.where(Extraction.user_id == user_id)
|
.where(Extraction.user_id == user_id)
|
||||||
@@ -76,7 +78,7 @@ class ExtractionRepository(BaseRepository[Extraction]):
|
|||||||
# Apply search filter
|
# Apply search filter
|
||||||
if search:
|
if search:
|
||||||
search_pattern = f"%{search}%"
|
search_pattern = f"%{search}%"
|
||||||
query = query.where(
|
base_query = base_query.where(
|
||||||
or_(
|
or_(
|
||||||
Extraction.title.ilike(search_pattern),
|
Extraction.title.ilike(search_pattern),
|
||||||
Extraction.url.ilike(search_pattern),
|
Extraction.url.ilike(search_pattern),
|
||||||
@@ -86,17 +88,26 @@ class ExtractionRepository(BaseRepository[Extraction]):
|
|||||||
|
|
||||||
# Apply status filter
|
# Apply status filter
|
||||||
if status_filter:
|
if status_filter:
|
||||||
query = query.where(Extraction.status == status_filter)
|
base_query = base_query.where(Extraction.status == status_filter)
|
||||||
|
|
||||||
# Apply sorting
|
# Get total count before pagination
|
||||||
|
count_query = select(func.count()).select_from(
|
||||||
|
base_query.subquery()
|
||||||
|
)
|
||||||
|
count_result = await self.session.exec(count_query)
|
||||||
|
total_count = count_result.one()
|
||||||
|
|
||||||
|
# Apply sorting and pagination
|
||||||
sort_column = getattr(Extraction, sort_by, Extraction.created_at)
|
sort_column = getattr(Extraction, sort_by, Extraction.created_at)
|
||||||
if sort_order.lower() == "asc":
|
if sort_order.lower() == "asc":
|
||||||
query = query.order_by(asc(sort_column))
|
base_query = base_query.order_by(asc(sort_column))
|
||||||
else:
|
else:
|
||||||
query = query.order_by(desc(sort_column))
|
base_query = base_query.order_by(desc(sort_column))
|
||||||
|
|
||||||
result = await self.session.exec(query)
|
paginated_query = base_query.limit(limit).offset(offset)
|
||||||
return list(result.all())
|
result = await self.session.exec(paginated_query)
|
||||||
|
|
||||||
|
return list(result.all()), total_count
|
||||||
|
|
||||||
async def get_all_extractions_filtered(
|
async def get_all_extractions_filtered(
|
||||||
self,
|
self,
|
||||||
@@ -104,14 +115,16 @@ class ExtractionRepository(BaseRepository[Extraction]):
|
|||||||
sort_by: str = "created_at",
|
sort_by: str = "created_at",
|
||||||
sort_order: str = "desc",
|
sort_order: str = "desc",
|
||||||
status_filter: str | None = None,
|
status_filter: str | None = None,
|
||||||
) -> list[tuple[Extraction, User]]:
|
limit: int = 50,
|
||||||
|
offset: int = 0,
|
||||||
|
) -> tuple[list[tuple[Extraction, User]], int]:
|
||||||
"""Get all extractions with filtering, search, and sorting."""
|
"""Get all extractions with filtering, search, and sorting."""
|
||||||
query = select(Extraction, User).join(User, Extraction.user_id == User.id)
|
base_query = select(Extraction, User).join(User, Extraction.user_id == User.id)
|
||||||
|
|
||||||
# Apply search filter
|
# Apply search filter
|
||||||
if search:
|
if search:
|
||||||
search_pattern = f"%{search}%"
|
search_pattern = f"%{search}%"
|
||||||
query = query.where(
|
base_query = base_query.where(
|
||||||
or_(
|
or_(
|
||||||
Extraction.title.ilike(search_pattern),
|
Extraction.title.ilike(search_pattern),
|
||||||
Extraction.url.ilike(search_pattern),
|
Extraction.url.ilike(search_pattern),
|
||||||
@@ -121,14 +134,23 @@ class ExtractionRepository(BaseRepository[Extraction]):
|
|||||||
|
|
||||||
# Apply status filter
|
# Apply status filter
|
||||||
if status_filter:
|
if status_filter:
|
||||||
query = query.where(Extraction.status == status_filter)
|
base_query = base_query.where(Extraction.status == status_filter)
|
||||||
|
|
||||||
# Apply sorting
|
# Get total count before pagination
|
||||||
|
count_query = select(func.count()).select_from(
|
||||||
|
base_query.subquery()
|
||||||
|
)
|
||||||
|
count_result = await self.session.exec(count_query)
|
||||||
|
total_count = count_result.one()
|
||||||
|
|
||||||
|
# Apply sorting and pagination
|
||||||
sort_column = getattr(Extraction, sort_by, Extraction.created_at)
|
sort_column = getattr(Extraction, sort_by, Extraction.created_at)
|
||||||
if sort_order.lower() == "asc":
|
if sort_order.lower() == "asc":
|
||||||
query = query.order_by(asc(sort_column))
|
base_query = base_query.order_by(asc(sort_column))
|
||||||
else:
|
else:
|
||||||
query = query.order_by(desc(sort_column))
|
base_query = base_query.order_by(desc(sort_column))
|
||||||
|
|
||||||
result = await self.session.exec(query)
|
paginated_query = base_query.limit(limit).offset(offset)
|
||||||
return list(result.all())
|
result = await self.session.exec(paginated_query)
|
||||||
|
|
||||||
|
return list(result.all()), total_count
|
||||||
|
|||||||
@@ -343,7 +343,9 @@ class PlaylistRepository(BaseRepository[Playlist]):
|
|||||||
offset: int = 0,
|
offset: int = 0,
|
||||||
favorites_only: bool = False,
|
favorites_only: bool = False,
|
||||||
current_user_id: int | None = None,
|
current_user_id: int | None = None,
|
||||||
) -> list[dict]:
|
*,
|
||||||
|
return_count: bool = False,
|
||||||
|
) -> list[dict] | tuple[list[dict], int]:
|
||||||
"""Search and sort playlists with optional statistics."""
|
"""Search and sort playlists with optional statistics."""
|
||||||
try:
|
try:
|
||||||
if include_stats and sort_by in (
|
if include_stats and sort_by in (
|
||||||
@@ -491,6 +493,14 @@ class PlaylistRepository(BaseRepository[Playlist]):
|
|||||||
# Default sorting by name ascending
|
# Default sorting by name ascending
|
||||||
subquery = subquery.order_by(Playlist.name.asc())
|
subquery = subquery.order_by(Playlist.name.asc())
|
||||||
|
|
||||||
|
# Get total count if requested
|
||||||
|
total_count = 0
|
||||||
|
if return_count:
|
||||||
|
# Create count query from the subquery before pagination
|
||||||
|
count_query = select(func.count()).select_from(subquery.subquery())
|
||||||
|
count_result = await self.session.exec(count_query)
|
||||||
|
total_count = count_result.one()
|
||||||
|
|
||||||
# Apply pagination
|
# Apply pagination
|
||||||
if offset > 0:
|
if offset > 0:
|
||||||
subquery = subquery.offset(offset)
|
subquery = subquery.offset(offset)
|
||||||
@@ -532,4 +542,6 @@ class PlaylistRepository(BaseRepository[Playlist]):
|
|||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
|
if return_count:
|
||||||
|
return playlists, total_count
|
||||||
return playlists
|
return playlists
|
||||||
|
|||||||
@@ -38,6 +38,16 @@ class ExtractionInfo(TypedDict):
|
|||||||
updated_at: str
|
updated_at: str
|
||||||
|
|
||||||
|
|
||||||
|
class PaginatedExtractionsResponse(TypedDict):
|
||||||
|
"""Type definition for paginated extractions response."""
|
||||||
|
|
||||||
|
extractions: list[ExtractionInfo]
|
||||||
|
total: int
|
||||||
|
page: int
|
||||||
|
limit: int
|
||||||
|
total_pages: int
|
||||||
|
|
||||||
|
|
||||||
class ExtractionService:
|
class ExtractionService:
|
||||||
"""Service for extracting audio from external services using yt-dlp."""
|
"""Service for extracting audio from external services using yt-dlp."""
|
||||||
|
|
||||||
@@ -565,17 +575,22 @@ class ExtractionService:
|
|||||||
sort_by: str = "created_at",
|
sort_by: str = "created_at",
|
||||||
sort_order: str = "desc",
|
sort_order: str = "desc",
|
||||||
status_filter: str | None = None,
|
status_filter: str | None = None,
|
||||||
) -> list[ExtractionInfo]:
|
page: int = 1,
|
||||||
|
limit: int = 50,
|
||||||
|
) -> PaginatedExtractionsResponse:
|
||||||
"""Get all extractions for a user with filtering, search, and sorting."""
|
"""Get all extractions for a user with filtering, search, and sorting."""
|
||||||
extraction_user_tuples = await self.extraction_repo.get_user_extractions_filtered(
|
offset = (page - 1) * limit
|
||||||
|
extraction_user_tuples, total_count = await self.extraction_repo.get_user_extractions_filtered(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
search=search,
|
search=search,
|
||||||
sort_by=sort_by,
|
sort_by=sort_by,
|
||||||
sort_order=sort_order,
|
sort_order=sort_order,
|
||||||
status_filter=status_filter,
|
status_filter=status_filter,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset,
|
||||||
)
|
)
|
||||||
|
|
||||||
return [
|
extractions = [
|
||||||
{
|
{
|
||||||
"id": extraction.id
|
"id": extraction.id
|
||||||
or 0, # Should never be None for existing extraction
|
or 0, # Should never be None for existing extraction
|
||||||
@@ -594,22 +609,37 @@ class ExtractionService:
|
|||||||
for extraction, user in extraction_user_tuples
|
for extraction, user in extraction_user_tuples
|
||||||
]
|
]
|
||||||
|
|
||||||
|
total_pages = (total_count + limit - 1) // limit # Ceiling division
|
||||||
|
|
||||||
|
return {
|
||||||
|
"extractions": extractions,
|
||||||
|
"total": total_count,
|
||||||
|
"page": page,
|
||||||
|
"limit": limit,
|
||||||
|
"total_pages": total_pages,
|
||||||
|
}
|
||||||
|
|
||||||
async def get_all_extractions(
|
async def get_all_extractions(
|
||||||
self,
|
self,
|
||||||
search: str | None = None,
|
search: str | None = None,
|
||||||
sort_by: str = "created_at",
|
sort_by: str = "created_at",
|
||||||
sort_order: str = "desc",
|
sort_order: str = "desc",
|
||||||
status_filter: str | None = None,
|
status_filter: str | None = None,
|
||||||
) -> list[ExtractionInfo]:
|
page: int = 1,
|
||||||
|
limit: int = 50,
|
||||||
|
) -> PaginatedExtractionsResponse:
|
||||||
"""Get all extractions with filtering, search, and sorting."""
|
"""Get all extractions with filtering, search, and sorting."""
|
||||||
extraction_user_tuples = await self.extraction_repo.get_all_extractions_filtered(
|
offset = (page - 1) * limit
|
||||||
|
extraction_user_tuples, total_count = await self.extraction_repo.get_all_extractions_filtered(
|
||||||
search=search,
|
search=search,
|
||||||
sort_by=sort_by,
|
sort_by=sort_by,
|
||||||
sort_order=sort_order,
|
sort_order=sort_order,
|
||||||
status_filter=status_filter,
|
status_filter=status_filter,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset,
|
||||||
)
|
)
|
||||||
|
|
||||||
return [
|
extractions = [
|
||||||
{
|
{
|
||||||
"id": extraction.id
|
"id": extraction.id
|
||||||
or 0, # Should never be None for existing extraction
|
or 0, # Should never be None for existing extraction
|
||||||
@@ -628,6 +658,16 @@ class ExtractionService:
|
|||||||
for extraction, user in extraction_user_tuples
|
for extraction, user in extraction_user_tuples
|
||||||
]
|
]
|
||||||
|
|
||||||
|
total_pages = (total_count + limit - 1) // limit # Ceiling division
|
||||||
|
|
||||||
|
return {
|
||||||
|
"extractions": extractions,
|
||||||
|
"total": total_count,
|
||||||
|
"page": page,
|
||||||
|
"limit": limit,
|
||||||
|
"total_pages": total_pages,
|
||||||
|
}
|
||||||
|
|
||||||
async def get_pending_extractions(self) -> list[ExtractionInfo]:
|
async def get_pending_extractions(self) -> list[ExtractionInfo]:
|
||||||
"""Get all pending extractions."""
|
"""Get all pending extractions."""
|
||||||
extraction_user_tuples = await self.extraction_repo.get_pending_extractions()
|
extraction_user_tuples = await self.extraction_repo.get_pending_extractions()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Playlist service for business logic operations."""
|
"""Playlist service for business logic operations."""
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any, TypedDict
|
||||||
|
|
||||||
from fastapi import HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
@@ -14,6 +14,15 @@ from app.repositories.sound import SoundRepository
|
|||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PaginatedPlaylistsResponse(TypedDict):
|
||||||
|
"""Response type for paginated playlists."""
|
||||||
|
playlists: list[dict]
|
||||||
|
total: int
|
||||||
|
page: int
|
||||||
|
limit: int
|
||||||
|
total_pages: int
|
||||||
|
|
||||||
|
|
||||||
async def _reload_player_playlist() -> None:
|
async def _reload_player_playlist() -> None:
|
||||||
"""Reload the player playlist after current playlist changes."""
|
"""Reload the player playlist after current playlist changes."""
|
||||||
try:
|
try:
|
||||||
@@ -262,6 +271,45 @@ class PlaylistService:
|
|||||||
current_user_id=current_user_id,
|
current_user_id=current_user_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def search_and_sort_playlists_paginated( # noqa: PLR0913
|
||||||
|
self,
|
||||||
|
search_query: str | None = None,
|
||||||
|
sort_by: PlaylistSortField | None = None,
|
||||||
|
sort_order: SortOrder = SortOrder.ASC,
|
||||||
|
user_id: int | None = None,
|
||||||
|
*,
|
||||||
|
include_stats: bool = False,
|
||||||
|
page: int = 1,
|
||||||
|
limit: int = 50,
|
||||||
|
favorites_only: bool = False,
|
||||||
|
current_user_id: int | None = None,
|
||||||
|
) -> PaginatedPlaylistsResponse:
|
||||||
|
"""Search and sort playlists with pagination."""
|
||||||
|
offset = (page - 1) * limit
|
||||||
|
|
||||||
|
playlists, total_count = await self.playlist_repo.search_and_sort(
|
||||||
|
search_query=search_query,
|
||||||
|
sort_by=sort_by,
|
||||||
|
sort_order=sort_order,
|
||||||
|
user_id=user_id,
|
||||||
|
include_stats=include_stats,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset,
|
||||||
|
favorites_only=favorites_only,
|
||||||
|
current_user_id=current_user_id,
|
||||||
|
return_count=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
total_pages = (total_count + limit - 1) // limit # Ceiling division
|
||||||
|
|
||||||
|
return PaginatedPlaylistsResponse(
|
||||||
|
playlists=playlists,
|
||||||
|
total=total_count,
|
||||||
|
page=page,
|
||||||
|
limit=limit,
|
||||||
|
total_pages=total_pages,
|
||||||
|
)
|
||||||
|
|
||||||
async def get_playlist_sounds(self, playlist_id: int) -> list[Sound]:
|
async def get_playlist_sounds(self, playlist_id: int) -> list[Sound]:
|
||||||
"""Get all sounds in a playlist."""
|
"""Get all sounds in a playlist."""
|
||||||
await self.get_playlist_by_id(playlist_id) # Verify playlist exists
|
await self.get_playlist_by_id(playlist_id) # Verify playlist exists
|
||||||
|
|||||||
Reference in New Issue
Block a user