-- =============================================================
-- WebRTCSignalServer 数据库建表脚本 (MySQL 8.0+)
--
-- 【请手动执行，服务端代码当前仍为内存实现，不会自动建表】
--
-- 执行方式：
--   mysql -u root -p < schema.sql
--
-- 字符集统一使用 utf8mb4 / utf8mb4_0900_ai_ci
-- 所有时间字段使用 DATETIME(3)，由应用层写入 UTC 时间
-- =============================================================

CREATE DATABASE IF NOT EXISTS `webrtc_signal`
    DEFAULT CHARACTER SET utf8mb4
    DEFAULT COLLATE utf8mb4_0900_ai_ci;

USE `webrtc_signal`;


-- =============================================================
-- 1. 主控端用户账号
-- =============================================================
CREATE TABLE IF NOT EXISTS `app_user` (
    `user_id`         VARCHAR(32)  NOT NULL COMMENT '用户ID，格式 usr_<20位Base62>',
    `username`        VARCHAR(32)  NOT NULL COMMENT '登录名',
    `password_hash`   VARCHAR(100) NOT NULL COMMENT 'BCrypt 哈希（cost=12）',
    `status`          VARCHAR(16)  NOT NULL DEFAULT 'ACTIVE'
                      COMMENT '账号状态：ACTIVE/SUSPENDED/BANNED',
    `status_until`    DATETIME(3)  NULL COMMENT '临时封禁到期时间，NULL 表示永久或未封禁',
    `status_reason`   VARCHAR(255) NULL COMMENT '封禁原因',
    `token_version`   BIGINT       NOT NULL DEFAULT 1
                      COMMENT '凭据版本号，递增后所有已签发令牌立即失效',
    `is_admin`        TINYINT(1)   NOT NULL DEFAULT 0 COMMENT '是否管理员',

    -- 双因子认证
    `totp_secret`     VARCHAR(64)  NULL COMMENT 'TOTP 密钥（Base32），NULL 表示未启用',
    `totp_enabled`    TINYINT(1)   NOT NULL DEFAULT 0 COMMENT 'TOTP 是否已完成绑定',

    -- 防爆破
    `failed_attempts` INT          NOT NULL DEFAULT 0 COMMENT '连续登录失败次数',
    `locked_until`    DATETIME(3)  NULL COMMENT '锁定截止时间',

    `last_login_at`   DATETIME(3)  NULL,
    `last_login_ip`   VARCHAR(64)  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 (`user_id`),
    UNIQUE KEY `uk_username` (`username`),
    KEY `idx_status` (`status`)
) ENGINE=InnoDB COMMENT='主控端用户账号';


-- =============================================================
-- 2. 密码历史（防止重复使用近期密码）
-- =============================================================
CREATE TABLE IF NOT EXISTS `password_history` (
    `id`            BIGINT       NOT NULL AUTO_INCREMENT,
    `user_id`       VARCHAR(32)  NOT NULL,
    `password_hash` VARCHAR(100) NOT NULL,
    `created_at`    DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3),

    PRIMARY KEY (`id`),
    KEY `idx_user_created` (`user_id`, `created_at` DESC),
    CONSTRAINT `fk_pwdhist_user` FOREIGN KEY (`user_id`)
        REFERENCES `app_user` (`user_id`) ON DELETE CASCADE
) ENGINE=InnoDB COMMENT='密码历史，建议保留最近 5 条';


-- =============================================================
-- 3. 登录会话（刷新令牌上下文）
-- =============================================================
CREATE TABLE IF NOT EXISTS `login_session` (
    `session_id`         VARCHAR(32)  NOT NULL COMMENT '会话ID，格式 ses_<20位Base62>',
    `principal_id`       VARCHAR(32)  NOT NULL COMMENT '主体ID（user_id）',
    `principal_type`     VARCHAR(16)  NOT NULL DEFAULT 'USER' COMMENT 'USER/DEVICE',
    `refresh_token_hash` VARCHAR(64)  NOT NULL COMMENT '当前刷新令牌的 SHA-256，轮转后更新',
    `refresh_expires_at` DATETIME(3)  NOT NULL,
    `ip`                 VARCHAR(64)  NULL,
    `user_agent`         VARCHAR(256) NULL,
    `revoked`            TINYINT(1)   NOT NULL DEFAULT 0,
    `revoked_reason`     VARCHAR(255) NULL,
    `last_seen_at`       DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    `created_at`         DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3),

    PRIMARY KEY (`session_id`),
    KEY `idx_principal` (`principal_id`, `revoked`),
    KEY `idx_expires` (`refresh_expires_at`)
) ENGINE=InnoDB COMMENT='登录会话，支持踢线与刷新令牌轮转';


