185 lines
5.6 KiB
Go
185 lines
5.6 KiB
Go
package initialize
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/perfect-panel/server/internal/svc"
|
|
"github.com/perfect-panel/server/pkg/logger"
|
|
"github.com/pkg/errors"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type schemaTablePatch struct {
|
|
table string
|
|
ddl string
|
|
}
|
|
|
|
type schemaColumnPatch struct {
|
|
table string
|
|
column string
|
|
ddl string
|
|
}
|
|
|
|
func EnsureSchemaCompatibility(ctx *svc.ServiceContext) error {
|
|
tablePatches := []schemaTablePatch{
|
|
{
|
|
table: "log_message",
|
|
ddl: `CREATE TABLE IF NOT EXISTS log_message (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
platform VARCHAR(32) NOT NULL,
|
|
app_version VARCHAR(32) NULL,
|
|
os_name VARCHAR(32) NULL,
|
|
os_version VARCHAR(32) NULL,
|
|
device_id VARCHAR(64) NULL,
|
|
user_id BIGINT NULL DEFAULT NULL,
|
|
session_id VARCHAR(64) NULL,
|
|
level TINYINT UNSIGNED NOT NULL DEFAULT 3,
|
|
error_code VARCHAR(64) NULL,
|
|
message TEXT NOT NULL,
|
|
stack MEDIUMTEXT NULL,
|
|
context JSON NULL,
|
|
client_ip VARCHAR(45) NULL,
|
|
user_agent VARCHAR(255) NULL,
|
|
locale VARCHAR(16) NULL,
|
|
digest VARCHAR(64) NULL,
|
|
occurred_at DATETIME NULL,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uniq_digest (digest),
|
|
KEY idx_platform_time (platform, created_at),
|
|
KEY idx_user_time (user_id, created_at),
|
|
KEY idx_device_time (device_id, created_at),
|
|
KEY idx_error_code (error_code)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`,
|
|
},
|
|
{
|
|
table: "user_family",
|
|
ddl: `CREATE TABLE IF NOT EXISTS user_family (
|
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
owner_user_id BIGINT NOT NULL COMMENT 'Owner User ID',
|
|
max_members INT NOT NULL DEFAULT 5 COMMENT 'Max members in family',
|
|
status TINYINT NOT NULL DEFAULT 1 COMMENT 'Status: 1=active, 0=disabled',
|
|
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),
|
|
deleted_at DATETIME(3) DEFAULT NULL,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uniq_owner_user_id (owner_user_id),
|
|
KEY idx_status (status),
|
|
KEY idx_deleted_at (deleted_at)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`,
|
|
},
|
|
{
|
|
table: "user_family_member",
|
|
ddl: `CREATE TABLE IF NOT EXISTS user_family_member (
|
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
family_id BIGINT NOT NULL COMMENT 'Family ID',
|
|
user_id BIGINT NOT NULL COMMENT 'Member User ID',
|
|
role TINYINT NOT NULL DEFAULT 2 COMMENT 'Role: 1=owner, 2=member',
|
|
status TINYINT NOT NULL DEFAULT 1 COMMENT 'Status: 1=active, 2=left, 3=removed',
|
|
join_source VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'Join source',
|
|
joined_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
left_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),
|
|
deleted_at DATETIME(3) DEFAULT NULL,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uniq_user_id (user_id),
|
|
KEY idx_family_status (family_id, status),
|
|
KEY idx_deleted_at (deleted_at)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`,
|
|
},
|
|
}
|
|
|
|
columnPatches := []schemaColumnPatch{
|
|
{
|
|
table: "user",
|
|
column: "rules",
|
|
ddl: "ALTER TABLE `user` ADD COLUMN `rules` TEXT NULL COMMENT 'User rules for subscription';",
|
|
},
|
|
{
|
|
table: "user",
|
|
column: "last_login_time",
|
|
ddl: "ALTER TABLE `user` ADD COLUMN `last_login_time` DATETIME DEFAULT NULL COMMENT 'Last Login Time';",
|
|
},
|
|
{
|
|
table: "user",
|
|
column: "member_status",
|
|
ddl: "ALTER TABLE `user` ADD COLUMN `member_status` VARCHAR(20) NOT NULL DEFAULT '' COMMENT 'Member Status';",
|
|
},
|
|
{
|
|
table: "user",
|
|
column: "remark",
|
|
ddl: "ALTER TABLE `user` ADD COLUMN `remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Remark';",
|
|
},
|
|
{
|
|
table: "user_subscribe",
|
|
column: "note",
|
|
ddl: "ALTER TABLE `user_subscribe` ADD COLUMN `note` VARCHAR(500) NOT NULL DEFAULT '' COMMENT 'User note for subscription';",
|
|
},
|
|
{
|
|
table: "redemption_code",
|
|
column: "status",
|
|
ddl: "ALTER TABLE `redemption_code` ADD COLUMN `status` TINYINT NOT NULL DEFAULT 1 COMMENT 'Status: 1=enabled, 0=disabled';",
|
|
},
|
|
}
|
|
|
|
for _, patch := range tablePatches {
|
|
exists, err := tableExists(ctx.DB, patch.table)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "check table %s failed", patch.table)
|
|
}
|
|
if exists {
|
|
continue
|
|
}
|
|
if err = ctx.DB.Exec(patch.ddl).Error; err != nil {
|
|
return errors.Wrapf(err, "create table %s failed", patch.table)
|
|
}
|
|
logger.Infof("[SchemaCompat] created missing table: %s", patch.table)
|
|
}
|
|
|
|
for _, patch := range columnPatches {
|
|
exists, err := columnExists(ctx.DB, patch.table, patch.column)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "check column %s.%s failed", patch.table, patch.column)
|
|
}
|
|
if exists {
|
|
continue
|
|
}
|
|
if err = ctx.DB.Exec(patch.ddl).Error; err != nil {
|
|
return errors.Wrapf(err, "add column %s.%s failed", patch.table, patch.column)
|
|
}
|
|
logger.Infof("[SchemaCompat] added missing column: %s.%s", patch.table, patch.column)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func tableExists(db *gorm.DB, table string) (bool, error) {
|
|
var count int64
|
|
err := db.Raw("SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?", table).Scan(&count).Error
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return count > 0, nil
|
|
}
|
|
|
|
func columnExists(db *gorm.DB, table, column string) (bool, error) {
|
|
var count int64
|
|
err := db.Raw(
|
|
"SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?",
|
|
table,
|
|
column,
|
|
).Scan(&count).Error
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return count > 0, nil
|
|
}
|
|
|
|
func _schemaCompatDebug(table, column string) string {
|
|
if column == "" {
|
|
return table
|
|
}
|
|
return fmt.Sprintf("%s.%s", table, column)
|
|
}
|