diff --git a/CHANGELOG-api-keys-refactor.md b/CHANGELOG-api-keys-refactor.md deleted file mode 100644 index f6e373f8e..000000000 --- a/CHANGELOG-api-keys-refactor.md +++ /dev/null @@ -1,317 +0,0 @@ -# API Keys Refactor - Changelog - -## Overview - -This branch removes all embedded API credentials from the QBZ binary to achieve Flathub compliance and eliminate security/technical debt. API keys are now managed server-side through a Cloudflare Workers proxy. - -**Branch:** `feature/api-keys-refactor` -**Target:** `main` -**Impact:** Security, Privacy, Flathub Compliance - ---- - -## ๐Ÿ” Security & Compliance - -### Removed Embedded API Keys -- โŒ Removed Last.fm API secret from binary -- โŒ Removed Spotify client credentials from binary -- โŒ Removed Tidal client credentials from binary -- โŒ Removed Discogs API credentials from binary - -### New Architecture -- โœ… Cloudflare Workers proxy handles all API authentication -- โœ… Client only knows proxy URL (no secrets exposed) -- โœ… Rate limiting and User-Agent validation server-side -- โœ… Ready for Flathub submission - ---- - -## ๐Ÿš€ New Features - -### 1. Discogs Artwork Selection UI -**Enhanced album artwork management with Discogs integration** - -- ๐ŸŽจ **Visual artwork selector** in album edit modal -- ๐Ÿ” **Smart search**: Uses catalog number if available, falls back to artist + album -- ๐Ÿ–ผ๏ธ **Multi-release support**: Shows images from top 2 release variants -- ๐Ÿ“„ **Carousel navigation**: Browse 4 images per page with prev/next controls -- ๐Ÿ“‹ **Release metadata**: Each image shows release title and year -- โœจ **Automatic download**: Select image โ†’ Save โ†’ automatically cached locally - -**User Benefits:** -- Audiophiles with catalog numbers get precise results -- See which release variant each artwork comes from -- Easy to find and set correct album covers - -### 2. Catalog Number Support -**Precise music metadata for audiophile libraries** - -- ๐Ÿท๏ธ **Reads catalog number** from audio file tags (CATALOGNUMBER field) -- ๐Ÿ’พ **Stores in database** for all tracks and albums -- ๐ŸŽฏ **Prioritizes in Discogs search** for accurate results -- ๐Ÿ‘๏ธ **Displays in UI** on album detail view (e.g., "Cat# MFSL 1-395") - -**User Benefits:** -- Professional/audiophile collections with catalog numbers get better search results -- Can identify specific pressings and editions -- More accurate metadata overall - -### 3. URI Scheme Registration -**Deep linking support for OAuth flows** - -- ๐Ÿ”— **Registered `qbz://` URI scheme** for OAuth callbacks -- ๐ŸŒ **Enables web-based authentication** flows -- ๐Ÿ”„ **Future-proof** for additional integrations - ---- - -## ๐Ÿ”ง Technical Changes - -### Cloudflare Workers Proxy -**Location:** `/home/blitzkriegfc/Personal/qbz/qbz-api-proxy` - -#### Endpoints Implemented - -**Last.fm** -- `POST /lastfm/auth.getMobileSession` - Mobile session authentication -- `GET /lastfm/*` - All Last.fm API methods (read-only) -- `POST /lastfm/*` - Write operations (scrobbles, love, etc.) -- Automatic signature generation with API secret - -**Spotify** -- `POST /spotify/token` - OAuth token exchange -- `POST /spotify/refresh` - Token refresh -- Client credentials managed server-side - -**Tidal** -- `POST /tidal/token` - OAuth token exchange -- `POST /tidal/refresh` - Token refresh -- Client credentials managed server-side - -**Discogs** -- `GET /discogs/search` - Database search -- `GET /discogs/release/{id}` - Full release details with all images -- `GET /discogs/image` - Authenticated image download proxy - -#### Security Features -- Rate limiting (configurable per client IP) -- User-Agent validation (rejects non-QBZ clients) -- CORS headers for web compatibility -- Request logging for monitoring - -### Rust Backend Changes - -#### Last.fm Client (`src-tauri/src/lastfm/client.rs`) -- Removed `api_secret` field and signature generation -- All requests now go through proxy -- Simplified authentication flow -- Maintains same public API for backward compatibility - -#### Playlist Import (`src-tauri/src/playlist_import/`) -- Spotify import uses proxy for OAuth -- Tidal import uses proxy for OAuth -- No client credentials needed in binary - -#### Discogs Integration (`src-tauri/src/discogs/mod.rs`) -- New `search_artwork_options()` method with catalog number support -- New `get_release_details()` for full image sets -- Intelligent image interleaving (4 from release #1, 4 from #2, 2 from others) -- Duplicate filtering by URL -- All requests through proxy (no credentials in binary) - -#### Database Schema (`src-tauri/src/library/database.rs`) -- Added `catalog_number` column to `local_tracks` table -- Migration runs automatically on first launch -- Included in album aggregation queries - -#### Metadata Extraction (`src-tauri/src/library/metadata.rs`) -- Reads `CatalogNumber` tag using lofty -- Extracts from FLAC, MP3, ALAC, etc. - -### Frontend Changes - -#### LocalLibraryView Component -- New Discogs artwork selector UI -- Carousel navigation for image options -- Release title and year display -- Catalog number display in album details -- Improved button consistency - ---- - -## ๐Ÿ“Š Database Migrations - -### Migration: Add catalog_number Column -```sql -ALTER TABLE local_tracks ADD COLUMN catalog_number TEXT; -``` - -**Impact:** -- Runs automatically on app startup -- Non-destructive (only adds column) -- Existing data unaffected -- New scans will populate catalog numbers - ---- - -## ๐Ÿงช Testing Notes - -### What Was Tested -- โœ… Last.fm authentication and scrobbling through proxy -- โœ… Spotify playlist import with OAuth through proxy -- โœ… Tidal playlist import with OAuth through proxy -- โœ… Discogs artwork search with catalog numbers -- โœ… Multi-release image fetching and display -- โœ… Carousel navigation with 4+ images -- โœ… Artwork download and caching -- โœ… Database migration (catalog_number) - -### Known Issues -- โš ๏ธ **429 Rate Limits**: Discogs API occasionally returns "Too Many Requests" - - **Impact**: User must retry after a few seconds - - **Frequency**: Occasional, especially when fetching detailed release info - - **Severity**: Low (transient error, retry works) - - **Mitigation**: Proxy uses rate limiting, but Discogs has strict limits - - **Future Fix**: Could add request caching or debouncing - ---- - -## ๐Ÿšจ Breaking Changes - -### For Developers -- `LastFmClient::new()` no longer requires `api_secret` parameter -- `LastFmClient::with_credentials()` signature changed (removed `api_secret`) -- Cloudflare Workers proxy must be deployed for app to function -- Environment variables needed for proxy deployment (see `.dev.vars.example`) - -### For Users -**None** - Changes are transparent to end users. All existing functionality works the same. - ---- - -## ๐ŸŒ Deployment Requirements - -### Cloudflare Workers -The proxy must be deployed to Cloudflare Workers with these secrets: - -```bash -# Last.fm -LAST_FM_API_KEY=your_key_here -LAST_FM_API_SHARED_SECRET=your_secret_here - -# Spotify -SPOTIFY_API_CLIENT_ID=your_client_id_here -SPOTIFY_API_CLIENT_SECRET=your_client_secret_here - -# Tidal -TIDAL_API_CLIENT_ID=your_client_id_here -TIDAL_API_CLIENT_SECRET=your_client_secret_here - -# Discogs -DISCOGS_API_CLIENT_KEY=your_key_here -DISCOGS_API_CLIENT_SECRET=your_secret_here -``` - -**Deployment:** -```bash -cd qbz-api-proxy -npm install -wrangler deploy -``` - -**URL:** `https://qbz-api-proxy.blitzkriegfc.workers.dev` - ---- - -## ๐Ÿ“ Migration Guide - -### Merging to Main - -1. **Ensure proxy is deployed** and responding -2. **Test all integrations** post-merge: - - Last.fm login and scrobbling - - Spotify playlist import - - Tidal playlist import - - Discogs artwork fetching -3. **Database migration runs automatically** on first launch -4. **No user action required** - changes are transparent - -### Rollback Plan -If issues arise, revert commits and redeploy previous version. Database migration is additive only (safe to rollback). - ---- - -## ๐ŸŽฏ Future Enhancements - -### Potential Improvements -- [ ] **Request caching** in proxy to reduce 429 errors -- [ ] **Artwork preview** before download -- [ ] **Batch artwork fetching** for multiple albums -- [ ] **MusicBrainz integration** using same proxy pattern -- [ ] **User-provided API keys** as optional override - ---- - -## ๐Ÿ“„ Files Changed - -### New Files -- `qbz-api-proxy/` - Complete Cloudflare Workers proxy project -- `CHANGELOG-api-keys-refactor.md` - This file - -### Modified Files -**Rust Backend:** -- `src-tauri/src/lastfm/client.rs` -- `src-tauri/src/lastfm/commands.rs` -- `src-tauri/src/playlist_import/spotify.rs` -- `src-tauri/src/playlist_import/tidal.rs` -- `src-tauri/src/discogs/mod.rs` -- `src-tauri/src/library/models.rs` -- `src-tauri/src/library/database.rs` -- `src-tauri/src/library/metadata.rs` -- `src-tauri/src/library/cue_parser.rs` -- `src-tauri/src/library/commands.rs` -- `src-tauri/src/lib.rs` - -**Frontend:** -- `src/lib/components/views/LocalLibraryView.svelte` - ---- - -## ๐Ÿ‘ฅ Credits - -- **Architecture Design**: API proxy pattern for credential management -- **Discogs Integration**: Multi-release artwork selection with metadata -- **Catalog Number Support**: Audiophile-grade metadata handling -- **UI/UX**: Carousel navigation and visual artwork selector - ---- - -## ๐Ÿ“… Timeline - -- **Started**: API keys refactor planning -- **Completed**: Full integration with all services migrated to proxy -- **Ready for Merge**: Pending main branch audio configuration updates -- **Target Merge**: After main stabilizes - ---- - -## โœ… Pre-Merge Checklist - -- [x] All API keys removed from binary -- [x] Cloudflare Workers proxy deployed and tested -- [x] Last.fm integration working through proxy -- [x] Spotify integration working through proxy -- [x] Tidal integration working through proxy -- [x] Discogs integration working through proxy -- [x] Database migration tested -- [x] UI updates complete and tested -- [x] No breaking changes for end users -- [x] Documentation complete (this CHANGELOG) -- [ ] Wait for main branch audio config changes to stabilize -- [ ] Final integration test after merge to main -- [ ] Update app version number -- [ ] Tag release - ---- - -**End of Changelog** diff --git a/HANDOFF-MERGE.md b/HANDOFF-MERGE.md deleted file mode 100644 index 515f69998..000000000 --- a/HANDOFF-MERGE.md +++ /dev/null @@ -1,505 +0,0 @@ -# ๐Ÿš€ HANDOFF: API Keys Refactor - Ready to Merge - -**Date:** January 17, 2026 -**Branch:** `feature/api-keys-refactor` -**Status:** โœ… Ready for merge to `main` -**Working Tree:** Clean (verified) -**New commits to merge:** 1 commit (removes API key injection from workflows) - ---- - -## โœ… Pre-Merge Verification - -```bash -# Verify current location -pwd -# Should be: /home/blitzkriegfc/Personal/qbz/qbz-worktrees/feature-api-keys-refactor - -# Verify branch -git branch -# Should show: * feature/api-keys-refactor - -# Verify clean state -git status -# Should show: nothing to commit, working tree clean - -# Verify commits ready -git log --oneline origin/main..HEAD -# Should show 3 commits (1 new + 1 merge + 1 doc update) -``` - -**All checks passed:** โœ… - ---- - -## ๐Ÿšจ POST-MERGE ACTION REQUIRED - -**After merging and testing, you MUST remove the API Keys UI from Settings.** - -**Why?** The UI is non-functional - it accepts user credentials but completely ignores them. All API requests go through the Cloudflare Workers proxy. Leaving this UI visible will confuse users and create unnecessary support requests. - -**What to do:** -1. Merge this branch โœ… -2. Test all integrations โœ… -3. **Follow `POST-MERGE-CLEANUP.md`** (30-45 min, removes ~715 lines) -4. Commit the cleanup -5. Done! - -**Document:** See `POST-MERGE-CLEANUP.md` for step-by-step instructions with exact line numbers. - ---- - -## ๐Ÿ“‹ Current Status - -**Previous merge (already in main):** 14 commits with API keys refactor, Discogs UI, and catalog number support โœ… - -**New commits to merge:** - -``` -b182d2d Remove API key injection from GitHub workflows -76ed4e0 Merge main into feature/api-keys-refactor (brings audio config updates) -2495636 Update HANDOFF document to emphasize API Keys UI cleanup requirement -``` - -**What this merge will add to main:** -- Removal of API key injection from GitHub workflows (release-linux.yml, release-flatpak.yml) -- Updated HANDOFF documentation - ---- - -## ๐ŸŽฏ Merge Commands (Copy & Paste) - -### Step 1: Ensure you're in the main worktree -```bash -cd /home/blitzkriegfc/Personal/qbz/qbz-nix -``` - -### Step 2: Ensure main is up to date -```bash -git checkout main -git pull origin main -``` - -### Step 3: Merge feature branch (no fast-forward for clean history) -```bash -git merge --no-ff feature/api-keys-refactor -m "Merge feature/api-keys-refactor: Remove embedded API keys and add Discogs artwork selector - -This major refactor achieves Flathub compliance by removing all embedded -API credentials from the binary. All API requests now go through a -Cloudflare Workers proxy for secure credential management. - -New Features: -- Discogs artwork selection UI with multi-release support -- Catalog number support for audiophile libraries -- Carousel navigation for artwork options -- Release metadata display (title, year) - -Technical Changes: -- Cloudflare Workers proxy for Last.fm, Spotify, Tidal, Discogs -- Database migration adds catalog_number column -- URI scheme registration (qbz://) for OAuth flows -- Rate limiting and User-Agent validation server-side - -See CHANGELOG-api-keys-refactor.md for complete details. -See POST-MERGE-CLEANUP.md for removing legacy API Keys UI." -``` - -### Step 4: Resolve conflicts (if any) -```bash -# If conflicts occur (likely in SettingsView.svelte due to audio config changes) -git status -# Check which files have conflicts - -# For each conflicted file: -# 1. Open in editor -# 2. Look for <<<<<<< HEAD markers -# 3. Keep BOTH changes (main's audio config + feature's other changes) -# 4. Remove conflict markers -# 5. git add - -# After resolving all conflicts: -git merge --continue -``` - -### Step 5: Push to origin -```bash -git push origin main -``` - -### Step 6: Tag the release -```bash -git tag -a v1.2.0 -m "API Keys Refactor - Flathub Compliance - -Major changes: -- Removed all embedded API credentials -- Added Cloudflare Workers proxy for credential management -- Added Discogs artwork selection UI -- Added catalog number support -- Database migration for catalog_number column -- Flathub submission now possible" - -git push origin v1.2.0 -``` - -### Step 7: Cleanup (optional - can keep branch for reference) -```bash -# Delete local feature branch (optional) -git branch -d feature/api-keys-refactor - -# Delete remote feature branch (optional) -git push origin --delete feature/api-keys-refactor - -# Remove worktree (optional) -git worktree remove /home/blitzkriegfc/Personal/qbz/qbz-worktrees/feature-api-keys-refactor -``` - ---- - -## ๐Ÿงช Post-Merge Testing Checklist - -### Critical Tests (Must Pass) -- [ ] App launches without errors -- [ ] Database migration runs (check logs for catalog_number column) -- [ ] Last.fm login works -- [ ] Last.fm scrobbling works -- [ ] Spotify playlist import works -- [ ] Tidal playlist import works -- [ ] Discogs artwork search works -- [ ] Discogs carousel navigation works (4+ images) -- [ ] Artwork save works (select image โ†’ Save) -- [ ] Settings view intact (audio config + other sections) - -### Regression Tests (Should Still Work) -- [ ] Audio playback -- [ ] Local library scanning -- [ ] Album/Artist browsing -- [ ] Search functionality -- [ ] Playlist management -- [ ] Downloads - -### Open browser DevTools and check console for: -- [ ] No errors on startup -- [ ] No errors when fetching Discogs artwork -- [ ] Logs show: "Downloading Discogs artwork from: ..." -- [ ] Logs show: "Downloaded to: ..." -- [ ] Logs show: "Set album artwork successfully" - ---- - -## ๐Ÿ“Š What Changed - Quick Summary - -### ๐Ÿ” Security & Compliance -- โŒ Removed Last.fm API secret from binary -- โŒ Removed Spotify credentials from binary -- โŒ Removed Tidal credentials from binary -- โŒ Removed Discogs credentials from binary -- โœ… Cloudflare Workers proxy handles all credentials -- โœ… Flathub submission now possible - -### โœจ New Features -1. **Discogs Artwork Selector** - - Visual UI in album edit modal - - Multi-release support (4 images from top 2 releases) - - Carousel navigation (4 per page) - - Shows release title and year - - Smart search (uses catalog number if available) - -2. **Catalog Number Support** - - Reads from audio file tags (CATALOGNUMBER field) - - Stored in database - - Displayed in UI ("Cat# XXXXX") - - Prioritized in Discogs searches - -### ๐Ÿ”ง Technical -- Cloudflare Workers proxy deployed: `https://qbz-api-proxy.blitzkriegfc.workers.dev` -- Database migration adds `catalog_number` column (automatic, non-destructive) -- URI scheme `qbz://` registered for OAuth - -### ๐Ÿงน Post-Merge Cleanup Required -- โš ๏ธ **API Keys UI section in Settings must be removed** (non-functional) -- ๐Ÿ“„ Complete instructions in `POST-MERGE-CLEANUP.md` -- ๐Ÿ—‘๏ธ Removes ~715 lines of dead code -- โฑ๏ธ Estimated time: 30-45 minutes -- ๐ŸŽฏ **Do this within 1-2 days after merge** - ---- - -## โš ๏ธ Known Issues - -### 1. Discogs 429 Rate Limits -- **Severity:** Low -- **Frequency:** Occasional (when fetching detailed release info) -- **Impact:** User must retry after a few seconds -- **Status:** Acceptable (transient error) -- **User Experience:** Works on retry - -### 2. ๐Ÿšจ API Keys UI Still Visible (REQUIRES CLEANUP) -- **Severity:** Medium (confusing to users) -- **Why:** Legacy code left to avoid merge conflicts with audio config changes -- **Status:** **MUST be removed after merge** (see POST-MERGE-CLEANUP.md) -- **Impact:** Users see non-functional settings section -- **What it does:** Accepts input but credentials are completely ignored -- **Reality:** All requests go through Cloudflare Workers proxy -- **Action Required:** Follow `POST-MERGE-CLEANUP.md` within 1-2 days of merge -- **Estimated effort:** 30-45 minutes, removes ~715 lines of dead code - ---- - -## ๐Ÿ“„ Important Documentation Files - -### 1. CHANGELOG-api-keys-refactor.md -**Location:** `/home/blitzkriegfc/Personal/qbz/qbz-worktrees/feature-api-keys-refactor/CHANGELOG-api-keys-refactor.md` - -**Contains:** -- Complete list of all changes -- Security improvements details -- New features with user benefits -- Technical architecture changes -- Database migrations -- Testing notes -- Breaking changes (none for users) -- Deployment requirements - -**Read this:** Before announcing release to users - ---- - -### 2. MERGE-PREP.md -**Location:** `/home/blitzkriegfc/Personal/qbz/qbz-worktrees/feature-api-keys-refactor/MERGE-PREP.md` - -**Contains:** -- Pre-merge checklist (all โœ…) -- Merge strategy recommendations -- Post-merge testing plan -- Rollback procedures -- Release notes template -- Success criteria - -**Read this:** Before doing the merge (you're reading the HANDOFF now, so this is optional) - ---- - -### 3. POST-MERGE-CLEANUP.md -**Location:** `/home/blitzkriegfc/Personal/qbz/qbz-worktrees/feature-api-keys-refactor/POST-MERGE-CLEANUP.md` - -**Contains:** -- Step-by-step instructions to remove API Keys UI -- 11 files to modify with line numbers -- Before/after code examples -- Verification steps -- Git commit message template -- Estimated 30-45 minutes to complete - -**Read this:** After merge, when ready to clean up legacy code - ---- - -## ๐ŸŒ Cloudflare Workers Proxy - -**Status:** โœ… Deployed and operational -**URL:** `https://qbz-api-proxy.blitzkriegfc.workers.dev` -**Repository:** `/home/blitzkriegfc/Personal/qbz/qbz-api-proxy` - -**Endpoints Active:** -- Last.fm: `/lastfm/*` -- Spotify: `/spotify/token`, `/spotify/refresh` -- Tidal: `/tidal/token`, `/tidal/refresh` -- Discogs: `/discogs/search`, `/discogs/release/{id}`, `/discogs/image` - -**Test Proxy:** -```bash -curl https://qbz-api-proxy.blitzkriegfc.workers.dev -# Should return: {"service":"QBZ API Proxy","version":"1.0.0",...} -``` - ---- - -## ๐ŸŽ‰ Release Notes Template - -```markdown -## QBZ v1.2.0 - API Keys Refactor & Discogs Enhancement - -### ๐Ÿ” Security & Flathub Compliance -We've completely removed all embedded API credentials from the application. -All API requests now go through a secure Cloudflare Workers proxy, making -QBZ ready for Flathub submission and improving overall security. - -### โœจ New Features - -**Discogs Artwork Selector** -Browse and select album artwork directly from Discogs! The new visual selector -shows multiple release variants with metadata, making it easy to find the -perfect cover for your albums. - -- ๐ŸŽจ Visual artwork browser in album edit modal -- ๐Ÿ–ผ๏ธ See images from different release variants -- ๐Ÿ“„ Navigate with carousel (4 images per page) -- ๐Ÿ“‹ View release titles and years -- ๐Ÿท๏ธ Smart search using catalog numbers - -**Catalog Number Support** -For audiophile and collector libraries with professional tagging: -- Automatically reads catalog numbers from audio file tags -- Displays in album details (e.g., "Cat# MFSL 1-395") -- Prioritized in Discogs searches for precise results - -### ๐Ÿ”ง Technical Improvements -- Cloudflare Workers proxy for all external APIs -- Database migration adds catalog_number support -- Improved error handling and logging -- Rate limiting and validation server-side - -### ๐Ÿ› Known Issues -- Occasional 429 rate limit errors from Discogs (retry after a few seconds) - -### ๐Ÿ™ Thanks -Special thanks to all users who requested catalog number support! -``` - ---- - -## ๐Ÿ”„ Rollback Plan (If Needed) - -If critical issues found after merge: - -```bash -# Find the merge commit -git log --oneline -10 - -# Revert the merge (safest option) -git revert -m 1 -git push origin main - -# OR hard reset (use with EXTREME caution) -git reset --hard -git push --force origin main -``` - ---- - -## ๐Ÿ“ž Support Checklist - -If users report issues after release: - -1. **Proxy Down** - - Check: https://qbz-api-proxy.blitzkriegfc.workers.dev - - Verify Cloudflare Workers dashboard - - Check environment variables are set - -2. **Database Migration Failed** - - Check app logs for migration errors - - May need manual `ALTER TABLE local_tracks ADD COLUMN catalog_number TEXT;` - -3. **API Integration Broken** - - Check proxy is responding - - Verify User-Agent is "QBZ/1.0.0" - - Check rate limits not exceeded - -4. **UI Issues** - - Clear browser cache - - Check browser console for errors - - Verify Settings changes don't conflict with audio config - ---- - -## โœ… Final Verification Before Merge - -```bash -# You are here -pwd -# โ†’ /home/blitzkriegfc/Personal/qbz/qbz-worktrees/feature-api-keys-refactor - -# Branch is clean -git status -# โ†’ nothing to commit, working tree clean โœ… - -# All commits ready -git log --oneline origin/main..HEAD | wc -l -# โ†’ 13 โœ… - -# Proxy is up -curl -s https://qbz-api-proxy.blitzkriegfc.workers.dev | grep service -# โ†’ "service":"QBZ API Proxy" โœ… - -# Ready to merge! -``` - ---- - -## ๐ŸŽฏ Next Steps After Merge - -### Immediate (Required) -1. โœ… **Test all integrations** - Use checklist above (critical) - -### Short-term (Within 1-2 days) -2. ๐Ÿงน **REMOVE API Keys UI** - Follow `POST-MERGE-CLEANUP.md` - - **Why:** UI is non-functional (accepts input but ignores it) - - **Impact:** Removes ~715 lines of confusing dead code - - **Time:** 30-45 minutes - - **File:** See detailed instructions in `POST-MERGE-CLEANUP.md` - - **Note:** Left in this merge to avoid conflicts with audio config changes - -3. ๐Ÿ“ข **Announce release** - Use release notes template above - -### Future -4. ๐Ÿš€ **Submit to Flathub** - Now possible with no embedded secrets! - ---- - -## โš ๏ธ IMPORTANT: API Keys UI Cleanup - -**The API Keys section in Settings must be removed after merge.** - -**Current State:** -- โœ… UI exists and looks functional -- โŒ User can enter credentials -- โŒ Credentials are saved to state -- โŒ **BUT credentials are completely ignored** -- โŒ All requests go through proxy (user keys never used) - -**Why it's confusing to users:** -- Promises functionality that doesn't work -- Makes users think they need to configure something -- Wastes support time explaining "it doesn't actually do anything" - -**Action Required:** -```bash -# After merge and testing, run: -cd /home/blitzkriegfc/Personal/qbz/qbz-nix - -# Follow step-by-step instructions in: -cat POST-MERGE-CLEANUP.md - -# Creates one commit removing ~715 lines of dead code -# Estimated time: 30-45 minutes -``` - -**What gets removed:** -- `src-tauri/src/api_keys.rs` - Entire module (175 lines) -- Settings UI section - API Keys collapsible section (~350 lines) -- Command registrations - 10 Tauri commands -- Provider parameters - Unused `ApiKeysState` params in 11 files -- Translations - API Keys strings in en.json and es.json - -**Document:** `POST-MERGE-CLEANUP.md` has exact line numbers and before/after code. - ---- - -## ๐Ÿ“ Notes - -- Feature branch can be kept for reference or deleted -- Worktree can remain or be removed -- CHANGELOG files will be in main after merge -- No user data affected by this merge -- All changes are backward compatible -- **API Keys UI removal is documented but not automated** (manual cleanup required) - ---- - -**Prepared by:** Claude Sonnet 4.5 -**Handoff Date:** January 17, 2026 -**Status:** โœ… READY TO MERGE - -๐Ÿš€ **You're all set! Just run the merge commands above.** - -โš ๏ธ **Don't forget:** Remove API Keys UI after testing (see POST-MERGE-CLEANUP.md) diff --git a/MERGE-PREP.md b/MERGE-PREP.md deleted file mode 100644 index 063d5b6ca..000000000 --- a/MERGE-PREP.md +++ /dev/null @@ -1,254 +0,0 @@ -# Merge Preparation: feature/api-keys-refactor โ†’ main - -## ๐Ÿ“Š Branch Status - -**Branch:** `feature/api-keys-refactor` -**Base:** `main` -**Status:** โœ… Ready for merge (waiting for main to stabilize) -**Total Commits:** 11 -**Working Tree:** Clean - ---- - -## ๐Ÿ“‹ Commits Ready for Merge - -``` -81b65bd Add comprehensive CHANGELOG for API keys refactor -80ba2bf Fix Discogs artwork selection UI issues -732a489 Improve Discogs artwork selection with multi-release images -279d433 Add catalog number support for precise Discogs searches -2a5c181 Fix Discogs artwork selection to show unique images -dab5c28 Add Discogs artwork selection UI in album edit modal -dd70f0a Refactor Discogs integration to use Cloudflare Workers proxy -9099123 Refactor Spotify and Tidal playlist import to use proxy -179a6b7 Refactor Last.fm integration to use Cloudflare Workers proxy -3a49e3f Remove embedded Last.fm API secret for Flathub compliance -b7048fa feat: register qbz:// URI scheme for OAuth callbacks -``` - ---- - -## ๐ŸŒ Cloudflare Workers Proxy Status - -**Repository:** `qbz-api-proxy/` -**Branch:** `main` -**Status:** โœ… Deployed and operational -**URL:** `https://qbz-api-proxy.blitzkriegfc.workers.dev` - -**Recent Commits:** -``` -0346925 Add release details endpoint for Discogs -7b9137a Add Discogs API proxy handler -0a5a9d6 Enhance Last.fm proxy to return authorization URL with token -66f8f29 Fix environment variable names to match QBZ conventions -``` - ---- - -## โœ… Pre-Merge Checklist - -### Code Quality -- [x] All commits have descriptive messages -- [x] No debug code or console.logs (except intentional logging) -- [x] No TODO/FIXME comments without issues -- [x] Code follows project conventions -- [x] No hardcoded credentials or secrets - -### Testing -- [x] Last.fm authentication works through proxy -- [x] Last.fm scrobbling works through proxy -- [x] Spotify playlist import works through proxy -- [x] Tidal playlist import works through proxy -- [x] Discogs artwork search works (with catalog numbers) -- [x] Discogs multi-release image fetching works -- [x] Carousel navigation works (4 images per page) -- [x] Artwork download and save works -- [x] Database migration runs successfully -- [x] Catalog number extraction from audio files works - -### Documentation -- [x] CHANGELOG created and comprehensive -- [x] All new features documented -- [x] Breaking changes documented (none for users) -- [x] Deployment requirements documented -- [x] Known issues documented (429 rate limits) - -### Dependencies -- [x] Cloudflare Workers proxy deployed -- [x] All proxy endpoints tested and working -- [x] Rate limiting configured -- [x] Environment variables set correctly - ---- - -## ๐Ÿšจ Known Issues - -### 1. Discogs 429 Rate Limiting -- **Severity:** Low -- **Frequency:** Occasional -- **Impact:** User must retry after a few seconds -- **Status:** Acceptable (transient error) -- **Future Fix:** Could add request caching - ---- - -## ๐Ÿ”„ Merge Strategy - -### Recommended Approach -```bash -# Ensure you're on main branch -git checkout main - -# Pull latest changes -git pull origin main - -# Merge feature branch (no fast-forward for clear history) -git merge --no-ff feature/api-keys-refactor - -# Resolve any conflicts (unlikely, but check audio config changes) - -# Push to origin -git push origin main - -# Tag the release -git tag -a v1.2.0 -m "API Keys Refactor - Flathub Compliance" -git push origin v1.2.0 -``` - -### Conflict Resolution -If conflicts arise (likely in audio config files): -1. Carefully review both changes -2. Prefer main's audio configuration changes -3. Keep feature branch's API refactoring -4. Test thoroughly after resolution - ---- - -## ๐Ÿงช Post-Merge Testing Plan - -### Critical Tests -1. **Launch app** - Should run without errors -2. **Database migration** - Check for catalog_number column -3. **Last.fm login** - Authenticate new user -4. **Last.fm scrobble** - Play track and verify scrobble -5. **Spotify import** - Import a playlist -6. **Tidal import** - Import a playlist -7. **Discogs search** - Search for artwork -8. **Discogs catalog** - Test with album that has catalog number -9. **Artwork save** - Select and save Discogs artwork -10. **Carousel** - Navigate through 8+ artwork options - -### Regression Tests -- Audio playback still works -- Playlists still load -- Album view still works -- Artist view still works -- Search still works -- Settings still work - ---- - -## ๐Ÿ“ฆ Release Notes Template - -```markdown -## v1.2.0 - API Keys Refactor & Discogs Enhancement - -### ๐Ÿ” Security & Compliance -- Removed all embedded API credentials from binary -- Flathub compliance achieved -- Server-side credential management via Cloudflare Workers - -### โœจ New Features -- **Discogs Artwork Selector**: Visual UI for selecting album artwork - - Browse multiple release variants - - See release titles and years - - Carousel navigation for 4+ options -- **Catalog Number Support**: Professional metadata for audiophile libraries - - Automatic extraction from audio tags - - Prioritized in Discogs searches - - Displayed in album details - -### ๐Ÿ”ง Technical Improvements -- Cloudflare Workers proxy for all external APIs -- Multi-release image fetching from Discogs -- Database migration for catalog numbers -- Improved error handling and logging - -### ๐Ÿ› Known Issues -- Occasional 429 rate limit errors from Discogs (retry after a few seconds) - -### ๐Ÿ™ Thanks -Special thanks to all audiophile users who requested catalog number support! -``` - ---- - -## ๐Ÿ“ž Support Plan - -### If Issues Arise Post-Merge - -1. **Proxy Down**: Check Cloudflare Workers dashboard -2. **Database Migration Fails**: Check logs, may need manual ALTER TABLE -3. **API Errors**: Verify proxy environment variables are set -4. **UI Broken**: Clear browser cache, check console for errors - -### Rollback Plan -```bash -# If critical issues found -git revert -git push origin main - -# Or hard reset (use with caution) -git reset --hard -git push --force origin main -``` - ---- - -## ๐Ÿ“… Timeline - -- **Branch Created**: December 2024 -- **Development**: January 2025 -- **Ready for Merge**: January 17, 2026 -- **Waiting On**: Main branch audio configuration stabilization -- **Target Merge**: TBD (when main is stable) - ---- - -## ๐ŸŽฏ Success Criteria - -Merge is successful when: -- [x] All commits merged cleanly -- [ ] App launches without errors -- [ ] Database migration runs -- [ ] All 4 API integrations work (Last.fm, Spotify, Tidal, Discogs) -- [ ] No user-facing regressions -- [ ] Flathub submission possible (no embedded secrets) - ---- - -## ๐Ÿ“ Notes for Reviewer - -### Why This Matters -This refactor is **critical** for: -1. **Flathub submission** - Can't have embedded secrets -2. **Security** - API keys were visible in binary -3. **Flexibility** - Can update credentials without app release -4. **Rate limiting** - Server-side control - -### What Changed -- **User Experience**: Transparent (no changes except new Discogs UI) -- **Architecture**: API credentials now server-side -- **Code**: Simplified clients, removed signature logic -- **Database**: Added catalog_number column - -### What Didn't Change -- All existing features work exactly the same -- User data is unaffected -- Performance is similar (proxy adds ~50-100ms latency) - ---- - -**Prepared by:** Claude Sonnet 4.5 -**Date:** January 17, 2026 -**Branch Status:** Ready for merge โœ… diff --git a/POST-MERGE-CLEANUP.md b/POST-MERGE-CLEANUP.md deleted file mode 100644 index 7db9cbc24..000000000 --- a/POST-MERGE-CLEANUP.md +++ /dev/null @@ -1,511 +0,0 @@ -# Post-Merge Cleanup: Remove API Keys UI - -**Context:** The API Keys section in Settings UI is non-functional legacy code. All API requests now go through Cloudflare Workers proxy. User-provided credentials are accepted but ignored. - -**When to do this:** After merging to main and audio configuration changes are stable. - ---- - -## Files to Delete Completely - -### 1. Delete API Keys Store Module -```bash -rm src-tauri/src/api_keys.rs -``` - -**Why:** Entire module is unused. User credentials are collected but never used. - ---- - -## Files to Modify - -### 2. src-tauri/src/lib.rs - -**Remove these imports:** -```rust -// Line ~8 -mod api_keys; -``` - -**Remove state initialization:** -```rust -// Around line 162 -let api_keys_state = api_keys::create_api_keys_state(); -``` - -**Remove from .manage() call:** -```rust -// Around line 400-420 in the .manage() chain -.manage(api_keys_state) -``` - -**Remove command registrations:** -```rust -// Around line 530-539 in invoke_handler![...] -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, -``` - ---- - -### 3. src-tauri/src/commands/playlist_import.rs - -**Remove import:** -```rust -// Line 5 -use crate::api_keys::ApiKeysState; -``` - -**Remove state parameter from commands:** - -**In `playlist_import_preview()` (line 13-42):** - -Before: -```rust -pub async fn playlist_import_preview( - url: String, - api_keys: State<'_, ApiKeysState>, -) -> Result { - // Get user-provided credentials from state - let keys = api_keys.lock().await; - let spotify_creds = if keys.spotify.is_set() { - Some(ProviderCredentials { ... }) - } else { - None - }; - let tidal_creds = if keys.tidal.is_set() { - Some(ProviderCredentials { ... }) - } else { - None - }; - drop(keys); - - preview_public_playlist(&url, spotify_creds, tidal_creds) - .await - .map_err(|e| e.to_string()) -} -``` - -After: -```rust -pub async fn playlist_import_preview( - url: String, -) -> Result { - preview_public_playlist(&url, None, None) - .await - .map_err(|e| e.to_string()) -} -``` - -**In `playlist_import_execute()` (line 45-90):** - -Remove the same pattern: -- Remove `api_keys: State<'_, ApiKeysState>` parameter -- Remove credential extraction logic (lines 54-68) -- Pass `None, None` instead of `spotify_creds, tidal_creds` - ---- - -### 4. src-tauri/src/library/commands.rs - -**Remove import:** -```rust -// Line 10 -use crate::api_keys::ApiKeysState; -``` - -**Remove state parameter from these commands:** - -1. **`library_fetch_missing_artwork`** (line ~1168) -2. **`discogs_search_artist`** (line ~1187) -3. **`library_import_musicbrainz_tags`** (line ~1240) -4. **`library_set_artist_image`** (line ~1312) - -**Pattern to follow for each:** - -Before: -```rust -pub async fn command_name( - // ... other parameters - api_keys: State<'_, ApiKeysState>, -) -> Result { - // Get Discogs client - let keys = api_keys.lock().await; - let discogs_client = if keys.discogs.is_set() { - DiscogsClient::with_user_credentials( - keys.discogs.client_id.clone(), - keys.discogs.client_secret.clone(), - ) - } else { - DiscogsClient::new() - }; - drop(keys); - - // ... rest of function -} -``` - -After: -```rust -pub async fn command_name( - // ... other parameters -) -> Result { - // Get Discogs client (proxy handles credentials) - let discogs_client = DiscogsClient::new(); - - // ... rest of function -} -``` - -**Specific locations:** -- Line ~1175-1182: Remove credential logic in `library_fetch_missing_artwork` -- Line ~1193-1200: Remove credential logic in `discogs_search_artist` -- Line ~1256-1263: Remove credential logic in `library_import_musicbrainz_tags` -- Line ~1320-1327: Remove credential logic in `library_set_artist_image` - ---- - -### 5. src-tauri/src/playlist_import/providers/mod.rs - -**Simplify `fetch_playlist` function:** - -Before (line ~43-61): -```rust -pub async fn fetch_playlist( - kind: ProviderKind, - spotify_creds: Option, - tidal_creds: Option, -) -> Result { - match kind { - ProviderKind::Spotify { playlist_id } => { - spotify::fetch_playlist(&playlist_id, spotify_creds).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 - } - ProviderKind::Deezer { playlist_id } => deezer::fetch_playlist(&playlist_id).await, - } -} -``` - -After: -```rust -pub async fn fetch_playlist( - kind: ProviderKind, -) -> Result { - match kind { - ProviderKind::Spotify { playlist_id } => { - 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).await - } - ProviderKind::Deezer { playlist_id } => deezer::fetch_playlist(&playlist_id).await, - } -} -``` - ---- - -### 6. src-tauri/src/playlist_import/providers/spotify.rs - -**Simplify `fetch_playlist` signature:** - -Before (line ~39-50): -```rust -pub async fn fetch_playlist( - playlist_id: &str, - user_creds: Option, -) -> Result { - if let Ok(token) = get_app_token(user_creds).await { - if let Ok(playlist) = fetch_playlist_with_token(playlist_id, &token).await { - return Ok(playlist); - } - } - fetch_playlist_from_embed(playlist_id).await -} -``` - -After: -```rust -pub async fn fetch_playlist( - playlist_id: &str, -) -> Result { - if let Ok(token) = get_app_token().await { - if let Ok(playlist) = fetch_playlist_with_token(playlist_id, &token).await { - return Ok(playlist); - } - } - fetch_playlist_from_embed(playlist_id).await -} -``` - -**Simplify `get_app_token` signature:** - -Before (line ~252-280): -```rust -async fn get_app_token(_user_creds: Option) -> Result { - // Proxy handles credentials - user_creds ignored (compatibility) - // ... -} -``` - -After: -```rust -async fn get_app_token() -> Result { - // Proxy handles credentials - // ... -} -``` - ---- - -### 7. src-tauri/src/playlist_import/providers/tidal.rs - -**Same changes as Spotify:** - -1. Remove `user_creds` parameter from `fetch_playlist()` signature -2. Remove `_user_creds` parameter from `get_app_token()` signature -3. Update function calls to not pass credentials - ---- - -### 8. src-tauri/src/discogs/mod.rs - -**Simplify `DiscogsClient`:** - -**Remove this method entirely (line ~51-69):** -```rust -pub fn with_user_credentials( - _user_key: Option, - _user_secret: Option, -) -> Self { - // ...entire method... -} -``` - -**Update documentation comment (line ~46):** - -Before: -```rust -/// 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(...) -> Self { ... } -``` - -After: -```rust -/// Create a new Discogs client (proxy handles credentials) -pub fn new() -> Self { - let mut headers = reqwest::header::HeaderMap::new(); - headers.insert( - reqwest::header::USER_AGENT, - reqwest::header::HeaderValue::from_static("QBZ/1.0.0"), - ); - - let client = Client::builder() - .timeout(Duration::from_secs(10)) - .default_headers(headers) - .build() - .expect("Failed to create HTTP client"); - - Self { client } -} -``` - ---- - -### 9. src/lib/components/views/SettingsView.svelte - -**Remove from sections array:** -```svelte -// Around line 121 -{ id: 'api-keys', label: 'API Keys' }, -``` - -**Remove state variables (line 262-271):** -```svelte -// API Keys section state -let apiKeysExpanded = $state(false); -let spotifyClientId = $state(''); -let spotifyClientSecret = $state(''); -let tidalClientId = $state(''); -let tidalClientSecret = $state(''); -let discogsConsumerKey = $state(''); -let discogsConsumerSecret = $state(''); -let embeddedStatus = $state({ spotify: false, tidal: false, discogs: false, lastfm: false }); -let apiKeysSaving = $state(false); -``` - -**Remove helper functions (find and delete):** -```svelte -function hasAnyUserCredentials() { ... } -async function loadApiKeysStatus() { ... } -async function saveSpotifyCredentials() { ... } -async function clearSpotifyCredentials() { ... } -async function saveTidalCredentials() { ... } -async function clearTidalCredentials() { ... } -async function saveDiscogsCredentials() { ... } -async function clearDiscogsCredentials() { ... } -``` - -**Remove from onMount:** -```svelte -// Around line 300-320 in onMount() -// Load embedded credentials status -loadApiKeysStatus(); -``` - -**Remove entire section element (line ~1494-1650):** -```svelte - -
- -
-``` - -**Remove CSS styles (line ~2112-2300+):** -```css -/* API Keys section styles */ -.api-keys-section { ... } -.section-title-btn { ... } -.keys-badge { ... } -.api-keys-info { ... } -.api-key-group { ... } -.api-key-header { ... } -.api-key-title { ... } -.status-badge { ... } -.api-key-desc { ... } -.api-key-fields { ... } -.input-group { ... } -.input-group label { ... } -.input-group input { ... } -.api-key-actions { ... } -``` - ---- - -### 10. src/lib/i18n/locales/en.json - -**Remove API Keys translations:** -```json -// Find and remove entire "apiKeys" object under "settings.integrations" -"apiKeys": "API Keys", -"apiKeysDesc": "Optional custom API credentials...", -// ... etc -``` - ---- - -### 11. src/lib/i18n/locales/es.json - -**Remove API Keys translations:** -```json -// Find and remove entire "apiKeys" object under "settings.integrations" -"apiKeys": "Claves API", -"apiKeysDesc": "Credenciales API personalizadas opcionales...", -// ... etc -``` - ---- - -## Verification Steps - -After cleanup: - -1. **Build check:** - ```bash - cargo check - ``` - -2. **Search for remnants:** - ```bash - # Should return nothing - grep -r "api_keys" src-tauri/src/ --include="*.rs" - grep -r "ApiKeysState" src-tauri/src/ --include="*.rs" - grep -r "apiKeys" src/lib/ --include="*.svelte" --include="*.ts" - ``` - -3. **Test that app builds:** - ```bash - npm run tauri build - ``` - -4. **Verify Settings UI:** - - Open Settings - - "API Keys" section should be gone - - Audio settings should be intact - - No console errors - ---- - -## Size Reduction Estimate - -**Code removed:** -- `api_keys.rs`: ~175 lines -- Command registrations: ~10 lines -- State management: ~5 lines -- Settings UI: ~150 lines HTML + ~200 lines CSS -- Helper functions: ~120 lines -- Translations: ~40 lines -- Import cleanup: ~15 lines - -**Total: ~715 lines of unused code removed** - ---- - -## Git Commit Message Template - -``` -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 - -This cleanup reduces codebase by ~715 lines and removes -confusing UI that promised functionality that didn't work. - -See: POST-MERGE-CLEANUP.md for complete removal instructions -``` - ---- - -## Notes - -- **Do this AFTER** main branch audio configuration changes are merged -- **Do this in a separate commit** for clean history -- **Test thoroughly** after removal -- If proxy goes down in future, this can be reverted and implemented properly -- Keep this file in repo as documentation of what was removed and why - ---- - -**Created:** January 17, 2026 -**Status:** Pending execution post-merge -**Estimated time:** 30-45 minutes diff --git a/src-tauri/src/api_keys.rs b/src-tauri/src/api_keys.rs deleted file mode 100644 index d77ec69b6..000000000 --- a/src-tauri/src/api_keys.rs +++ /dev/null @@ -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, - pub client_secret: Option, -} - -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>; - -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 { - 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 { - 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 { - 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, -} diff --git a/src-tauri/src/commands/playlist_import.rs b/src-tauri/src/commands/playlist_import.rs index 68f4db7f0..1b8ff1ade 100644 --- a/src-tauri/src/commands/playlist_import.rs +++ b/src-tauri/src/commands/playlist_import.rs @@ -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 { 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, is_public: Option, state: State<'_, AppState>, - api_keys: State<'_, ApiKeysState>, ) -> Result { 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()) diff --git a/src-tauri/src/discogs/mod.rs b/src-tauri/src/discogs/mod.rs index 98339a05c..bf64427c6 100644 --- a/src-tauri/src/discogs/mod.rs +++ b/src-tauri/src/discogs/mod.rs @@ -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, - _user_secret: Option, - ) -> Self { let mut headers = reqwest::header::HeaderMap::new(); headers.insert( reqwest::header::USER_AGENT, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ed4755b77..d8812b2cd 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -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, diff --git a/src-tauri/src/library/commands.rs b/src-tauri/src/library/commands.rs index 96b970fb1..0ff9f9198 100644 --- a/src-tauri/src/library/commands.rs +++ b/src-tauri/src/library/commands.rs @@ -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 { - // 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 { + // 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 { 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, 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 { 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 } diff --git a/src-tauri/src/playlist_import/providers/mod.rs b/src-tauri/src/playlist_import/providers/mod.rs index 29ce197d6..31f27bace 100644 --- a/src-tauri/src/playlist_import/providers/mod.rs +++ b/src-tauri/src/playlist_import/providers/mod.rs @@ -40,21 +40,19 @@ pub fn detect_provider(url: &str) -> Result { 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, - tidal_creds: Option, ) -> Result { 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, } diff --git a/src-tauri/src/playlist_import/providers/spotify.rs b/src-tauri/src/playlist_import/providers/spotify.rs index 6bd8ee5ae..84e4e7e83 100644 --- a/src-tauri/src/playlist_import/providers/spotify.rs +++ b/src-tauri/src/playlist_import/providers/spotify.rs @@ -38,9 +38,8 @@ pub fn parse_playlist_id(url: &str) -> Option { pub async fn fetch_playlist( playlist_id: &str, - user_creds: Option, ) -> Result { - 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) -> Result { - // Proxy handles credentials - user_creds ignored (compatibility) +async fn get_app_token() -> Result { + // Proxy handles credentials let url = format!("{}/token", SPOTIFY_PROXY_URL); let response: Value = reqwest::Client::builder() diff --git a/src-tauri/src/playlist_import/providers/tidal.rs b/src-tauri/src/playlist_import/providers/tidal.rs index 7be61cdea..1c3b4cec0 100644 --- a/src-tauri/src/playlist_import/providers/tidal.rs +++ b/src-tauri/src/playlist_import/providers/tidal.rs @@ -39,9 +39,8 @@ pub fn parse_playlist_id(url: &str) -> Option { pub async fn fetch_playlist( playlist_id: &str, - user_creds: Option, ) -> Result { - 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 { } } -async fn get_app_token(_user_creds: Option) -> Result { - // Proxy handles credentials - user_creds ignored (compatibility) +async fn get_app_token() -> Result { + // Proxy handles credentials let url = format!("{}/token", TIDAL_PROXY_URL); let response: Value = reqwest::Client::builder() diff --git a/src/lib/components/views/SettingsView.svelte b/src/lib/components/views/SettingsView.svelte index a180132a0..ea43a8630 100644 --- a/src/lib/components/views/SettingsView.svelte +++ b/src/lib/components/views/SettingsView.svelte @@ -104,7 +104,6 @@ let integrationsSection: HTMLElement; let storageSection: HTMLElement; let lyricsSection: HTMLElement; - let apiKeysSection: HTMLElement; let activeSection = $state('audio'); // Navigation section definitions (static, refs resolved at click/scroll time) @@ -118,7 +117,6 @@ { id: 'integrations', label: 'Integrations' }, { id: 'storage', label: 'Storage' }, { id: 'lyrics', label: 'Lyrics' }, - { id: 'api-keys', label: 'API Keys' }, ]; // Get section element by id (resolved at call time, not definition time) @@ -133,7 +131,6 @@ case 'integrations': return integrationsSection; case 'storage': return storageSection; case 'lyrics': return lyricsSection; - case 'api-keys': return apiKeysSection; default: return undefined; } } @@ -267,17 +264,6 @@ let showLastfmConfig = $state(false); let hasEmbeddedCredentials = $state(false); - // API Keys section state - let apiKeysExpanded = $state(false); - let spotifyClientId = $state(''); - let spotifyClientSecret = $state(''); - let tidalClientId = $state(''); - let tidalClientSecret = $state(''); - let discogsConsumerKey = $state(''); - let discogsConsumerSecret = $state(''); - let embeddedStatus = $state({ spotify: false, tidal: false, discogs: false, lastfm: false }); - let apiKeysSaving = $state(false); - // Load saved settings on mount onMount(() => { // Load theme @@ -339,9 +325,6 @@ // Load Last.fm state loadLastfmState(); - // Load API keys state - loadApiKeysState(); - // Load notification preferences loadToastsPreference(); toastsEnabled = getToastsEnabled(); @@ -513,76 +496,6 @@ localStorage.setItem('qbz-lastfm-scrobbling', String(enabled)); } - // API Keys management - async function loadApiKeysState() { - try { - // Get embedded credentials status from backend - embeddedStatus = await invoke('get_embedded_credentials_status'); - - // Load user-provided credentials from localStorage - spotifyClientId = localStorage.getItem('qbz-spotify-client-id') || ''; - spotifyClientSecret = localStorage.getItem('qbz-spotify-client-secret') || ''; - tidalClientId = localStorage.getItem('qbz-tidal-client-id') || ''; - tidalClientSecret = localStorage.getItem('qbz-tidal-client-secret') || ''; - discogsConsumerKey = localStorage.getItem('qbz-discogs-consumer-key') || ''; - discogsConsumerSecret = localStorage.getItem('qbz-discogs-consumer-secret') || ''; - - // Restore credentials to backend state if we have saved ones - if (spotifyClientId && spotifyClientSecret) { - await invoke('set_spotify_credentials', { - clientId: spotifyClientId, - clientSecret: spotifyClientSecret - }); - } - if (tidalClientId && tidalClientSecret) { - await invoke('set_tidal_credentials', { - clientId: tidalClientId, - clientSecret: tidalClientSecret - }); - } - if (discogsConsumerKey && discogsConsumerSecret) { - await invoke('set_discogs_credentials', { - consumerKey: discogsConsumerKey, - consumerSecret: discogsConsumerSecret - }); - } - } catch (err) { - console.error('Failed to load API keys state:', err); - } - } - - async function handleSaveSpotifyCredentials() { - if (!spotifyClientId || !spotifyClientSecret) return; - apiKeysSaving = true; - try { - await invoke('set_spotify_credentials', { - clientId: spotifyClientId, - clientSecret: spotifyClientSecret - }); - localStorage.setItem('qbz-spotify-client-id', spotifyClientId); - localStorage.setItem('qbz-spotify-client-secret', spotifyClientSecret); - } catch (err) { - console.error('Failed to save Spotify credentials:', err); - } finally { - apiKeysSaving = false; - } - } - - async function handleClearSpotifyCredentials() { - apiKeysSaving = true; - try { - await invoke('clear_spotify_credentials'); - localStorage.removeItem('qbz-spotify-client-id'); - localStorage.removeItem('qbz-spotify-client-secret'); - spotifyClientId = ''; - spotifyClientSecret = ''; - } catch (err) { - console.error('Failed to clear Spotify credentials:', err); - } finally { - apiKeysSaving = false; - } - } - async function handleShowDownloadsChange(enabled: boolean) { try { await invoke('set_show_downloads_in_library', { show: enabled }); @@ -592,79 +505,6 @@ } } - async function handleSaveTidalCredentials() { - if (!tidalClientId || !tidalClientSecret) return; - apiKeysSaving = true; - try { - await invoke('set_tidal_credentials', { - clientId: tidalClientId, - clientSecret: tidalClientSecret - }); - localStorage.setItem('qbz-tidal-client-id', tidalClientId); - localStorage.setItem('qbz-tidal-client-secret', tidalClientSecret); - } catch (err) { - console.error('Failed to save Tidal credentials:', err); - } finally { - apiKeysSaving = false; - } - } - - async function handleClearTidalCredentials() { - apiKeysSaving = true; - try { - await invoke('clear_tidal_credentials'); - localStorage.removeItem('qbz-tidal-client-id'); - localStorage.removeItem('qbz-tidal-client-secret'); - tidalClientId = ''; - tidalClientSecret = ''; - } catch (err) { - console.error('Failed to clear Tidal credentials:', err); - } finally { - apiKeysSaving = false; - } - } - - async function handleSaveDiscogsCredentials() { - if (!discogsConsumerKey || !discogsConsumerSecret) return; - apiKeysSaving = true; - try { - await invoke('set_discogs_credentials', { - consumerKey: discogsConsumerKey, - consumerSecret: discogsConsumerSecret - }); - localStorage.setItem('qbz-discogs-consumer-key', discogsConsumerKey); - localStorage.setItem('qbz-discogs-consumer-secret', discogsConsumerSecret); - } catch (err) { - console.error('Failed to save Discogs credentials:', err); - } finally { - apiKeysSaving = false; - } - } - - async function handleClearDiscogsCredentials() { - apiKeysSaving = true; - try { - await invoke('clear_discogs_credentials'); - localStorage.removeItem('qbz-discogs-consumer-key'); - localStorage.removeItem('qbz-discogs-consumer-secret'); - discogsConsumerKey = ''; - discogsConsumerSecret = ''; - } catch (err) { - console.error('Failed to clear Discogs credentials:', err); - } finally { - apiKeysSaving = false; - } - } - - // Check if any user credentials are configured - function hasAnyUserCredentials(): boolean { - return !!( - (spotifyClientId && spotifyClientSecret) || - (tidalClientId && tidalClientSecret) || - (discogsConsumerKey && discogsConsumerSecret) - ); - } - function handleQualityChange(quality: string) { streamingQuality = quality; localStorage.setItem('qbz-streaming-quality', quality); @@ -1752,177 +1592,6 @@ - - -
- - - {#if apiKeysExpanded} -
-

- You don't need to configure anything here unless playlist import or album artwork features stop working. - If you experience issues, you can provide your own API credentials to restore functionality. -

-
- - -
-
- Spotify - {#if embeddedStatus.spotify} - Active - {:else if spotifyClientId && spotifyClientSecret} - Custom - {:else} - Not configured - {/if} -
-

- Used for importing Spotify playlists. - Create an app to get credentials. -

-
- - -
- - {#if spotifyClientId || spotifyClientSecret} - - {/if} -
-
-
- - -
-
- Tidal - {#if embeddedStatus.tidal} - Active - {:else if tidalClientId && tidalClientSecret} - Custom - {:else} - Not configured - {/if} -
-

- Used for importing Tidal playlists. - Register to get API credentials. -

-
- - -
- - {#if tidalClientId || tidalClientSecret} - - {/if} -
-
-
- - -
-
- Discogs - {#if embeddedStatus.discogs} - Active - {:else if discogsConsumerKey && discogsConsumerSecret} - Custom - {:else} - Not configured - {/if} -
-

- Used for fetching album artwork for local library. - Create an application to get credentials. -

-
- - -
- - {#if discogsConsumerKey || discogsConsumerSecret} - - {/if} -
-
-
- {/if} -
{#if isCheckingNetwork} @@ -2383,188 +2052,6 @@ cursor: not-allowed; } - /* API Keys section styles */ - .api-keys-section { - padding-bottom: 16px; - } - - .section-title-btn { - display: flex; - align-items: center; - gap: 8px; - width: 100%; - background: none; - border: none; - font-size: 16px; - font-weight: 600; - color: var(--text-primary); - cursor: pointer; - padding: 0; - text-align: left; - } - - .section-title-btn:hover { - color: var(--accent-primary); - } - - .keys-badge { - font-size: 10px; - font-weight: 500; - padding: 2px 6px; - border-radius: 4px; - background-color: var(--accent-primary); - color: white; - margin-left: auto; - } - - .api-keys-info { - margin: 16px 0; - padding: 12px; - background-color: var(--bg-tertiary); - border-radius: 8px; - } - - .api-keys-info p { - font-size: 13px; - color: var(--text-muted); - margin: 0; - line-height: 1.5; - } - - .api-key-group { - padding: 16px 0; - border-bottom: 1px solid var(--bg-tertiary); - } - - .api-key-group.last { - border-bottom: none; - padding-bottom: 0; - } - - .api-key-header { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 4px; - } - - .api-key-title { - font-size: 14px; - font-weight: 600; - color: var(--text-primary); - } - - .status-badge { - font-size: 10px; - font-weight: 500; - padding: 2px 6px; - border-radius: 4px; - } - - .status-badge.active { - background-color: rgba(29, 185, 84, 0.2); - color: #1db954; - } - - .status-badge.custom { - background-color: rgba(99, 102, 241, 0.2); - color: #6366f1; - } - - .status-badge.inactive { - background-color: var(--bg-tertiary); - color: var(--text-muted); - } - - .api-key-desc { - font-size: 12px; - color: var(--text-muted); - margin: 0 0 12px 0; - } - - .api-key-desc a { - color: var(--accent-primary); - text-decoration: none; - } - - .api-key-desc a:hover { - text-decoration: underline; - } - - .api-key-fields { - display: flex; - gap: 8px; - flex-wrap: wrap; - } - - .api-key-fields input { - flex: 1; - min-width: 150px; - padding: 8px 12px; - background-color: var(--bg-tertiary); - border: 1px solid var(--border-subtle); - border-radius: 6px; - color: var(--text-primary); - font-size: 13px; - } - - .api-key-fields input:focus { - outline: none; - border-color: var(--accent-primary); - } - - .api-key-fields input::placeholder { - color: var(--text-disabled); - } - - .api-key-actions { - display: flex; - gap: 8px; - } - - .save-btn { - padding: 8px 16px; - border-radius: 6px; - font-size: 13px; - font-weight: 500; - cursor: pointer; - transition: all 150ms ease; - background-color: var(--accent-primary); - color: white; - border: none; - } - - .save-btn:hover:not(:disabled) { - background-color: var(--accent-hover); - } - - .save-btn:disabled { - opacity: 0.5; - cursor: not-allowed; - } - - .clear-btn-small { - padding: 8px 12px; - border-radius: 6px; - font-size: 13px; - font-weight: 500; - cursor: pointer; - transition: all 150ms ease; - background: none; - border: 1px solid var(--text-muted); - color: var(--text-muted); - } - - .clear-btn-small:hover:not(:disabled) { - border-color: #ff6b6b; - color: #ff6b6b; - } - - .clear-btn-small:disabled { - opacity: 0.5; - cursor: not-allowed; - } - /* Offline status indicator */ .status-indicator { font-weight: 500; diff --git a/src/lib/i18n/locales/en.json b/src/lib/i18n/locales/en.json index a6431e310..de35c2a82 100644 --- a/src/lib/i18n/locales/en.json +++ b/src/lib/i18n/locales/en.json @@ -345,12 +345,7 @@ "connect": "Connect", "disconnect": "Disconnect", "scrobbling": "Scrobbling", - "configureApi": "Configure API", - "apiKeys": "API Keys", - "clientId": "Client ID", - "clientSecret": "Client Secret", - "consumerKey": "Consumer Key", - "consumerSecret": "Consumer Secret" + "configureApi": "Configure API" }, "storage": { "title": "Storage", diff --git a/src/lib/i18n/locales/es.json b/src/lib/i18n/locales/es.json index 0f813bd8c..d89ed262c 100644 --- a/src/lib/i18n/locales/es.json +++ b/src/lib/i18n/locales/es.json @@ -345,12 +345,7 @@ "connect": "Conectar", "disconnect": "Desconectar", "scrobbling": "Scrobbling", - "configureApi": "Configurar API", - "apiKeys": "Claves API", - "clientId": "Client ID", - "clientSecret": "Client Secret", - "consumerKey": "Consumer Key", - "consumerSecret": "Consumer Secret" + "configureApi": "Configurar API" }, "storage": { "title": "Almacenamiento",