From 078b1130f82148e28d4370e668ee8fa1dc29655e Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Sun, 15 Mar 2026 15:21:19 -0700 Subject: [PATCH] Fix watchlist migration dropping profile_id & fix profile delete dialog hidden behind overlay - Watchlist nullable migration now preserves profile_id column and composite UNIQUE constraints when rebuilding the table - Profile support migration always repairs missing profile_id columns on all tables, even if the migration metadata key already exists (handles tables rebuilt by other migrations) - Confirm dialog z-index raised to 100000 to appear above profile picker overlay (99999), fixing invisible delete confirmation --- database/music_database.py | 121 ++++++++++++++++++++++++------------- webui/static/style.css | 2 +- 2 files changed, 79 insertions(+), 44 deletions(-) diff --git a/database/music_database.py b/database/music_database.py index c69a4754..6dd0c73a 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -1128,31 +1128,60 @@ class MusicDatabase: if has_not_null: logger.info("Migrating watchlist_artists table to make spotify_artist_id nullable...") + # Check if old table already has profile_id (from profile migration) + old_has_profile = 'profile_id' in columns + # Drop leftover temp table from any previous failed migration cursor.execute("DROP TABLE IF EXISTS watchlist_artists_new") # Create new table with nullable spotify_artist_id - cursor.execute(""" - CREATE TABLE watchlist_artists_new ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - spotify_artist_id TEXT UNIQUE, - artist_name TEXT NOT NULL, - date_added TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - last_scan_timestamp TIMESTAMP, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - image_url TEXT, - include_albums INTEGER DEFAULT 1, - include_eps INTEGER DEFAULT 1, - include_singles INTEGER DEFAULT 1, - include_live INTEGER DEFAULT 0, - include_remixes INTEGER DEFAULT 0, - include_acoustic INTEGER DEFAULT 0, - include_compilations INTEGER DEFAULT 0, - itunes_artist_id TEXT - ) - """) - + # Include profile_id + composite UNIQUE if old table had profile support + if old_has_profile: + cursor.execute(""" + CREATE TABLE watchlist_artists_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + spotify_artist_id TEXT, + artist_name TEXT NOT NULL, + date_added TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + last_scan_timestamp TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + image_url TEXT, + include_albums INTEGER DEFAULT 1, + include_eps INTEGER DEFAULT 1, + include_singles INTEGER DEFAULT 1, + include_live INTEGER DEFAULT 0, + include_remixes INTEGER DEFAULT 0, + include_acoustic INTEGER DEFAULT 0, + include_compilations INTEGER DEFAULT 0, + itunes_artist_id TEXT, + profile_id INTEGER DEFAULT 1, + UNIQUE(profile_id, spotify_artist_id), + UNIQUE(profile_id, itunes_artist_id) + ) + """) + else: + cursor.execute(""" + CREATE TABLE watchlist_artists_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + spotify_artist_id TEXT UNIQUE, + artist_name TEXT NOT NULL, + date_added TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + last_scan_timestamp TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + image_url TEXT, + include_albums INTEGER DEFAULT 1, + include_eps INTEGER DEFAULT 1, + include_singles INTEGER DEFAULT 1, + include_live INTEGER DEFAULT 0, + include_remixes INTEGER DEFAULT 0, + include_acoustic INTEGER DEFAULT 0, + include_compilations INTEGER DEFAULT 0, + itunes_artist_id TEXT + ) + """) + # Copy data from old table (only columns that exist in both) cursor.execute("PRAGMA table_info(watchlist_artists)") old_cols = [col[1] for col in cursor.fetchall()] @@ -1160,19 +1189,21 @@ class MusicDatabase: 'last_scan_timestamp', 'created_at', 'updated_at', 'image_url', 'include_albums', 'include_eps', 'include_singles', 'include_live', 'include_remixes', 'include_acoustic', 'include_compilations', - 'itunes_artist_id'] + 'itunes_artist_id', 'profile_id'] shared_cols = [c for c in new_cols if c in old_cols] cols_str = ', '.join(shared_cols) cursor.execute(f"INSERT INTO watchlist_artists_new ({cols_str}) SELECT {cols_str} FROM watchlist_artists") - + # Drop old table cursor.execute("DROP TABLE watchlist_artists") - + # Rename new table cursor.execute("ALTER TABLE watchlist_artists_new RENAME TO watchlist_artists") - + # Recreate indexes cursor.execute("CREATE INDEX IF NOT EXISTS idx_watchlist_spotify_id ON watchlist_artists (spotify_artist_id)") + if old_has_profile: + cursor.execute("CREATE INDEX IF NOT EXISTS idx_watchlist_profile ON watchlist_artists (profile_id)") logger.info("Successfully migrated watchlist_artists table - spotify_artist_id is now nullable") else: @@ -1767,8 +1798,27 @@ class MusicDatabase: try: # Check if migration already applied cursor.execute("SELECT value FROM metadata WHERE key = 'profiles_migration_v1' LIMIT 1") - if cursor.fetchone(): - return # Already migrated + already_migrated = cursor.fetchone() is not None + + # Even if already migrated, ensure profile_id columns exist on all tables + # (another migration may have rebuilt a table without profile_id) + tables_needing_profile_id = [ + 'watchlist_artists', 'wishlist_tracks', 'similar_artists', + 'discovery_pool', 'discovery_recent_albums', 'discovery_curated_playlists', + 'bubble_snapshots', 'recent_releases' + ] + for table in tables_needing_profile_id: + try: + cursor.execute(f"PRAGMA table_info({table})") + columns = [col[1] for col in cursor.fetchall()] + if 'profile_id' not in columns: + cursor.execute(f"ALTER TABLE {table} ADD COLUMN profile_id INTEGER DEFAULT 1") + logger.info(f"Repaired missing profile_id column on {table}") + except Exception: + pass + + if already_migrated: + return # Rest of migration already done logger.info("Adding multi-profile support...") @@ -1791,22 +1841,7 @@ class MusicDatabase: VALUES (1, 'Admin', 1) """) - # 3. Add profile_id column to per-profile tables - tables_needing_profile_id = [ - 'watchlist_artists', 'wishlist_tracks', 'similar_artists', - 'discovery_pool', 'discovery_recent_albums', 'discovery_curated_playlists', - 'bubble_snapshots', 'recent_releases' - ] - - for table in tables_needing_profile_id: - try: - cursor.execute(f"PRAGMA table_info({table})") - columns = [col[1] for col in cursor.fetchall()] - if 'profile_id' not in columns: - cursor.execute(f"ALTER TABLE {table} ADD COLUMN profile_id INTEGER DEFAULT 1") - logger.info(f"Added profile_id column to {table}") - except Exception as e: - logger.warning(f"Could not add profile_id to {table}: {e}") + # 3. profile_id columns already ensured above (before early-return guard) # 4. Rebuild watchlist_artists to change UNIQUE constraint # Old: UNIQUE(spotify_artist_id) diff --git a/webui/static/style.css b/webui/static/style.css index 2cc4b94e..a2fb0b55 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -4337,7 +4337,7 @@ body { /* Confirm Dialog Modal */ /* Confirm dialog must sit above all other modals (playlist, download, etc.) */ #confirm-modal-overlay { - z-index: 11000; + z-index: 100000; } .confirm-modal {