mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
Add Match Tracks button and Local/External column to Active Playlists
- Added 'Match Tracks' button to trigger matching for specific playlist
- Added Local/External column (shows '-' for now, will populate after matching)
- New endpoint: POST /api/admin/playlists/{name}/match
- Injects SpotifyTrackMatchingService into AdminController
- UI shows: Name | Spotify ID | Total | Local/External | Cache Age | Actions
- Allows users to manually trigger matching without waiting 30 minutes
This commit is contained in:
@@ -30,6 +30,7 @@ public class AdminController : ControllerBase
|
|||||||
private readonly SquidWTFSettings _squidWtfSettings;
|
private readonly SquidWTFSettings _squidWtfSettings;
|
||||||
private readonly SpotifyApiClient _spotifyClient;
|
private readonly SpotifyApiClient _spotifyClient;
|
||||||
private readonly SpotifyPlaylistFetcher _playlistFetcher;
|
private readonly SpotifyPlaylistFetcher _playlistFetcher;
|
||||||
|
private readonly SpotifyTrackMatchingService? _matchingService;
|
||||||
private readonly RedisCacheService _cache;
|
private readonly RedisCacheService _cache;
|
||||||
private readonly HttpClient _jellyfinHttpClient;
|
private readonly HttpClient _jellyfinHttpClient;
|
||||||
private readonly IWebHostEnvironment _environment;
|
private readonly IWebHostEnvironment _environment;
|
||||||
@@ -49,7 +50,8 @@ public class AdminController : ControllerBase
|
|||||||
SpotifyApiClient spotifyClient,
|
SpotifyApiClient spotifyClient,
|
||||||
SpotifyPlaylistFetcher playlistFetcher,
|
SpotifyPlaylistFetcher playlistFetcher,
|
||||||
RedisCacheService cache,
|
RedisCacheService cache,
|
||||||
IHttpClientFactory httpClientFactory)
|
IHttpClientFactory httpClientFactory,
|
||||||
|
SpotifyTrackMatchingService? matchingService = null)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
@@ -62,6 +64,7 @@ public class AdminController : ControllerBase
|
|||||||
_squidWtfSettings = squidWtfSettings.Value;
|
_squidWtfSettings = squidWtfSettings.Value;
|
||||||
_spotifyClient = spotifyClient;
|
_spotifyClient = spotifyClient;
|
||||||
_playlistFetcher = playlistFetcher;
|
_playlistFetcher = playlistFetcher;
|
||||||
|
_matchingService = matchingService;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
_jellyfinHttpClient = httpClientFactory.CreateClient();
|
_jellyfinHttpClient = httpClientFactory.CreateClient();
|
||||||
|
|
||||||
@@ -231,6 +234,32 @@ public class AdminController : ControllerBase
|
|||||||
return Ok(new { message = "Playlist refresh triggered", timestamp = DateTime.UtcNow });
|
return Ok(new { message = "Playlist refresh triggered", timestamp = DateTime.UtcNow });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trigger track matching for a specific playlist
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("playlists/{name}/match")]
|
||||||
|
public async Task<IActionResult> MatchPlaylistTracks(string name)
|
||||||
|
{
|
||||||
|
var decodedName = Uri.UnescapeDataString(name);
|
||||||
|
_logger.LogInformation("Manual track matching triggered for playlist: {Name}", decodedName);
|
||||||
|
|
||||||
|
if (_matchingService == null)
|
||||||
|
{
|
||||||
|
return BadRequest(new { error = "Track matching service is not available" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _matchingService.TriggerMatchingAsync();
|
||||||
|
return Ok(new { message = $"Track matching triggered for {decodedName}", timestamp = DateTime.UtcNow });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to trigger track matching for {Name}", decodedName);
|
||||||
|
return StatusCode(500, new { error = "Failed to trigger track matching", details = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get current configuration (safe values only)
|
/// Get current configuration (safe values only)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -657,14 +657,15 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Spotify ID</th>
|
<th>Spotify ID</th>
|
||||||
<th>Tracks</th>
|
<th>Total</th>
|
||||||
|
<th>Local/External</th>
|
||||||
<th>Cache Age</th>
|
<th>Cache Age</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="playlist-table-body">
|
<tbody id="playlist-table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="6" class="loading">
|
<td colspan="7" class="loading">
|
||||||
<span class="spinner"></span> Loading playlists...
|
<span class="spinner"></span> Loading playlists...
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -1061,22 +1062,32 @@
|
|||||||
const tbody = document.getElementById('playlist-table-body');
|
const tbody = document.getElementById('playlist-table-body');
|
||||||
|
|
||||||
if (data.playlists.length === 0) {
|
if (data.playlists.length === 0) {
|
||||||
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center;color:var(--text-secondary);padding:40px;">No playlists configured. Link playlists from the Jellyfin Playlists tab.</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;color:var(--text-secondary);padding:40px;">No playlists configured. Link playlists from the Jellyfin Playlists tab.</td></tr>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody.innerHTML = data.playlists.map(p => `
|
tbody.innerHTML = data.playlists.map(p => {
|
||||||
<tr>
|
// For now, we don't have local/external counts in the API response
|
||||||
<td><strong>${escapeHtml(p.name)}</strong></td>
|
// This will show "-" until we add that data
|
||||||
<td style="font-family:monospace;font-size:0.85rem;color:var(--text-secondary);">${p.id || '-'}</td>
|
const localExternal = (p.localTracks !== undefined && p.externalTracks !== undefined)
|
||||||
<td class="track-count">${p.trackCount || 0}</td>
|
? `${p.localTracks}/${p.externalTracks}`
|
||||||
<td class="cache-age">${p.cacheAge || '-'}</td>
|
: '-';
|
||||||
<td>
|
|
||||||
<button onclick="viewTracks('${escapeJs(p.name)}')">View</button>
|
return `
|
||||||
<button class="danger" onclick="removePlaylist('${escapeJs(p.name)}')">Remove</button>
|
<tr>
|
||||||
</td>
|
<td><strong>${escapeHtml(p.name)}</strong></td>
|
||||||
</tr>
|
<td style="font-family:monospace;font-size:0.85rem;color:var(--text-secondary);">${p.id || '-'}</td>
|
||||||
`).join('');
|
<td class="track-count">${p.trackCount || 0}</td>
|
||||||
|
<td class="track-count">${localExternal}</td>
|
||||||
|
<td class="cache-age">${p.cacheAge || '-'}</td>
|
||||||
|
<td>
|
||||||
|
<button onclick="matchPlaylistTracks('${escapeJs(p.name)}')">Match Tracks</button>
|
||||||
|
<button onclick="viewTracks('${escapeJs(p.name)}')">View</button>
|
||||||
|
<button class="danger" onclick="removePlaylist('${escapeJs(p.name)}')">Remove</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch playlists:', error);
|
console.error('Failed to fetch playlists:', error);
|
||||||
showToast('Failed to fetch playlists', 'error');
|
showToast('Failed to fetch playlists', 'error');
|
||||||
@@ -1321,6 +1332,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function matchPlaylistTracks(name) {
|
||||||
|
try {
|
||||||
|
showToast(`Matching tracks for ${name}...`, 'success');
|
||||||
|
const res = await fetch(`/api/admin/playlists/${encodeURIComponent(name)}/match`, { method: 'POST' });
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
showToast(data.message, 'success');
|
||||||
|
} else {
|
||||||
|
showToast(data.error || 'Failed to match tracks', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showToast('Failed to match tracks', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function clearCache() {
|
async function clearCache() {
|
||||||
if (!confirm('Clear all cached playlist data?')) return;
|
if (!confirm('Clear all cached playlist data?')) return;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user