fix mysql tenant database schema cleanup
This commit is contained in:
@@ -0,0 +1,735 @@
|
|||||||
|
-- IM product database changes summary, chapters 1-11.
|
||||||
|
-- Target:
|
||||||
|
-- public backend database: hotgo
|
||||||
|
-- default tenant business database: tinode
|
||||||
|
-- Notes:
|
||||||
|
-- 1. Public tenant registry tables live in hotgo.
|
||||||
|
-- 2. Tenant-scoped business tables live in each tenant database and do not include tenant_id.
|
||||||
|
-- 3. This file is for review and manual execution. Existing legacy columns/indexes should be checked before ALTER/DROP.
|
||||||
|
-- 4. Table definitions are aligned with server/db/mysql/adapter.go plus im_verification_codes used by chapter 1 verification APIs.
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
|
||||||
|
CREATE DATABASE IF NOT EXISTS hotgo CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||||
|
CREATE DATABASE IF NOT EXISTS tinode CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Public database: tenant registry.
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS hotgo.im_tenant (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
code VARCHAR(64) NOT NULL,
|
||||||
|
name VARCHAR(128) NOT NULL,
|
||||||
|
tenant_desc VARCHAR(256),
|
||||||
|
db_name VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
db_dsn VARCHAR(1024) NOT NULL DEFAULT '',
|
||||||
|
state SMALLINT NOT NULL,
|
||||||
|
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
created_by BIGINT NOT NULL,
|
||||||
|
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
updated_by BIGINT NOT NULL,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE INDEX uk_im_tenant_code (code),
|
||||||
|
INDEX idx_im_tenant_db_name (db_name)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS hotgo.im_tenant_config (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
tenant_id BIGINT NOT NULL,
|
||||||
|
max_users BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '0 means unlimited',
|
||||||
|
max_groups BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '0 means unlimited',
|
||||||
|
max_group_members INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '0 means unlimited',
|
||||||
|
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
created_by BIGINT NOT NULL,
|
||||||
|
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
updated_by BIGINT NOT NULL,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE INDEX uk_im_tenant_config_tenant_id (tenant_id),
|
||||||
|
CONSTRAINT fk_im_tenant_config_tenant FOREIGN KEY (tenant_id) REFERENCES hotgo.im_tenant(id) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Tenant database: chapter 1, tenant-aware login, verification, security.
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_user_security (
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
auth_version INT NOT NULL DEFAULT 1,
|
||||||
|
password_updated_at DATETIME(3),
|
||||||
|
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)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_verification_codes (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
challenge_id VARCHAR(64) NOT NULL,
|
||||||
|
scene VARCHAR(32) NOT NULL,
|
||||||
|
action VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
phone_hash CHAR(64) NOT NULL,
|
||||||
|
phone_mask VARCHAR(32) NOT NULL,
|
||||||
|
target_user BIGINT,
|
||||||
|
code_hash CHAR(64) NOT NULL,
|
||||||
|
verification_token_hash CHAR(64),
|
||||||
|
state SMALLINT NOT NULL,
|
||||||
|
attempts INT NOT NULL DEFAULT 0,
|
||||||
|
max_attempts INT NOT NULL,
|
||||||
|
expires_at DATETIME(3) NOT NULL,
|
||||||
|
resend_after DATETIME(3) NOT NULL,
|
||||||
|
verified_at DATETIME(3),
|
||||||
|
consumed_at DATETIME(3),
|
||||||
|
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 INDEX uk_im_verification_challenge (challenge_id),
|
||||||
|
INDEX idx_im_verification_scene_phone_state (scene, phone_hash, state, expires_at)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_realname_verifications (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
submission_id VARCHAR(64) NOT NULL,
|
||||||
|
status VARCHAR(16) NOT NULL,
|
||||||
|
name_cipher TEXT NOT NULL,
|
||||||
|
id_type VARCHAR(32) NOT NULL,
|
||||||
|
id_number_cipher TEXT NOT NULL,
|
||||||
|
id_number_hash CHAR(64) NOT NULL,
|
||||||
|
masked_name VARCHAR(64) NOT NULL,
|
||||||
|
masked_id_number VARCHAR(64) NOT NULL,
|
||||||
|
review_reason VARCHAR(256),
|
||||||
|
verified_at DATETIME(3),
|
||||||
|
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 INDEX uk_im_realname_submission (submission_id),
|
||||||
|
INDEX idx_im_realname_user_latest (user_id, created_at),
|
||||||
|
INDEX idx_im_realname_id_hash (id_number_hash)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_action_tokens (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
token_id VARCHAR(64) NOT NULL,
|
||||||
|
action VARCHAR(64) NOT NULL,
|
||||||
|
target VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
token_hash CHAR(64) NOT NULL,
|
||||||
|
state SMALLINT NOT NULL,
|
||||||
|
expires_at DATETIME(3) NOT NULL,
|
||||||
|
consumed_at DATETIME(3),
|
||||||
|
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 INDEX uk_im_action_tokens_token (token_id),
|
||||||
|
INDEX idx_im_action_tokens_user_action (user_id, action, target, state, expires_at)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_youth_mode_settings (
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
purpose VARCHAR(64) NOT NULL,
|
||||||
|
password_hash VARBINARY(255) NOT NULL,
|
||||||
|
enabled TINYINT NOT NULL DEFAULT 0,
|
||||||
|
policy_version INT NOT NULL DEFAULT 1,
|
||||||
|
restrictions JSON,
|
||||||
|
enabled_at DATETIME(3),
|
||||||
|
failed_attempts INT NOT NULL DEFAULT 0,
|
||||||
|
locked_until DATETIME(3),
|
||||||
|
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, purpose)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Tenant database: chapters 2-7, notifications, messages, friends, files, favorites.
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_system_notifications (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
event_id VARCHAR(64) NOT NULL,
|
||||||
|
event_type VARCHAR(64) NOT NULL,
|
||||||
|
title VARCHAR(128) NOT NULL,
|
||||||
|
summary VARCHAR(512) NOT NULL,
|
||||||
|
action JSON,
|
||||||
|
read_at DATETIME(3),
|
||||||
|
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 INDEX uk_im_system_notifications_event (user_id, event_id),
|
||||||
|
INDEX idx_im_system_notifications_inbox (user_id, id DESC)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_message_revisions (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
createdat DATETIME(3) NOT NULL,
|
||||||
|
updatedat DATETIME(3) NOT NULL,
|
||||||
|
topic CHAR(25) NOT NULL,
|
||||||
|
seqid INT NOT NULL,
|
||||||
|
version INT NOT NULL,
|
||||||
|
actor BIGINT NOT NULL,
|
||||||
|
head JSON,
|
||||||
|
content JSON,
|
||||||
|
reason VARCHAR(256) NOT NULL DEFAULT '',
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE INDEX uk_im_message_revisions_version (topic, seqid, version)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_friend_requests (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
request_id VARCHAR(64) NOT NULL,
|
||||||
|
applicant_id BIGINT NOT NULL,
|
||||||
|
target_id BIGINT NOT NULL,
|
||||||
|
message VARCHAR(200) NOT NULL DEFAULT '',
|
||||||
|
source VARCHAR(32) NOT NULL DEFAULT '',
|
||||||
|
source_topic VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
status VARCHAR(16) NOT NULL,
|
||||||
|
idempotency_key VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
expires_at DATETIME(3) NOT NULL,
|
||||||
|
handled_at DATETIME(3),
|
||||||
|
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 INDEX uk_im_friend_requests_request (request_id),
|
||||||
|
UNIQUE INDEX uk_im_friend_requests_idempotency (applicant_id, idempotency_key),
|
||||||
|
INDEX idx_im_friend_requests_pair (applicant_id, target_id, status, created_at),
|
||||||
|
INDEX idx_im_friend_requests_inbox (target_id, status, created_at)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_friendships (
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
peer_id BIGINT NOT NULL,
|
||||||
|
status VARCHAR(16) NOT NULL,
|
||||||
|
remark VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
topic CHAR(25) NOT NULL DEFAULT '',
|
||||||
|
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, peer_id),
|
||||||
|
INDEX idx_im_friendships_peer (peer_id, status)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_user_blocks (
|
||||||
|
blocker_id BIGINT NOT NULL,
|
||||||
|
blocked_id BIGINT NOT 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 (blocker_id, blocked_id),
|
||||||
|
INDEX idx_im_user_blocks_blocked (blocked_id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_favorites (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
favorite_id VARCHAR(64) NOT NULL,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
item_type VARCHAR(16) NOT NULL,
|
||||||
|
source_topic CHAR(25) NOT NULL,
|
||||||
|
source_seq INT NOT NULL,
|
||||||
|
snapshot JSON,
|
||||||
|
attachment_refs JSON,
|
||||||
|
availability VARCHAR(16) NOT NULL DEFAULT 'available',
|
||||||
|
idempotency_key VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
collected_at DATETIME(3) NOT 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 INDEX uk_im_favorites_favorite (favorite_id),
|
||||||
|
UNIQUE INDEX uk_im_favorites_source (user_id, source_topic, source_seq),
|
||||||
|
UNIQUE INDEX uk_im_favorites_idempotency (user_id, ((NULLIF(idempotency_key, '')))),
|
||||||
|
INDEX idx_im_favorites_inbox (user_id, id DESC),
|
||||||
|
INDEX idx_im_favorites_type (user_id, item_type, id DESC)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
-- Existing Tinode tables must match the current schema for chapters 2-7:
|
||||||
|
-- topics.aux, fileuploads.etag, and filemsglinks.topic/userid are required by current code.
|
||||||
|
|
||||||
|
SET @im_sql = IF(
|
||||||
|
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'tinode' AND TABLE_NAME = 'messages' AND COLUMN_NAME = 'client_msg_id') = 0,
|
||||||
|
'ALTER TABLE tinode.messages ADD COLUMN client_msg_id VARCHAR(64) AFTER `from`',
|
||||||
|
'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 = 'tinode' AND TABLE_NAME = 'messages' AND COLUMN_NAME = 'version') = 0,
|
||||||
|
'ALTER TABLE tinode.messages ADD COLUMN version INT NOT NULL DEFAULT 1 AFTER client_msg_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.COLUMNS WHERE TABLE_SCHEMA = 'tinode' AND TABLE_NAME = 'messages' AND COLUMN_NAME = 'recalled_at') = 0,
|
||||||
|
'ALTER TABLE tinode.messages ADD COLUMN recalled_at DATETIME(3) AFTER version',
|
||||||
|
'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 = 'tinode' AND TABLE_NAME = 'messages' AND COLUMN_NAME = 'recalled_by') = 0,
|
||||||
|
'ALTER TABLE tinode.messages ADD COLUMN recalled_by BIGINT AFTER recalled_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.STATISTICS WHERE TABLE_SCHEMA = 'tinode' AND TABLE_NAME = 'messages' AND INDEX_NAME = 'messages_sender_client_mid') = 0,
|
||||||
|
'CREATE UNIQUE INDEX messages_sender_client_mid ON tinode.messages(`from`, client_msg_id)',
|
||||||
|
'SELECT 1'
|
||||||
|
);
|
||||||
|
PREPARE im_stmt FROM @im_sql;
|
||||||
|
EXECUTE im_stmt;
|
||||||
|
DEALLOCATE PREPARE im_stmt;
|
||||||
|
|
||||||
|
-- Independent-DB tenant isolation: tenant business tables must not keep tenant_id.
|
||||||
|
DROP TABLE IF EXISTS tinode.im_tenant_config;
|
||||||
|
DROP TABLE IF EXISTS tinode.im_tenant;
|
||||||
|
|
||||||
|
SET @im_sql = IF(
|
||||||
|
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'tinode' AND TABLE_NAME = 'auth' AND COLUMN_NAME = 'tenant_id') > 0,
|
||||||
|
'ALTER TABLE tinode.auth DROP COLUMN tenant_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.COLUMNS WHERE TABLE_SCHEMA = 'tinode' AND TABLE_NAME = 'credentials' AND COLUMN_NAME = 'tenant_id') > 0,
|
||||||
|
'ALTER TABLE tinode.credentials DROP COLUMN tenant_id',
|
||||||
|
'SELECT 1'
|
||||||
|
);
|
||||||
|
PREPARE im_stmt FROM @im_sql;
|
||||||
|
EXECUTE im_stmt;
|
||||||
|
DEALLOCATE PREPARE im_stmt;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Tenant database: chapter 8, realtime call records.
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_call_records (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
call_id VARCHAR(64) NOT NULL,
|
||||||
|
topic CHAR(25) NOT NULL,
|
||||||
|
seq_id INT NOT NULL,
|
||||||
|
caller_id BIGINT NOT NULL,
|
||||||
|
callee_id BIGINT NOT NULL,
|
||||||
|
call_type VARCHAR(16) NOT NULL,
|
||||||
|
status VARCHAR(32) NOT NULL,
|
||||||
|
end_reason VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
duration_ms BIGINT NOT NULL DEFAULT 0,
|
||||||
|
started_at DATETIME(3) NOT NULL,
|
||||||
|
accepted_at DATETIME(3),
|
||||||
|
finished_at DATETIME(3),
|
||||||
|
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 INDEX uk_im_call_records_call (call_id),
|
||||||
|
UNIQUE INDEX uk_im_call_records_topic_seq (topic, seq_id),
|
||||||
|
INDEX idx_im_call_records_caller (caller_id, id DESC),
|
||||||
|
INDEX idx_im_call_records_callee (callee_id, id DESC),
|
||||||
|
INDEX idx_im_call_records_status (status, updated_at)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Tenant database: chapter 9, workbench and office collaboration.
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_workbench_apps (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
code VARCHAR(64) NOT NULL,
|
||||||
|
name VARCHAR(128) NOT NULL,
|
||||||
|
icon_file_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
route VARCHAR(128) NOT NULL,
|
||||||
|
sort_order INT NOT NULL DEFAULT 0,
|
||||||
|
unread_key VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
enabled TINYINT NOT NULL DEFAULT 1,
|
||||||
|
permission VARCHAR(64) NOT NULL DEFAULT 'enabled',
|
||||||
|
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 INDEX uk_im_workbench_apps_code (code),
|
||||||
|
INDEX idx_im_workbench_apps_enabled_order (enabled, sort_order)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_org_departments (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
department_id VARCHAR(64) NOT NULL,
|
||||||
|
name VARCHAR(128) NOT NULL,
|
||||||
|
parent_id VARCHAR(64) NOT NULL DEFAULT 'root',
|
||||||
|
sort_order INT NOT NULL DEFAULT 0,
|
||||||
|
version INT NOT NULL DEFAULT 1,
|
||||||
|
state VARCHAR(16) NOT NULL DEFAULT 'active',
|
||||||
|
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 INDEX uk_im_org_departments_department (department_id),
|
||||||
|
INDEX idx_im_org_departments_parent (parent_id, state, sort_order, id),
|
||||||
|
INDEX idx_im_org_departments_name (name)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_org_members (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
department_id VARCHAR(64) NOT NULL,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
display_name VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
position VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
avatar_file_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
mobile VARCHAR(32) NOT NULL DEFAULT '',
|
||||||
|
email VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
employment_state VARCHAR(16) NOT NULL DEFAULT 'active',
|
||||||
|
contactable TINYINT NOT NULL DEFAULT 1,
|
||||||
|
sort_order INT NOT NULL DEFAULT 0,
|
||||||
|
version INT NOT NULL DEFAULT 1,
|
||||||
|
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 INDEX uk_im_org_members_department_user (department_id, user_id),
|
||||||
|
INDEX idx_im_org_members_department (department_id, employment_state, sort_order, id),
|
||||||
|
INDEX idx_im_org_members_user (user_id),
|
||||||
|
INDEX idx_im_org_members_display_name (display_name),
|
||||||
|
CONSTRAINT fk_im_org_members_department FOREIGN KEY (department_id) REFERENCES tinode.im_org_departments(department_id) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_meetings (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
meeting_id VARCHAR(64) NOT NULL,
|
||||||
|
organizer_id BIGINT NOT NULL,
|
||||||
|
title VARCHAR(200) NOT NULL,
|
||||||
|
start_at DATETIME(3) NOT NULL,
|
||||||
|
end_at DATETIME(3) NOT NULL,
|
||||||
|
location VARCHAR(256) NOT NULL DEFAULT '',
|
||||||
|
state VARCHAR(16) NOT NULL DEFAULT 'scheduled',
|
||||||
|
reminder_minutes JSON,
|
||||||
|
idempotency_key VARCHAR(128),
|
||||||
|
version INT NOT NULL DEFAULT 1,
|
||||||
|
canceled_at DATETIME(3),
|
||||||
|
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 INDEX uk_im_meetings_meeting (meeting_id),
|
||||||
|
UNIQUE INDEX uk_im_meetings_idempotency (organizer_id, idempotency_key),
|
||||||
|
INDEX idx_im_meetings_organizer (organizer_id, start_at),
|
||||||
|
INDEX idx_im_meetings_state_time (state, start_at)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_meeting_participants (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
meeting_id VARCHAR(64) NOT NULL,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
response VARCHAR(16) NOT NULL DEFAULT 'pending',
|
||||||
|
response_at DATETIME(3),
|
||||||
|
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 INDEX uk_im_meeting_participants_user (meeting_id, user_id),
|
||||||
|
INDEX idx_im_meeting_participants_user_time (user_id, meeting_id),
|
||||||
|
INDEX idx_im_meeting_participants_response (meeting_id, response),
|
||||||
|
CONSTRAINT fk_im_meeting_participants_meeting FOREIGN KEY (meeting_id) REFERENCES tinode.im_meetings(meeting_id) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_daily_reports (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
report_id VARCHAR(64) NOT NULL,
|
||||||
|
author_id BIGINT NOT NULL,
|
||||||
|
report_date DATE NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
attachments JSON,
|
||||||
|
status VARCHAR(16) NOT NULL DEFAULT 'draft',
|
||||||
|
read_state VARCHAR(16) NOT NULL DEFAULT 'unread',
|
||||||
|
version INT NOT NULL DEFAULT 1,
|
||||||
|
submitted_at DATETIME(3),
|
||||||
|
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 INDEX uk_im_daily_reports_report (report_id),
|
||||||
|
UNIQUE INDEX uk_im_daily_reports_author_date (author_id, report_date),
|
||||||
|
INDEX idx_im_daily_reports_author_id (author_id, id DESC),
|
||||||
|
INDEX idx_im_daily_reports_date_status (report_date, status, id DESC)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_monthly_reports (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
report_id VARCHAR(64) NOT NULL,
|
||||||
|
author_id BIGINT NOT NULL,
|
||||||
|
report_month CHAR(7) NOT NULL,
|
||||||
|
work_summary TEXT NOT NULL,
|
||||||
|
next_plan TEXT NOT NULL,
|
||||||
|
attachments JSON,
|
||||||
|
status VARCHAR(16) NOT NULL DEFAULT 'draft',
|
||||||
|
read_state VARCHAR(16) NOT NULL DEFAULT 'unread',
|
||||||
|
version INT NOT NULL DEFAULT 1,
|
||||||
|
submitted_at DATETIME(3),
|
||||||
|
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 INDEX uk_im_monthly_reports_report (report_id),
|
||||||
|
UNIQUE INDEX uk_im_monthly_reports_author_month (author_id, report_month),
|
||||||
|
INDEX idx_im_monthly_reports_author_id (author_id, id DESC),
|
||||||
|
INDEX idx_im_monthly_reports_month_status (report_month, status, id DESC)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_mailbox_config (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
enabled TINYINT NOT NULL DEFAULT 1,
|
||||||
|
allow_anonymous TINYINT NOT NULL DEFAULT 1,
|
||||||
|
required_fields JSON,
|
||||||
|
max_length INT NOT NULL DEFAULT 5000,
|
||||||
|
attachment_policy JSON,
|
||||||
|
notice_title VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
notice_content TEXT NOT NULL,
|
||||||
|
notice_updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
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)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_mailbox_submissions (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
submission_id VARCHAR(64) NOT NULL,
|
||||||
|
sender_id BIGINT NOT NULL,
|
||||||
|
subject VARCHAR(200) NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
anonymous TINYINT NOT NULL DEFAULT 0,
|
||||||
|
attachment_file_ids JSON,
|
||||||
|
notice_version VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
idempotency_key VARCHAR(128) NOT NULL,
|
||||||
|
status VARCHAR(32) NOT NULL DEFAULT 'received',
|
||||||
|
submitted_at DATETIME(3) NOT 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 INDEX uk_im_mailbox_submissions_submission (submission_id),
|
||||||
|
UNIQUE INDEX uk_im_mailbox_submissions_idempotency (sender_id, idempotency_key),
|
||||||
|
INDEX idx_im_mailbox_submissions_sender (sender_id, id DESC),
|
||||||
|
INDEX idx_im_mailbox_submissions_status (status, id DESC)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Tenant database: chapter 10, profile, privacy, app release, push, youth mode.
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_user_profiles (
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
avatar_file_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
nickname VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
birth_date DATE,
|
||||||
|
signature VARCHAR(256) NOT NULL DEFAULT '',
|
||||||
|
gender VARCHAR(16) NOT NULL DEFAULT '',
|
||||||
|
region VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
public_account VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
version INT NOT NULL DEFAULT 1,
|
||||||
|
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)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_user_privacy_settings (
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
add_me_methods JSON,
|
||||||
|
profile_visibility VARCHAR(32) NOT NULL DEFAULT 'friends',
|
||||||
|
last_seen_visibility VARCHAR(32) NOT NULL DEFAULT 'friends',
|
||||||
|
read_receipts TINYINT NOT NULL DEFAULT 1,
|
||||||
|
version INT NOT NULL DEFAULT 1,
|
||||||
|
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)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_app_releases (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
platform VARCHAR(32) NOT NULL,
|
||||||
|
channel VARCHAR(32) NOT NULL DEFAULT 'stable',
|
||||||
|
latest_version VARCHAR(64) NOT NULL,
|
||||||
|
min_supported_version VARCHAR(64) NOT NULL,
|
||||||
|
update_type VARCHAR(16) NOT NULL DEFAULT 'none',
|
||||||
|
release_notes TEXT NOT NULL,
|
||||||
|
store_url VARCHAR(512) NOT NULL DEFAULT '',
|
||||||
|
package_url VARCHAR(512) NOT NULL DEFAULT '',
|
||||||
|
checksum VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
remind_after_seconds INT NOT NULL DEFAULT 0,
|
||||||
|
published_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
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 INDEX uk_im_app_releases_platform_channel (platform, channel)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_device_push_tokens (
|
||||||
|
device_id VARCHAR(128) NOT NULL,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
provider VARCHAR(32) NOT NULL,
|
||||||
|
token VARCHAR(512) NOT NULL,
|
||||||
|
app_version VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
platform VARCHAR(32) NOT NULL DEFAULT '',
|
||||||
|
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 (device_id),
|
||||||
|
UNIQUE INDEX uk_im_device_push_tokens_provider_token (provider, token),
|
||||||
|
INDEX idx_im_device_push_tokens_user (user_id, updated_at)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Tenant database: chapter 11, risk challenge, abuse report, feedback.
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_risk_challenges (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
challenge_id VARCHAR(64) NOT NULL,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
action VARCHAR(64) NOT NULL,
|
||||||
|
level VARCHAR(16) NOT NULL,
|
||||||
|
notice_code VARCHAR(64) NOT NULL,
|
||||||
|
notice_params JSON,
|
||||||
|
state VARCHAR(16) NOT NULL DEFAULT 'pending',
|
||||||
|
expires_at DATETIME(3) NOT NULL,
|
||||||
|
decision VARCHAR(16) NOT NULL DEFAULT '',
|
||||||
|
action_token_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
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 INDEX uk_im_risk_challenges_challenge (challenge_id),
|
||||||
|
INDEX idx_im_risk_challenges_user_state (user_id, state, expires_at)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_abuse_reasons (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
target_type VARCHAR(16) NOT NULL,
|
||||||
|
reason_code VARCHAR(64) NOT NULL,
|
||||||
|
label VARCHAR(128) NOT NULL,
|
||||||
|
requires_description TINYINT NOT NULL DEFAULT 0,
|
||||||
|
allows_evidence TINYINT NOT NULL DEFAULT 1,
|
||||||
|
sort_order INT NOT NULL DEFAULT 0,
|
||||||
|
enabled TINYINT NOT NULL DEFAULT 1,
|
||||||
|
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 INDEX uk_im_abuse_reasons_type_code (target_type, reason_code),
|
||||||
|
INDEX idx_im_abuse_reasons_type_order (target_type, enabled, sort_order)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_abuse_reports (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
report_id VARCHAR(64) NOT NULL,
|
||||||
|
reporter_id BIGINT NOT NULL,
|
||||||
|
target_type VARCHAR(16) NOT NULL,
|
||||||
|
target_user_id BIGINT NOT NULL DEFAULT 0,
|
||||||
|
target_topic VARCHAR(32) NOT NULL DEFAULT '',
|
||||||
|
target_seq INT NOT NULL DEFAULT 0,
|
||||||
|
reason_code VARCHAR(64) NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
evidence_file_ids JSON,
|
||||||
|
snapshot JSON,
|
||||||
|
idempotency_key VARCHAR(128) NOT NULL,
|
||||||
|
status VARCHAR(32) NOT NULL DEFAULT 'received',
|
||||||
|
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 INDEX uk_im_abuse_reports_report (report_id),
|
||||||
|
UNIQUE INDEX uk_im_abuse_reports_idempotency (reporter_id, idempotency_key),
|
||||||
|
INDEX idx_im_abuse_reports_target (target_type, target_topic, target_seq, target_user_id),
|
||||||
|
INDEX idx_im_abuse_reports_reporter (reporter_id, id DESC)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_feedback_config (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
categories JSON,
|
||||||
|
max_length INT NOT NULL DEFAULT 1000,
|
||||||
|
attachment_policy JSON,
|
||||||
|
allow_contact TINYINT NOT NULL DEFAULT 1,
|
||||||
|
diagnostics_notice TEXT NOT 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)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS tinode.im_feedback_submissions (
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
feedback_id VARCHAR(64) NOT NULL,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
category VARCHAR(64) NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
contact VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
attachment_file_ids JSON,
|
||||||
|
client_context JSON,
|
||||||
|
include_diagnostics TINYINT NOT NULL DEFAULT 0,
|
||||||
|
idempotency_key VARCHAR(128) NOT NULL,
|
||||||
|
status VARCHAR(32) NOT NULL DEFAULT 'received',
|
||||||
|
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 INDEX uk_im_feedback_submissions_feedback (feedback_id),
|
||||||
|
UNIQUE INDEX uk_im_feedback_submissions_idempotency (user_id, idempotency_key),
|
||||||
|
INDEX idx_im_feedback_submissions_user (user_id, id DESC)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Seed data.
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
INSERT INTO hotgo.im_tenant(id, code, name, tenant_desc, db_name, db_dsn, state, created_by, updated_by)
|
||||||
|
VALUES (1, 'default', 'Default Tenant', NULL, 'tinode', '', 1, 0, 0)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
name = VALUES(name),
|
||||||
|
db_name = VALUES(db_name),
|
||||||
|
state = VALUES(state),
|
||||||
|
updated_at = CURRENT_TIMESTAMP(3);
|
||||||
|
|
||||||
|
INSERT INTO hotgo.im_tenant_config(tenant_id, max_users, max_groups, max_group_members, created_by, updated_by)
|
||||||
|
SELECT id, 0, 0, 0, 0, 0 FROM hotgo.im_tenant WHERE code = 'default'
|
||||||
|
ON DUPLICATE KEY UPDATE updated_at = CURRENT_TIMESTAMP(3);
|
||||||
|
|
||||||
|
INSERT INTO tinode.im_workbench_apps(code, name, route, sort_order, unread_key, enabled, permission)
|
||||||
|
VALUES
|
||||||
|
('organization', '组织架构', '/organization', 10, 'organization', 1, 'enabled'),
|
||||||
|
('meetings', '会议通知', '/meetings', 20, 'meetings', 1, 'enabled'),
|
||||||
|
('daily_reports', '日报', '/reports/daily', 30, 'daily_reports', 1, 'enabled'),
|
||||||
|
('monthly_reports', '月报', '/reports/monthly', 40, 'monthly_reports', 1, 'enabled'),
|
||||||
|
('executive_mailbox', '总经理信箱', '/executive-mailbox', 50, 'executive_mailbox', 1, 'enabled')
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
name = VALUES(name),
|
||||||
|
route = VALUES(route),
|
||||||
|
sort_order = VALUES(sort_order),
|
||||||
|
unread_key = VALUES(unread_key),
|
||||||
|
updated_at = CURRENT_TIMESTAMP(3);
|
||||||
|
|
||||||
|
INSERT INTO tinode.im_mailbox_config(id, enabled, allow_anonymous, required_fields, max_length, attachment_policy, notice_title, notice_content)
|
||||||
|
VALUES (1, 1, 1, '["subject","content"]', 5000, '{"max_files":5,"purpose":"executive_mailbox"}', '总经理信箱', '请如实填写诉求,匿名提交将按企业政策保护展示身份。')
|
||||||
|
ON DUPLICATE KEY UPDATE updated_at = updated_at;
|
||||||
|
|
||||||
|
INSERT INTO tinode.im_app_releases(platform, channel, latest_version, min_supported_version, update_type, release_notes, remind_after_seconds)
|
||||||
|
VALUES
|
||||||
|
('ios', 'stable', '1.0.0', '1.0.0', 'none', '当前已是最新版本。', 0),
|
||||||
|
('android', 'stable', '1.0.0', '1.0.0', 'none', '当前已是最新版本。', 0)
|
||||||
|
ON DUPLICATE KEY UPDATE updated_at = CURRENT_TIMESTAMP(3);
|
||||||
|
|
||||||
|
INSERT INTO tinode.im_abuse_reasons(target_type, reason_code, label, requires_description, allows_evidence, sort_order)
|
||||||
|
VALUES
|
||||||
|
('user', 'fraud', '诈骗欺诈', 0, 1, 10),
|
||||||
|
('user', 'harassment', '骚扰辱骂', 0, 1, 20),
|
||||||
|
('user', 'illegal', '违法违规', 1, 1, 30),
|
||||||
|
('group', 'fraud', '诈骗欺诈', 0, 1, 10),
|
||||||
|
('group', 'spam', '垃圾广告', 0, 1, 20),
|
||||||
|
('group', 'illegal', '违法违规', 1, 1, 30),
|
||||||
|
('message', 'fraud', '诈骗欺诈', 0, 1, 10),
|
||||||
|
('message', 'spam', '垃圾广告', 0, 1, 20),
|
||||||
|
('message', 'harassment', '骚扰辱骂', 0, 1, 30),
|
||||||
|
('message', 'illegal', '违法违规', 1, 1, 40)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
label = VALUES(label),
|
||||||
|
requires_description = VALUES(requires_description),
|
||||||
|
allows_evidence = VALUES(allows_evidence),
|
||||||
|
sort_order = VALUES(sort_order),
|
||||||
|
updated_at = CURRENT_TIMESTAMP(3);
|
||||||
|
|
||||||
|
INSERT INTO tinode.im_feedback_config(id, categories, max_length, attachment_policy, allow_contact, diagnostics_notice)
|
||||||
|
VALUES (1, '["bug","suggestion","complaint","other"]', 1000, '{"max_files":5,"purpose":"feedback"}', 1, '仅在你同意时上传脱敏诊断信息。')
|
||||||
|
ON DUPLICATE KEY UPDATE updated_at = updated_at;
|
||||||
|
|
||||||
|
-- Current MySQL adapter schema version expected by server/db/mysql/adapter.go.
|
||||||
|
UPDATE tinode.kvmeta SET `value` = 123 WHERE `key` = 'version';
|
||||||
@@ -57,48 +57,9 @@ func main() {
|
|||||||
CONSTRAINT fk_im_tenant_config_tenant FOREIGN KEY(tenant_id) REFERENCES hotgo.im_tenant(id) ON DELETE CASCADE
|
CONSTRAINT fk_im_tenant_config_tenant FOREIGN KEY(tenant_id) REFERENCES hotgo.im_tenant(id) ON DELETE CASCADE
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
|
||||||
"CREATE DATABASE IF NOT EXISTS tinode CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci",
|
"CREATE DATABASE IF NOT EXISTS tinode CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci",
|
||||||
// The main server binary only creates tinode.im_tenant/im_tenant_config
|
// createIMSecurityTables, createIMNotificationTables and createIMRelationTables
|
||||||
// (see createTenantRegistry in server/db/mysql/adapter.go) as part of its
|
// (server/db/mysql/adapter.go) are only reached from CreateDb's fresh-install
|
||||||
// version-gated schema upgrade chain. A tinode DB that was already at the
|
// path, which our pre-existing tinode DB never runs. Recreate their tables here.
|
||||||
// target schema version before this table was introduced never runs that
|
|
||||||
// step, so the ALTERs below would otherwise fail with "table doesn't
|
|
||||||
// exist". Create it here too (schema kept identical to createTenantRegistry).
|
|
||||||
`CREATE TABLE IF NOT EXISTS tinode.im_tenant(
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
code VARCHAR(64) NOT NULL,
|
|
||||||
name VARCHAR(128) NOT NULL,
|
|
||||||
tenant_desc VARCHAR(256),
|
|
||||||
db_name VARCHAR(128) NOT NULL DEFAULT '',
|
|
||||||
db_dsn VARCHAR(1024) NOT NULL DEFAULT '',
|
|
||||||
state SMALLINT NOT NULL,
|
|
||||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
||||||
created_by BIGINT NOT NULL,
|
|
||||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
|
||||||
updated_by BIGINT NOT NULL,
|
|
||||||
PRIMARY KEY(id),
|
|
||||||
UNIQUE INDEX uk_im_tenant_code(code),
|
|
||||||
INDEX idx_im_tenant_db_name(db_name)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
|
|
||||||
`CREATE TABLE IF NOT EXISTS tinode.im_tenant_config(
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
tenant_id BIGINT NOT NULL,
|
|
||||||
max_users BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '0 means unlimited',
|
|
||||||
max_groups BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '0 means unlimited',
|
|
||||||
max_group_members INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '0 means unlimited',
|
|
||||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
||||||
created_by BIGINT NOT NULL,
|
|
||||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
|
||||||
updated_by BIGINT NOT NULL,
|
|
||||||
PRIMARY KEY(id),
|
|
||||||
UNIQUE INDEX uk_im_tenant_config_tenant_id(tenant_id),
|
|
||||||
CONSTRAINT fk_im_tenant_config_tenant FOREIGN KEY(tenant_id) REFERENCES tinode.im_tenant(id) ON DELETE CASCADE
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`,
|
|
||||||
`INSERT INTO tinode.im_tenant(id,code,name,tenant_desc,db_name,db_dsn,state,created_by,updated_by)
|
|
||||||
VALUES(1,'default','Default Tenant',NULL,'tinode','',1,0,0)
|
|
||||||
ON DUPLICATE KEY UPDATE db_name=VALUES(db_name), updated_at=CURRENT_TIMESTAMP(3)`,
|
|
||||||
// Same root cause as im_tenant above: createIMSecurityTables, createIMNotificationTables
|
|
||||||
// and createIMRelationTables (server/db/mysql/adapter.go) are only reached from CreateDb's
|
|
||||||
// fresh-install path, which our pre-existing tinode DB never runs. Recreate their tables here.
|
|
||||||
`CREATE TABLE IF NOT EXISTS tinode.im_realname_verifications(
|
`CREATE TABLE IF NOT EXISTS tinode.im_realname_verifications(
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
user_id BIGINT NOT NULL,
|
user_id BIGINT NOT NULL,
|
||||||
@@ -559,21 +520,10 @@ func main() {
|
|||||||
"ALTER TABLE hotgo.im_tenant ADD COLUMN tenant_desc VARCHAR(256) AFTER name",
|
"ALTER TABLE hotgo.im_tenant ADD COLUMN tenant_desc VARCHAR(256) AFTER name",
|
||||||
"ALTER TABLE hotgo.im_tenant ADD COLUMN db_name VARCHAR(128) NOT NULL DEFAULT '' AFTER tenant_desc",
|
"ALTER TABLE hotgo.im_tenant ADD COLUMN db_name VARCHAR(128) NOT NULL DEFAULT '' AFTER tenant_desc",
|
||||||
"ALTER TABLE hotgo.im_tenant ADD COLUMN db_dsn VARCHAR(1024) NOT NULL DEFAULT '' AFTER db_name",
|
"ALTER TABLE hotgo.im_tenant ADD COLUMN db_dsn VARCHAR(1024) NOT NULL DEFAULT '' AFTER db_name",
|
||||||
"ALTER TABLE tinode.im_tenant ADD COLUMN tenant_desc VARCHAR(256) AFTER name",
|
|
||||||
"ALTER TABLE tinode.im_tenant ADD COLUMN db_name VARCHAR(128) NOT NULL DEFAULT '' AFTER tenant_desc",
|
|
||||||
"ALTER TABLE tinode.im_tenant ADD COLUMN db_dsn VARCHAR(1024) NOT NULL DEFAULT '' AFTER db_name",
|
|
||||||
"ALTER TABLE tinode.im_youth_mode_settings ADD COLUMN enabled TINYINT NOT NULL DEFAULT 0 AFTER password_hash",
|
"ALTER TABLE tinode.im_youth_mode_settings ADD COLUMN enabled TINYINT NOT NULL DEFAULT 0 AFTER password_hash",
|
||||||
"ALTER TABLE tinode.im_youth_mode_settings ADD COLUMN policy_version INT NOT NULL DEFAULT 1 AFTER enabled",
|
"ALTER TABLE tinode.im_youth_mode_settings ADD COLUMN policy_version INT NOT NULL DEFAULT 1 AFTER enabled",
|
||||||
"ALTER TABLE tinode.im_youth_mode_settings ADD COLUMN restrictions JSON AFTER policy_version",
|
"ALTER TABLE tinode.im_youth_mode_settings ADD COLUMN restrictions JSON AFTER policy_version",
|
||||||
"ALTER TABLE tinode.im_youth_mode_settings ADD COLUMN enabled_at DATETIME(3) AFTER restrictions",
|
"ALTER TABLE tinode.im_youth_mode_settings ADD COLUMN enabled_at DATETIME(3) AFTER restrictions",
|
||||||
// auth/credentials: server/db/mysql/adapter.go's CreateDb never defined a
|
|
||||||
// tenant_id column here even on a fresh install, yet every auth/credentials
|
|
||||||
// query unconditionally filters/inserts by tenant_id - a latent bug in the
|
|
||||||
// fresh-schema definition, not something version-skip alone explains. Backfill
|
|
||||||
// existing rows to the default tenant (id=1); adapter.go's CREATE TABLE was
|
|
||||||
// fixed alongside this to include the column on future fresh installs too.
|
|
||||||
"ALTER TABLE tinode.auth ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 1 AFTER id",
|
|
||||||
"ALTER TABLE tinode.credentials ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 1 AFTER id",
|
|
||||||
} {
|
} {
|
||||||
if _, err = db.Exec(stmt); err != nil && !strings.Contains(err.Error(), "Duplicate column name") {
|
if _, err = db.Exec(stmt); err != nil && !strings.Contains(err.Error(), "Duplicate column name") {
|
||||||
fatal(fmt.Errorf("exec failed: %s: %w", stmt, err))
|
fatal(fmt.Errorf("exec failed: %s: %w", stmt, err))
|
||||||
@@ -611,9 +561,6 @@ func main() {
|
|||||||
ON DUPLICATE KEY UPDATE updated_at=CURRENT_TIMESTAMP(3)`); err != nil {
|
ON DUPLICATE KEY UPDATE updated_at=CURRENT_TIMESTAMP(3)`); err != nil {
|
||||||
fatal(err)
|
fatal(err)
|
||||||
}
|
}
|
||||||
if _, err = db.Exec("UPDATE tinode.im_tenant SET db_name='tinode' WHERE code='default' AND (db_name='' OR db_name IS NULL)"); err != nil {
|
|
||||||
fatal(err)
|
|
||||||
}
|
|
||||||
if _, err = db.Exec(`INSERT INTO tinode.im_abuse_reasons(target_type,reason_code,label,requires_description,allows_evidence,sort_order)
|
if _, err = db.Exec(`INSERT INTO tinode.im_abuse_reasons(target_type,reason_code,label,requires_description,allows_evidence,sort_order)
|
||||||
VALUES
|
VALUES
|
||||||
('user','fraud','诈骗欺诈',0,1,10),
|
('user','fraud','诈骗欺诈',0,1,10),
|
||||||
@@ -644,13 +591,6 @@ func main() {
|
|||||||
fatal(err)
|
fatal(err)
|
||||||
}
|
}
|
||||||
fmt.Printf("hotgo.im_tenant default: code=%s db_name=%s state=%d\n", code, dbName, state)
|
fmt.Printf("hotgo.im_tenant default: code=%s db_name=%s state=%d\n", code, dbName, state)
|
||||||
var columnCount int
|
|
||||||
if err = db.QueryRow(`SELECT COUNT(*) FROM information_schema.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA='tinode' AND TABLE_NAME='im_tenant' AND COLUMN_NAME IN ('tenant_desc','db_name','db_dsn')`).Scan(&columnCount); err != nil {
|
|
||||||
fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Printf("tinode.im_tenant routing columns: %d/3\n", columnCount)
|
|
||||||
|
|
||||||
rows, err := db.Query(`SELECT table_name FROM information_schema.COLUMNS
|
rows, err := db.Query(`SELECT table_name FROM information_schema.COLUMNS
|
||||||
WHERE TABLE_SCHEMA='tinode' AND COLUMN_NAME='tenant_id'
|
WHERE TABLE_SCHEMA='tinode' AND COLUMN_NAME='tenant_id'
|
||||||
ORDER BY table_name`)
|
ORDER BY table_name`)
|
||||||
@@ -681,17 +621,10 @@ func fatal(v any) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// keptTenantIDTables lists tables whose tenant_id column must survive
|
|
||||||
// dropTenantIDColumns: server/db/mysql/adapter.go queries auth and credentials
|
|
||||||
// by tenant_id unconditionally (not gated behind tenant_db_routing), so unlike
|
|
||||||
// the newer per-tenant-DB feature tables, these two are row-tagged by design.
|
|
||||||
var keptTenantIDTables = "'auth','credentials'"
|
|
||||||
|
|
||||||
func dropTenantIDColumns(db *sql.DB, schema string) error {
|
func dropTenantIDColumns(db *sql.DB, schema string) error {
|
||||||
fks, err := queryPairs(db, `SELECT DISTINCT table_name, constraint_name
|
fks, err := queryPairs(db, `SELECT DISTINCT table_name, constraint_name
|
||||||
FROM information_schema.KEY_COLUMN_USAGE
|
FROM information_schema.KEY_COLUMN_USAGE
|
||||||
WHERE TABLE_SCHEMA=? AND COLUMN_NAME='tenant_id' AND REFERENCED_TABLE_NAME IS NOT NULL
|
WHERE TABLE_SCHEMA=? AND COLUMN_NAME='tenant_id' AND REFERENCED_TABLE_NAME IS NOT NULL`,
|
||||||
AND table_name NOT IN (`+keptTenantIDTables+`)`,
|
|
||||||
schema)
|
schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -704,8 +637,7 @@ func dropTenantIDColumns(db *sql.DB, schema string) error {
|
|||||||
|
|
||||||
indexes, err := queryPairs(db, `SELECT DISTINCT table_name, index_name
|
indexes, err := queryPairs(db, `SELECT DISTINCT table_name, index_name
|
||||||
FROM information_schema.STATISTICS
|
FROM information_schema.STATISTICS
|
||||||
WHERE TABLE_SCHEMA=? AND COLUMN_NAME='tenant_id' AND INDEX_NAME<>'PRIMARY'
|
WHERE TABLE_SCHEMA=? AND COLUMN_NAME='tenant_id' AND INDEX_NAME<>'PRIMARY'`,
|
||||||
AND table_name NOT IN (`+keptTenantIDTables+`)`,
|
|
||||||
schema)
|
schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -718,7 +650,6 @@ func dropTenantIDColumns(db *sql.DB, schema string) error {
|
|||||||
|
|
||||||
rows, err := db.Query(`SELECT table_name FROM information_schema.COLUMNS
|
rows, err := db.Query(`SELECT table_name FROM information_schema.COLUMNS
|
||||||
WHERE TABLE_SCHEMA=? AND COLUMN_NAME='tenant_id'
|
WHERE TABLE_SCHEMA=? AND COLUMN_NAME='tenant_id'
|
||||||
AND table_name NOT IN (`+keptTenantIDTables+`)
|
|
||||||
ORDER BY table_name`, schema)
|
ORDER BY table_name`, schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
//go:build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
dsn := strings.TrimPrefix(os.Getenv("IM_MYSQL_DSN"), "mysql://")
|
||||||
|
sqlFile := os.Getenv("IM_SQL_FILE")
|
||||||
|
if dsn == "" || sqlFile == "" {
|
||||||
|
fatal("IM_MYSQL_DSN and IM_SQL_FILE are required")
|
||||||
|
}
|
||||||
|
if !strings.Contains(dsn, "multiStatements=") {
|
||||||
|
sep := "?"
|
||||||
|
if strings.Contains(dsn, "?") {
|
||||||
|
sep = "&"
|
||||||
|
}
|
||||||
|
dsn += sep + "multiStatements=true"
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := os.ReadFile(sqlFile)
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
db, err := sql.Open("mysql", dsn)
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
if err = db.Ping(); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
if _, err = db.Exec(string(body)); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println("applied", sqlFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatal(v any) {
|
||||||
|
fmt.Fprintln(os.Stderr, v)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
//go:build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
dsn := strings.TrimPrefix(os.Getenv("IM_MYSQL_DSN"), "mysql://")
|
||||||
|
query := os.Getenv("IM_SQL_QUERY")
|
||||||
|
if dsn == "" || query == "" {
|
||||||
|
fatal("IM_MYSQL_DSN and IM_SQL_QUERY are required")
|
||||||
|
}
|
||||||
|
db, err := sql.Open("mysql", dsn)
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
rows, err := db.Query(query)
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
cols, err := rows.Columns()
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println(strings.Join(cols, "\t"))
|
||||||
|
for rows.Next() {
|
||||||
|
values := make([]sql.NullString, len(cols))
|
||||||
|
scan := make([]any, len(cols))
|
||||||
|
for i := range values {
|
||||||
|
scan[i] = &values[i]
|
||||||
|
}
|
||||||
|
if err := rows.Scan(scan...); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
out := make([]string, len(cols))
|
||||||
|
for i, value := range values {
|
||||||
|
if value.Valid {
|
||||||
|
out[i] = value.String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println(strings.Join(out, "\t"))
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatal(v any) {
|
||||||
|
fmt.Fprintln(os.Stderr, v)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
@@ -658,7 +658,6 @@ func (a *adapter) CreateDb(reset bool) error {
|
|||||||
if _, err = tx.Exec(
|
if _, err = tx.Exec(
|
||||||
`CREATE TABLE auth(
|
`CREATE TABLE auth(
|
||||||
id INT NOT NULL AUTO_INCREMENT,
|
id INT NOT NULL AUTO_INCREMENT,
|
||||||
tenant_id BIGINT NOT NULL,
|
|
||||||
uname VARCHAR(32) NOT NULL,
|
uname VARCHAR(32) NOT NULL,
|
||||||
userid BIGINT NOT NULL,
|
userid BIGINT NOT NULL,
|
||||||
scheme VARCHAR(16) NOT NULL,
|
scheme VARCHAR(16) NOT NULL,
|
||||||
@@ -836,7 +835,6 @@ func (a *adapter) CreateDb(reset bool) error {
|
|||||||
if _, err = tx.Exec(
|
if _, err = tx.Exec(
|
||||||
`CREATE TABLE credentials(
|
`CREATE TABLE credentials(
|
||||||
id INT NOT NULL AUTO_INCREMENT,
|
id INT NOT NULL AUTO_INCREMENT,
|
||||||
tenant_id BIGINT NOT NULL,
|
|
||||||
createdat DATETIME(3) NOT NULL,
|
createdat DATETIME(3) NOT NULL,
|
||||||
updatedat DATETIME(3) NOT NULL,
|
updatedat DATETIME(3) NOT NULL,
|
||||||
deletedat DATETIME(3),
|
deletedat DATETIME(3),
|
||||||
|
|||||||
+18
-5
@@ -105,15 +105,28 @@ func TestDispatchHello(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispatchHelloRequiresTenant(t *testing.T) {
|
func TestDispatchHelloDefaultsTenant(t *testing.T) {
|
||||||
|
oldTenants := store.Tenants
|
||||||
|
store.Tenants = testTenantStore{tenant: &types.Tenant{
|
||||||
|
ID: 1,
|
||||||
|
Code: "default",
|
||||||
|
Name: "Default Tenant",
|
||||||
|
State: types.TenantStateActive,
|
||||||
|
}}
|
||||||
|
defer func() { store.Tenants = oldTenants }()
|
||||||
|
|
||||||
s := &Session{send: make(chan any, 1)}
|
s := &Session{send: make(chan any, 1)}
|
||||||
s.dispatch(&ClientComMessage{Hi: &MsgClientHi{Id: "123", Version: "1"}})
|
s.dispatch(&ClientComMessage{Hi: &MsgClientHi{Id: "123", Version: "1"}})
|
||||||
resp := (<-s.send).(*ServerComMessage)
|
resp := (<-s.send).(*ServerComMessage)
|
||||||
if resp.Ctrl.Code != http.StatusBadRequest {
|
if resp.Ctrl.Code != http.StatusCreated {
|
||||||
t.Fatalf("response code: expected %d, got %d", http.StatusBadRequest, resp.Ctrl.Code)
|
t.Fatalf("response code: expected %d, got %d", http.StatusCreated, resp.Ctrl.Code)
|
||||||
}
|
}
|
||||||
if !s.tenantID.IsZero() || s.ver != 0 {
|
if s.tenantID != 1 || s.tenantCode != "default" || s.ver == 0 {
|
||||||
t.Fatalf("invalid hello must not bind session: tenant=%d version=%d", s.tenantID, s.ver)
|
t.Fatalf("default hello did not bind session: tenant=%d code=%q version=%d", s.tenantID, s.tenantCode, s.ver)
|
||||||
|
}
|
||||||
|
tenantParams := resp.Ctrl.Params.(map[string]any)["tenant"].(map[string]any)
|
||||||
|
if tenantParams["code"] != "default" || tenantParams["name"] != "Default Tenant" {
|
||||||
|
t.Fatalf("unexpected tenant params: %#v", tenantParams)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user