465 lines
21 KiB
SQL
465 lines
21 KiB
SQL
-- IM Server schema changes for the GedaDa management-console adaptation.
|
|
-- Target: MySQL 8.0+, one independent tenant business database.
|
|
-- Default local tenant database: `tinode`.
|
|
-- IMPORTANT: execute this migration once for every tenant database. Tenant
|
|
-- business tables intentionally do not contain tenant_id.
|
|
-- Review before execution. DDL statements auto-commit in MySQL.
|
|
|
|
SET NAMES utf8mb4;
|
|
USE `tinode`;
|
|
|
|
-- ============================================================
|
|
-- 0. User account number.
|
|
-- account_no is the short user number shown in Admin. The real primary key
|
|
-- im_users.id remains the internal Tinode/Snowflake user ID.
|
|
-- Existing users are backfilled from 1000 by created time; new users are
|
|
-- assigned by IM Server in the user creation transaction.
|
|
-- ============================================================
|
|
|
|
SET @sql := IF(
|
|
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_users' AND COLUMN_NAME = 'account_no') = 0,
|
|
'ALTER TABLE `im_users` ADD COLUMN `account_no` int unsigned NOT NULL DEFAULT 0 COMMENT ''用户编号'' AFTER `id`',
|
|
'SELECT 1'
|
|
);
|
|
PREPARE stmt FROM @sql;
|
|
EXECUTE stmt;
|
|
DEALLOCATE PREPARE stmt;
|
|
|
|
SET @im_user_account_no := (
|
|
SELECT GREATEST(COALESCE(MAX(`account_no`), 999), 999)
|
|
FROM `im_users`
|
|
WHERE `account_no` > 0
|
|
);
|
|
|
|
UPDATE `im_users` AS u
|
|
JOIN (
|
|
SELECT ordered.`id`, (@im_user_account_no := @im_user_account_no + 1) AS next_no
|
|
FROM (
|
|
SELECT `id`
|
|
FROM `im_users`
|
|
WHERE `account_no` = 0
|
|
ORDER BY `createdat`, `id`
|
|
) AS ordered
|
|
) AS seq ON seq.`id` = u.`id`
|
|
SET u.`account_no` = seq.next_no
|
|
WHERE u.`account_no` = 0;
|
|
|
|
SET @sql := IF(
|
|
(SELECT COUNT(*) FROM information_schema.STATISTICS
|
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_users' AND INDEX_NAME = 'uk_im_users_account_no') = 0,
|
|
'CREATE UNIQUE INDEX `uk_im_users_account_no` ON `im_users` (`account_no`)',
|
|
'SELECT 1'
|
|
);
|
|
PREPARE stmt FROM @sql;
|
|
EXECUTE stmt;
|
|
DEALLOCATE PREPARE stmt;
|
|
|
|
-- ============================================================
|
|
-- 1. Applied runtime policy snapshots.
|
|
-- policy_type: security, user_policy_groups, sensitive_words.
|
|
-- The application uses the latest row with apply_status=applied and also keeps
|
|
-- an in-memory cache. This table makes restart and Kafka replay deterministic.
|
|
-- ============================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS `im_runtime_policy_snapshots` (
|
|
`id` bigint NOT NULL AUTO_INCREMENT,
|
|
`policy_type` varchar(32) NOT NULL,
|
|
`policy_version` bigint unsigned NOT NULL,
|
|
`payload` json NOT NULL,
|
|
`payload_sha256` char(64) NOT NULL,
|
|
`source_event_id` varchar(96) DEFAULT NULL COMMENT 'NULL for a manual bootstrap snapshot',
|
|
`apply_status` varchar(16) NOT NULL DEFAULT 'applied' COMMENT 'applied or failed',
|
|
`apply_error` varchar(1000) NOT NULL DEFAULT '',
|
|
`applied_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `uk_im_runtime_policy_version` (`policy_type`, `policy_version`),
|
|
UNIQUE KEY `uk_im_runtime_policy_event` (`source_event_id`),
|
|
KEY `idx_im_runtime_policy_latest` (`policy_type`, `apply_status`, `policy_version` DESC)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Last applied Admin policy payloads';
|
|
|
|
-- ============================================================
|
|
-- 2. User login history and login failure history.
|
|
-- Never store plaintext passwords, verification codes, access tokens or refresh
|
|
-- tokens. account_hash is SHA-256 of the normalized login account; account_mask
|
|
-- is the value shown in Admin.
|
|
-- login_type: password, sms_code, sso, qr_code, token_refresh.
|
|
-- event_type: login, refresh, logout, kicked, session_expired.
|
|
-- failure_type: password_error, code_error, account_not_found,
|
|
-- account_disabled, rate_limited, risk_blocked.
|
|
-- ============================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS `im_user_login_logs` (
|
|
`id` bigint NOT NULL AUTO_INCREMENT,
|
|
`user_id` bigint NOT NULL,
|
|
`username` varchar(64) NOT NULL DEFAULT '',
|
|
`account_type` varchar(24) NOT NULL DEFAULT '',
|
|
`account_hash` char(64) NOT NULL DEFAULT '',
|
|
`account_mask` varchar(128) NOT NULL DEFAULT '',
|
|
`login_type` varchar(24) NOT NULL,
|
|
`event_type` varchar(24) NOT NULL DEFAULT 'login',
|
|
`session_id` varchar(96) NOT NULL DEFAULT '' COMMENT 'Opaque session identifier, never an auth token',
|
|
`device_id` varchar(255) NOT NULL DEFAULT '',
|
|
`platform` varchar(32) NOT NULL DEFAULT 'unknown',
|
|
`app_version` varchar(32) NOT NULL DEFAULT '',
|
|
`ip` varchar(64) NOT NULL DEFAULT '',
|
|
`region` varchar(128) NOT NULL DEFAULT '',
|
|
`user_agent` varchar(512) NOT NULL DEFAULT '',
|
|
`risk_level` varchar(16) NOT NULL DEFAULT 'low',
|
|
`request_id` varchar(64) NOT NULL DEFAULT '',
|
|
`login_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
`logout_at` datetime(3) DEFAULT NULL,
|
|
`metadata` json DEFAULT NULL,
|
|
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (`id`),
|
|
KEY `idx_im_user_login_user_time` (`user_id`, `login_at` DESC, `id` DESC),
|
|
KEY `idx_im_user_login_ip_time` (`ip`, `login_at` DESC, `id` DESC),
|
|
KEY `idx_im_user_login_device_time` (`device_id`(64), `login_at` DESC),
|
|
KEY `idx_im_user_login_request` (`request_id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='User login and session event history';
|
|
|
|
CREATE TABLE IF NOT EXISTS `im_login_failure_logs` (
|
|
`id` bigint NOT NULL AUTO_INCREMENT,
|
|
`user_id` bigint DEFAULT NULL COMMENT 'NULL when the account cannot be resolved',
|
|
`account_type` varchar(24) NOT NULL DEFAULT '',
|
|
`account_hash` char(64) NOT NULL DEFAULT '',
|
|
`account_mask` varchar(128) NOT NULL DEFAULT '',
|
|
`failure_type` varchar(32) NOT NULL,
|
|
`reason` varchar(96) NOT NULL DEFAULT '' COMMENT 'Stable backend reason code',
|
|
`detail` varchar(500) NOT NULL DEFAULT '' COMMENT 'Sanitized detail without credentials',
|
|
`ip` varchar(64) NOT NULL DEFAULT '',
|
|
`region` varchar(128) NOT NULL DEFAULT '',
|
|
`device_id` varchar(255) NOT NULL DEFAULT '',
|
|
`platform` varchar(32) NOT NULL DEFAULT 'unknown',
|
|
`app_version` varchar(32) NOT NULL DEFAULT '',
|
|
`user_agent` varchar(512) NOT NULL DEFAULT '',
|
|
`risk_level` varchar(16) NOT NULL DEFAULT 'medium',
|
|
`risk_action` varchar(24) NOT NULL DEFAULT 'audit_only',
|
|
`request_id` varchar(64) NOT NULL DEFAULT '',
|
|
`attempt_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
`metadata` json DEFAULT NULL,
|
|
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (`id`),
|
|
KEY `idx_im_login_failure_account_time` (`account_hash`, `attempt_at` DESC, `id` DESC),
|
|
KEY `idx_im_login_failure_user_time` (`user_id`, `attempt_at` DESC, `id` DESC),
|
|
KEY `idx_im_login_failure_ip_time` (`ip`, `attempt_at` DESC, `id` DESC),
|
|
KEY `idx_im_login_failure_type_time` (`failure_type`, `attempt_at` DESC, `id` DESC),
|
|
KEY `idx_im_login_failure_request` (`request_id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Authentication failure and risk-block history';
|
|
|
|
-- ============================================================
|
|
-- 3. Message administration actions.
|
|
-- Messages remain in im_messages/im_group_messages. This table is an immutable
|
|
-- action overlay so hide/delete can be audited and restored.
|
|
-- conversation_type: private, group.
|
|
-- action: hide, unhide, delete, restore.
|
|
-- action_status: pending, success, failed, dead.
|
|
-- ============================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS `im_message_admin_actions` (
|
|
`id` bigint NOT NULL AUTO_INCREMENT,
|
|
`action_id` varchar(96) NOT NULL,
|
|
`command_id` varchar(96) NOT NULL,
|
|
`conversation_type` varchar(16) NOT NULL,
|
|
`topic` varchar(64) NOT NULL,
|
|
`seqid` bigint NOT NULL,
|
|
`sender_user_id` bigint DEFAULT NULL,
|
|
`action` varchar(16) NOT NULL,
|
|
`action_status` varchar(16) NOT NULL DEFAULT 'pending',
|
|
`reason` varchar(500) NOT NULL DEFAULT '',
|
|
`actor_admin_id` bigint NOT NULL DEFAULT 0,
|
|
`request_id` varchar(64) NOT NULL DEFAULT '',
|
|
`before_snapshot` json DEFAULT NULL,
|
|
`after_snapshot` json DEFAULT NULL,
|
|
`failure_reason` varchar(96) NOT NULL DEFAULT '',
|
|
`failure_message` varchar(1000) NOT NULL DEFAULT '',
|
|
`processed_at` datetime(3) DEFAULT NULL,
|
|
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
`updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `uk_im_message_admin_action_id` (`action_id`),
|
|
UNIQUE KEY `uk_im_message_admin_command` (`command_id`),
|
|
KEY `idx_im_message_admin_target` (`conversation_type`, `topic`, `seqid`, `id` DESC),
|
|
KEY `idx_im_message_admin_status` (`action_status`, `created_at`, `id`),
|
|
KEY `idx_im_message_admin_actor` (`actor_admin_id`, `created_at` DESC)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Auditable Admin message actions';
|
|
|
|
-- ============================================================
|
|
-- 4. Sensitive-word hits and policy decisions.
|
|
-- Store only a digest and a short masked excerpt. Do not duplicate complete
|
|
-- private messages in log tables.
|
|
-- policy decision reasons include FRIEND_DISABLED and MESSAGE_CURFEW_ACTIVE.
|
|
-- ============================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS `im_sensitive_word_hits` (
|
|
`id` bigint NOT NULL AUTO_INCREMENT,
|
|
`word_id` bigint NOT NULL DEFAULT 0 COMMENT 'Admin control-database word ID',
|
|
`word_group_id` bigint NOT NULL DEFAULT 0,
|
|
`word_snapshot` varchar(255) NOT NULL DEFAULT '' COMMENT 'Masked word snapshot',
|
|
`scene` varchar(32) NOT NULL,
|
|
`user_id` bigint NOT NULL DEFAULT 0,
|
|
`target_user_id` bigint DEFAULT NULL,
|
|
`topic` varchar(64) NOT NULL DEFAULT '',
|
|
`seqid` bigint DEFAULT NULL,
|
|
`content_sha256` char(64) NOT NULL DEFAULT '',
|
|
`matched_excerpt` varchar(500) NOT NULL DEFAULT '',
|
|
`match_type` varchar(24) NOT NULL DEFAULT 'contains',
|
|
`action` varchar(24) NOT NULL,
|
|
`risk_level` varchar(16) NOT NULL DEFAULT 'medium',
|
|
`request_id` varchar(64) NOT NULL DEFAULT '',
|
|
`metadata` json DEFAULT NULL,
|
|
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (`id`),
|
|
KEY `idx_im_sensitive_hit_scene_time` (`scene`, `created_at` DESC, `id` DESC),
|
|
KEY `idx_im_sensitive_hit_user_time` (`user_id`, `created_at` DESC, `id` DESC),
|
|
KEY `idx_im_sensitive_hit_target` (`topic`, `seqid`, `id` DESC),
|
|
KEY `idx_im_sensitive_hit_word` (`word_id`, `created_at` DESC),
|
|
KEY `idx_im_sensitive_hit_request` (`request_id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Sensitive-word match events';
|
|
|
|
CREATE TABLE IF NOT EXISTS `im_policy_decision_logs` (
|
|
`id` bigint NOT NULL AUTO_INCREMENT,
|
|
`policy_type` varchar(32) NOT NULL COMMENT 'security or user_policy_group',
|
|
`policy_version` bigint unsigned NOT NULL DEFAULT 0,
|
|
`policy_group_id` bigint NOT NULL DEFAULT 0,
|
|
`scene` varchar(64) NOT NULL COMMENT 'friend_request, qrcode_resolve, private_message and so on',
|
|
`action` varchar(24) NOT NULL COMMENT 'deny, challenge or audit_only',
|
|
`reason_code` varchar(96) NOT NULL COMMENT 'For example FRIEND_DISABLED or MESSAGE_CURFEW_ACTIVE',
|
|
`user_id` bigint NOT NULL DEFAULT 0,
|
|
`target_user_id` bigint DEFAULT NULL,
|
|
`topic` varchar(64) NOT NULL DEFAULT '',
|
|
`request_id` varchar(64) NOT NULL DEFAULT '',
|
|
`ip` varchar(64) NOT NULL DEFAULT '',
|
|
`metadata` json DEFAULT NULL,
|
|
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (`id`),
|
|
KEY `idx_im_policy_decision_user_time` (`user_id`, `created_at` DESC, `id` DESC),
|
|
KEY `idx_im_policy_decision_reason_time` (`reason_code`, `created_at` DESC, `id` DESC),
|
|
KEY `idx_im_policy_decision_scene_time` (`scene`, `created_at` DESC, `id` DESC),
|
|
KEY `idx_im_policy_decision_request` (`request_id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Runtime policy deny/challenge decisions';
|
|
|
|
-- ============================================================
|
|
-- 5. Group event history and daily IP/account statistics.
|
|
-- im_group_events_outbox remains the delivery table; this log is the queryable
|
|
-- business history after the operation result is known.
|
|
-- ============================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS `im_group_event_logs` (
|
|
`id` bigint NOT NULL AUTO_INCREMENT,
|
|
`event_id` varchar(96) NOT NULL,
|
|
`topic` varchar(64) NOT NULL,
|
|
`group_id` bigint DEFAULT NULL,
|
|
`event_type` varchar(64) NOT NULL,
|
|
`actor_user_id` bigint NOT NULL DEFAULT 0,
|
|
`target_user_id` bigint DEFAULT NULL,
|
|
`result` varchar(16) NOT NULL DEFAULT 'success',
|
|
`reason` varchar(500) NOT NULL DEFAULT '',
|
|
`request_id` varchar(64) NOT NULL DEFAULT '',
|
|
`before_snapshot` json DEFAULT NULL,
|
|
`after_snapshot` json DEFAULT NULL,
|
|
`metadata` json DEFAULT NULL,
|
|
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `uk_im_group_event_log_event` (`event_id`),
|
|
KEY `idx_im_group_event_topic_time` (`topic`, `created_at` DESC, `id` DESC),
|
|
KEY `idx_im_group_event_type_time` (`event_type`, `created_at` DESC, `id` DESC),
|
|
KEY `idx_im_group_event_actor_time` (`actor_user_id`, `created_at` DESC, `id` DESC),
|
|
KEY `idx_im_group_event_request` (`request_id`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Queryable group event audit history';
|
|
|
|
CREATE TABLE IF NOT EXISTS `im_ip_account_stats_daily` (
|
|
`id` bigint NOT NULL AUTO_INCREMENT,
|
|
`stat_date` date NOT NULL,
|
|
`ip` varchar(64) NOT NULL,
|
|
`region` varchar(128) NOT NULL DEFAULT '',
|
|
`account_count` int unsigned NOT NULL DEFAULT 0,
|
|
`user_count` int unsigned NOT NULL DEFAULT 0,
|
|
`login_success_count` int unsigned NOT NULL DEFAULT 0,
|
|
`login_failure_count` int unsigned NOT NULL DEFAULT 0,
|
|
`first_seen_at` datetime(3) DEFAULT NULL,
|
|
`last_seen_at` datetime(3) DEFAULT NULL,
|
|
`risk_level` varchar(16) NOT NULL DEFAULT 'low',
|
|
`risk_action` varchar(24) NOT NULL DEFAULT 'audit_only',
|
|
`sample_user_ids` json DEFAULT NULL COMMENT 'Bounded sample only, not an unlimited array',
|
|
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
`updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `uk_im_ip_account_stats_day_ip` (`stat_date`, `ip`),
|
|
KEY `idx_im_ip_account_stats_ip_day` (`ip`, `stat_date` DESC),
|
|
KEY `idx_im_ip_account_stats_risk_day` (`risk_level`, `stat_date` DESC, `id` DESC)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Daily IP and account aggregation';
|
|
|
|
-- ============================================================
|
|
-- 6. Compatible enhancements to existing tables.
|
|
-- Every ALTER is guarded by information_schema and can be rerun.
|
|
-- ============================================================
|
|
|
|
-- Message management queries need topic/time and sender/time indexes.
|
|
SET @im_sql = IF(
|
|
(SELECT COUNT(*) FROM information_schema.STATISTICS
|
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_messages'
|
|
AND INDEX_NAME = 'idx_im_messages_topic_created') = 0,
|
|
'ALTER TABLE `im_messages` ADD INDEX `idx_im_messages_topic_created` (`topic`, `createdat`, `id`)',
|
|
'SELECT 1'
|
|
);
|
|
PREPARE im_stmt FROM @im_sql;
|
|
EXECUTE im_stmt;
|
|
DEALLOCATE PREPARE im_stmt;
|
|
|
|
SET @im_sql = IF(
|
|
(SELECT COUNT(*) FROM information_schema.STATISTICS
|
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_messages'
|
|
AND INDEX_NAME = 'idx_im_messages_from_created') = 0,
|
|
'ALTER TABLE `im_messages` ADD INDEX `idx_im_messages_from_created` (`from`, `createdat`, `id`)',
|
|
'SELECT 1'
|
|
);
|
|
PREPARE im_stmt FROM @im_sql;
|
|
EXECUTE im_stmt;
|
|
DEALLOCATE PREPARE im_stmt;
|
|
|
|
-- Extend the existing Tinode device record for device and login-history views.
|
|
SET @im_sql = IF(
|
|
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_devices'
|
|
AND COLUMN_NAME = 'firstseen') = 0,
|
|
'ALTER TABLE `im_devices` ADD COLUMN `firstseen` DATETIME(3) DEFAULT NULL AFTER `platform`',
|
|
'SELECT 1'
|
|
);
|
|
PREPARE im_stmt FROM @im_sql;
|
|
EXECUTE im_stmt;
|
|
DEALLOCATE PREPARE im_stmt;
|
|
|
|
SET @im_sql = IF(
|
|
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_devices'
|
|
AND COLUMN_NAME = 'lastip') = 0,
|
|
'ALTER TABLE `im_devices` ADD COLUMN `lastip` VARCHAR(64) NOT NULL DEFAULT '''' AFTER `lastseen`',
|
|
'SELECT 1'
|
|
);
|
|
PREPARE im_stmt FROM @im_sql;
|
|
EXECUTE im_stmt;
|
|
DEALLOCATE PREPARE im_stmt;
|
|
|
|
SET @im_sql = IF(
|
|
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_devices'
|
|
AND COLUMN_NAME = 'useragent') = 0,
|
|
'ALTER TABLE `im_devices` ADD COLUMN `useragent` VARCHAR(512) NOT NULL DEFAULT '''' AFTER `lastip`',
|
|
'SELECT 1'
|
|
);
|
|
PREPARE im_stmt FROM @im_sql;
|
|
EXECUTE im_stmt;
|
|
DEALLOCATE PREPARE im_stmt;
|
|
|
|
SET @im_sql = IF(
|
|
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_devices'
|
|
AND COLUMN_NAME = 'appversion') = 0,
|
|
'ALTER TABLE `im_devices` ADD COLUMN `appversion` VARCHAR(32) NOT NULL DEFAULT '''' AFTER `useragent`',
|
|
'SELECT 1'
|
|
);
|
|
PREPARE im_stmt FROM @im_sql;
|
|
EXECUTE im_stmt;
|
|
DEALLOCATE PREPARE im_stmt;
|
|
|
|
SET @im_sql = IF(
|
|
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_devices'
|
|
AND COLUMN_NAME = 'status') = 0,
|
|
'ALTER TABLE `im_devices` ADD COLUMN `status` VARCHAR(16) NOT NULL DEFAULT ''active'' AFTER `appversion`',
|
|
'SELECT 1'
|
|
);
|
|
PREPARE im_stmt FROM @im_sql;
|
|
EXECUTE im_stmt;
|
|
DEALLOCATE PREPARE im_stmt;
|
|
|
|
-- Feedback handling fields required by the Admin processing workflow.
|
|
SET @im_sql = IF(
|
|
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_feedback_submissions'
|
|
AND COLUMN_NAME = 'assigned_to') = 0,
|
|
'ALTER TABLE `im_feedback_submissions` ADD COLUMN `assigned_to` BIGINT DEFAULT NULL AFTER `status`',
|
|
'SELECT 1'
|
|
);
|
|
PREPARE im_stmt FROM @im_sql;
|
|
EXECUTE im_stmt;
|
|
DEALLOCATE PREPARE im_stmt;
|
|
|
|
SET @im_sql = IF(
|
|
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_feedback_submissions'
|
|
AND COLUMN_NAME = 'handled_by') = 0,
|
|
'ALTER TABLE `im_feedback_submissions` ADD COLUMN `handled_by` BIGINT DEFAULT NULL AFTER `assigned_to`',
|
|
'SELECT 1'
|
|
);
|
|
PREPARE im_stmt FROM @im_sql;
|
|
EXECUTE im_stmt;
|
|
DEALLOCATE PREPARE im_stmt;
|
|
|
|
SET @im_sql = IF(
|
|
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_feedback_submissions'
|
|
AND COLUMN_NAME = 'handled_at') = 0,
|
|
'ALTER TABLE `im_feedback_submissions` ADD COLUMN `handled_at` DATETIME(3) DEFAULT NULL AFTER `handled_by`',
|
|
'SELECT 1'
|
|
);
|
|
PREPARE im_stmt FROM @im_sql;
|
|
EXECUTE im_stmt;
|
|
DEALLOCATE PREPARE im_stmt;
|
|
|
|
SET @im_sql = IF(
|
|
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_feedback_submissions'
|
|
AND COLUMN_NAME = 'resolution') = 0,
|
|
'ALTER TABLE `im_feedback_submissions` ADD COLUMN `resolution` VARCHAR(1000) NOT NULL DEFAULT '''' AFTER `handled_at`',
|
|
'SELECT 1'
|
|
);
|
|
PREPARE im_stmt FROM @im_sql;
|
|
EXECUTE im_stmt;
|
|
DEALLOCATE PREPARE im_stmt;
|
|
|
|
SET @im_sql = IF(
|
|
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_feedback_submissions'
|
|
AND COLUMN_NAME = 'admin_remark') = 0,
|
|
'ALTER TABLE `im_feedback_submissions` ADD COLUMN `admin_remark` VARCHAR(1000) NOT NULL DEFAULT '''' AFTER `resolution`',
|
|
'SELECT 1'
|
|
);
|
|
PREPARE im_stmt FROM @im_sql;
|
|
EXECUTE im_stmt;
|
|
DEALLOCATE PREPARE im_stmt;
|
|
|
|
SET @im_sql = IF(
|
|
(SELECT COUNT(*) FROM information_schema.STATISTICS
|
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_feedback_submissions'
|
|
AND INDEX_NAME = 'idx_im_feedback_status_time') = 0,
|
|
'ALTER TABLE `im_feedback_submissions` ADD INDEX `idx_im_feedback_status_time` (`status`, `created_at`, `id`)',
|
|
'SELECT 1'
|
|
);
|
|
PREPARE im_stmt FROM @im_sql;
|
|
EXECUTE im_stmt;
|
|
DEALLOCATE PREPARE im_stmt;
|
|
|
|
SET @im_sql = IF(
|
|
(SELECT COUNT(*) FROM information_schema.STATISTICS
|
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_audit_logs'
|
|
AND INDEX_NAME = 'idx_im_audit_logs_ip_time') = 0,
|
|
'ALTER TABLE `im_audit_logs` ADD INDEX `idx_im_audit_logs_ip_time` (`ip`, `created_at`, `id`)',
|
|
'SELECT 1'
|
|
);
|
|
PREPARE im_stmt FROM @im_sql;
|
|
EXECUTE im_stmt;
|
|
DEALLOCATE PREPARE im_stmt;
|
|
|
|
-- Post-deployment checks (read-only):
|
|
-- SELECT policy_type, policy_version, apply_status, applied_at
|
|
-- FROM im_runtime_policy_snapshots ORDER BY policy_type, policy_version DESC;
|
|
-- SELECT reason_code, scene, COUNT(*) AS deny_count
|
|
-- FROM im_policy_decision_logs
|
|
-- WHERE created_at >= CURRENT_DATE
|
|
-- GROUP BY reason_code, scene;
|
|
-- SELECT stat_date, ip, account_count, login_failure_count, risk_level
|
|
-- FROM im_ip_account_stats_daily ORDER BY stat_date DESC, login_failure_count DESC;
|