-- =============================================================
-- 4. SN 白名单（出厂/部署时批量导入）
-- =============================================================
CREATE TABLE IF NOT EXISTS `allowed_device` (
    `sn`          VARCHAR(64)  NOT NULL COMMENT '设备序列号',
    `batch`       VARCHAR(64)  NULL COMMENT '批次标识',
    `note`        VARCHAR(255) NULL,
    `imported_by` VARCHAR(64)  NULL,
    `imported_at` DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3),

    PRIMARY KEY (`sn`),
    KEY `idx_batch` (`batch`)
) ENGINE=InnoDB COMMENT='允许激活的 SN 白名单';


-- =============================================================
-- 5. 被控端设备
--    SN 仅作内部主键，对外一律使用高熵不可枚举的 device_uid
-- =============================================================
CREATE TABLE IF NOT EXISTS `device` (
    `device_uid`     VARCHAR(32)  NOT NULL COMMENT '对外设备ID，格式 dev_<22位Base62>',
    `sn`             VARCHAR(64)  NOT NULL COMMENT '设备序列号（内部使用，勿对外暴露）',
    `secret_hash`    VARCHAR(64)  NOT NULL COMMENT 'deviceSecret 的 SHA-256',
    `model`          VARCHAR(64)  NULL,
    `status`         VARCHAR(16)  NOT NULL DEFAULT 'ACTIVE'
                     COMMENT 'ACTIVE/SUSPENDED/BANNED',
    `status_until`   DATETIME(3)  NULL,
    `status_reason`  VARCHAR(255) NULL,
    `token_version`  BIGINT       NOT NULL DEFAULT 1,

    -- 防骚扰配置（P1 阶段使用）
    `strict_mode`    TINYINT(1)   NOT NULL DEFAULT 1
                     COMMENT '严格模式：仅允许已绑定账号连接',
    `quiet_start`    TIME         NULL COMMENT '勿扰时段开始',
    `quiet_end`      TIME         NULL COMMENT '勿扰时段结束',

    `provisioned_at` DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    `last_online_at` DATETIME(3)  NULL,

    PRIMARY KEY (`device_uid`),
    UNIQUE KEY `uk_sn` (`sn`),
    KEY `idx_status` (`status`)
) ENGINE=InnoDB COMMENT='被控端设备身份';


-- =============================================================
-- 6. 设备激活 nonce（防重放）
--    建议配合定时任务清理过期记录，或改用 Redis
-- =============================================================
CREATE TABLE IF NOT EXISTS `provision_nonce` (
    `nonce`      VARCHAR(64) NOT NULL,
    `sn`         VARCHAR(64) NULL,
    `expires_at` DATETIME(3) NOT NULL,
    `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),

    PRIMARY KEY (`nonce`),
    KEY `idx_expires` (`expires_at`)
) ENGINE=InnoDB COMMENT='激活请求 nonce，用于防重放';


-- =============================================================
-- 7. 设备绑定关系（P1 阶段：主控端唯一寻址凭证）
-- =============================================================
CREATE TABLE IF NOT EXISTS `device_binding` (
    `binding_id` VARCHAR(36)  NOT NULL COMMENT 'UUID',
    `device_uid` VARCHAR(32)  NOT NULL,
    `user_id`    VARCHAR(32)  NOT NULL,
    `role`       VARCHAR(16)  NOT NULL DEFAULT 'OPERATOR'
                 COMMENT 'OWNER/OPERATOR/VIEWER',
    `alias`      VARCHAR(64)  NULL COMMENT '主控端自定义备注名',
    `status`     VARCHAR(16)  NOT NULL DEFAULT 'ACTIVE' COMMENT 'ACTIVE/REVOKED',
    `bound_by`   VARCHAR(32)  NULL,
    `bound_at`   DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    `expire_at`  DATETIME(3)  NULL COMMENT '绑定过期时间，NULL 表示长期有效',

    PRIMARY KEY (`binding_id`),
    UNIQUE KEY `uk_device_user` (`device_uid`, `user_id`),
    KEY `idx_user_status` (`user_id`, `status`),
    KEY `idx_device_status` (`device_uid`, `status`)
) ENGINE=InnoDB COMMENT='设备与账号绑定关系';


-- =============================================================
-- 8. 配对码（P1 阶段：建立绑定）
-- =============================================================
CREATE TABLE IF NOT EXISTS `pairing_code` (
    `code_hash`  VARCHAR(64) NOT NULL COMMENT '配对码的 SHA-256',
    `device_uid` VARCHAR(32) NOT NULL,
    `attempts`   INT         NOT NULL DEFAULT 0 COMMENT '错误尝试次数，超限即失效',
    `used`       TINYINT(1)  NOT NULL DEFAULT 0,
    `expires_at` DATETIME(3) NOT NULL,
    `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),

    PRIMARY KEY (`code_hash`),
    KEY `idx_device` (`device_uid`),
    KEY `idx_expires` (`expires_at`)
) ENGINE=InnoDB COMMENT='一次性配对码';


