Gate Discogs primary source by token

- Show Discogs with a lock icon until a personal access token is present.
- Prevent selecting locked Discogs and steer users to the Discogs settings section.
- Keep metadata-source availability and selection state synced as the token changes.
This commit is contained in:
Antti Kettunen 2026-05-01 12:59:38 +03:00
parent 5ff20fbfec
commit 4e40bce3e9
No known key found for this signature in database
GPG key ID: C6B2A3D250359BD7
3 changed files with 106 additions and 25 deletions

View file

@ -476,6 +476,12 @@ function handleServiceStatusUpdate(data) {
if (typeof syncSpotifySettingsAuthState === 'function') {
syncSpotifySettingsAuthState(data?.spotify || null);
}
if (typeof syncPrimaryMetadataSourceAvailability === 'function') {
syncPrimaryMetadataSourceAvailability(data?.spotify || null);
}
if (typeof sanitizeMetadataSourceSelection === 'function') {
sanitizeMetadataSourceSelection({ quiet: true });
}
// Same logic as fetchAndUpdateServiceStatus response handler
updateServiceStatus('spotify', data.spotify);

View file

@ -58,8 +58,25 @@ function syncMetadataSourceSelection(source) {
select.dataset.lastValidSource = source;
}
function focusSpotifySettingsSection() {
const card = document.querySelector('#settings-page .stg-service[data-service="spotify"]');
function _isMetadataSourceSelectable(source) {
if (source === 'spotify') {
return _lastServiceStatus?.spotify?.authenticated === true;
}
if (source === 'discogs') {
const token = document.getElementById('discogs-token');
return !!token?.value?.trim();
}
return true;
}
function _metadataSourceFallback(source) {
if (source === 'spotify') return 'deezer';
if (source === 'discogs') return 'itunes';
return 'itunes';
}
function focusServiceSettingsSection(service, message) {
const card = document.querySelector(`#settings-page .stg-service[data-service="${service}"]`);
if (!card) return;
const header = card.querySelector('.stg-service-header');
@ -74,7 +91,39 @@ function focusSpotifySettingsSection() {
firstControl.focus({ preventScroll: true });
}
showToast('Spotify must be authenticated before it can be selected as the primary metadata source.', 'warning');
if (message) {
showToast(message, 'warning');
}
}
function sanitizeMetadataSourceSelection({ quiet = true } = {}) {
const select = document.getElementById('metadata-fallback-source');
if (!select) return false;
const selectedSource = select.value || 'itunes';
if (_isMetadataSourceSelectable(selectedSource)) {
select.dataset.lastValidSource = selectedSource;
return false;
}
const lastValid = select.dataset.lastValidSource;
const fallbackSource = lastValid && lastValid !== selectedSource && _isMetadataSourceSelectable(lastValid)
? lastValid
: _metadataSourceFallback(selectedSource);
if (fallbackSource && fallbackSource !== selectedSource) {
select.value = fallbackSource;
}
select.dataset.lastValidSource = fallbackSource;
if (!quiet) {
const message = selectedSource === 'discogs'
? 'Discogs requires a personal access token before it can be selected as the primary metadata source.'
: 'Spotify must be authenticated before it can be selected as the primary metadata source.';
focusServiceSettingsSection(selectedSource, message);
}
return true;
}
function handleMetadataSourceChange(event) {
@ -82,22 +131,12 @@ function handleMetadataSourceChange(event) {
if (!select || select.id !== 'metadata-fallback-source') return;
const selectedSource = select.value;
if (selectedSource !== 'spotify') {
if (_isMetadataSourceSelectable(selectedSource)) {
select.dataset.lastValidSource = selectedSource;
return;
}
const spotifySessionActive = _lastServiceStatus?.spotify?.authenticated === true;
if (spotifySessionActive) {
select.dataset.lastValidSource = selectedSource;
return;
}
const fallbackSource = select.dataset.lastValidSource || _lastServiceStatus?.spotify?.source || 'deezer';
if (fallbackSource && fallbackSource !== 'spotify') {
select.value = fallbackSource;
}
focusSpotifySettingsSection();
sanitizeMetadataSourceSelection({ quiet: false });
}
function initializeSettings() {
@ -130,6 +169,15 @@ function initializeSettings() {
if (metadataSourceSelect) {
metadataSourceSelect.addEventListener('change', handleMetadataSourceChange);
}
const discogsTokenInput = document.getElementById('discogs-token');
if (discogsTokenInput) {
discogsTokenInput.addEventListener('input', () => {
if (typeof syncPrimaryMetadataSourceAvailability === 'function') {
syncPrimaryMetadataSourceAvailability(_lastServiceStatus?.spotify || null);
}
sanitizeMetadataSourceSelection({ quiet: true });
});
}
// Server toggle buttons
const plexToggle = document.getElementById('plex-toggle');
@ -158,11 +206,12 @@ function initializeSettings() {
// Test connection buttons
// Test button event listeners removed - they use onclick attributes in HTML to avoid double firing
if (typeof syncSpotifyMetadataSourceAvailability === 'function') {
syncSpotifyMetadataSourceAvailability(_lastServiceStatus?.spotify || null);
if (typeof syncPrimaryMetadataSourceAvailability === 'function') {
syncPrimaryMetadataSourceAvailability(_lastServiceStatus?.spotify || null);
}
syncSpotifySettingsAuthState(_lastServiceStatus?.spotify || null);
syncMetadataSourceSelection(_lastServiceStatus?.spotify?.source);
sanitizeMetadataSourceSelection({ quiet: true });
if (metadataSourceSelect) {
metadataSourceSelect.dataset.lastValidSource = metadataSourceSelect.value;
}
@ -2491,6 +2540,8 @@ async function saveSettings(quiet = false) {
}
const metadataSourceSelect = document.getElementById('metadata-fallback-source');
const discogsTokenInput = document.getElementById('discogs-token');
const discogsTokenPresent = !!discogsTokenInput?.value?.trim();
let metadataSource = metadataSourceSelect?.value || 'itunes';
const spotifySessionActive = _lastServiceStatus?.spotify?.authenticated === true;
if (metadataSource === 'spotify' && !spotifySessionActive) {
@ -2499,6 +2550,12 @@ async function saveSettings(quiet = false) {
if (!quiet) {
showToast('Spotify is disconnected, so Deezer is used as the primary metadata source.', 'warning');
}
} else if (metadataSource === 'discogs' && !discogsTokenPresent) {
metadataSource = 'itunes';
if (metadataSourceSelect) metadataSourceSelect.value = metadataSource;
if (!quiet) {
showToast('Discogs requires a personal access token before it can be selected as the primary metadata source.', 'warning');
}
}
const settings = {

View file

@ -3148,6 +3148,12 @@ async function fetchAndUpdateServiceStatus() {
if (typeof syncSpotifySettingsAuthState === 'function') {
syncSpotifySettingsAuthState(data?.spotify || null);
}
if (typeof syncPrimaryMetadataSourceAvailability === 'function') {
syncPrimaryMetadataSourceAvailability(data?.spotify || null);
}
if (typeof sanitizeMetadataSourceSelection === 'function') {
sanitizeMetadataSourceSelection({ quiet: true });
}
// Update service status indicators and text (dashboard)
updateServiceStatus('spotify', data.spotify);
@ -3190,20 +3196,32 @@ async function fetchAndUpdateServiceStatus() {
}
}
function syncSpotifyMetadataSourceAvailability(statusData) {
function syncPrimaryMetadataSourceAvailability(statusData) {
const select = document.getElementById('metadata-fallback-source');
if (!select) return;
if (!statusData) return;
const spotifyOption = select.querySelector('option[value="spotify"]');
if (!spotifyOption) return;
const discogsOption = select.querySelector('option[value="discogs"]');
const spotifyAvailable = statusData?.authenticated === true;
spotifyOption.dataset.unavailable = spotifyAvailable ? 'false' : 'true';
spotifyOption.textContent = spotifyAvailable ? 'Spotify' : '🔒 Spotify';
spotifyOption.title = spotifyAvailable
? 'Spotify'
: 'Spotify authentication is required before this source can be selected.';
if (spotifyOption) {
spotifyOption.dataset.unavailable = spotifyAvailable ? 'false' : 'true';
spotifyOption.textContent = spotifyAvailable ? 'Spotify' : '🔒 Spotify';
spotifyOption.title = spotifyAvailable
? 'Spotify'
: 'Spotify authentication is required before this source can be selected.';
}
if (discogsOption) {
const discogsToken = document.getElementById('discogs-token');
const discogsAvailable = !!discogsToken?.value?.trim();
discogsOption.dataset.unavailable = discogsAvailable ? 'false' : 'true';
discogsOption.textContent = discogsAvailable ? 'Discogs' : '🔒 Discogs';
discogsOption.title = discogsAvailable
? 'Discogs'
: 'Discogs personal access token is required before this source can be selected.';
}
}
function getMetadataSourceLabel(source) {
@ -3304,7 +3322,7 @@ function updateServiceStatus(service, statusData) {
disconnectBtn.style.display = spotifySessionActive ? '' : 'none';
}
syncSpotifyMetadataSourceAvailability(statusData);
syncPrimaryMetadataSourceAvailability(statusData);
}
// Update download source title on dashboard card