diff --git a/allstarr/Controllers/AdminController.cs b/allstarr/Controllers/AdminController.cs
index 4883cfd..9dc9fce 100644
--- a/allstarr/Controllers/AdminController.cs
+++ b/allstarr/Controllers/AdminController.cs
@@ -76,7 +76,7 @@ public class AdminController : ControllerBase
var token = await _spotifyClient.GetWebAccessTokenAsync();
if (!string.IsNullOrEmpty(token))
{
- // Try to get user info (may fail even with valid token)
+ // Try to get user info (may fail even with valid token due to scope limitations)
var (success, userId, displayName) = await _spotifyClient.GetCurrentUserAsync();
if (success)
{
@@ -85,10 +85,10 @@ public class AdminController : ControllerBase
}
else
{
- // Token is valid but /me endpoint failed - still consider it authenticated
- // (the token being non-anonymous is the real indicator)
+ // Token is valid but /me endpoint failed - this is normal for TOTP tokens
+ // which don't have user-read-private scope. Playlists still work fine.
spotifyAuthStatus = "authenticated";
- spotifyUser = "(profile not accessible)";
+ spotifyUser = "(working - profile scope not available)";
}
}
else
@@ -498,6 +498,36 @@ public class AdminController : ControllerBase
return Ok(new { message = "Cache cleared", filesDeleted = clearedFiles });
}
+ ///
+ /// Initialize cookie date to current date if cookie exists but date is not set
+ ///
+ [HttpPost("config/init-cookie-date")]
+ public async Task InitCookieDate()
+ {
+ // Only init if cookie exists but date is not set
+ if (string.IsNullOrEmpty(_spotifyApiSettings.SessionCookie))
+ {
+ return BadRequest(new { error = "No cookie set" });
+ }
+
+ if (!string.IsNullOrEmpty(_spotifyApiSettings.SessionCookieSetDate))
+ {
+ return Ok(new { message = "Cookie date already set", date = _spotifyApiSettings.SessionCookieSetDate });
+ }
+
+ _logger.LogInformation("Initializing cookie date to current date (cookie existed without date tracking)");
+
+ var updateRequest = new ConfigUpdateRequest
+ {
+ Updates = new Dictionary
+ {
+ ["SPOTIFY_API_SESSION_COOKIE_SET_DATE"] = DateTime.UtcNow.ToString("o")
+ }
+ };
+
+ return await UpdateConfig(updateRequest);
+ }
+
private static string MaskValue(string? value, int showLast = 0)
{
if (string.IsNullOrEmpty(value)) return "(not set)";
diff --git a/allstarr/wwwroot/index.html b/allstarr/wwwroot/index.html
index 8723f0b..3528ea9 100644
--- a/allstarr/wwwroot/index.html
+++ b/allstarr/wwwroot/index.html
@@ -532,7 +532,6 @@
Name
Spotify ID
Tracks
-
Position
Cache Age
Actions
@@ -685,13 +684,6 @@
-
-
-
-
@@ -774,8 +766,13 @@
});
// Format cookie age with color coding
- function formatCookieAge(setDateStr) {
- if (!setDateStr) return { text: 'Unknown age', class: 'warning', detail: 'Set the cookie to start tracking its age' };
+ function formatCookieAge(setDateStr, hasCookie = false) {
+ if (!setDateStr) {
+ if (hasCookie) {
+ return { text: 'Unknown age', class: 'warning', detail: 'Initializing tracking...', needsInit: true };
+ }
+ return { text: 'No cookie', class: '', detail: '' };
+ }
const setDate = new Date(setDateStr);
const now = new Date();
@@ -804,6 +801,20 @@
return { text, class: status, detail };
}
+ // Initialize cookie date if cookie exists but date is not set
+ async function initCookieDate() {
+ try {
+ const res = await fetch('/api/admin/config/init-cookie-date', { method: 'POST' });
+ if (res.ok) {
+ // Refresh status after initialization
+ fetchStatus();
+ fetchConfig();
+ }
+ } catch (error) {
+ console.error('Failed to init cookie date:', error);
+ }
+ }
+
// API calls
async function fetchStatus() {
try {
@@ -847,8 +858,14 @@
// Update cookie age display
if (cookieAgeEl) {
- const age = formatCookieAge(data.spotify.cookieSetDate);
+ const hasCookie = data.spotify.hasCookie;
+ const age = formatCookieAge(data.spotify.cookieSetDate, hasCookie);
cookieAgeEl.innerHTML = `${age.text} ${age.detail}`;
+
+ // Auto-init cookie date if cookie exists but date is not set
+ if (age.needsInit) {
+ initCookieDate();
+ }
}
} catch (error) {
console.error('Failed to fetch status:', error);
@@ -873,7 +890,6 @@
${escapeHtml(p.name)}
${p.id || '-'}
${p.trackCount || 0}
-
${p.localTracksPosition}
${p.cacheAge || '-'}
@@ -901,7 +917,8 @@
// Cookie age in config tab
const configCookieAge = document.getElementById('config-cookie-age');
if (configCookieAge) {
- const age = formatCookieAge(data.spotifyApi.sessionCookieSetDate);
+ const hasCookie = data.spotifyApi.sessionCookie && data.spotifyApi.sessionCookie !== '(not set)';
+ const age = formatCookieAge(data.spotifyApi.sessionCookieSetDate, hasCookie);
configCookieAge.innerHTML = `${age.text} - ${age.detail}`;
}
@@ -963,14 +980,12 @@
function openAddPlaylist() {
document.getElementById('new-playlist-name').value = '';
document.getElementById('new-playlist-id').value = '';
- document.getElementById('new-playlist-position').value = 'first';
openModal('add-playlist-modal');
}
async function addPlaylist() {
const name = document.getElementById('new-playlist-name').value.trim();
const id = document.getElementById('new-playlist-id').value.trim();
- const position = document.getElementById('new-playlist-position').value;
if (!name || !id) {
showToast('Name and ID are required', 'error');
@@ -981,7 +996,7 @@
const res = await fetch('/api/admin/playlists', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ name, spotifyId: id, localTracksPosition: position })
+ body: JSON.stringify({ name, spotifyId: id })
});
const data = await res.json();