Fix Track Match search ignoring Track/Artist fields and low result limit

- Frontend was concatenating Track and Artist inputs into a single
  query string, causing Spotify to return mixed results matching
  either word in any field. Now sends track and artist as separate
  params; backend builds field-filtered query (track:X artist:Y).
- Result limit was silently capped at 10 in spotify_client.search_tracks
  via min(limit, 10). Raised to respect requested limit up to
  Spotify's API max of 50.
- iTunes fallback endpoint updated with same field-specific params.
- Legacy ?query= param still supported for backward compatibility.

Fixes #194
This commit is contained in:
Broque Thomas 2026-03-19 08:23:19 -07:00
parent 0a3cca4f1f
commit 99481a0232
3 changed files with 51 additions and 13 deletions

View file

@ -955,7 +955,8 @@ class SpotifyClient:
if use_spotify:
# Check Spotify cache
cached_results = cache.get_search_results('spotify', 'track', query, min(limit, 10))
effective_limit = min(limit, 50) # Spotify API max is 50
cached_results = cache.get_search_results('spotify', 'track', query, effective_limit)
if cached_results is not None:
tracks = []
for raw in cached_results:
@ -967,7 +968,7 @@ class SpotifyClient:
return tracks
try:
results = self.sp.search(q=query, type='track', limit=min(limit, 10))
results = self.sp.search(q=query, type='track', limit=effective_limit)
tracks = []
raw_items = results['tracks']['items']
@ -979,7 +980,7 @@ class SpotifyClient:
entries = [(td.get('id'), td) for td in raw_items if td.get('id')]
if entries:
cache.store_entities_bulk('spotify', 'track', entries)
cache.store_search_results('spotify', 'track', query, min(limit, 10),
cache.store_search_results('spotify', 'track', query, effective_limit,
[td.get('id') for td in raw_items if td.get('id')])
return tracks

View file

@ -25069,10 +25069,23 @@ def search_spotify_tracks():
return jsonify({"error": "Spotify not authenticated."}), 401
try:
query = request.args.get('query', '').strip()
# Support field-specific search params (track, artist) or legacy combined query
track_q = request.args.get('track', '').strip()
artist_q = request.args.get('artist', '').strip()
legacy_query = request.args.get('query', '').strip()
limit = int(request.args.get('limit', 20))
if not query:
# Build Spotify field-filtered query
if track_q or artist_q:
parts = []
if track_q:
parts.append(f'track:{track_q}')
if artist_q:
parts.append(f'artist:{artist_q}')
query = ' '.join(parts)
elif legacy_query:
query = legacy_query
else:
return jsonify({"error": "Query parameter is required"}), 400
if use_hydrabase:
@ -25101,10 +25114,22 @@ def search_spotify_tracks():
def search_itunes_tracks():
"""Search for tracks on iTunes - used by discovery fix modal when iTunes is the source"""
try:
query = request.args.get('query', '').strip()
# Support field-specific search params or legacy combined query
track_q = request.args.get('track', '').strip()
artist_q = request.args.get('artist', '').strip()
legacy_query = request.args.get('query', '').strip()
limit = int(request.args.get('limit', 20))
if not query:
if track_q or artist_q:
parts = []
if track_q:
parts.append(track_q)
if artist_q:
parts.append(artist_q)
query = ' '.join(parts)
elif legacy_query:
query = legacy_query
else:
return jsonify({"error": "Query parameter is required"}), 400
use_hydrabase = _is_hydrabase_active()

View file

@ -15392,12 +15392,19 @@ async function searchDiscoveryFix() {
resultsContainer.innerHTML = `<div class="loading">🔍 Searching ${sourceLabel}...</div>`;
try {
// Build search query
const query = `${artistInput} ${trackInput}`.trim();
// Build search params — send track and artist separately for field-specific filtering
const params = new URLSearchParams();
if (trackInput) params.set('track', trackInput);
if (artistInput) params.set('artist', artistInput);
if (!trackInput && !artistInput) {
resultsContainer.innerHTML = '<div class="no-results">Enter a track name or artist.</div>';
return;
}
params.set('limit', '50');
// Call appropriate search API based on discovery source
const searchEndpoint = useFallback ? '/api/itunes/search_tracks' : '/api/spotify/search_tracks';
const response = await fetch(`${searchEndpoint}?query=${encodeURIComponent(query)}&limit=20`);
const response = await fetch(`${searchEndpoint}?${params.toString()}`);
const data = await response.json();
if (data.error) {
@ -55496,8 +55503,9 @@ async function searchPoolFix() {
const resultsContainer = document.getElementById('pool-fix-results');
if (!trackInput || !resultsContainer) return;
const query = `${artistInput.value.trim()} ${trackInput.value.trim()}`.trim();
if (!query) {
const trackVal = trackInput.value.trim();
const artistVal = artistInput.value.trim();
if (!trackVal && !artistVal) {
resultsContainer.innerHTML = '<div class="pool-empty">Enter a search term</div>';
return;
}
@ -55505,7 +55513,11 @@ async function searchPoolFix() {
resultsContainer.innerHTML = '<div class="pool-empty">Searching...</div>';
try {
const res = await fetch(`/api/spotify/search_tracks?query=${encodeURIComponent(query)}&limit=20`);
const params = new URLSearchParams();
if (trackVal) params.set('track', trackVal);
if (artistVal) params.set('artist', artistVal);
params.set('limit', '50');
const res = await fetch(`/api/spotify/search_tracks?${params.toString()}`);
const data = await res.json();
const tracks = data.tracks || [];