Commit Graph

32 Commits

Author SHA1 Message Date
5680b9c7c9 Fix GetPlaylists to use pre-built cache with manual mappings for accurate counts 2026-02-04 18:49:12 -05:00
1d31784ff8 Fix manual mapping: add immediate playlist rebuild and manual mapping priority in cache builder 2026-02-04 18:38:25 -05:00
0937fcf163 fix: accurate playlist counting and three-color progress bars
- Fix playlist counting logic to use fuzzy matching (same as track view)
- Count local tracks by matching Jellyfin tracks to Spotify tracks
- Count external matched tracks from cache
- Count missing tracks (not found locally or externally)
- Progress bars now show three colors:
  * Green: Local tracks in Jellyfin
  * Orange: External matched tracks (SquidWTF/Deezer/Qobuz)
  * Grey: Missing tracks (not found anywhere)
- Add 'Missing' badge to tracks that couldn't be found
- Missing tracks can still be manually mapped
- Fixes incorrect counts like '28 matched • 1 missing' showing 29 external tracks

All 225 tests pass.
2026-02-04 17:49:10 -05:00
506f39d606 feat: instant UI update after manual track mapping
- Backend now returns mapped track details after saving
- Frontend updates track in-place without requiring page refresh
- Track status changes from External to Local immediately
- Map button is removed after successful mapping
- Playlist counts refresh in background
- Improved UX: no more 'refresh the playlist' message

All 225 tests pass.
2026-02-04 17:44:57 -05:00
7bb7c6a40e fix: manual mapping UI and [S] tag consistency
- Fix manual mapping track selection visual feedback (use accent color + background)
- Clear all playlist caches after manual mapping (matched, ordered, items)
- Strip [S] suffix from titles/artists/albums when searching for lyrics
- Add [S] suffix to artist and album names when song has [S] for consistency
- Ensures external tracks are clearly marked across all metadata fields

