// Schema initialization, migration execution, and database bootstrap helpers. const { version: appVersion } = require('#root/package.json'); const { compareVersions, detectSchemaVersion, getPendingMigrations, recordSchemaVersion, runMigrations } = require('./migrations'); // Snapshot only: keep this file aligned with the current schema state. async function ensureSchema(pool, options) { const schemaLockName = 'pulse_signage_schema_lock'; const schemaConnection = await pool.getConnection(); try { pool = schemaConnection; const [lockRows] = await pool.query('SELECT GET_LOCK(?, 120) AS lock_acquired', [schemaLockName]); if (!Number(lockRows && lockRows[0] && lockRows[0].lock_acquired)) { throw new Error('Unable to acquire the schema migration lock.'); } try { const currentVersion = await detectSchemaVersion(pool); const pendingMigrations = await getPendingMigrations(pool, { currentVersion: currentVersion }); const updateRequired = pendingMigrations.length > 0; console.info('[schema] previous=' + currentVersion + ' current=' + appVersion + ' update=' + (updateRequired ? 'yes' : 'no')); await pool.query(` CREATE TABLE IF NOT EXISTS c_canvas_sizes ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, width INT NOT NULL, height INT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL, UNIQUE KEY uq_canvas_sizes_dimensions (width, height) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS c_playlists ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, fade_between_slides TINYINT(1) NOT NULL DEFAULT 0, skip_unavailable_rtmp TINYINT(1) NOT NULL DEFAULT 0, canvas_id INT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL, CONSTRAINT fk_playlists_canvas FOREIGN KEY (canvas_id) REFERENCES c_canvas_sizes(id) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS c_templates ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, canvas_size_id INT NULL, background_image_path VARCHAR(512) NULL, background_color VARCHAR(32) NULL, background_gradient LONGTEXT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS c_template_regions ( id INT AUTO_INCREMENT PRIMARY KEY, template_id INT NOT NULL, region_key VARCHAR(100) NOT NULL, region_type VARCHAR(20) NOT NULL, label VARCHAR(255) NOT NULL, font_family VARCHAR(100) NULL, lock_ratio VARCHAR(20) NULL, animation_json JSON NULL, x INT NOT NULL DEFAULT 0, y INT NOT NULL DEFAULT 0, width INT NOT NULL DEFAULT 100, height INT NOT NULL DEFAULT 100, z_index INT NOT NULL DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS c_slides ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, template_id INT NULL, content_json JSON NULL, thumbnail_path VARCHAR(512) NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS c_playlist_slides ( id INT AUTO_INCREMENT PRIMARY KEY, playlist_id INT NOT NULL, slide_id INT NOT NULL, position INT NOT NULL DEFAULT 0, duration_seconds DECIMAL(10,3) NOT NULL DEFAULT 10.000, use_video_duration TINYINT(1) NOT NULL DEFAULT 0, disable_audio TINYINT(1) NOT NULL DEFAULT 1, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL, CONSTRAINT fk_playlist_slides_playlist FOREIGN KEY (playlist_id) REFERENCES c_playlists(id) ON DELETE CASCADE, CONSTRAINT fk_playlist_slides_slide FOREIGN KEY (slide_id) REFERENCES c_slides(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS c_playlist_slide_schedule_rules ( id INT AUTO_INCREMENT PRIMARY KEY, playlist_slide_id INT NOT NULL, position INT NOT NULL DEFAULT 0, start_datetime DATETIME NULL, end_datetime DATETIME NULL, start_time TIME NULL, end_time TIME NULL, schedule_days_json JSON NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL, CONSTRAINT fk_playlist_slide_schedule_rules_slide FOREIGN KEY (playlist_slide_id) REFERENCES c_playlist_slides(id) ON DELETE CASCADE, INDEX idx_playlist_slide_schedule_rules_slide_position (playlist_slide_id, position) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS d_players ( id INT AUTO_INCREMENT PRIMARY KEY, identifier VARCHAR(128) NOT NULL UNIQUE, public_base_url VARCHAR(512) NULL, internal_base_url VARCHAR(512) NULL, last_seen_at TIMESTAMP NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS d_screens ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL UNIQUE, playlist_id INT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL, CONSTRAINT fk_screens_playlist FOREIGN KEY (playlist_id) REFERENCES c_playlists(id) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS d_announcements ( id INT AUTO_INCREMENT PRIMARY KEY, message TEXT NOT NULL, short_label VARCHAR(255) NOT NULL DEFAULT '', announcement_type VARCHAR(32) NOT NULL DEFAULT 'lower-third', color_key VARCHAR(32) NOT NULL DEFAULT 'primary', icon_key VARCHAR(64) NOT NULL DEFAULT 'megaphone-fill', duration_seconds INT NULL, expires_at TIMESTAMP NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL, INDEX idx_announcements_expires_at (expires_at), INDEX idx_announcements_modified_at (modified_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS d_announcement_screens ( id BIGINT AUTO_INCREMENT PRIMARY KEY, announcement_id INT NOT NULL, screen_id INT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL, UNIQUE KEY uq_announcement_screens_pair (announcement_id, screen_id), INDEX idx_announcement_screens_screen_id (screen_id), CONSTRAINT fk_announcement_screens_announcement FOREIGN KEY (announcement_id) REFERENCES d_announcements(id) ON DELETE CASCADE, CONSTRAINT fk_announcement_screens_screen FOREIGN KEY (screen_id) REFERENCES d_screens(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS i_rss_feeds ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, feed_url VARCHAR(1024) NOT NULL, enabled TINYINT(1) NOT NULL DEFAULT 1, update_interval_value INT NOT NULL DEFAULT 60, update_interval_unit VARCHAR(10) NOT NULL DEFAULT 'minutes', item_limit INT NOT NULL DEFAULT 1, last_pulled_at DATETIME NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS i_rss_feed_items ( id INT AUTO_INCREMENT PRIMARY KEY, rss_feed_id INT NOT NULL, position INT NOT NULL, item_json MEDIUMTEXT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL, CONSTRAINT fk_rss_feed_items_rss_feed FOREIGN KEY (rss_feed_id) REFERENCES i_rss_feeds(id) ON DELETE CASCADE, UNIQUE KEY uq_rss_feed_items_feed_position (rss_feed_id, position) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS i_api_sources ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, api_url VARCHAR(1024) NOT NULL, request_method VARCHAR(10) NOT NULL DEFAULT 'GET', request_body_json MEDIUMTEXT NULL, auth_method VARCHAR(32) NOT NULL DEFAULT 'none', auth_username VARCHAR(255) NULL, auth_password MEDIUMTEXT NULL, auth_bearer_token MEDIUMTEXT NULL, auth_header_name VARCHAR(255) NULL, auth_header_value MEDIUMTEXT NULL, token_url VARCHAR(1024) NULL, token_request_body_json MEDIUMTEXT NULL, token_response_path VARCHAR(255) NULL DEFAULT 'access_token', token_header_name VARCHAR(255) NULL DEFAULT 'Authorization', token_header_prefix VARCHAR(64) NULL DEFAULT 'Bearer', items_path VARCHAR(255) NULL, enabled TINYINT(1) NOT NULL DEFAULT 1, update_interval_value INT NOT NULL DEFAULT 60, update_interval_unit VARCHAR(10) NOT NULL DEFAULT 'minutes', last_pulled_at TIMESTAMP NULL, last_pull_error MEDIUMTEXT NULL, last_response_status INT NULL, last_response_content_type VARCHAR(255) NULL, last_response_json MEDIUMTEXT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS i_weather_locations ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL UNIQUE, location_label VARCHAR(255) NOT NULL, latitude DECIMAL(9,6) NOT NULL, longitude DECIMAL(9,6) NOT NULL, timezone VARCHAR(128) NOT NULL, provider VARCHAR(32) NOT NULL DEFAULT 'open-meteo', temperature_unit VARCHAR(16) NOT NULL DEFAULT 'celsius', wind_unit VARCHAR(16) NOT NULL DEFAULT 'kmh', precipitation_unit VARCHAR(16) NOT NULL DEFAULT 'mm', enabled TINYINT(1) NOT NULL DEFAULT 1, update_interval_value INT NOT NULL DEFAULT 30, update_interval_unit VARCHAR(16) NOT NULL DEFAULT 'minutes', last_pulled_at DATETIME NULL, last_pull_error VARCHAR(1024) NULL, last_response_json MEDIUMTEXT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL, INDEX idx_weather_locations_modified_at (modified_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS i_timetable_groups ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, short_description VARCHAR(255) NULL, timezone VARCHAR(64) NOT NULL DEFAULT 'Europe/London', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS i_timetable_entries ( id INT AUTO_INCREMENT PRIMARY KEY, schedule_group_id INT NOT NULL, title VARCHAR(255) NOT NULL, short_description VARCHAR(255) NULL, start_datetime DATETIME NOT NULL, end_datetime DATETIME NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL, CONSTRAINT fk_timetable_entries_group FOREIGN KEY (schedule_group_id) REFERENCES i_timetable_groups(id) ON DELETE CASCADE, INDEX idx_timetable_entries_group_start (schedule_group_id, start_datetime) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS d_onboarding_devices ( id BIGINT AUTO_INCREMENT PRIMARY KEY, device_id VARCHAR(128) NOT NULL UNIQUE, client_name VARCHAR(255) NULL, screen_id INT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL, last_seen_at TIMESTAMP NULL, CONSTRAINT fk_player_onboarding_devices_screen FOREIGN KEY (screen_id) REFERENCES d_screens(id) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS a_users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NULL, username VARCHAR(255) NOT NULL UNIQUE, password_hash CHAR(64) NOT NULL, password_salt VARCHAR(64) NOT NULL, password_iterations INT NOT NULL, must_change_password TINYINT(1) NOT NULL DEFAULT 0, account_locked TINYINT(1) NOT NULL DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS a_roles ( id INT AUTO_INCREMENT PRIMARY KEY, role_key VARCHAR(100) NOT NULL UNIQUE, name VARCHAR(255) NOT NULL, description TEXT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS a_permissions ( id INT AUTO_INCREMENT PRIMARY KEY, permission_key VARCHAR(100) NOT NULL UNIQUE, name VARCHAR(255) NOT NULL, section_name VARCHAR(255) NOT NULL, description TEXT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS a_role_permissions ( id BIGINT AUTO_INCREMENT PRIMARY KEY, role_id INT NOT NULL, permission_id INT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL, UNIQUE KEY uq_role_permissions_pair (role_id, permission_id), CONSTRAINT fk_role_permissions_role FOREIGN KEY (role_id) REFERENCES a_roles(id) ON DELETE CASCADE, CONSTRAINT fk_role_permissions_permission FOREIGN KEY (permission_id) REFERENCES a_permissions(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS a_user_roles ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, role_id INT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL, UNIQUE KEY uq_user_roles_pair (user_id, role_id), CONSTRAINT fk_user_roles_user FOREIGN KEY (user_id) REFERENCES a_users(id) ON DELETE CASCADE, CONSTRAINT fk_user_roles_role FOREIGN KEY (role_id) REFERENCES a_roles(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS a_sessions ( id BIGINT AUTO_INCREMENT PRIMARY KEY, session_hash CHAR(64) NOT NULL UNIQUE, user_id INT NOT NULL, ip_address VARCHAR(255) NULL, user_agent VARCHAR(512) NULL, expires_at DATETIME NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, last_used_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL, CONSTRAINT fk_auth_sessions_user FOREIGN KEY (user_id) REFERENCES a_users(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS a_login_attempts ( id BIGINT AUTO_INCREMENT PRIMARY KEY, rate_key VARCHAR(600) NOT NULL UNIQUE, failed_count INT NOT NULL DEFAULT 0, last_failed_at DATETIME NULL, locked_until DATETIME NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS o_background_tasks ( id BIGINT AUTO_INCREMENT PRIMARY KEY, task_key VARCHAR(191) NULL, task_type VARCHAR(100) NOT NULL, title VARCHAR(255) NOT NULL, category VARCHAR(100) NOT NULL DEFAULT 'general', status VARCHAR(20) NOT NULL, payload_json MEDIUMTEXT NULL, metadata_json MEDIUMTEXT NULL, attempts INT NOT NULL DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, started_at TIMESTAMP NULL, finished_at TIMESTAMP NULL, error_message MEDIUMTEXT NULL, INDEX idx_background_tasks_status (status), INDEX idx_background_tasks_key (task_key), INDEX idx_background_tasks_type (task_type) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await pool.query(` CREATE TABLE IF NOT EXISTS o_app_settings ( id BIGINT AUTO_INCREMENT PRIMARY KEY, setting_key VARCHAR(191) NOT NULL UNIQUE, setting_value JSON NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_by INT NULL, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified_by INT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); await runMigrations(pool, Object.assign({}, options, { currentVersion: currentVersion })); await recordSchemaVersion(pool, appVersion); } finally { await pool.query('SELECT RELEASE_LOCK(?)', [schemaLockName]).catch(function () { }); } } finally { schemaConnection.release(); } } module.exports = { ensureSchema, };