Remove unused API Keys UI and infrastructure
All API requests now go through Cloudflare Workers proxy. User-provided credentials were collected but never used. Removed: - src-tauri/src/api_keys.rs (entire module) - API Keys section from Settings UI - ApiKeysState and related commands - User credential parameters from providers - Simplified DiscogsClient (removed with_user_credentials) - Cleaned up imports and translations Moved documentation: - CHANGELOG-api-keys-refactor.md - HANDOFF-MERGE.md - MERGE-PREP.md - POST-MERGE-CLEANUP.md Moved to: qbz-nix-docs/feature-api-keys-refactor/ This cleanup reduces codebase by ~715 lines and removes confusing UI that promised functionality that didn't work.
This commit is contained in:
1 parent
7631462522
commit
cac5a0c173
15 files changed
+24
-2420
No files matched your search
@@ -1,174 +0,0 @@
|
||||
//! User-provided API credentials store
|
||||
//!
|
||||
//! Manages runtime credentials that override embedded (build-time) credentials.
|
||||
//! Users can provide their own API keys if the embedded ones stop working.
|
||||
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
/// Credentials for a specific provider
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ProviderCredentials {
|
||||
pub client_id: Option<String>,
|
||||
pub client_secret: Option<String>,
|
||||
}
|
||||
|
||||
impl ProviderCredentials {
|
||||
pub fn is_set(&self) -> bool {
|
||||
self.client_id.is_some() && self.client_secret.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// Store for all user-provided API credentials
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ApiKeysStore {
|
||||
pub spotify: ProviderCredentials,
|
||||
pub tidal: ProviderCredentials,
|
||||
pub discogs: ProviderCredentials,
|
||||
}
|
||||
|
||||
impl ApiKeysStore {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Thread-safe wrapper for the API keys store
|
||||
pub type ApiKeysState = Arc<Mutex<ApiKeysStore>>;
|
||||
|
||||
pub fn create_api_keys_state() -> ApiKeysState {
|
||||
Arc::new(Mutex::new(ApiKeysStore::new()))
|
||||
}
|
||||
|
||||
// Tauri commands
|
||||
|
||||
/// Set Spotify credentials
|
||||
#[tauri::command]
|
||||
pub async fn set_spotify_credentials(
|
||||
client_id: String,
|
||||
client_secret: String,
|
||||
state: tauri::State<'_, ApiKeysState>,
|
||||
) -> Result<(), String> {
|
||||
log::info!("Setting user-provided Spotify credentials");
|
||||
let mut store = state.lock().await;
|
||||
store.spotify = ProviderCredentials {
|
||||
client_id: Some(client_id),
|
||||
client_secret: Some(client_secret),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clear Spotify credentials
|
||||
#[tauri::command]
|
||||
pub async fn clear_spotify_credentials(
|
||||
state: tauri::State<'_, ApiKeysState>,
|
||||
) -> Result<(), String> {
|
||||
log::info!("Clearing user-provided Spotify credentials");
|
||||
let mut store = state.lock().await;
|
||||
store.spotify = ProviderCredentials::default();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if Spotify has user-provided credentials
|
||||
#[tauri::command]
|
||||
pub async fn has_spotify_user_credentials(
|
||||
state: tauri::State<'_, ApiKeysState>,
|
||||
) -> Result<bool, String> {
|
||||
let store = state.lock().await;
|
||||
Ok(store.spotify.is_set())
|
||||
}
|
||||
|
||||
/// Set Tidal credentials
|
||||
#[tauri::command]
|
||||
pub async fn set_tidal_credentials(
|
||||
client_id: String,
|
||||
client_secret: String,
|
||||
state: tauri::State<'_, ApiKeysState>,
|
||||
) -> Result<(), String> {
|
||||
log::info!("Setting user-provided Tidal credentials");
|
||||
let mut store = state.lock().await;
|
||||
store.tidal = ProviderCredentials {
|
||||
client_id: Some(client_id),
|
||||
client_secret: Some(client_secret),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clear Tidal credentials
|
||||
#[tauri::command]
|
||||
pub async fn clear_tidal_credentials(
|
||||
state: tauri::State<'_, ApiKeysState>,
|
||||
) -> Result<(), String> {
|
||||
log::info!("Clearing user-provided Tidal credentials");
|
||||
let mut store = state.lock().await;
|
||||
store.tidal = ProviderCredentials::default();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if Tidal has user-provided credentials
|
||||
#[tauri::command]
|
||||
pub async fn has_tidal_user_credentials(
|
||||
state: tauri::State<'_, ApiKeysState>,
|
||||
) -> Result<bool, String> {
|
||||
let store = state.lock().await;
|
||||
Ok(store.tidal.is_set())
|
||||
}
|
||||
|
||||
/// Set Discogs credentials
|
||||
#[tauri::command]
|
||||
pub async fn set_discogs_credentials(
|
||||
consumer_key: String,
|
||||
consumer_secret: String,
|
||||
state: tauri::State<'_, ApiKeysState>,
|
||||
) -> Result<(), String> {
|
||||
log::info!("Setting user-provided Discogs credentials");
|
||||
let mut store = state.lock().await;
|
||||
store.discogs = ProviderCredentials {
|
||||
client_id: Some(consumer_key),
|
||||
client_secret: Some(consumer_secret),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clear Discogs credentials
|
||||
#[tauri::command]
|
||||
pub async fn clear_discogs_credentials(
|
||||
state: tauri::State<'_, ApiKeysState>,
|
||||
) -> Result<(), String> {
|
||||
log::info!("Clearing user-provided Discogs credentials");
|
||||
let mut store = state.lock().await;
|
||||
store.discogs = ProviderCredentials::default();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if Discogs has user-provided credentials
|
||||
#[tauri::command]
|
||||
pub async fn has_discogs_user_credentials(
|
||||
state: tauri::State<'_, ApiKeysState>,
|
||||
) -> Result<bool, String> {
|
||||
let store = state.lock().await;
|
||||
Ok(store.discogs.is_set())
|
||||
}
|
||||
|
||||
/// Check which providers have embedded credentials available
|
||||
#[tauri::command]
|
||||
pub fn get_embedded_credentials_status() -> EmbeddedCredentialsStatus {
|
||||
EmbeddedCredentialsStatus {
|
||||
spotify: option_env!("SPOTIFY_API_CLIENT_ID").is_some()
|
||||
&& option_env!("SPOTIFY_API_CLIENT_SECRET").is_some(),
|
||||
tidal: option_env!("TIDAL_API_CLIENT_ID").is_some()
|
||||
&& option_env!("TIDAL_API_CLIENT_SECRET").is_some(),
|
||||
discogs: option_env!("DISCOGS_API_CLIENT_KEY").is_some()
|
||||
&& option_env!("DISCOGS_API_CLIENT_SECRET").is_some(),
|
||||
lastfm: option_env!("LAST_FM_API_KEY").is_some()
|
||||
&& option_env!("LAST_FM_API_SHARED_SECRET").is_some(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct EmbeddedCredentialsStatus {
|
||||
pub spotify: bool,
|
||||
pub tidal: bool,
|
||||
pub discogs: bool,
|
||||
pub lastfm: bool,
|
||||
}
|
||||
@@ -2,41 +2,18 @@
|
||||
|
||||
use tauri::State;
|
||||
|
||||
use crate::api_keys::ApiKeysState;
|
||||
use crate::playlist_import::{
|
||||
import_public_playlist, preview_public_playlist, ImportPlaylist, ImportSummary,
|
||||
ProviderCredentials,
|
||||
};
|
||||
use crate::AppState;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn playlist_import_preview(
|
||||
url: String,
|
||||
api_keys: State<'_, ApiKeysState>,
|
||||
) -> Result<ImportPlaylist, String> {
|
||||
log::info!("Command: playlist_import_preview {}", url);
|
||||
|
||||
// Get user-provided credentials from state
|
||||
let keys = api_keys.lock().await;
|
||||
let spotify_creds = if keys.spotify.is_set() {
|
||||
Some(ProviderCredentials {
|
||||
client_id: keys.spotify.client_id.clone(),
|
||||
client_secret: keys.spotify.client_secret.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let tidal_creds = if keys.tidal.is_set() {
|
||||
Some(ProviderCredentials {
|
||||
client_id: keys.tidal.client_id.clone(),
|
||||
client_secret: keys.tidal.client_secret.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
drop(keys);
|
||||
|
||||
preview_public_playlist(&url, spotify_creds, tidal_creds)
|
||||
preview_public_playlist(&url, None, None)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
@@ -47,38 +24,17 @@ pub async fn playlist_import_execute(
|
||||
name_override: Option<String>,
|
||||
is_public: Option<bool>,
|
||||
state: State<'_, AppState>,
|
||||
api_keys: State<'_, ApiKeysState>,
|
||||
) -> Result<ImportSummary, String> {
|
||||
log::info!("Command: playlist_import_execute {}", url);
|
||||
|
||||
// Get user-provided credentials from state
|
||||
let keys = api_keys.lock().await;
|
||||
let spotify_creds = if keys.spotify.is_set() {
|
||||
Some(ProviderCredentials {
|
||||
client_id: keys.spotify.client_id.clone(),
|
||||
client_secret: keys.spotify.client_secret.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let tidal_creds = if keys.tidal.is_set() {
|
||||
Some(ProviderCredentials {
|
||||
client_id: keys.tidal.client_id.clone(),
|
||||
client_secret: keys.tidal.client_secret.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
drop(keys);
|
||||
|
||||
let client = state.client.lock().await;
|
||||
import_public_playlist(
|
||||
&url,
|
||||
&client,
|
||||
name_override.as_deref(),
|
||||
is_public.unwrap_or(false),
|
||||
spotify_creds,
|
||||
tidal_creds,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
|
||||
@@ -66,14 +66,6 @@ struct ReleaseImage {
|
||||
impl DiscogsClient {
|
||||
/// Create a new Discogs client (proxy handles credentials)
|
||||
pub fn new() -> Self {
|
||||
Self::with_user_credentials(None, None)
|
||||
}
|
||||
|
||||
/// Create a new Discogs client - compatibility method (proxy handles credentials)
|
||||
pub fn with_user_credentials(
|
||||
_user_key: Option<String>,
|
||||
_user_secret: Option<String>,
|
||||
) -> Self {
|
||||
let mut headers = reqwest::header::HeaderMap::new();
|
||||
headers.insert(
|
||||
reqwest::header::USER_AGENT,
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
pub mod api;
|
||||
pub mod api_cache;
|
||||
pub mod api_keys;
|
||||
pub mod audio;
|
||||
pub mod cache;
|
||||
pub mod cast;
|
||||
@@ -166,8 +165,6 @@ pub fn run() {
|
||||
// Initialize download settings state
|
||||
let download_settings_state = config::download_settings::create_download_settings_state()
|
||||
.expect("Failed to initialize download settings");
|
||||
// Initialize API keys state (for user-provided credentials)
|
||||
let api_keys_state = api_keys::create_api_keys_state();
|
||||
// Initialize offline mode state
|
||||
let offline_state = offline::OfflineState::new()
|
||||
.expect("Failed to initialize offline state");
|
||||
@@ -294,7 +291,6 @@ pub fn run() {
|
||||
.manage(session_store_state)
|
||||
.manage(audio_settings_state)
|
||||
.manage(download_settings_state)
|
||||
.manage(api_keys_state)
|
||||
.manage(offline_state)
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
// Auth commands
|
||||
@@ -558,17 +554,6 @@ pub fn run() {
|
||||
config::download_settings::set_download_root,
|
||||
config::download_settings::set_show_downloads_in_library,
|
||||
config::download_settings::validate_download_root,
|
||||
// API keys commands (user-provided credentials)
|
||||
api_keys::set_spotify_credentials,
|
||||
api_keys::clear_spotify_credentials,
|
||||
api_keys::has_spotify_user_credentials,
|
||||
api_keys::set_tidal_credentials,
|
||||
api_keys::clear_tidal_credentials,
|
||||
api_keys::has_tidal_user_credentials,
|
||||
api_keys::set_discogs_credentials,
|
||||
api_keys::clear_discogs_credentials,
|
||||
api_keys::has_discogs_user_credentials,
|
||||
api_keys::get_embedded_credentials_status,
|
||||
// Offline mode commands
|
||||
offline::commands::get_offline_status,
|
||||
offline::commands::get_offline_settings,
|
||||
|
||||
@@ -7,7 +7,6 @@ use std::sync::Arc;
|
||||
use tauri::State;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::api_keys::ApiKeysState;
|
||||
use crate::discogs::DiscogsClient;
|
||||
use crate::library::{
|
||||
cue_to_tracks, get_artwork_cache_dir, CueParser, LibraryDatabase, LibraryFolder, LibraryScanner, LibraryStats,
|
||||
@@ -1188,21 +1187,11 @@ pub async fn playlist_increment_play_count(
|
||||
|
||||
// === Discogs Artwork ===
|
||||
|
||||
/// Check if Discogs credentials are configured (embedded or user-provided)
|
||||
/// Check if Discogs credentials are configured (proxy handles credentials)
|
||||
#[tauri::command]
|
||||
pub async fn discogs_has_credentials(
|
||||
api_keys: State<'_, ApiKeysState>,
|
||||
) -> Result<bool, String> {
|
||||
// Check user-provided credentials first
|
||||
let keys = api_keys.lock().await;
|
||||
if keys.discogs.is_set() {
|
||||
return Ok(true);
|
||||
}
|
||||
drop(keys);
|
||||
|
||||
// Fall back to embedded/env credentials
|
||||
let client = DiscogsClient::new();
|
||||
Ok(client.has_credentials())
|
||||
pub async fn discogs_has_credentials() -> Result<bool, String> {
|
||||
// Proxy always provides credentials
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Fetch missing artwork from Discogs for albums without artwork
|
||||
@@ -1210,21 +1199,11 @@ pub async fn discogs_has_credentials(
|
||||
#[tauri::command]
|
||||
pub async fn library_fetch_missing_artwork(
|
||||
state: State<'_, LibraryState>,
|
||||
api_keys: State<'_, ApiKeysState>,
|
||||
) -> Result<u32, String> {
|
||||
log::info!("Command: library_fetch_missing_artwork");
|
||||
|
||||
// Get user-provided credentials if available
|
||||
let keys = api_keys.lock().await;
|
||||
let discogs = DiscogsClient::with_user_credentials(
|
||||
keys.discogs.client_id.clone(),
|
||||
keys.discogs.client_secret.clone(),
|
||||
);
|
||||
drop(keys);
|
||||
|
||||
if !discogs.has_credentials() {
|
||||
return Err("Discogs credentials not configured".to_string());
|
||||
}
|
||||
// Get Discogs client (proxy handles credentials)
|
||||
let discogs = DiscogsClient::new();
|
||||
|
||||
let artwork_cache = get_artwork_cache_dir();
|
||||
let mut updated_count = 0u32;
|
||||
@@ -1263,21 +1242,11 @@ pub async fn library_fetch_album_artwork(
|
||||
artist: String,
|
||||
album: String,
|
||||
state: State<'_, LibraryState>,
|
||||
api_keys: State<'_, ApiKeysState>,
|
||||
) -> Result<Option<String>, String> {
|
||||
log::info!("Command: library_fetch_album_artwork {} - {}", artist, album);
|
||||
|
||||
// Get user-provided credentials if available
|
||||
let keys = api_keys.lock().await;
|
||||
let discogs = DiscogsClient::with_user_credentials(
|
||||
keys.discogs.client_id.clone(),
|
||||
keys.discogs.client_secret.clone(),
|
||||
);
|
||||
drop(keys);
|
||||
|
||||
if !discogs.has_credentials() {
|
||||
return Err("Discogs credentials not configured".to_string());
|
||||
}
|
||||
// Get Discogs client (proxy handles credentials)
|
||||
let discogs = DiscogsClient::new();
|
||||
|
||||
let artwork_cache = get_artwork_cache_dir();
|
||||
|
||||
@@ -1335,21 +1304,11 @@ pub async fn library_set_album_artwork(
|
||||
#[tauri::command]
|
||||
pub async fn discogs_search_artist(
|
||||
query: String,
|
||||
api_keys: State<'_, ApiKeysState>,
|
||||
) -> Result<crate::discogs::SearchResponse, String> {
|
||||
log::info!("Command: discogs_search_artist query={}", query);
|
||||
|
||||
// Get user-provided credentials if available
|
||||
let keys = api_keys.lock().await;
|
||||
let discogs = DiscogsClient::with_user_credentials(
|
||||
keys.discogs.client_id.clone(),
|
||||
keys.discogs.client_secret.clone(),
|
||||
);
|
||||
drop(keys);
|
||||
|
||||
if !discogs.has_credentials() {
|
||||
return Err("Discogs credentials not configured".to_string());
|
||||
}
|
||||
// Get Discogs client (proxy handles credentials)
|
||||
let discogs = DiscogsClient::new();
|
||||
|
||||
discogs.search_artist(&query).await
|
||||
}
|
||||
|
||||
@@ -40,21 +40,19 @@ pub fn detect_provider(url: &str) -> Result<ProviderKind, PlaylistImportError> {
|
||||
Err(PlaylistImportError::UnsupportedProvider(url.to_string()))
|
||||
}
|
||||
|
||||
/// Fetch playlist with optional user-provided credentials
|
||||
/// Fetch playlist (proxy handles credentials)
|
||||
pub async fn fetch_playlist(
|
||||
kind: ProviderKind,
|
||||
spotify_creds: Option<ProviderCredentials>,
|
||||
tidal_creds: Option<ProviderCredentials>,
|
||||
) -> Result<ImportPlaylist, PlaylistImportError> {
|
||||
match kind {
|
||||
ProviderKind::Spotify { playlist_id } => {
|
||||
spotify::fetch_playlist(&playlist_id, spotify_creds).await
|
||||
spotify::fetch_playlist(&playlist_id).await
|
||||
}
|
||||
ProviderKind::AppleMusic { storefront, playlist_id } => {
|
||||
apple::fetch_playlist(&storefront, &playlist_id).await
|
||||
}
|
||||
ProviderKind::Tidal { playlist_id } => {
|
||||
tidal::fetch_playlist(&playlist_id, tidal_creds).await
|
||||
tidal::fetch_playlist(&playlist_id).await
|
||||
}
|
||||
ProviderKind::Deezer { playlist_id } => deezer::fetch_playlist(&playlist_id).await,
|
||||
}
|
||||
|
||||
@@ -38,9 +38,8 @@ pub fn parse_playlist_id(url: &str) -> Option<String> {
|
||||
|
||||
pub async fn fetch_playlist(
|
||||
playlist_id: &str,
|
||||
user_creds: Option<ProviderCredentials>,
|
||||
) -> Result<ImportPlaylist, PlaylistImportError> {
|
||||
if let Ok(token) = get_app_token(user_creds).await {
|
||||
if let Ok(token) = get_app_token().await {
|
||||
if let Ok(playlist) = fetch_playlist_with_token(playlist_id, &token).await {
|
||||
return Ok(playlist);
|
||||
}
|
||||
@@ -249,8 +248,8 @@ async fn fetch_playlist_from_embed(playlist_id: &str) -> Result<ImportPlaylist,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_app_token(_user_creds: Option<ProviderCredentials>) -> Result<String, PlaylistImportError> {
|
||||
// Proxy handles credentials - user_creds ignored (compatibility)
|
||||
async fn get_app_token() -> Result<String, PlaylistImportError> {
|
||||
// Proxy handles credentials
|
||||
let url = format!("{}/token", SPOTIFY_PROXY_URL);
|
||||
|
||||
let response: Value = reqwest::Client::builder()
|
||||
|
||||
@@ -39,9 +39,8 @@ pub fn parse_playlist_id(url: &str) -> Option<String> {
|
||||
|
||||
pub async fn fetch_playlist(
|
||||
playlist_id: &str,
|
||||
user_creds: Option<ProviderCredentials>,
|
||||
) -> Result<ImportPlaylist, PlaylistImportError> {
|
||||
let token = get_app_token(user_creds).await?;
|
||||
let token = get_app_token().await?;
|
||||
let country_code = env::var("TIDAL_COUNTRY_CODE").unwrap_or_else(|_| "US".to_string());
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
@@ -331,8 +330,8 @@ fn parse_duration_ms(value: &str) -> Option<u64> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_app_token(_user_creds: Option<ProviderCredentials>) -> Result<String, PlaylistImportError> {
|
||||
// Proxy handles credentials - user_creds ignored (compatibility)
|
||||
async fn get_app_token() -> Result<String, PlaylistImportError> {
|
||||
// Proxy handles credentials
|
||||
let url = format!("{}/token", TIDAL_PROXY_URL);
|
||||
|
||||
let response: Value = reqwest::Client::builder()
|
||||
|
||||
Reference in new issue
Block a user