All 225 tests pass.
2026-02-04 17:31:56 -05:00
24c6219189 Fix external track counting by checking matched tracks cache
- External tracks are injected on-the-fly, not stored in Jellyfin DB
- Check spotify:matched:ordered cache to get accurate external count
- Calculate external tracks as: total matched - local tracks
- This will properly show the two-color progress bar (green local + orange external)
- All 225 tests passing
2026-02-04 16:54:56 -05:00
bf02dc5a57 Add MusicBrainz genre enrichment and improve track counting
- Fixed external track detection (check for provider prefix in ID)
- Added genre support to MusicBrainz service (inc=genres+tags)
- Created GenreEnrichmentService for async genre lookup with caching
- Show provider name and search query for external tracks in admin UI
- Display search query that will be used for external track streaming
- Aggregate playlist genres from track genres
- All 225 tests passing
2026-02-04 16:43:17 -05:00
7938871556 Release 1.0.0 - Production ready
- Fixed AdminController export/import .env endpoints (moved from ConfigUpdateRequest class)
- Added ArtistId and AlbumId to integration test fixtures
- All 225 tests passing
- Version set to 1.0.0 (semantic versioning)
- MusicBrainz service ready for future ISRC-based matching (1.1.0)
- Import/export handles full .env configuration with timestamped backups
2026-02-04 16:33:58 -05:00
aadda9b873 Fix apostrophe matching, add URL input for track mapping, improve search caching
Some checks failed
CI / build-and-test (push) Has been cancelled
- Enhanced FuzzyMatcher to normalize apostrophes (', ', ', etc) for better matching
- Added Redis-only caching for search results (15 min TTL)
- Added pattern-based cache deletion for search and image keys
- Added URL input field in Map to Local modal to paste Jellyfin track URLs
- Added /api/admin/jellyfin/track/{id} endpoint to fetch track details by ID
- Fixed duplicate cache key declaration in GetSpotifyPlaylistTracksOrderedAsync
- Updated cache clearing to include new spotify:playlist:items:* keys
2026-02-04 01:44:56 -05:00
af03a53af5 Enhanced playlist statistics in admin dashboard
Backend changes:
- Distinguish between local tracks (in Jellyfin library) and external tracks (downloaded)
- Track external matched vs external missing counts
- Calculate completion percentage for each playlist

Frontend changes:
- Show detailed breakdown: X local • Y matched • Z missing
- Display completion percentage with progress bar
- Color-coded stats (green=local, blue=matched, yellow=missing)
- Updated table headers for clarity
2026-02-03 19:49:32 -05:00
6ab314f603 Enhanced cache clearing to include all Spotify playlist Redis keys
- Added clearing of spotify:matched:* and spotify:matched:ordered:* keys
- This ensures bitrate metadata fix takes effect after cache clear
- Returns count of cleared Redis keys in response
2026-02-03 19:41:16 -05:00
48f69b766d Add manual track mapping feature
- Add 'Map to Local' button for external tracks in playlist viewer
- Search Jellyfin library to find local tracks
- Save manual mappings (Spotify ID → Jellyfin ID) in cache
- Manual mappings take priority over fuzzy matching
- Clear playlist cache when mapping is saved to force refresh
- UI shows which tracks are manually mapped in logs
2026-02-03 18:57:19 -05:00
f240423822 Fix: Prioritize LOCAL tracks in Spotify playlist injection - match by name only
- Remove Spotify ID/ISRC matching (Jellyfin plugin doesn't add these)
- Use ONLY fuzzy name matching (title + artist, 70% threshold)
- LOCAL tracks ALWAYS used first before external providers
- Include ALL tracks from Jellyfin playlist (even if not in Spotify)
- Prevent duplicate track usage with HashSet tracking
- AdminController also updated to match by name for Local/External badges
- Better logging with emojis for debugging
2026-02-03 18:36:33 -05:00
1492778b14 UI fixes: Match per playlist, Match All button, local/external labels, preserve tab on reload 2026-02-03 18:27:29 -05:00
c44be48eb9 Fix: Add UserId parameter for Jellyfin playlist operations + UI improvements
- CRITICAL FIX: Add UserId parameter to all Jellyfin playlist item fetches (fixes 400 BadRequest errors)
- Fix GetPlaylists to correctly count local/missing tracks
- Fix GetSpotifyPlaylistTracksOrderedAsync to find local tracks (was serving external tracks for everything)
- Fix SpotifyTrackMatchingService to skip tracks already in Jellyfin
- Add detailed debug logging for track matching (LOCAL by ISRC/Spotify ID, EXTERNAL match, NO MATCH)
- Add 'Match Tracks' button for individual playlists (not all playlists)
- Add 'Match All Tracks' button for matching all playlists at once
- Add JELLYFIN_USER_ID to web UI configuration tab for easy setup
- Add /api/admin/playlists/match-all endpoint

This fixes the issue where local tracks weren't being used - the system was downloading
from SquidWTF even when files existed locally in Jellyfin.
2026-02-03 18:14:13 -05:00
b16d16c9c9 Fix: Register SpotifyTrackMatchingService as singleton for DI
- Register as singleton first, then as hosted service
- Allows AdminController to inject and trigger matching manually
- Added better logging for Jellyfin playlist fetch failures
- Logs when JellyfinId is missing or API calls fail
- Helps debug '0 local / 0 missing' issue
2026-02-03 17:51:55 -05:00
e51d569d79 Show actual local/missing track counts in Active Playlists
- Fetches current Jellyfin playlist track count
- Compares with Spotify playlist total to calculate missing tracks
- Shows: '25 local / 5 missing' instead of just track count
- Local = tracks currently in Jellyfin playlist
- Missing = Spotify tracks not yet in Jellyfin (need to be matched)
- Helps users see which playlists need track matching
2026-02-03 17:48:49 -05:00
363c9e6f1b Add Match Tracks button and Local/External column to Active Playlists
- Added 'Match Tracks' button to trigger matching for specific playlist
- Added Local/External column (shows '-' for now, will populate after matching)
- New endpoint: POST /api/admin/playlists/{name}/match
- Injects SpotifyTrackMatchingService into AdminController
- UI shows: Name | Spotify ID | Total | Local/External | Cache Age | Actions
- Allows users to manually trigger matching without waiting 30 minutes
2026-02-03 17:47:44 -05:00
b3bfa16b93 Fix RemovePlaylist to use new 4-field config format
- Include JellyfinId when serializing playlists after removal
- Ensures config format stays consistent: [Name,SpotifyId,JellyfinId,position]
- Fixes issue where removed playlists would stay in the list
2026-02-03 17:35:54 -05:00
5646aa07ea Fix playlist injection: Store Jellyfin playlist ID in config
- Added JellyfinId field to SpotifyPlaylistConfig model
- Updated config format: [[Name,SpotifyId,JellyfinId,position],...]
- LinkPlaylist now stores both Jellyfin and Spotify playlist IDs
- IsSpotifyPlaylist() now checks by Jellyfin playlist ID (not Spotify ID)
- GetJellyfinPlaylists shows linked status by checking JellyfinId
- Updated Program.cs to parse new 4-field format
- Backward compatible: JellyfinId defaults to empty string for old configs

This fixes the issue where playlists weren't being recognized as configured
because the code was checking Jellyfin playlist IDs against Spotify IDs.
2026-02-03 17:18:08 -05:00
63324def62 Fix Jellyfin BadRequest errors when fetching playlist items
- Add UserId parameter to playlist items API call (required by Jellyfin)
- Auto-fetch first user if no user ID configured
- Simplify Fields parameter to only request Path
- This fixes the 400 BadRequest errors when loading Jellyfin playlists tab
2026-02-03 17:03:04 -05:00
ff72ae2395 Fix playlist linking to save all playlists and add restart banner
- Read playlists from .env file instead of stale in-memory config
- This fixes issue where linking multiple playlists only saved the last one
- Add prominent restart banner at top when config changes
- Update UI immediately after linking/unlinking playlists
- Banner shows 'Restart Now' button and dismissible option
- All config changes now show the restart banner instead of toast messages
- GetJellyfinPlaylists now reads from .env for accurate linked status
2026-02-03 17:00:23 -05:00
5606706dc8 Fix web UI config persistence and cookie age tracking
- Add SPOTIFY_API_SESSION_COOKIE_SET_DATE to docker-compose.yml env mapping
- Mount .env file in container for web UI to update
- Add SessionCookieSetDate loading in Program.cs
- Improve .env update logic with better error handling and logging
- Auto-initialize cookie date when cookie exists but date not set
- Simplify local vs external track detection in Jellyfin playlists
- Enhanced Spotify playlist ID parsing (supports ID, URI, and URL formats)
- Better UI clarity: renamed tabs to 'Link Playlists' and 'Active Playlists'
2026-02-03 16:50:19 -05:00
df7f11e769 Add local/external track columns to Jellyfin playlists, remove libraries filter 2026-02-03 16:08:49 -05:00
75c7acb745 Fix Jellyfin playlists tab: linked Spotify ID, track count, and user/library filters 2026-02-03 15:13:29 -05:00
c7f6783fa2 CRITICAL: Stop hammering Spotify API on status checks
- GetStatus() no longer calls Spotify API at all
- Status is now determined purely from configuration
- Fixes rate limiting issue (429 errors every 30 seconds)
- Spotify API should only be called when actually fetching playlists
2026-02-03 15:08:17 -05:00
3ddf51924b Add container restart capability from admin UI
- Added POST /api/admin/restart endpoint using Docker socket
- Mounts Docker socket read-only in docker-compose.yml
- Admin UI now has working 'Restart Container' button
- Auto-reloads page after container comes back up
- Falls back to manual restart instructions if socket unavailable
2026-02-03 15:05:43 -05:00
3826f29019 Add Jellyfin playlist discovery and linking feature
- Added GET /api/admin/jellyfin/playlists to fetch all playlists from Jellyfin
- Added POST /api/admin/jellyfin/playlists/{id}/link to link playlist to Spotify
- Added DELETE /api/admin/jellyfin/playlists/{name}/unlink to remove link
- Added new 'Jellyfin Playlists' tab in admin UI showing all playlists
- Shows link status for each playlist (Linked/Not Linked)
- Link modal accepts Spotify playlist ID or full URL
- Renamed 'Playlists' tab to 'Configured Playlists' for clarity
2026-02-03 15:03:31 -05:00
4036c739a3 Remove LocalTracksPosition UI, auto-init cookie date tracking
- Removed LocalTracksPosition from playlist table (uses Spotify order now)
- Removed LocalTracksPosition from add playlist modal and JS
- Added /api/admin/config/init-cookie-date endpoint to auto-set date
- Cookie date auto-initializes when cookie exists but date is unknown
- Improved user display message when profile scope unavailable
- TOTP tokens work for playlists but don't have user-read-private scope
2026-02-03 15:00:12 -05:00
c9895f6d1a Expand admin UI with full config editing and sp_dc cookie age tracking
- Fix auth status detection to use token validity instead of /me endpoint
- Add SessionCookieSetDate to SpotifyApiSettings for tracking cookie age
- Auto-set cookie date when updating sp_dc via admin UI
- Add edit buttons for all config settings (Spotify, Deezer, Qobuz, SquidWTF, Jellyfin)
- Show cookie age with color-coded expiration warnings (green/yellow/red)
- Display cookie age on both Dashboard and Config tabs
- Add generic edit setting modal supporting text/password/number/toggle/select inputs
- Remove SquidWTF base URL (not configurable)
- Add restart container button with manual restart instructions
2026-02-03 14:52:40 -05:00
a8d04b225b Move admin UI to separate internal port (5275) for security
- Admin API and static files only accessible on port 5275
- Main proxy port (8080) no longer serves admin endpoints
- AdminPortFilter rejects admin requests on wrong port
- AdminStaticFilesMiddleware only serves static files on admin port
- Port 5275 NOT exposed in Dockerfile or docker-compose by default
- Access admin UI via SSH tunnel or by uncommenting port mapping
2026-02-03 14:39:07 -05:00
6abf0e0717 Add web dashboard UI for configuration and playlist management
- Add AdminController with API endpoints for status, playlists, config
- Add web UI dashboard at /admin with dark theme
- Disable SpotifyMissingTracksFetcher when SpotifyApi is enabled with cookie
- Support viewing playlists, tracks, and managing configuration
- Add .env file modification from UI (requires restart to apply)
- Dashboard auto-refreshes status every 30 seconds
2026-02-03 14:37:26 -05:00