feat: add admin UI improvements and forwarded headers support

Enhanced admin configuration UI with missing fields, required indicators, and sp_dc warning. Added Spotify playlist selector for linking with auto-filtering of already-linked playlists. Configured forwarded headers to pass real client IPs from nginx to Jellyfin. Improved track view modal error handling.
This commit is contained in:
2026-02-09 12:49:50 -05:00
parent 0a07804833
commit bdd753fd02
3 changed files with 375 additions and 15 deletions

View File

@@ -1379,6 +1379,12 @@ public class AdminController : ControllerBase
{
return Ok(new
{
backendType = _configuration.GetValue<string>("Backend:Type") ?? "Jellyfin",
musicService = _configuration.GetValue<string>("MusicService") ?? "SquidWTF",
explicitFilter = _configuration.GetValue<string>("ExplicitFilter") ?? "All",
enableExternalPlaylists = _configuration.GetValue<bool>("EnableExternalPlaylists", false),
playlistsDirectory = _configuration.GetValue<string>("PlaylistsDirectory") ?? "(not set)",
redisEnabled = _configuration.GetValue<bool>("Redis:Enabled", false),
spotifyApi = new
{
enabled = _spotifyApiSettings.Enabled,
@@ -1392,6 +1398,9 @@ public class AdminController : ControllerBase
{
enabled = _spotifyImportSettings.Enabled,
matchingIntervalHours = _spotifyImportSettings.MatchingIntervalHours,
syncStartHour = _spotifyImportSettings.SyncStartHour,
syncStartMinute = _spotifyImportSettings.SyncStartMinute,
syncWindowHours = _spotifyImportSettings.SyncWindowHours,
playlists = _spotifyImportSettings.Playlists.Select(p => new
{
name = p.Name,
@@ -1919,6 +1928,111 @@ public class AdminController : ControllerBase
}
}
/// <summary>
/// Get all playlists from the user's Spotify account
/// </summary>
[HttpGet("spotify/user-playlists")]
public async Task<IActionResult> GetSpotifyUserPlaylists()
{
if (!_spotifyApiSettings.Enabled || string.IsNullOrEmpty(_spotifyApiSettings.SessionCookie))
{
return BadRequest(new { error = "Spotify API not configured. Please set sp_dc session cookie." });
}
try
{
var token = await _spotifyClient.GetWebAccessTokenAsync();
if (string.IsNullOrEmpty(token))
{
return StatusCode(401, new { error = "Failed to authenticate with Spotify. Check your sp_dc cookie." });
}
// Get list of already-configured Spotify playlist IDs
var configuredPlaylists = await ReadPlaylistsFromEnvFile();
var linkedSpotifyIds = new HashSet<string>(
configuredPlaylists.Select(p => p.Id),
StringComparer.OrdinalIgnoreCase
);
var playlists = new List<object>();
var offset = 0;
const int limit = 50;
while (true)
{
var url = $"https://api.spotify.com/v1/me/playlists?offset={offset}&limit={limit}";
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var response = await _jellyfinHttpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning("Failed to fetch Spotify playlists: {StatusCode}", response.StatusCode);
break;
}
var json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
if (!root.TryGetProperty("items", out var items) || items.GetArrayLength() == 0)
break;
foreach (var item in items.EnumerateArray())
{
var id = item.TryGetProperty("id", out var itemId) ? itemId.GetString() : null;
var name = item.TryGetProperty("name", out var n) ? n.GetString() : null;
var trackCount = 0;
if (item.TryGetProperty("tracks", out var tracks) &&
tracks.TryGetProperty("total", out var total))
{
trackCount = total.GetInt32();
}
var owner = "";
if (item.TryGetProperty("owner", out var ownerObj) &&
ownerObj.TryGetProperty("display_name", out var displayName))
{
owner = displayName.GetString() ?? "";
}
var isPublic = item.TryGetProperty("public", out var pub) && pub.GetBoolean();
// Check if this playlist is already linked
var isLinked = !string.IsNullOrEmpty(id) && linkedSpotifyIds.Contains(id);
playlists.Add(new
{
id,
name,
trackCount,
owner,
isPublic,
isLinked
});
}
if (items.GetArrayLength() < limit) break;
offset += limit;
// Rate limiting
if (_spotifyApiSettings.RateLimitDelayMs > 0)
{
await Task.Delay(_spotifyApiSettings.RateLimitDelayMs);
}
}
return Ok(new { playlists });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching Spotify user playlists");
return StatusCode(500, new { error = "Failed to fetch Spotify playlists", details = ex.Message });
}
}
/// <summary>
/// Get all playlists from Jellyfin
/// </summary>