-- =============================================================
-- 9. 被控端黑名单（P1 阶段：拉黑骚扰账号）
-- =============================================================
CREATE TABLE IF NOT EXISTS `device_blacklist` (
    `id`               BIGINT       NOT NULL AUTO_INCREMENT,
    `device_uid`       VARCHAR(32)  NOT NULL,
    `blocked_user_id`  VARCHAR(32)  NOT NULL,
    `reason`           VARCHAR(255) NULL,
    `created_at`       DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3),

    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_device_user` (`device_uid`, `blocked_user_id`),
    KEY `idx_device` (`device_uid`)
) ENGINE=InnoDB COMMENT='被控端黑名单';


-- =============================================================
-- 10. 连接会话记录（WebRTC 通话审计）
-- =============================================================
CREATE TABLE IF NOT EXISTS `connection_session` (
    `session_id`  VARCHAR(36)  NOT NULL,
    `binding_id`  VARCHAR(36)  NULL,
    `device_uid`  VARCHAR(32)  NOT NULL,
    `user_id`     VARCHAR(32)  NOT NULL,
    `auth_type`   VARCHAR(16)  NULL COMMENT 'NONE/CODE/PASSWORD',
    `started_at`  DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    `ended_at`    DATETIME(3)  NULL,
    `end_reason`  VARCHAR(64)  NULL,

    PRIMARY KEY (`session_id`),
    KEY `idx_device_started` (`device_uid`, `started_at` DESC),
    KEY `idx_user_started` (`user_id`, `started_at` DESC)
) ENGINE=InnoDB COMMENT='远程控制会话记录';


-- =============================================================
-- 11. 审计日志（只追加，不修改）
-- =============================================================
CREATE TABLE IF NOT EXISTS `audit_log` (
    `id`          BIGINT       NOT NULL AUTO_INCREMENT,
    `actor_type`  VARCHAR(16)  NOT NULL COMMENT 'USER/DEVICE/ADMIN/SYSTEM',
    `actor_id`    VARCHAR(64)  NULL,
    `action`      VARCHAR(64)  NOT NULL
                  COMMENT 'LOGIN/LOGIN_FAILED/LOGOUT/REGISTER/PROVISION/BAN/UNBAN/KICK/BIND/UNBIND/BLACKLIST...',
    `target_type` VARCHAR(16)  NULL,
    `target_id`   VARCHAR(64)  NULL,
    `result`      VARCHAR(16)  NOT NULL DEFAULT 'SUCCESS' COMMENT 'SUCCESS/FAILURE',
    `ip`          VARCHAR(64)  NULL,
    `user_agent`  VARCHAR(256) NULL,
    `detail`      VARCHAR(512) NULL COMMENT '注意：敏感信息需脱敏后再写入',
    `created_at`  DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3),

    PRIMARY KEY (`id`),
    KEY `idx_actor` (`actor_id`, `created_at` DESC),
    KEY `idx_action` (`action`, `created_at` DESC),
    KEY `idx_created` (`created_at` DESC)
) ENGINE=InnoDB COMMENT='安全审计日志';


-- =============================================================
-- 12. 骚扰举报（P2 阶段风控）
-- =============================================================
CREATE TABLE IF NOT EXISTS `abuse_report` (
    `id`               BIGINT       NOT NULL AUTO_INCREMENT,
    `device_uid`       VARCHAR(32)  NOT NULL COMMENT '举报方（被控端）',
    `reported_user_id` VARCHAR(32)  NOT NULL COMMENT '被举报账号',
    `reason`           VARCHAR(255) NULL,
    `handled`          TINYINT(1)   NOT NULL DEFAULT 0,
    `created_at`       DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3),

    PRIMARY KEY (`id`),
    KEY `idx_reported` (`reported_user_id`, `created_at` DESC),
    KEY `idx_handled` (`handled`)
) ENGINE=InnoDB COMMENT='骚扰举报记录';


-- =============================================================
-- 清理建议（可配置为 MySQL Event 或应用层定时任务）
-- =============================================================
-- DELETE FROM provision_nonce WHERE expires_at < NOW();
-- DELETE FROM pairing_code    WHERE expires_at < NOW();
-- DELETE FROM login_session   WHERE refresh_expires_at < NOW() OR revoked = 1;
-- DELETE FROM audit_log       WHERE created_at < DATE_SUB(NOW(), INTERVAL 180 DAY);


-- =============================================================
-- 初始管理员账号
-- =============================================================
-- 不在此处插入明文密码。请通过环境变量启动服务自动创建：
--   BOOTSTRAP_ADMIN_USERNAME=admin
--   BOOTSTRAP_ADMIN_PASSWORD=<强密码>
-- 或注册后手动提升权限：
--   UPDATE app_user SET is_admin = 1 WHERE username = 'admin';
