SET NAMES utf8mb4; SET time_zone = '+00:00'; -- ----------------------- -- settings -- ----------------------- CREATE TABLE IF NOT EXISTS settings ( `key` VARCHAR(64) NOT NULL, value_json LONGTEXT NOT NULL, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ----------------------- -- settings_snapshots -- ----------------------- CREATE TABLE IF NOT EXISTS settings_snapshots ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, label VARCHAR(255) NULL, data_json LONGTEXT NOT NULL, created_by BIGINT UNSIGNED NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_created_at (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ----------------------- -- scan_profiles -- ----------------------- CREATE TABLE IF NOT EXISTS scan_profiles ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, sort_order INT NOT NULL DEFAULT 1000, profile_type ENUM('scan','analyze') NOT NULL DEFAULT 'scan', enabled TINYINT(1) NOT NULL DEFAULT 1, name VARCHAR(128) NOT NULL, root_path VARCHAR(1024) NOT NULL, max_depth TINYINT UNSIGNED NOT NULL DEFAULT 3, exclude_patterns_json LONGTEXT NOT NULL DEFAULT '[]', include_ext_mode ENUM('default','custom') NOT NULL DEFAULT 'default', include_ext_json LONGTEXT NULL, last_scan_at DATETIME NULL, last_result ENUM('ok','error','never') NOT NULL DEFAULT 'never', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_sort (sort_order), KEY idx_enabled (enabled), KEY idx_root_path (root_path(255)) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ----------------------- -- items -- ----------------------- CREATE TABLE IF NOT EXISTS items ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, scan_profile_id INT UNSIGNED NOT NULL, abs_path VARCHAR(2048) NOT NULL, rel_path VARCHAR(2048) NULL, display_name VARCHAR(512) NOT NULL, kind ENUM('auto','movie','series') NOT NULL DEFAULT 'auto', year SMALLINT UNSIGNED NULL, structure ENUM('file','folder','dvd','bluray') NOT NULL DEFAULT 'folder', confidence TINYINT UNSIGNED NOT NULL DEFAULT 0, video_count INT UNSIGNED NOT NULL DEFAULT 0, file_count INT UNSIGNED NOT NULL DEFAULT 0, status ENUM('active','gone','ignored') NOT NULL DEFAULT 'active', last_seen_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_profile_path (scan_profile_id, abs_path(255)), KEY idx_kind (kind), KEY idx_status (status), KEY idx_last_seen (last_seen_at), CONSTRAINT fk_items_profile FOREIGN KEY (scan_profile_id) REFERENCES scan_profiles(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ----------------------- -- jobs -- ----------------------- CREATE TABLE IF NOT EXISTS jobs ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, type ENUM('scan','apply','db_reset','clear_index','task') NOT NULL, status ENUM('queued','running','done','error','canceled') NOT NULL DEFAULT 'queued', title VARCHAR(255) NOT NULL, payload_json LONGTEXT NULL, progress_current BIGINT UNSIGNED NOT NULL DEFAULT 0, progress_total BIGINT UNSIGNED NOT NULL DEFAULT 0, progress_pct TINYINT UNSIGNED NOT NULL DEFAULT 0, cancel_requested TINYINT(1) NOT NULL DEFAULT 0, last_heartbeat DATETIME NULL, error_message TEXT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, started_at DATETIME NULL, finished_at DATETIME NULL, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_type (type), KEY idx_status (status), KEY idx_created (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ----------------------- -- job_logs -- ----------------------- CREATE TABLE IF NOT EXISTS job_logs ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, job_id BIGINT UNSIGNED NOT NULL, ts DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, level ENUM('info','warn','error','debug') NOT NULL DEFAULT 'info', message TEXT NOT NULL, PRIMARY KEY (id), KEY idx_job_ts (job_id, ts), CONSTRAINT fk_job_logs_job FOREIGN KEY (job_id) REFERENCES jobs(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ----------------------- -- app_logs -- ----------------------- CREATE TABLE IF NOT EXISTS app_logs ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, ts DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, level ENUM('info','warn','error','debug') NOT NULL DEFAULT 'info', message TEXT NOT NULL, context_json LONGTEXT NULL, PRIMARY KEY (id), KEY idx_app_logs_ts (ts), KEY idx_app_logs_level (level) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ----------------------- -- media_file_meta -- ----------------------- CREATE TABLE IF NOT EXISTS media_file_meta ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, abs_path VARCHAR(2048) NOT NULL, size_bytes BIGINT UNSIGNED NOT NULL, mtime INT UNSIGNED NOT NULL, inode BIGINT UNSIGNED NOT NULL DEFAULT 0, info_json LONGTEXT NULL, last_scanned_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_media_path (abs_path(255)), KEY idx_media_mtime (mtime) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ----------------------- -- media_files -- ----------------------- CREATE TABLE IF NOT EXISTS media_files ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, scan_profile_id INT UNSIGNED NOT NULL, abs_path VARCHAR(2048) NOT NULL, rel_path VARCHAR(2048) NOT NULL, name VARCHAR(512) NOT NULL, ext VARCHAR(16) NOT NULL, size_bytes BIGINT UNSIGNED NOT NULL, mtime INT UNSIGNED NOT NULL, is_mkv TINYINT(1) NOT NULL DEFAULT 0, kind ENUM('movie','series','unknown') NOT NULL DEFAULT 'unknown', series_key VARCHAR(512) NULL, container VARCHAR(64) NULL, duration_ms BIGINT UNSIGNED NULL, last_analyzed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_media_files_path (abs_path(255)), KEY idx_media_files_profile (scan_profile_id), KEY idx_media_files_kind (kind), KEY idx_media_files_series (series_key(255)), CONSTRAINT fk_media_files_profile FOREIGN KEY (scan_profile_id) REFERENCES scan_profiles(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ----------------------- -- media_metadata -- ----------------------- CREATE TABLE IF NOT EXISTS media_metadata ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, subject_kind ENUM('movie','series') NOT NULL, subject_key VARCHAR(2048) NOT NULL, provider VARCHAR(64) NULL, provider_id VARCHAR(128) NULL, title_map_json LONGTEXT NULL, original_title VARCHAR(512) NULL, year SMALLINT UNSIGNED NULL, manual_title VARCHAR(512) NULL, manual_year SMALLINT UNSIGNED NULL, source ENUM('auto','manual') NOT NULL DEFAULT 'auto', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_media_meta_subject (subject_kind, subject_key(255)), KEY idx_media_meta_provider (provider, provider_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ----------------------- -- auth: users and roles -- ----------------------- CREATE TABLE IF NOT EXISTS users ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, email VARCHAR(255) NOT NULL, password_hash VARCHAR(255) NOT NULL, status ENUM('active','disabled') NOT NULL DEFAULT 'active', token_version INT UNSIGNED NOT NULL DEFAULT 1, last_login_at DATETIME NULL, password_changed_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_users_email (email) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS user_profiles ( user_id BIGINT UNSIGNED NOT NULL, nickname VARCHAR(128) NULL, avatar_blob MEDIUMBLOB NULL, avatar_mime VARCHAR(64) NULL, ui_prefs_json LONGTEXT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (user_id), CONSTRAINT fk_user_profiles_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 roles ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(64) NOT NULL, description VARCHAR(255) NULL, PRIMARY KEY (id), UNIQUE KEY uq_roles_name (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS permissions ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, `key` VARCHAR(128) NOT NULL, description VARCHAR(255) NULL, PRIMARY KEY (id), UNIQUE KEY uq_permissions_key (`key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS role_permissions ( role_id INT UNSIGNED NOT NULL, permission_id INT UNSIGNED NOT NULL, PRIMARY KEY (role_id, permission_id), CONSTRAINT fk_role_permissions_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE, CONSTRAINT fk_role_permissions_permission FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS user_roles ( user_id BIGINT UNSIGNED NOT NULL, role_id INT UNSIGNED NOT NULL, PRIMARY KEY (user_id, role_id), CONSTRAINT fk_user_roles_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, CONSTRAINT fk_user_roles_role FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS refresh_tokens ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL, device_id VARCHAR(64) NOT NULL, token_hash CHAR(64) NOT NULL, user_agent VARCHAR(255) NULL, ip_addr VARCHAR(64) NULL, expires_at DATETIME NOT NULL, revoked_at DATETIME NULL, rotated_from_id BIGINT UNSIGNED NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_refresh_token_hash (token_hash), KEY idx_refresh_user (user_id), KEY idx_refresh_expires (expires_at), CONSTRAINT fk_refresh_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 auth_challenges ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL, token_hash CHAR(64) NOT NULL, expires_at DATETIME NOT NULL, used_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_auth_challenge_hash (token_hash), KEY idx_auth_challenge_user (user_id), KEY idx_auth_challenge_expires (expires_at), CONSTRAINT fk_auth_challenge_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 auth_rate_limits ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, ip_addr VARCHAR(64) NOT NULL, action VARCHAR(32) NOT NULL, failed_count INT UNSIGNED NOT NULL DEFAULT 0, window_started_at DATETIME NOT NULL, blocked_until DATETIME NULL, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_auth_rate_key (ip_addr, action), KEY idx_auth_rate_blocked (blocked_until) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS auth_sse_keys ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL, key_hash CHAR(64) NOT NULL, expires_at DATETIME NOT NULL, used_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_sse_key_hash (key_hash), KEY idx_sse_user (user_id), KEY idx_sse_expires (expires_at), CONSTRAINT fk_sse_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 auth_sse_sessions ( user_id BIGINT UNSIGNED NOT NULL, session_token CHAR(64) NOT NULL, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (user_id), KEY idx_sse_session_updated (updated_at), CONSTRAINT fk_sse_session_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 mfa_methods ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL, type ENUM('totp') NOT NULL DEFAULT 'totp', secret_enc VARCHAR(255) NOT NULL, enabled_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_mfa_user (user_id), CONSTRAINT fk_mfa_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 mfa_backup_codes ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL, code_hash CHAR(64) NOT NULL, used_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_mfa_backup_user (user_id), CONSTRAINT fk_mfa_backup_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 password_resets ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL, token_hash CHAR(64) NOT NULL, expires_at DATETIME NOT NULL, used_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_password_reset_hash (token_hash), KEY idx_password_reset_user (user_id), KEY idx_password_reset_expires (expires_at), CONSTRAINT fk_password_reset_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 audit_log ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, actor_user_id BIGINT UNSIGNED NULL, action VARCHAR(64) NOT NULL, target_type VARCHAR(64) NULL, target_id BIGINT UNSIGNED NULL, ip_addr VARCHAR(64) NULL, user_agent VARCHAR(255) NULL, meta_json LONGTEXT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_audit_actor (actor_user_id), KEY idx_audit_action (action), CONSTRAINT fk_audit_actor FOREIGN KEY (actor_user_id) REFERENCES users(id) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ----------------------- -- Seed settings (minimal) -- ----------------------- INSERT IGNORE INTO settings (`key`, value_json) VALUES ('general', '{"timezone":"Europe/Berlin","language":"ru","safe_mode":false,"auto_refresh_seconds":0,"date_format":"YYYY-MM-DD"}'), ('scanner_defaults', '{"video_ext":["mkv","mp4","avi","mov","m4v","ts","m2ts","wmv"],"max_depth_default":3,"max_files_per_item":3000,"max_items_per_scan":0}'), ('paths', '{"movies_root":"","series_root":"","staging_root":""}'), ('tools', '{"mkvmerge_path":"","mkvpropedit_path":"","ffmpeg_path":""}'), ('logs', '{"retention_days":7,"level":"info"}'), ('layout', '{"movies":{"strategy":"prefix","params":{"n":2},"template":null},"series":{"strategy":"first_letter","params":{},"template":null,"season_naming":"season_2digit"},"normalization":{"ignore_articles":true,"transliterate_non_latin":true,"uppercase_shards":true,"replace_unsafe_chars":true,"trim_dots_spaces":true,"ignore_words":[]},"collision_policy":"stop"}'), ('media_rules', '{"name_map":[],"delete_rules":[],"language_priority":["ru","en"],"audio_type_priority":["dub","voiceover","original","commentary","unknown"],"require_audio_type":true,"series_order_threshold":0.7}'), ('rules', '[]'), ('sources', '{"transmission":{"enabled":false,"last_test_ok":false,"last_test_at":null,"protocol":"http","host":"","port":9091,"path":"/transmission/rpc","username":"","password":""}}'), ('metadata', '{"enabled":false,"languages":["de","ru","en"],"provider_priority":["tvdb","omdb"],"providers":{"omdb":{"enabled":false,"api_key":"","base_url":"https://www.omdbapi.com/"},"tvdb":{"enabled":false,"api_key":"","pin":""}}}'), ('exports', '{"kodi":{"enabled":false},"jellyfin":{"enabled":false}}'), ('background', '{"mode":"light","max_parallel_jobs":1,"max_network_jobs":1,"max_io_jobs":1,"batch_sleep_ms":500,"watchdog_minutes":10,"sse_session_ttl_seconds":20,"paused":false}'), ('ui', '{"sse_tick_seconds":10}'), ('safety', '{"max_depth":10,"max_files_per_item":200000,"max_items_per_scan":1000000}'), ('_system', '{"settings_revision":1,"first_run_completed":false}'); -- ----------------------- -- Seed roles -- ----------------------- INSERT IGNORE INTO roles (id, name, description) VALUES (1, 'admin', 'Full access'), (2, 'manager', 'Manager access'), (3, 'user', 'User access'); -- ----------------------- -- Seed admin user -- ----------------------- INSERT IGNORE INTO users (id, email, password_hash, status, token_version, created_at, updated_at) VALUES (1, 'admin@admin.local', '$2y$12$2op4ztskqxDR376xnZ6sR.Ccbi.zyPu3KRwJsDAPmeNf5B.ZjEcfC', 'active', 1, NOW(), NOW()); INSERT IGNORE INTO user_roles (user_id, role_id) VALUES (1, 1);