fix: push wishlist pagination to SQL

The wishlist list endpoint previously loaded and JSON-decoded the full
wishlist, filtered by category in Python, then sliced in memory. Cost
grew linearly with wishlist size on every page request.

get_wishlist_tracks now accepts offset and category parameters, both
applied in SQL via LIMIT/OFFSET and json_extract. get_wishlist_count
also accepts category so COUNT(*) matches the filtered page. The API
endpoint uses these to return only the requested page.

Backward compatible: other callers (core/wishlist_service) pass no
offset/category and still receive the full list.
This commit is contained in:
JohnBaumb 2026-04-19 14:35:32 -07:00
parent 133af45199
commit 327275a3fa
2 changed files with 49 additions and 33 deletions

View file

@ -19,21 +19,19 @@ def register_routes(bp):
fields = parse_fields(request)
profile_id = parse_profile_id(request)
category_filter = category if category in ("singles", "albums") else None
try:
from database.music_database import get_database
db = get_database()
raw_tracks = db.get_wishlist_tracks(profile_id=profile_id)
# Category filter
if category in ("singles", "albums"):
raw_tracks = [
t for t in raw_tracks
if _track_category(t) == category
]
total = len(raw_tracks)
start = (page - 1) * limit
tracks = raw_tracks[start:start + limit]
offset = (page - 1) * limit
tracks = db.get_wishlist_tracks(
profile_id=profile_id,
category=category_filter,
limit=limit,
offset=offset,
)
total = db.get_wishlist_count(profile_id=profile_id, category=category_filter)
return api_success(
{"tracks": [serialize_wishlist_track(t, fields) for t in tracks]},
@ -104,15 +102,3 @@ def register_routes(bp):
return api_error("NOT_AVAILABLE", "Wishlist processing function not available.", 501)
except Exception as e:
return api_error("WISHLIST_ERROR", str(e), 500)
def _track_category(track):
"""Determine if a wishlist track is a single or album track."""
album_type = ""
if isinstance(track, dict):
sd = track.get("spotify_data", {})
if isinstance(sd, dict):
album = sd.get("album", {})
if isinstance(album, dict):
album_type = album.get("album_type", "")
return "albums" if album_type == "album" else "singles"

View file

@ -6632,8 +6632,14 @@ class MusicDatabase:
logger.error(f"Error removing track from wishlist: {e}")
return False
def get_wishlist_tracks(self, limit: Optional[int] = None, profile_id: int = 1) -> List[Dict[str, Any]]:
"""Get all tracks in the wishlist for the given profile, ordered by date added (oldest first for retry priority)"""
def get_wishlist_tracks(self, limit: Optional[int] = None, profile_id: int = 1,
offset: int = 0, category: Optional[str] = None) -> List[Dict[str, Any]]:
"""Get tracks in the wishlist for the given profile, ordered by date added
(oldest first for retry priority).
Supports SQL-level pagination via limit/offset and optional category
filtering (singles vs albums) pushed down to SQL using json_extract.
"""
try:
with self._get_connection() as conn:
cursor = conn.cursor()
@ -6643,12 +6649,26 @@ class MusicDatabase:
last_attempted, date_added, source_type, source_info
FROM wishlist_tracks
WHERE profile_id = ?
ORDER BY date_added
"""
params = [profile_id]
params: List[Any] = [profile_id]
if category == "albums":
query += " AND json_extract(spotify_data, '$.album.album_type') = 'album'"
elif category == "singles":
query += (
" AND (json_extract(spotify_data, '$.album.album_type') IS NULL"
" OR json_extract(spotify_data, '$.album.album_type') != 'album')"
)
query += " ORDER BY date_added"
if limit:
query += f" LIMIT {limit}"
query += " LIMIT ?"
params.append(int(limit))
if offset:
query += " OFFSET ?"
params.append(int(offset))
cursor.execute(query, params)
rows = cursor.fetchall()
@ -6679,7 +6699,7 @@ class MusicDatabase:
except Exception as e:
logger.error(f"Error getting wishlist tracks: {e}")
return []
def update_wishlist_retry(self, spotify_track_id: str, success: bool, error_message: str = None, profile_id: int = 1) -> bool:
"""Update retry count and status for a wishlist track"""
try:
@ -6706,12 +6726,22 @@ class MusicDatabase:
logger.error(f"Error updating wishlist retry status: {e}")
return False
def get_wishlist_count(self, profile_id: int = 1) -> int:
"""Get the total number of tracks in the wishlist for the given profile"""
def get_wishlist_count(self, profile_id: int = 1, category: Optional[str] = None) -> int:
"""Get the total number of tracks in the wishlist for the given profile,
optionally filtered by category ('singles' or 'albums')."""
try:
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM wishlist_tracks WHERE profile_id = ?", (profile_id,))
query = "SELECT COUNT(*) FROM wishlist_tracks WHERE profile_id = ?"
params: List[Any] = [profile_id]
if category == "albums":
query += " AND json_extract(spotify_data, '$.album.album_type') = 'album'"
elif category == "singles":
query += (
" AND (json_extract(spotify_data, '$.album.album_type') IS NULL"
" OR json_extract(spotify_data, '$.album.album_type') != 'album')"
)
cursor.execute(query, params)
result = cursor.fetchone()
return result[0] if result else 0
except Exception as e: