-- Replace user_saved_filters with global bookmark system. -- Hard cut: existing saved filters are removed and not migrated. -- Hard cut: bookmark-level icons are removed; icons live on groups only. -- Idempotent: safe to run multiple times. DROP TABLE IF EXISTS `user_saved_filters`; CREATE TABLE IF NOT EXISTS `user_bookmark_groups` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `user_id` INT UNSIGNED NOT NULL, `name` VARCHAR(100) NOT NULL, `icon` VARCHAR(40) NOT NULL DEFAULT 'bi-folder', `sort_order` SMALLINT UNSIGNED NOT NULL DEFAULT 0, `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `modified` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_ubg_user_sort` (`user_id`, `sort_order`), CONSTRAINT `fk_ubg_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS `user_bookmarks` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `user_id` INT UNSIGNED NOT NULL, `group_id` INT UNSIGNED NULL, `name` VARCHAR(120) NOT NULL, `url` VARCHAR(500) NOT NULL, `sort_order` SMALLINT UNSIGNED NOT NULL DEFAULT 0, `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `modified` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uniq_ub_user_url` (`user_id`, `url`), KEY `idx_ub_user_sort` (`user_id`, `sort_order`), KEY `idx_ub_group` (`group_id`), CONSTRAINT `fk_ub_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE, CONSTRAINT `fk_ub_group` FOREIGN KEY (`group_id`) REFERENCES `user_bookmark_groups` (`id`) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; SET @ubg_icon_exists = ( SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_bookmark_groups' AND COLUMN_NAME = 'icon' ); SET @sql = IF(@ubg_icon_exists = 0, 'ALTER TABLE `user_bookmark_groups` ADD COLUMN `icon` VARCHAR(40) NOT NULL DEFAULT ''bi-folder'' AFTER `name`', 'SELECT 1' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; UPDATE `user_bookmark_groups` SET `icon` = 'bi-folder' WHERE `icon` IS NULL OR TRIM(`icon`) = ''; SET @ub_icon_exists = ( SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_bookmarks' AND COLUMN_NAME = 'icon' ); SET @sql = IF(@ub_icon_exists > 0, 'ALTER TABLE `user_bookmarks` DROP COLUMN `icon`', 'SELECT 1' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- Keep the newest row for exact duplicate user+url pairs before adding unique key. DELETE ub1 FROM user_bookmarks ub1 JOIN user_bookmarks ub2 ON ub1.user_id = ub2.user_id AND ub1.url = ub2.url AND ub1.id < ub2.id; SET @idx_exists = ( SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_bookmarks' AND INDEX_NAME = 'uniq_ub_user_url' ); SET @sql = IF(@idx_exists = 0, 'ALTER TABLE `user_bookmarks` ADD UNIQUE KEY `uniq_ub_user_url` (`user_id`, `url`)', 'SELECT 1' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @idx_group_sort_exists = ( SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_bookmarks' AND INDEX_NAME = 'idx_ub_user_group_sort' ); SET @sql = IF(@idx_group_sort_exists = 0, 'ALTER TABLE `user_bookmarks` ADD KEY `idx_ub_user_group_sort` (`user_id`, `group_id`, `sort_order`)', 'SELECT 1' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;