init
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"embed"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
"gorm.io/driver/mysql"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/perfect-panel/ppanel-server/initialize/migrate"
|
||||
"github.com/perfect-panel/ppanel-server/internal/config"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/conf"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/orm"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/tool"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
//go:embed templates/*.html
|
||||
var templateFS embed.FS
|
||||
|
||||
var initStatus = make(chan bool)
|
||||
var configPath string
|
||||
|
||||
func Config(path string) (chan bool, *http.Server) {
|
||||
// Set the configuration file path
|
||||
configPath = path
|
||||
// Create a new Gin instance
|
||||
r := gin.Default()
|
||||
|
||||
// Create a new HTTP server
|
||||
server := &http.Server{
|
||||
Addr: ":8080",
|
||||
Handler: r,
|
||||
}
|
||||
// Load templates
|
||||
tmpl := template.Must(template.ParseFS(templateFS, "templates/*.html"))
|
||||
r.SetHTMLTemplate(tmpl)
|
||||
|
||||
r.GET("/init", handleInit)
|
||||
r.POST("/init/config", handleInitConfig)
|
||||
r.POST("/init/mysql/test", HandleMySQLTest)
|
||||
r.POST("/init/redis/test", HandleRedisTest)
|
||||
// Handle 404
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
c.Redirect(http.StatusFound, "/init")
|
||||
})
|
||||
|
||||
go func(server *http.Server) {
|
||||
// Start the server
|
||||
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("listen: %s\n", err)
|
||||
}
|
||||
}(server)
|
||||
|
||||
return initStatus, server
|
||||
}
|
||||
|
||||
func handleInit(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "index.html", nil)
|
||||
}
|
||||
func handleInitConfig(c *gin.Context) {
|
||||
// Load configuration file
|
||||
|
||||
var cfg config.File
|
||||
conf.MustLoad(configPath, &cfg)
|
||||
var request struct {
|
||||
AdminEmail string `json:"adminEmail"`
|
||||
AdminPassword string `json:"adminPassword"`
|
||||
|
||||
MysqlHost string `json:"mysqlHost"`
|
||||
MysqlPort string `json:"mysqlPort"`
|
||||
MysqlDatabase string `json:"mysqlDatabase"`
|
||||
MysqlUser string `json:"mysqlUser"`
|
||||
MysqlPassword string `json:"mysqlPassword"`
|
||||
|
||||
RedisHost string `json:"redisHost"`
|
||||
RedisPort string `json:"redisPort"`
|
||||
RedisPassword string `json:"redisPassword"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"msg": "Invalid request",
|
||||
"data": nil,
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
cfg.Debug = false
|
||||
// jwt secret
|
||||
cfg.JwtAuth.AccessSecret = uuid.New().String()
|
||||
// mysql
|
||||
cfg.MySQL.Addr = fmt.Sprintf("%s:%s", request.MysqlHost, request.MysqlPort)
|
||||
cfg.MySQL.Dbname = request.MysqlDatabase
|
||||
cfg.MySQL.Username = request.MysqlUser
|
||||
cfg.MySQL.Password = request.MysqlPassword
|
||||
// redis
|
||||
cfg.Redis.Host = fmt.Sprintf("%s:%s", request.RedisHost, request.RedisPort)
|
||||
cfg.Redis.Pass = request.RedisPassword
|
||||
|
||||
// save config
|
||||
fileData, err := yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"msg": "Configuration initialization failed",
|
||||
"data": nil,
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// create mysql connection
|
||||
db, err := orm.ConnectMysql(orm.Mysql{
|
||||
Config: orm.Config{
|
||||
Addr: fmt.Sprintf("%s:%s", request.MysqlHost, request.MysqlPort),
|
||||
Username: request.MysqlUser,
|
||||
Password: request.MysqlPassword,
|
||||
Dbname: request.MysqlDatabase,
|
||||
Config: "charset%3Dutf8mb4%26parseTime%3Dtrue%26loc%3DLocal",
|
||||
MaxIdleConns: 10,
|
||||
MaxOpenConns: 10,
|
||||
SlowThreshold: 1000,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"msg": "MySQL connection failed",
|
||||
"data": nil,
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// init
|
||||
if err := initMysql(db, request.AdminEmail, request.AdminPassword); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"msg": "MySQL initialization failed",
|
||||
"data": nil,
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// write to file
|
||||
if err := os.WriteFile(configPath, fileData, 0644); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"msg": "Configuration initialization failed",
|
||||
"data": nil,
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"msg": "Configuration initialized",
|
||||
"status": true,
|
||||
})
|
||||
initStatus <- true
|
||||
}
|
||||
|
||||
func HandleMySQLTest(c *gin.Context) {
|
||||
var request struct {
|
||||
Host string `json:"host"`
|
||||
Port string `json:"port"`
|
||||
Database string `json:"database"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"msg": "Invalid request",
|
||||
"data": nil,
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", request.User, request.Password, request.Host, request.Port, request.Database)
|
||||
var status = true
|
||||
var message string
|
||||
var tx *sql.DB
|
||||
var tables []string
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
logger.Errorf("connect mysql failed, err: %v\n", err.Error())
|
||||
status = false
|
||||
message = "MySQL connection failed"
|
||||
goto result
|
||||
}
|
||||
tx, _ = db.DB()
|
||||
if err := tx.Ping(); err != nil {
|
||||
logger.Errorf("ping mysql failed, err: %v\n", err.Error())
|
||||
status = false
|
||||
message = "MySQL connection failed"
|
||||
}
|
||||
|
||||
tables, err = db.Migrator().GetTables()
|
||||
if err != nil {
|
||||
logger.Errorf("database table check failed, err: %v\n", err.Error())
|
||||
status = false
|
||||
message = "Database table check failed"
|
||||
goto result
|
||||
}
|
||||
if len(tables) > 0 {
|
||||
status = false
|
||||
message = "The database contains existing data. Please clear it before proceeding with the installation."
|
||||
goto result
|
||||
}
|
||||
|
||||
result:
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"msg": message,
|
||||
"status": status,
|
||||
})
|
||||
}
|
||||
|
||||
func HandleRedisTest(c *gin.Context) {
|
||||
var request struct {
|
||||
Host string `json:"host"`
|
||||
Port string `json:"port"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"msg": "Invalid request",
|
||||
"data": nil,
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if err := tool.RedisPing(fmt.Sprintf("%s:%s", request.Host, request.Port), request.Password, 0); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"msg": nil,
|
||||
"status": false,
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"msg": nil,
|
||||
"status": true,
|
||||
})
|
||||
}
|
||||
|
||||
func initMysql(tx *gorm.DB, email, password string) error {
|
||||
tables, err := tx.Migrator().GetTables()
|
||||
if err != nil {
|
||||
return fmt.Errorf("database table validation failed: %w", err)
|
||||
}
|
||||
if len(tables) > 0 {
|
||||
return errors.New("the database contains existing data. Please clear it before proceeding with the installation")
|
||||
}
|
||||
if err := migrate.InitPPanelSQL(tx); err != nil {
|
||||
return fmt.Errorf("failed to initialize database: %w", err)
|
||||
}
|
||||
if err := migrate.CreateAdminUser(email, password, tx); err != nil {
|
||||
return fmt.Errorf("failed to create admin user: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/internal/config"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/auth"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/tool"
|
||||
)
|
||||
|
||||
// Email get email smtp config
|
||||
func Email(ctx *svc.ServiceContext) {
|
||||
logger.Debug("Email config initialization")
|
||||
method, err := ctx.AuthModel.FindOneByMethod(context.Background(), "email")
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to find email auth method: %v", err.Error()))
|
||||
}
|
||||
var cfg config.EmailConfig
|
||||
var emailConfig = new(auth.EmailAuthConfig)
|
||||
if err := emailConfig.Unmarshal(method.Config); err != nil {
|
||||
panic(fmt.Sprintf("failed to unmarshal email auth config: %v", err.Error()))
|
||||
}
|
||||
tool.DeepCopy(&cfg, emailConfig)
|
||||
cfg.Enable = *method.Enabled
|
||||
value, _ := json.Marshal(emailConfig.PlatformConfig)
|
||||
cfg.PlatformConfig = string(value)
|
||||
ctx.Config.Email = cfg
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package initialize
|
||||
|
||||
import "github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
|
||||
func StartInitSystemConfig(svc *svc.ServiceContext) {
|
||||
// Initialize the system configuration
|
||||
Mysql(svc)
|
||||
VerifyVersion(svc)
|
||||
Site(svc)
|
||||
Node(svc)
|
||||
Email(svc)
|
||||
Invite(svc)
|
||||
Verify(svc)
|
||||
Subscribe(svc)
|
||||
Register(svc)
|
||||
Mobile(svc)
|
||||
TrafficDataToRedis(svc)
|
||||
if !svc.Config.Debug {
|
||||
Telegram(svc)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/internal/config"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/tool"
|
||||
)
|
||||
|
||||
func Invite(ctx *svc.ServiceContext) {
|
||||
// Initialize the system configuration
|
||||
logger.Debug("Register config initialization")
|
||||
configs, err := ctx.SystemModel.GetInviteConfig(context.Background())
|
||||
if err != nil {
|
||||
logger.Error("[Init Invite Config] Get Invite Config Error: ", logger.Field("error", err.Error()))
|
||||
return
|
||||
}
|
||||
var inviteConfig config.InviteConfig
|
||||
tool.SystemConfigSliceReflectToStruct(configs, &inviteConfig)
|
||||
ctx.Config.Invite = inviteConfig
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
-- 先检查 `email` 列是否存在,再删除
|
||||
SELECT COUNT(*) INTO @col_exists FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'user' AND column_name = 'email';
|
||||
|
||||
SET @sql = IF(@col_exists > 0, 'ALTER TABLE `user` DROP COLUMN `email`', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 先检查 `telephone` 列是否存在,再删除
|
||||
SELECT COUNT(*) INTO @col_exists FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'user' AND column_name = 'telephone';
|
||||
|
||||
SET @sql = IF(@col_exists > 0, 'ALTER TABLE `user` DROP COLUMN `telephone`', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 先检查 `telephone_area_code` 列是否存在,再删除
|
||||
SELECT COUNT(*) INTO @col_exists FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'user' AND column_name = 'telephone_area_code';
|
||||
|
||||
SET @sql = IF(@col_exists > 0, 'ALTER TABLE `user` DROP COLUMN `telephone_area_code`', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
|
||||
-- 先检查 `idx_email` 索引是否存在,再删除
|
||||
SELECT COUNT(*) INTO @idx_exists FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE() AND table_name = 'user' AND index_name = 'idx_email';
|
||||
|
||||
SET @sql = IF(@idx_exists > 0, 'ALTER TABLE `user` DROP INDEX `idx_email`', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 先检查 `idx_telephone` 索引是否存在,再删除
|
||||
SELECT COUNT(*) INTO @idx_exists FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE() AND table_name = 'user' AND index_name = 'idx_telephone';
|
||||
|
||||
SET @sql = IF(@idx_exists > 0, 'ALTER TABLE `user` DROP INDEX `idx_telephone`', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 先检查 `idx_telephone_area_code` 索引是否存在,再删除
|
||||
SELECT COUNT(*) INTO @idx_exists FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE() AND table_name = 'user' AND index_name = 'idx_telephone_area_code';
|
||||
|
||||
SET @sql = IF(@idx_exists > 0, 'ALTER TABLE `user` DROP INDEX `idx_telephone_area_code`', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
@@ -0,0 +1,118 @@
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- 检查表是否存在,如果存在则跳过创建
|
||||
CREATE TABLE IF NOT EXISTS `oauth_config` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`platform` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'platform',
|
||||
`config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'OAuth Configuration',
|
||||
`redirect` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Redirect URL',
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Enabled',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uni_oauth_config_platform` (`platform`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- 插入记录时忽略重复记录
|
||||
BEGIN;
|
||||
INSERT IGNORE INTO `oauth_config` (`id`, `platform`, `config`, `redirect`, `enabled`, `created_at`, `updated_at`) VALUES
|
||||
(1, 'apple', '{\"team_id\":\"\",\"key_id\":\"\",\"client_id\":\"\",\"client_secret\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292'),
|
||||
(2, 'google', '{\"client_id\":\"\",\"client_secret\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292'),
|
||||
(3, 'github', '{\"client_id\":\"\",\"client_secret\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292'),
|
||||
(4, 'facebook', '{\"client_id\":\"\",\"client_secret\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292'),
|
||||
(5, 'telegram', '{\"bot\":\"\",\"bot_token\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292');
|
||||
COMMIT;
|
||||
|
||||
-- 检测更新设置表
|
||||
BEGIN;
|
||||
INSERT IGNORE INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`) VALUES
|
||||
('sms', 'SmsEnabled', 'false', 'bool', '是否启用短信功能', NOW(), NOW()),
|
||||
('sms', 'SmsKey', 'your-key', 'string', '短信服务用户名或Key',NOW(), NOW()),
|
||||
('sms', 'SmsSecret', 'your-secret', 'string', '短信服务密码或Secret', NOW(), NOW()),
|
||||
('sms', 'SmsSign', 'your-sign', 'string', '短信签名', NOW(), NOW()),
|
||||
('sms', 'SmsTemplate', 'your-template', 'string', '短信模板ID', NOW(), NOW()),
|
||||
('sms', 'SmsRegion', 'cn-hangzhou', 'string', '短信服务所在区域(适用于阿里云)', NOW(), NOW()),
|
||||
('sms', 'SmsTemplate', '您的验证码是{{.Code}},请在5分钟内使用。', 'string', '自定义短信模板', NOW(), NOW()),
|
||||
('sms', 'SmsTemplateCode', 'SMS_12345678', 'string', '阿里云国内短信模板代码',NOW(),NOW()),
|
||||
('sms', 'SmsTemplateParam', '{\"code\":{{.Code}}}', 'string', '短信模板参数', NOW(), NOW()),
|
||||
('sms', 'SmsPlatform', 'smsbao', 'string', '当前使用的短信平台', NOW(), NOW()),
|
||||
('sms', 'SmsLimit', '10', 'int64', '可以发送的短信最大数量', NOW(), NOW()),
|
||||
('sms', 'SmsInterval', '60', 'int64', '发送短信的时间间隔(单位:秒)',NOW(), NOW()),
|
||||
('sms', 'SmsExpireTime', '300', 'int64', '短信验证码的过期时间(单位:秒)',NOW(), NOW()),
|
||||
('email', 'EmailEnabled', 'true', 'bool', '启用邮箱登陆',NOW(), NOW()),
|
||||
('email', 'EmailSmtpHost', '', 'string', '邮箱服务器地址', NOW(), NOW()),
|
||||
('email', 'EmailSmtpPort', '465', 'int', '邮箱服务器端口',NOW(), NOW()),
|
||||
('email', 'EmailSmtpUser', 'domain@f1shyu.com', 'string', '邮箱服务器用户名', NOW(), NOW()),
|
||||
('email', 'EmailSmtpPass', 'password', 'string', '邮箱服务器密码', NOW(), NOW()),
|
||||
('email', 'EmailSmtpFrom', 'domain@f1shyu.com', 'string', '发送邮件的邮箱',NOW(), NOW()),
|
||||
('email', 'EmailSmtpSSL', 'true', 'bool', '邮箱服务器加密方式',NOW(), NOW()),
|
||||
('email', 'EmailTemplate', '%s', 'string', '邮件模板',NOW(), NOW()),
|
||||
('email', 'VerifyEmailTemplate', '', 'string', 'Verify Email template',NOW(), NOW()),
|
||||
('email', 'MaintenanceEmailTemplate', '', 'string', 'Maintenance Email template',NOW(), NOW()),
|
||||
('email', 'ExpirationEmailTemplate', '', 'string', 'Expiration Email template', NOW(), NOW()),
|
||||
('email', 'EmailEnableVerify', 'true', 'bool', '是否开启邮箱验证', NOW(), NOW()),
|
||||
('email', 'EmailEnableDomainSuffix', 'false', 'bool', '是否开启邮箱域名后缀限制',NOW(), NOW()),
|
||||
('email', 'EmailDomainSuffixList', 'qq.com', 'string', '邮箱域名后缀列表',NOW(), NOW());
|
||||
COMMIT;
|
||||
|
||||
-- User Device
|
||||
CREATE TABLE IF NOT EXISTS `user_device` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint NOT NULL COMMENT 'User ID',
|
||||
`device_number` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Number.',
|
||||
`online` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Online',
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'EnableDeviceNumber',
|
||||
`last_online` datetime(3) DEFAULT NULL COMMENT 'Last Online',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`),
|
||||
CONSTRAINT `fk_user_user_devices` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- Mobile
|
||||
CREATE TABLE IF NOT EXISTS `sms` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`content` text COLLATE utf8mb4_general_ci,
|
||||
`platform` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`area_code` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`telephone` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`status` tinyint(1) DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- Application Config
|
||||
CREATE TABLE IF NOT EXISTS `application_config` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`app_id` bigint NOT NULL DEFAULT '0' COMMENT 'App id',
|
||||
`encryption_key` text COLLATE utf8mb4_general_ci COMMENT 'Encryption Key',
|
||||
`encryption_method` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Encryption Method',
|
||||
`domains` text COLLATE utf8mb4_general_ci,
|
||||
`startup_picture` text COLLATE utf8mb4_general_ci,
|
||||
`startup_picture_skip_time` bigint NOT NULL DEFAULT '0' COMMENT 'Startup Picture Skip Time',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- Application Version
|
||||
CREATE TABLE IF NOT EXISTS `application_version` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`url` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用地址',
|
||||
`version` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用版本',
|
||||
`platform` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用平台',
|
||||
`is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT '默认版本',
|
||||
`description` text COLLATE utf8mb4_general_ci COMMENT '更新描述',
|
||||
`application_id` bigint DEFAULT NULL COMMENT '所属应用',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `fk_application_application_versions` (`application_id`),
|
||||
CONSTRAINT `fk_application_application_versions` FOREIGN KEY (`application_id`) REFERENCES `application` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
UPDATE `subscribe` SET `unit_time`='Month' WHERE unit_time = '';
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
@@ -0,0 +1,44 @@
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
DROP TABLE IF EXISTS `user_device`;
|
||||
-- User Device
|
||||
CREATE TABLE IF NOT EXISTS `user_device` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint NOT NULL COMMENT 'User ID',
|
||||
`subscribe_id` bigint DEFAULT NULL COMMENT 'Subscribe ID',
|
||||
`ip` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Ip.',
|
||||
`Identifier` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Identifier.',
|
||||
`user_agent` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device User Agent.',
|
||||
`online` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Online',
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'EnableDeviceNumber',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`),
|
||||
CONSTRAINT `fk_user_user_devices` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for server_rule_group
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `server_rule_group`;
|
||||
CREATE TABLE `server_rule_group` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Rule Group Name',
|
||||
`icon` text COLLATE utf8mb4_general_ci COMMENT 'Rule Group Icon',
|
||||
`description` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Rule Group Description',
|
||||
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Rule Group Enable',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `unique_name` (`name`) -- Add unique constraint to `name`
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
-- ----------------------------
|
||||
-- Records of server_rule_group
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
COMMIT;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
@@ -0,0 +1,562 @@
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ads
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `ads` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`title` varchar(255) COLLATE utf8mb4_german2_ci NOT NULL DEFAULT '' COMMENT 'Ads title',
|
||||
`type` varchar(255) COLLATE utf8mb4_german2_ci NOT NULL DEFAULT '' COMMENT 'Ads type',
|
||||
`content` text COLLATE utf8mb4_german2_ci COMMENT 'Ads content',
|
||||
`target_url` varchar(512) COLLATE utf8mb4_german2_ci DEFAULT '' COMMENT 'Ads target url',
|
||||
`start_time` datetime DEFAULT NULL COMMENT 'Ads start time',
|
||||
`end_time` datetime DEFAULT NULL COMMENT 'Ads end time',
|
||||
`status` tinyint(1) DEFAULT '0' COMMENT 'Ads status,0 disable,1 enable',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_german2_ci;
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for announcement
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `announcement` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`title` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Title',
|
||||
`content` text COLLATE utf8mb4_general_ci COMMENT 'Content',
|
||||
`show` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Show',
|
||||
`pinned` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Pinned',
|
||||
`popup` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Popup',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
-- ----------------------------
|
||||
-- Table structure for application
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `application` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用名称',
|
||||
`icon` text COLLATE utf8mb4_general_ci NOT NULL COMMENT '应用图标',
|
||||
`description` text COLLATE utf8mb4_general_ci COMMENT '更新描述',
|
||||
`subscribe_type` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订阅类型',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
-- ----------------------------
|
||||
-- Table structure for application_config
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `application_config` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`app_id` bigint NOT NULL DEFAULT '0' COMMENT 'App id',
|
||||
`encryption_key` text COLLATE utf8mb4_general_ci COMMENT 'Encryption Key',
|
||||
`encryption_method` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Encryption Method',
|
||||
`domains` text COLLATE utf8mb4_general_ci,
|
||||
`startup_picture` text COLLATE utf8mb4_general_ci,
|
||||
`startup_picture_skip_time` bigint NOT NULL DEFAULT '0' COMMENT 'Startup Picture Skip Time',
|
||||
`invitation_link` text COLLATE utf8mb4_general_ci COMMENT 'Invitation Link',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
-- ----------------------------
|
||||
-- Table structure for application_version
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `application_version` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`url` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用地址',
|
||||
`version` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用版本',
|
||||
`platform` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用平台',
|
||||
`is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT '默认版本',
|
||||
`description` text COLLATE utf8mb4_general_ci COMMENT '更新描述',
|
||||
`application_id` bigint DEFAULT NULL COMMENT '所属应用',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `fk_application_application_versions` (`application_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for coupon
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `coupon` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Coupon Name',
|
||||
`code` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Coupon Code',
|
||||
`count` bigint NOT NULL DEFAULT '0' COMMENT 'Count Limit',
|
||||
`type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Coupon Type: 1: Percentage 2: Fixed Amount',
|
||||
`discount` bigint NOT NULL DEFAULT '0' COMMENT 'Coupon Discount',
|
||||
`start_time` bigint NOT NULL DEFAULT '0' COMMENT 'Start Time',
|
||||
`expire_time` bigint NOT NULL DEFAULT '0' COMMENT 'Expire Time',
|
||||
`user_limit` bigint NOT NULL DEFAULT '0' COMMENT 'User Limit',
|
||||
`subscribe` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subscribe Limit',
|
||||
`used_count` bigint NOT NULL DEFAULT '0' COMMENT 'Used Count',
|
||||
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Enable',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uni_coupon_code` (`code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for document
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `document` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`title` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Document Title',
|
||||
`content` text COLLATE utf8mb4_general_ci COMMENT 'Document Content',
|
||||
`tags` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Document Tags',
|
||||
`show` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Show',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
-- ----------------------------
|
||||
-- Table structure for auth_method
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `auth_method` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`method` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'method',
|
||||
`config` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'OAuth Configuration',
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Enabled',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uni_auth_method` (`method`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for order
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `order` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`parent_id` bigint DEFAULT NULL COMMENT 'Parent Order Id',
|
||||
`user_id` bigint NOT NULL DEFAULT '0' COMMENT 'User Id',
|
||||
`order_no` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Order No',
|
||||
`type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Order Type: 1: Subscribe, 2: Renewal, 3: ResetTraffic, 4: Recharge',
|
||||
`quantity` bigint NOT NULL DEFAULT '1' COMMENT 'Quantity',
|
||||
`price` bigint NOT NULL DEFAULT '0' COMMENT 'Original price',
|
||||
`amount` bigint NOT NULL DEFAULT '0' COMMENT 'Order Amount',
|
||||
`gift_amount` bigint NOT NULL DEFAULT '0' COMMENT 'User Gift Amount',
|
||||
`discount` bigint NOT NULL DEFAULT '0' COMMENT 'Discount Amount',
|
||||
`coupon` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Coupon',
|
||||
`coupon_discount` bigint NOT NULL DEFAULT '0' COMMENT 'Coupon Discount Amount',
|
||||
`commission` bigint NOT NULL DEFAULT '0' COMMENT 'Order Commission',
|
||||
`payment_id` bigint NOT NULL DEFAULT '-1' COMMENT 'Payment Id',
|
||||
`method` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Payment Method',
|
||||
`fee_amount` bigint NOT NULL DEFAULT '0' COMMENT 'Fee Amount',
|
||||
`trade_no` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Trade No',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Order Status: 1: Pending, 2: Paid, 3:Close, 4: Failed, 5:Finished',
|
||||
`subscribe_id` bigint NOT NULL DEFAULT '0' COMMENT 'Subscribe Id',
|
||||
`subscribe_token` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Renewal Subscribe Token',
|
||||
`is_new` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is New Order',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uni_order_order_no` (`order_no`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for payment
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `payment` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Payment Name',
|
||||
`platform` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Payment Platform',
|
||||
`description` text COLLATE utf8mb4_general_ci COMMENT 'Payment Description',
|
||||
`icon` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Payment Icon',
|
||||
`domain` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Notification Domain',
|
||||
`config` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Payment Configuration',
|
||||
`fee_mode` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Fee Mode: 0: No Fee 1: Percentage 2: Fixed Amount 3: Percentage + Fixed Amount',
|
||||
`fee_percent` bigint DEFAULT '0' COMMENT 'Fee Percentage',
|
||||
`fee_amount` bigint DEFAULT '0' COMMENT 'Fixed Fee Amount',
|
||||
`enable` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Enabled',
|
||||
`token` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Payment Token',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uni_payment_token` (`token`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
-- ----------------------------
|
||||
-- Table structure for server
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `server` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Node Name',
|
||||
`tags` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Tags',
|
||||
`country` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Country',
|
||||
`city` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'City',
|
||||
`latitude` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'latitude',
|
||||
`longitude` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'longitude',
|
||||
`server_addr` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Server Address',
|
||||
`relay_mode` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'none' COMMENT 'Relay Mode',
|
||||
`relay_node` text COLLATE utf8mb4_general_ci COMMENT 'Relay Node',
|
||||
`speed_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Speed Limit',
|
||||
`traffic_ratio` decimal(4,2) NOT NULL DEFAULT '0.00' COMMENT 'Traffic Ratio',
|
||||
`group_id` bigint DEFAULT NULL COMMENT 'Group ID',
|
||||
`protocol` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Protocol',
|
||||
`config` text COLLATE utf8mb4_general_ci COMMENT 'Config',
|
||||
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Enabled',
|
||||
`sort` bigint NOT NULL DEFAULT '0' COMMENT 'Sort',
|
||||
`last_reported_at` datetime(3) DEFAULT NULL COMMENT 'Last Reported Time',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_group_id` (`group_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
-- ----------------------------
|
||||
-- Table structure for server_group
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `server_group` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Group Name',
|
||||
`description` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Group Description',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
-- ----------------------------
|
||||
-- Table structure for sms
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `sms` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`content` text COLLATE utf8mb4_general_ci,
|
||||
`platform` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`area_code` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`telephone` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||
`status` tinyint(1) DEFAULT '1',
|
||||
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
-- ----------------------------
|
||||
-- Table structure for subscribe
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `subscribe` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subscribe Name',
|
||||
`description` text COLLATE utf8mb4_general_ci COMMENT 'Subscribe Description',
|
||||
`unit_price` bigint NOT NULL DEFAULT '0' COMMENT 'Unit Price',
|
||||
`unit_time` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Unit Time',
|
||||
`discount` text COLLATE utf8mb4_general_ci COMMENT 'Discount',
|
||||
`replacement` bigint NOT NULL DEFAULT '0' COMMENT 'Replacement',
|
||||
`inventory` bigint NOT NULL DEFAULT '0' COMMENT 'Inventory',
|
||||
`traffic` bigint NOT NULL DEFAULT '0' COMMENT 'Traffic',
|
||||
`speed_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Speed Limit',
|
||||
`device_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Device Limit',
|
||||
`quota` bigint NOT NULL DEFAULT '0' COMMENT 'Quota',
|
||||
`group_id` bigint DEFAULT NULL COMMENT 'Group Id',
|
||||
`server_group` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Server Group',
|
||||
`server` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Server',
|
||||
`show` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Show portal page',
|
||||
`sell` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Sell',
|
||||
`sort` bigint NOT NULL DEFAULT '0' COMMENT 'Sort',
|
||||
`deduction_ratio` bigint DEFAULT '0' COMMENT 'Deduction Ratio',
|
||||
`allow_deduction` tinyint(1) DEFAULT '1' COMMENT 'Allow deduction',
|
||||
`reset_cycle` bigint DEFAULT '0' COMMENT 'Reset Cycle: 0: No Reset, 1: 1st, 2: Monthly, 3: Yearly',
|
||||
`renewal_reset` tinyint(1) DEFAULT '0' COMMENT 'Renew Reset',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
-- ----------------------------
|
||||
-- Table structure for subscribe_group
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `subscribe_group` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Group Name',
|
||||
`description` text COLLATE utf8mb4_general_ci COMMENT 'Group Description',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
-- ----------------------------
|
||||
-- Table structure for subscribe_type
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `subscribe_type` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订阅类型',
|
||||
`mark` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订阅标识',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for system
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `system` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`category` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Category',
|
||||
`key` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Key Name',
|
||||
`value` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Key Value',
|
||||
`type` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Type',
|
||||
`desc` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Description',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uni_system_key` (`key`),
|
||||
KEY `index_key` (`key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ticket
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `ticket` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`title` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Title',
|
||||
`description` text COLLATE utf8mb4_general_ci COMMENT 'Description',
|
||||
`user_id` bigint NOT NULL DEFAULT '0' COMMENT 'UserId',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Status',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for ticket_follow
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `ticket_follow` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`ticket_id` bigint NOT NULL DEFAULT '0' COMMENT 'TicketId',
|
||||
`from` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'From',
|
||||
`type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Type: 1 text, 2 image',
|
||||
`content` text COLLATE utf8mb4_general_ci COMMENT 'Content',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for traffic_log
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `traffic_log` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`server_id` bigint NOT NULL COMMENT 'Server ID',
|
||||
`user_id` bigint NOT NULL COMMENT 'User ID',
|
||||
`subscribe_id` bigint NOT NULL COMMENT 'Subscription ID',
|
||||
`download` bigint DEFAULT '0' COMMENT 'Download Traffic',
|
||||
`upload` bigint DEFAULT '0' COMMENT 'Upload Traffic',
|
||||
`timestamp` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Traffic Log Time',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_subscribe_id` (`subscribe_id`),
|
||||
KEY `idx_server_id` (`server_id`),
|
||||
KEY `idx_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `user` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`password` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'User Password',
|
||||
`avatar` text COLLATE utf8mb4_general_ci COMMENT 'User Avatar',
|
||||
`balance` bigint DEFAULT '0' COMMENT 'User Balance',
|
||||
`telegram` bigint DEFAULT NULL COMMENT 'Telegram Account',
|
||||
`refer_code` varchar(20) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Referral Code',
|
||||
`referer_id` bigint DEFAULT NULL COMMENT 'Referrer ID',
|
||||
`commission` bigint DEFAULT '0' COMMENT 'Commission',
|
||||
`gift_amount` bigint DEFAULT '0' COMMENT 'User Gift Amount',
|
||||
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Is Account Enabled',
|
||||
`is_admin` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Admin',
|
||||
`valid_email` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Email Verified',
|
||||
`enable_email_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Email Notifications',
|
||||
`enable_telegram_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Telegram Notifications',
|
||||
`enable_balance_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Balance Change Notifications',
|
||||
`enable_login_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Login Notifications',
|
||||
`enable_subscribe_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Subscription Notifications',
|
||||
`enable_trade_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Trade Notifications',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
`deleted_at` datetime(3) DEFAULT NULL COMMENT 'Deletion Time',
|
||||
`is_del` bigint unsigned DEFAULT NULL COMMENT '1: Normal 0: Deleted',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_referer` (`referer_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_auth_methods
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `user_auth_methods` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint NOT NULL COMMENT 'User ID',
|
||||
`auth_type` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Auth Type 1: apple 2: google 3: github 4: facebook 5: telegram 6: email 7: phone',
|
||||
`auth_identifier` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Auth Identifier',
|
||||
`verified` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Verified',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`),
|
||||
UNIQUE KEY `idx_auth_identifier` (`auth_identifier`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_balance_log
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `user_balance_log` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint NOT NULL COMMENT 'User ID',
|
||||
`amount` bigint NOT NULL COMMENT 'Amount',
|
||||
`type` tinyint(1) NOT NULL COMMENT 'Type: 1: Recharge 2: Withdraw 3: Payment 4: Refund 5: Reward',
|
||||
`order_id` bigint DEFAULT NULL COMMENT 'Order ID',
|
||||
`balance` bigint NOT NULL COMMENT 'Balance',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_commission_log
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `user_commission_log` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint NOT NULL COMMENT 'User ID',
|
||||
`order_no` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Order No.',
|
||||
`amount` bigint NOT NULL COMMENT 'Amount',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_device
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `user_device` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint NOT NULL COMMENT 'User ID',
|
||||
`subscribe_id` bigint DEFAULT NULL COMMENT 'Subscribe ID',
|
||||
`ip` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Ip.',
|
||||
`Identifier` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Identifier.',
|
||||
`user_agent` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device User Agent.',
|
||||
`online` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Online',
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'EnableDeviceNumber',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_gift_amount_log
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `user_gift_amount_log` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint NOT NULL COMMENT 'User ID',
|
||||
`user_subscribe_id` bigint DEFAULT NULL COMMENT 'Deduction User Subscribe ID',
|
||||
`order_no` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Order No.',
|
||||
`type` tinyint(1) NOT NULL COMMENT 'Type: 1: Increase 2: Reduce',
|
||||
`amount` bigint NOT NULL COMMENT 'Amount',
|
||||
`balance` bigint NOT NULL COMMENT 'Balance',
|
||||
`remark` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Remark',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_subscribe
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `user_subscribe` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint NOT NULL COMMENT 'User ID',
|
||||
`order_id` bigint NOT NULL COMMENT 'Order ID',
|
||||
`subscribe_id` bigint NOT NULL COMMENT 'Subscription ID',
|
||||
`start_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Subscription Start Time',
|
||||
`expire_time` datetime(3) DEFAULT NULL COMMENT 'Subscription Expire Time',
|
||||
`traffic` bigint DEFAULT '0' COMMENT 'Traffic',
|
||||
`download` bigint DEFAULT '0' COMMENT 'Download Traffic',
|
||||
`upload` bigint DEFAULT '0' COMMENT 'Upload Traffic',
|
||||
`token` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Token',
|
||||
`uuid` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'UUID',
|
||||
`status` tinyint(1) DEFAULT '0' COMMENT 'Subscription Status: 0: Pending 1: Active 2: Finished 3: Expired 4: Deducted',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
`finished_at` datetime(3) DEFAULT NULL COMMENT 'Finished At',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uni_user_subscribe_token` (`token`),
|
||||
UNIQUE KEY `uni_user_subscribe_uuid` (`uuid`),
|
||||
KEY `idx_user_id` (`user_id`),
|
||||
KEY `idx_order_id` (`order_id`),
|
||||
KEY `idx_subscribe_id` (`subscribe_id`),
|
||||
KEY `idx_token` (`token`),
|
||||
KEY `idx_uuid` (`uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `server_rule_group` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Rule Group Name',
|
||||
`icon` text COLLATE utf8mb4_general_ci COMMENT 'Rule Group Icon',
|
||||
`description` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Rule Group Description',
|
||||
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Rule Group Enable',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `unique_name` (`name`) -- Add unique constraint to `name`
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_login_log
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `user_login_log` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint NOT NULL COMMENT 'User ID',
|
||||
`login_ip` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Login IP',
|
||||
`user_agent` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'UserAgent',
|
||||
`success` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Login Success',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_subscribe_log
|
||||
-- ----------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `user_subscribe_log` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint NOT NULL COMMENT 'User ID',
|
||||
`user_subscribe_id` bigint NOT NULL COMMENT 'User Subscribe ID',
|
||||
`token` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Token',
|
||||
`ip` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'IP',
|
||||
`user_agent` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'UserAgent',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`),
|
||||
KEY `idx_user_subscribe_id` (`user_subscribe_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `message_log` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`type` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'email' COMMENT 'Message Type',
|
||||
`platform` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'smtp' COMMENT 'Platform',
|
||||
`to` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'To',
|
||||
`subject` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subject',
|
||||
`content` text COLLATE utf8mb4_general_ci COMMENT 'Content',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Status',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
|
||||
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_device_online_record
|
||||
-- ----------------------------
|
||||
CREATE TABLE IF NOT EXISTS `user_device_online_record` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint NULL DEFAULT NULL COMMENT 'User ID',
|
||||
`identifier` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT 'Device Identifier',
|
||||
`online_time` datetime(3) NULL DEFAULT NULL COMMENT 'Online Time',
|
||||
`offline_time` datetime(3) NULL DEFAULT NULL COMMENT 'Offline Time',
|
||||
`online_seconds` bigint NOT NULL DEFAULT '0' COMMENT 'Online Seconds ',
|
||||
`duration_days` bigint NOT NULL DEFAULT '0' COMMENT 'Duration Days ',
|
||||
`created_at` datetime(3) NULL DEFAULT NULL COMMENT 'Creation Time',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
@@ -0,0 +1,644 @@
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/auth"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/payment"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/subscribeType"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/system"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/user"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/constant"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/email"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/sms"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/tool"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/uuidx"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func InitPPanelSQL(db *gorm.DB) error {
|
||||
logger.Info("PPanel SQL initialization started")
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
logger.Info("PPanel SQL initialization completed", logger.Field("duration", time.Since(startTime).String()))
|
||||
|
||||
}()
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
var err error
|
||||
defer func() {
|
||||
// If an error occurs, delete all tables
|
||||
if err != nil {
|
||||
logger.Debugf("PPanel SQL initialization completed, err: %v", err.Error())
|
||||
tables, _ := tx.Migrator().GetTables()
|
||||
for _, table := range tables {
|
||||
tx.Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table))
|
||||
}
|
||||
}
|
||||
}()
|
||||
// init ppanel.sql file
|
||||
if err = ExecuteSQLFile(tx, "database/ppanel.sql"); err != nil {
|
||||
return err
|
||||
}
|
||||
//Insert basic system data
|
||||
if err = insertBasicSystemData(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
// insert into OAuth config
|
||||
if err = insertAuthMethodConfig(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
// insert into Payment config
|
||||
if err = insertPaymentConfig(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
// insert into SubscribeType
|
||||
if err = insertSubscribeType(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func insertBasicSystemData(tx *gorm.DB) error {
|
||||
if err := insertSiteConfig(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertSubscribeConfig(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertVerifyConfig(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertSeverConfig(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertInviteConfig(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertRegisterConfig(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertCurrencyConfig(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertVerifyCodeConfig(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
version := system.System{
|
||||
Category: "system",
|
||||
Key: "Version",
|
||||
Value: constant.Version,
|
||||
Type: "string",
|
||||
Desc: "System Version",
|
||||
}
|
||||
if err := tx.Model(&system.System{}).Save(&version).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// insertSiteConfig
|
||||
func insertSiteConfig(tx *gorm.DB) error {
|
||||
siteConfig := []system.System{
|
||||
{
|
||||
Category: "site",
|
||||
Key: "SiteLogo",
|
||||
Value: "/favicon.svg",
|
||||
Type: "string",
|
||||
Desc: "Site Logo",
|
||||
},
|
||||
{
|
||||
Category: "site",
|
||||
Key: "SiteName",
|
||||
Value: "Perfect Panel",
|
||||
Type: "string",
|
||||
Desc: "Site Name",
|
||||
},
|
||||
{
|
||||
Category: "site",
|
||||
Key: "SiteDesc",
|
||||
Value: "PPanel is a pure, professional, and perfect open-source proxy panel tool, designed to be your ideal choice for learning and practical use.",
|
||||
Type: "string",
|
||||
Desc: "Site Description",
|
||||
},
|
||||
{
|
||||
Category: "site",
|
||||
Key: "Host",
|
||||
Value: "",
|
||||
Type: "string",
|
||||
Desc: "Site Host",
|
||||
},
|
||||
{
|
||||
Category: "site",
|
||||
Key: "Keywords",
|
||||
Value: "Perfect Panel,PPanel",
|
||||
Type: "string",
|
||||
Desc: "Site Keywords",
|
||||
},
|
||||
{
|
||||
Category: "site",
|
||||
Key: "CustomHTML",
|
||||
Value: "",
|
||||
Type: "string",
|
||||
Desc: "Custom HTML",
|
||||
},
|
||||
{
|
||||
Category: "site",
|
||||
Key: "CustomData",
|
||||
Value: "{\"website\":\"\",\"contacts\":{\"email\":\"\",\"telephone\":\"\",\"address\":\"\"},\"community\":{\"telegram\":\"\",\"twitter\":\"\",\"discord\":\"\",\"instagram\":\"\",\"linkedin\":\"\",\"facebook\":\"\",\"github\":\"\"}}",
|
||||
Type: "string",
|
||||
Desc: "Custom data",
|
||||
},
|
||||
{
|
||||
Category: "tos",
|
||||
Key: "TosContent",
|
||||
Value: "Welcome to use Perfect Panel",
|
||||
Type: "string",
|
||||
Desc: "Terms of Service",
|
||||
},
|
||||
{
|
||||
Category: "tos",
|
||||
Key: "PrivacyPolicy",
|
||||
Value: "",
|
||||
Type: "string",
|
||||
Desc: "PrivacyPolicy",
|
||||
},
|
||||
{
|
||||
Category: "ad",
|
||||
Key: "WebAD",
|
||||
Value: "false",
|
||||
Type: "bool",
|
||||
Desc: "Display ad on the web",
|
||||
},
|
||||
}
|
||||
return tx.Model(&system.System{}).Save(&siteConfig).Error
|
||||
}
|
||||
|
||||
// insertSubscribeConfig
|
||||
func insertSubscribeConfig(tx *gorm.DB) error {
|
||||
subscribeConfig := []system.System{
|
||||
{
|
||||
Category: "subscribe",
|
||||
Key: "SingleModel",
|
||||
Value: "false",
|
||||
Type: "bool",
|
||||
Desc: "是否单订阅模式",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
{
|
||||
Category: "subscribe",
|
||||
Key: "SubscribePath",
|
||||
Value: "/api/subscribe",
|
||||
Type: "string",
|
||||
Desc: "订阅路径",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
{
|
||||
Category: "subscribe",
|
||||
Key: "SubscribeDomain",
|
||||
Value: "",
|
||||
Type: "string",
|
||||
Desc: "订阅域名",
|
||||
},
|
||||
{
|
||||
Category: "subscribe",
|
||||
Key: "PanDomain",
|
||||
Value: "false",
|
||||
Type: "bool",
|
||||
Desc: "是否使用泛域名",
|
||||
},
|
||||
}
|
||||
return tx.Model(&system.System{}).Save(&subscribeConfig).Error
|
||||
}
|
||||
|
||||
// insertVerifyConfig
|
||||
func insertVerifyConfig(tx *gorm.DB) error {
|
||||
verifyConfig := []system.System{
|
||||
{
|
||||
Category: "verify",
|
||||
Key: "TurnstileSiteKey",
|
||||
Value: "",
|
||||
Type: "string",
|
||||
Desc: "TurnstileSiteKey",
|
||||
},
|
||||
{
|
||||
Category: "verify",
|
||||
Key: "TurnstileSecret",
|
||||
Value: "",
|
||||
Type: "string",
|
||||
Desc: "TurnstileSecret",
|
||||
},
|
||||
{
|
||||
Category: "verify",
|
||||
Key: "EnableLoginVerify",
|
||||
Value: "false",
|
||||
Type: "bool",
|
||||
Desc: "is enable login verify",
|
||||
},
|
||||
{
|
||||
Category: "verify",
|
||||
Key: "EnableRegisterVerify",
|
||||
Value: "false",
|
||||
Type: "bool",
|
||||
Desc: "is enable register verify",
|
||||
},
|
||||
{
|
||||
Category: "verify",
|
||||
Key: "EnableResetPasswordVerify",
|
||||
Value: "false",
|
||||
Type: "bool",
|
||||
Desc: "is enable reset password verify",
|
||||
},
|
||||
}
|
||||
return tx.Model(&system.System{}).Save(&verifyConfig).Error
|
||||
}
|
||||
|
||||
// insertSeverConfig
|
||||
func insertSeverConfig(tx *gorm.DB) error {
|
||||
serverConfig := []system.System{
|
||||
{
|
||||
Category: "server",
|
||||
Key: "NodeSecret",
|
||||
Value: "12345678",
|
||||
Type: "string",
|
||||
Desc: "node secret",
|
||||
},
|
||||
{
|
||||
Category: "server",
|
||||
Key: "NodePullInterval",
|
||||
Value: "10",
|
||||
Type: "int",
|
||||
Desc: "node pull interval",
|
||||
},
|
||||
{
|
||||
Category: "server",
|
||||
Key: "NodePushInterval",
|
||||
Value: "60",
|
||||
Type: "int",
|
||||
Desc: "node push interval",
|
||||
},
|
||||
{
|
||||
Category: "server",
|
||||
Key: "NodeMultiplierConfig",
|
||||
Value: "[]",
|
||||
Type: "string",
|
||||
Desc: "node multiplier config",
|
||||
},
|
||||
}
|
||||
return tx.Model(&system.System{}).Save(&serverConfig).Error
|
||||
}
|
||||
|
||||
// insertInviteConfig
|
||||
func insertInviteConfig(tx *gorm.DB) error {
|
||||
inviteConfig := []system.System{
|
||||
{
|
||||
Category: "invite",
|
||||
Key: "ForcedInvite",
|
||||
Value: "false",
|
||||
Type: "bool",
|
||||
Desc: "Forced invite",
|
||||
},
|
||||
{
|
||||
Category: "invite",
|
||||
Key: "ReferralPercentage",
|
||||
Value: "20",
|
||||
Type: "int",
|
||||
Desc: "Referral percentage",
|
||||
},
|
||||
{
|
||||
Category: "invite",
|
||||
Key: "OnlyFirstPurchase",
|
||||
Value: "false",
|
||||
Type: "bool",
|
||||
Desc: "Only first purchase",
|
||||
},
|
||||
}
|
||||
return tx.Model(&system.System{}).Save(&inviteConfig).Error
|
||||
}
|
||||
|
||||
// insertRegisterConfig
|
||||
func insertRegisterConfig(tx *gorm.DB) error {
|
||||
registerConfig := []system.System{
|
||||
{
|
||||
Category: "register",
|
||||
Key: "StopRegister",
|
||||
Value: "false",
|
||||
Type: "bool",
|
||||
Desc: "is stop register",
|
||||
},
|
||||
{
|
||||
Category: "register",
|
||||
Key: "EnableTrial",
|
||||
Value: "false",
|
||||
Type: "bool",
|
||||
Desc: "is enable trial",
|
||||
},
|
||||
{
|
||||
Category: "register",
|
||||
Key: "TrialSubscribe",
|
||||
Value: "",
|
||||
Type: "int",
|
||||
Desc: "Trial subscription",
|
||||
},
|
||||
{
|
||||
Category: "register",
|
||||
Key: "TrialTime",
|
||||
Value: "24",
|
||||
Type: "int",
|
||||
Desc: "Trial time",
|
||||
},
|
||||
{
|
||||
Category: "register",
|
||||
Key: "TrialTimeUnit",
|
||||
Value: "Hour",
|
||||
Type: "string",
|
||||
Desc: "Trial time unit",
|
||||
},
|
||||
{
|
||||
Category: "register",
|
||||
Key: "EnableIpRegisterLimit",
|
||||
Value: "false",
|
||||
Type: "bool",
|
||||
Desc: "is enable IP register limit",
|
||||
},
|
||||
{
|
||||
Category: "register",
|
||||
Key: "IpRegisterLimit",
|
||||
Value: "3",
|
||||
Type: "int",
|
||||
Desc: "IP Register Limit",
|
||||
},
|
||||
{
|
||||
Category: "register",
|
||||
Key: "IpRegisterLimitDuration",
|
||||
Value: "64",
|
||||
Type: "int",
|
||||
Desc: "IP Register Limit Duration (minutes)",
|
||||
},
|
||||
}
|
||||
return tx.Model(&system.System{}).Save(®isterConfig).Error
|
||||
}
|
||||
|
||||
// insertAuthMethodConfig
|
||||
func insertAuthMethodConfig(tx *gorm.DB) error {
|
||||
// insert into OAuth config
|
||||
var methods []auth.Auth
|
||||
methods = append(methods, []auth.Auth{
|
||||
initEmailConfig(),
|
||||
initMobileConfig(),
|
||||
{
|
||||
Method: "apple",
|
||||
Config: new(auth.AppleAuthConfig).Marshal(),
|
||||
},
|
||||
{
|
||||
Method: "google",
|
||||
Config: new(auth.GoogleAuthConfig).Marshal(),
|
||||
},
|
||||
{
|
||||
Method: "github",
|
||||
Config: new(auth.GithubAuthConfig).Marshal(),
|
||||
},
|
||||
{
|
||||
Method: "facebook",
|
||||
Config: new(auth.FacebookAuthConfig).Marshal(),
|
||||
},
|
||||
{
|
||||
|
||||
Method: "telegram",
|
||||
Config: new(auth.TelegramAuthConfig).Marshal(),
|
||||
},
|
||||
{
|
||||
Method: "device",
|
||||
Config: new(auth.DeviceConfig).Marshal(),
|
||||
},
|
||||
}...)
|
||||
return tx.Model(&auth.Auth{}).Save(&methods).Error
|
||||
}
|
||||
|
||||
// insertPaymentConfig
|
||||
func insertPaymentConfig(tx *gorm.DB) error {
|
||||
enable := true
|
||||
payments := []payment.Payment{
|
||||
{
|
||||
Id: -1,
|
||||
Name: "Balance",
|
||||
Platform: "balance",
|
||||
Icon: "",
|
||||
Domain: "",
|
||||
Config: "",
|
||||
FeeMode: 0,
|
||||
FeePercent: 0,
|
||||
FeeAmount: 0,
|
||||
Enable: &enable,
|
||||
},
|
||||
}
|
||||
// reset auto increment
|
||||
if err := tx.Exec("ALTER TABLE `payment` AUTO_INCREMENT = 1").Error; err != nil {
|
||||
logger.Errorw("Reset auto increment failed", logger.Field("error", err))
|
||||
return err
|
||||
}
|
||||
return tx.Model(&payment.Payment{}).Save(&payments).Error
|
||||
}
|
||||
|
||||
// insertSubscribeType
|
||||
func insertSubscribeType(tx *gorm.DB) error {
|
||||
// insert into subscribe type
|
||||
var subscribeTypes []subscribeType.SubscribeType
|
||||
subscribeTypes = append(subscribeTypes, []subscribeType.SubscribeType{
|
||||
{
|
||||
Name: "Clash",
|
||||
Mark: "Clash",
|
||||
},
|
||||
{
|
||||
Name: "Hiddify",
|
||||
Mark: "Hiddify",
|
||||
},
|
||||
{
|
||||
Name: "Loon",
|
||||
Mark: "Loon",
|
||||
},
|
||||
{
|
||||
Name: "NekoBox",
|
||||
Mark: "NekoBox",
|
||||
},
|
||||
{
|
||||
Name: "NekoRay",
|
||||
Mark: "NekoRay",
|
||||
},
|
||||
{
|
||||
Name: "Netch",
|
||||
Mark: "Netch",
|
||||
},
|
||||
{
|
||||
Name: "Quantumult",
|
||||
Mark: "Quantumult",
|
||||
},
|
||||
{
|
||||
Name: "Shadowrocket",
|
||||
Mark: "Shadowrocket",
|
||||
},
|
||||
{
|
||||
Name: "Singbox",
|
||||
Mark: "Singbox",
|
||||
},
|
||||
{
|
||||
Name: "Surfboard",
|
||||
Mark: "Surfboard",
|
||||
},
|
||||
{
|
||||
Name: "Surge",
|
||||
Mark: "Surge",
|
||||
},
|
||||
{
|
||||
Name: "V2box",
|
||||
Mark: "V2box",
|
||||
},
|
||||
{
|
||||
Name: "V2rayN",
|
||||
Mark: "V2rayN",
|
||||
},
|
||||
{
|
||||
Name: "V2rayNg",
|
||||
Mark: "V2rayNg",
|
||||
},
|
||||
}...)
|
||||
// insert into payment
|
||||
return tx.Save(&subscribeTypes).Error
|
||||
}
|
||||
|
||||
// CreateAdminUser create admin user
|
||||
func CreateAdminUser(email, password string, tx *gorm.DB) error {
|
||||
enable := true
|
||||
return tx.Transaction(func(tx *gorm.DB) error {
|
||||
// Prevent duplicate creation
|
||||
if tx.Model(&user.User{}).Find(&user.User{}).RowsAffected != 0 {
|
||||
logger.Info("User already exists, skip creating administrator account")
|
||||
return nil
|
||||
}
|
||||
|
||||
u := user.User{
|
||||
Password: tool.EncodePassWord(password),
|
||||
IsAdmin: &enable,
|
||||
ReferCode: uuidx.UserInviteCode(time.Now().Unix()),
|
||||
}
|
||||
if err := tx.Model(&user.User{}).Save(&u).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
method := user.AuthMethods{
|
||||
UserId: u.Id,
|
||||
AuthType: "email",
|
||||
AuthIdentifier: email,
|
||||
Verified: true,
|
||||
}
|
||||
if err := tx.Model(&user.AuthMethods{}).Save(&method).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func initEmailConfig() auth.Auth {
|
||||
enable := true
|
||||
smtpConfig := new(auth.SMTPConfig)
|
||||
emailConfig := auth.EmailAuthConfig{
|
||||
Platform: "smtp",
|
||||
PlatformConfig: smtpConfig,
|
||||
EnableVerify: false,
|
||||
EnableDomainSuffix: false,
|
||||
DomainSuffixList: "",
|
||||
VerifyEmailTemplate: email.DefaultEmailVerifyTemplate,
|
||||
ExpirationEmailTemplate: email.DefaultExpirationEmailTemplate,
|
||||
MaintenanceEmailTemplate: email.DefaultMaintenanceEmailTemplate,
|
||||
TrafficExceedEmailTemplate: email.DefaultTrafficExceedEmailTemplate,
|
||||
}
|
||||
authMethod := auth.Auth{
|
||||
Method: "email",
|
||||
Config: emailConfig.Marshal(),
|
||||
Enabled: &enable,
|
||||
}
|
||||
return authMethod
|
||||
}
|
||||
|
||||
func initMobileConfig() auth.Auth {
|
||||
cfg := new(auth.AlibabaCloudConfig)
|
||||
mobileConfig := auth.MobileAuthConfig{
|
||||
Platform: sms.AlibabaCloud.String(),
|
||||
PlatformConfig: cfg,
|
||||
EnableWhitelist: false,
|
||||
Whitelist: make([]string, 0),
|
||||
}
|
||||
authMethod := auth.Auth{
|
||||
Method: "mobile",
|
||||
Config: mobileConfig.Marshal(),
|
||||
}
|
||||
return authMethod
|
||||
}
|
||||
|
||||
// insert into currency config
|
||||
func insertCurrencyConfig(tx *gorm.DB) error {
|
||||
currencyConfig := []system.System{
|
||||
{
|
||||
Category: "currency",
|
||||
Key: "Currency",
|
||||
Value: "USD",
|
||||
Type: "string",
|
||||
Desc: "Currency",
|
||||
},
|
||||
{
|
||||
Category: "currency",
|
||||
Key: "CurrencySymbol",
|
||||
Value: "$",
|
||||
Type: "string",
|
||||
Desc: "Currency Symbol",
|
||||
},
|
||||
{
|
||||
Category: "currency",
|
||||
Key: "CurrencyUnit",
|
||||
Value: "USD",
|
||||
Type: "string",
|
||||
Desc: "Currency Unit",
|
||||
},
|
||||
{
|
||||
Category: "currency",
|
||||
Key: "AccessKey",
|
||||
Value: "",
|
||||
Type: "string",
|
||||
Desc: "Exchangerate Access Key",
|
||||
},
|
||||
}
|
||||
return tx.Model(&system.System{}).Save(¤cyConfig).Error
|
||||
}
|
||||
|
||||
// insert into verify code config
|
||||
func insertVerifyCodeConfig(tx *gorm.DB) error {
|
||||
verifyCodeConfig := []system.System{
|
||||
{
|
||||
Category: "verify_code",
|
||||
Key: "VerifyCodeExpireTime",
|
||||
Value: "300",
|
||||
Type: "int",
|
||||
Desc: "Verify code expire time",
|
||||
},
|
||||
{
|
||||
Category: "verify_code",
|
||||
Key: "VerifyCodeLimit",
|
||||
Value: "15",
|
||||
Type: "int",
|
||||
Desc: "limits of verify code",
|
||||
},
|
||||
{
|
||||
Category: "verify_code",
|
||||
Key: "VerifyCodeInterval",
|
||||
Value: "60",
|
||||
Type: "int",
|
||||
Desc: "Interval of verify code",
|
||||
},
|
||||
}
|
||||
return tx.Model(&system.System{}).Save(&verifyCodeConfig).Error
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/orm"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func connMySQL() *gorm.DB {
|
||||
|
||||
cfg := orm.Config{
|
||||
Addr: "127.0.0.1",
|
||||
Username: "root",
|
||||
Password: "mylove520",
|
||||
Dbname: "ppanel",
|
||||
}
|
||||
db, err := orm.ConnectMysql(orm.Mysql{
|
||||
Config: cfg,
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return db
|
||||
}
|
||||
func TestInitPPanelSQL(t *testing.T) {
|
||||
t.Skipf("Skip TestInitPPanelSQL")
|
||||
db := connMySQL()
|
||||
if db == nil {
|
||||
t.Error("connect mysql failed")
|
||||
return
|
||||
}
|
||||
if err := InitPPanelSQL(db); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Logf("InitPPanelSQL success")
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/system"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
)
|
||||
|
||||
//go:embed database/*.sql
|
||||
var sqlFiles embed.FS
|
||||
|
||||
func Migrate(ctx *svc.ServiceContext) {
|
||||
logger.Debug("SQL Migrate started")
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
logger.WithDuration(time.Since(startTime)).Debug("PPanel SQL Migrate completed")
|
||||
}()
|
||||
db := ctx.DB
|
||||
if !db.Migrator().HasTable(&system.System{}) {
|
||||
if err := InitPPanelSQL(db); err != nil {
|
||||
logger.Error("SQL Migrate failed", logger.Field("err", err.Error()))
|
||||
panic(err)
|
||||
}
|
||||
// create admin user
|
||||
if err := CreateAdminUser(ctx.Config.Administrator.Email, ctx.Config.Administrator.Password, db); err != nil {
|
||||
logger.Error("Create admin User failed", logger.Field("err", err.Error()))
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
package patch
|
||||
|
||||
import (
|
||||
"github.com/perfect-panel/ppanel-server/initialize/migrate"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/application"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/auth"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/log"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/server"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/system"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/user"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/email"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/sms"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Migrate01200(db *gorm.DB) error {
|
||||
var version = "0.1.2(01200)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if exists := db.Migrator().HasColumn(&user.OldUser{}, "email"); !exists {
|
||||
logger.Debug("Migrate 01200 skipped", logger.Field("reason", "old user table not exists"))
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Debug("Migrate 01200 started", logger.Field("step", "1"), logger.Field("action", "migrate old user to user auth methods"))
|
||||
var users []*user.OldUser
|
||||
if err := tx.Model(&user.OldUser{}).Find(&users).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Migrator().AutoMigrate(&user.AuthMethods{}); err != nil {
|
||||
logger.Errorw("Migrate 01200 failed", logger.Field("step", "1"), logger.Field("action", "create user auth methods table"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
err := tx.Transaction(func(tx *gorm.DB) error {
|
||||
for _, oldUser := range users {
|
||||
if oldUser.Email == "" {
|
||||
continue
|
||||
}
|
||||
// create user auth method
|
||||
authMethod := &user.AuthMethods{
|
||||
UserId: oldUser.Id,
|
||||
AuthType: "email",
|
||||
AuthIdentifier: oldUser.Email,
|
||||
Verified: false,
|
||||
}
|
||||
if err := tx.Create(authMethod).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorw("Migrate 01200 failed", logger.Field("step", "1"), logger.Field("action", "migrate old user to user auth methods"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
logger.Debug("Migrate 01200 completed", logger.Field("step", "1"), logger.Field("action", "migrate old user to user auth methods"))
|
||||
|
||||
logger.Debug("Migrate 01200 started", logger.Field("step", "2"), logger.Field("action", "exclude sql files"))
|
||||
// exclude sql files
|
||||
if err := migrate.ExecuteSQLFile(tx, "database/01200-patch.sql"); err != nil {
|
||||
logger.Errorw("Migrate 01200 failed", logger.Field("step", "2"), logger.Field("action", "exclude sql files"), logger.Field("file", "database/01200-patch.sql"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
logger.Debug("Migrate 01200 completed", logger.Field("step", "2"), logger.Field("action", "exclude sql files"))
|
||||
|
||||
logger.Debug("Migrate 01200 started", logger.Field("step", "3"), logger.Field("action", "update system config"))
|
||||
versionConfig := &system.System{
|
||||
Category: "system",
|
||||
Key: "Version",
|
||||
Value: version,
|
||||
Type: "string",
|
||||
Desc: "Version of the system, eg: 1.0.0(10000)",
|
||||
}
|
||||
// update system config
|
||||
if err := tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Save(&versionConfig).Error; err != nil {
|
||||
logger.Errorw("Migrate 01200 failed", logger.Field("step", "3"), logger.Field("action", "update system config"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func Migrate01201(db *gorm.DB) error {
|
||||
version := "0.1.2(01201)"
|
||||
// exclude sql files
|
||||
if err := migrate.ExecuteSQLFile(db, "database/01201-patch.sql"); err != nil {
|
||||
logger.Errorw("Migrate 01201 failed", logger.Field("step", "1"), logger.Field("action", "exclude sql files"), logger.Field("file", "database/01200-patch.sql"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
// update system config
|
||||
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
|
||||
logger.Errorw("Migrate 01201 failed", logger.Field("step", "2"), logger.Field("action", "update system config"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Migrate01202(db *gorm.DB) error {
|
||||
version := "0.1.2(01202)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
// migrate email config to system config
|
||||
if err := db.Migrator().AutoMigrate(&auth.Auth{}); err != nil {
|
||||
logger.Errorw("Migrate01202: AutoMigrate Auth failed", logger.Field("version", version), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
if db.Migrator().HasColumn("oauth_config", "platform") {
|
||||
if err := db.Migrator().RenameColumn("oauth_config", "platform", "method"); err != nil {
|
||||
logger.Errorw("Migrate01202: RenameColumn platform to method failed", logger.Field("version", version), logger.Field("error", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
// init email config
|
||||
if err := initEmailConfig(db); err != nil {
|
||||
logger.Errorw("Migrate01202: initEmailConfig failed", logger.Field("version", version), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
// init mobile config
|
||||
if err := initMobileConfig(db); err != nil {
|
||||
logger.Errorw("Migrate01202: initMobileConfig failed", logger.Field("version", version), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
// drop oauth_config table
|
||||
err := db.Migrator().DropTable("oauth_config")
|
||||
if err != nil {
|
||||
logger.Debug("Migrate01202: DropTable oauth_config failed", logger.Field("version", version), logger.Field("error", err.Error()))
|
||||
}
|
||||
// exclude sql files
|
||||
if err := migrate.ExecuteSQLFile(db, "database/01202-patch.sql"); err != nil {
|
||||
logger.Errorw("Migrate 01202 failed", logger.Field("action", "exclude sql files"), logger.Field("file", "database/012002-patch.sql"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
|
||||
// update system config
|
||||
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
|
||||
logger.Errorw("Migrate 01202 failed", logger.Field("step", "2"), logger.Field("action", "update system config"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func Migrate01203(db *gorm.DB) error {
|
||||
version := "0.1.2(01203)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := db.AutoMigrate(&user.LoginLog{}, &user.SubscribeLog{}); err != nil {
|
||||
logger.Errorw("Migrate01203: AutoMigrate LoginLog/SubscribeLog failed", logger.Field("version", version), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
// update version
|
||||
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
|
||||
logger.Errorw("Migrate01203: Update Version failed", logger.Field("version", version), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func Migrate01204(db *gorm.DB) error {
|
||||
version := "0.1.2(01204)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := db.AutoMigrate(&log.MessageLog{}); err != nil {
|
||||
logger.Errorw("Migrate01204: AutoMigrate MessageLog failed", logger.Field("version", version), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
// Trial configuration
|
||||
if err := initTrialConfig(tx); err != nil {
|
||||
logger.Errorw("Migrate01204: initTrialConfig failed", logger.Field("version", version), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
// Add auth method with device
|
||||
if err := addAuthMethodWithDevice(tx); err != nil {
|
||||
logger.Errorw("Migrate01204: Add auth method with device failed", logger.Field("version", version), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
// update version
|
||||
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
|
||||
logger.Errorw("Migrate01204: Update Version failed", logger.Field("version", version), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func Migrate01205(db *gorm.DB) error {
|
||||
version := "0.1.2(01205)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
// Add VerifyCode public configuration
|
||||
configs := []system.System{
|
||||
{
|
||||
Category: "verify_code",
|
||||
Key: "VerifyCodeExpireTime",
|
||||
Value: "5",
|
||||
Type: "int",
|
||||
Desc: "Verify code expire time",
|
||||
},
|
||||
{
|
||||
Category: "verify_code",
|
||||
Key: "VerifyCodeLimit",
|
||||
Value: "15",
|
||||
Type: "int",
|
||||
Desc: "limits of verify code",
|
||||
},
|
||||
{
|
||||
Category: "verify_code",
|
||||
Key: "VerifyCodeInterval",
|
||||
Value: "60",
|
||||
Type: "int",
|
||||
Desc: "Interval of verify code",
|
||||
},
|
||||
}
|
||||
if err := tx.Model(&system.System{}).Save(&configs).Error; err != nil {
|
||||
logger.Errorw("Migrate01205: Save VerifyCode public configuration failed", logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
|
||||
// update version
|
||||
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
|
||||
logger.Errorw("Migrate01205: Update Version failed", logger.Field("version", version), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func Migrate01301(db *gorm.DB) error {
|
||||
version := "0.1.3(01301)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
err := tx.Migrator().AlterColumn(&application.Application{}, "icon")
|
||||
if err != nil {
|
||||
logger.Errorw("Migrate01301: AlterColumn failed", logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
// update version
|
||||
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
|
||||
logger.Errorw("Migrate01205: Update Version failed", logger.Field("version", version), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func Migrate01602(db *gorm.DB) error {
|
||||
version := "0.1.6(01602)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if tx.Model(&system.System{}).Where("`category` = 'tos' AND `key` = 'TosContent'").Find(&system.System{}).RowsAffected == 0 {
|
||||
if err := tx.Save(&system.System{
|
||||
Category: "tos",
|
||||
Key: "TosContent",
|
||||
Value: "Welcome to use Perfect Panel",
|
||||
Type: "string",
|
||||
Desc: "Terms of Service",
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// update version
|
||||
if err := tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func Migrate01701(db *gorm.DB) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
version := "0.1.7(01701)"
|
||||
if err := db.Migrator().AlterColumn(&user.User{}, "Avatar"); err != nil {
|
||||
return err
|
||||
}
|
||||
// update version
|
||||
if err := tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func Migrate01702(db *gorm.DB) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
version := "0.1.7(01702)"
|
||||
|
||||
if tx.Model(&system.System{}).Where("`category` = 'site' AND `key` = 'Keywords'").Find(&system.System{}).RowsAffected == 0 {
|
||||
if err := tx.Save(&system.System{
|
||||
Category: "site",
|
||||
Key: "Keywords",
|
||||
Value: "Perfect Panel,PPanel",
|
||||
Type: "string",
|
||||
Desc: "Keywords",
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if tx.Model(&system.System{}).Where("`category` = 'site' AND `key` = 'CustomHTML'").Find(&system.System{}).RowsAffected == 0 {
|
||||
if err := tx.Save(&system.System{
|
||||
Category: "site",
|
||||
Key: "CustomHTML",
|
||||
Value: "",
|
||||
Type: "string",
|
||||
Desc: "Custom HTML",
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// update version
|
||||
if err := tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func Migrate01703(db *gorm.DB) error {
|
||||
version := "0.1.7(01703)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if tx.Model(&system.System{}).Where("`category` = 'tos' AND `key` = 'PrivacyPolicy'").Find(&system.System{}).RowsAffected == 0 {
|
||||
if err := tx.Save(&system.System{
|
||||
Category: "tos",
|
||||
Key: "PrivacyPolicy",
|
||||
Value: "",
|
||||
Type: "string",
|
||||
Desc: "Privacy Policy",
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error
|
||||
})
|
||||
}
|
||||
func Migrate01704(db *gorm.DB) error {
|
||||
version := "0.1.7(01704)"
|
||||
|
||||
// check server table latitude column exists, if not exists, create it
|
||||
if exists := db.Migrator().HasColumn(&server.Server{}, "latitude"); !exists {
|
||||
if err := db.Migrator().AddColumn(&server.Server{}, "latitude"); err != nil {
|
||||
logger.Errorw("Migrate 01704 failed", logger.Field("action", "add latitude column"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
logger.Infow("Migrate 01704 success", logger.Field("action", "add latitude column"))
|
||||
}
|
||||
// check server table longitude column exists, if not exists, create it
|
||||
if exists := db.Migrator().HasColumn(&server.Server{}, "longitude"); !exists {
|
||||
if err := db.Migrator().AddColumn(&server.Server{}, "longitude"); err != nil {
|
||||
logger.Errorw("Migrate 01704 failed", logger.Field("action", "add longitude column"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
logger.Infow("Migrate 01704 success", logger.Field("action", "add longitude column"))
|
||||
}
|
||||
// update system config
|
||||
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
|
||||
logger.Errorw("Migrate 01704 failed", logger.Field("step", "2"), logger.Field("action", "update system config"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
logger.Infow("Migrate 01704 success", logger.Field("action", "update system config"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func Migrate01705(db *gorm.DB) error {
|
||||
version := "0.1.7(01705)"
|
||||
// check user_device table exists, if not exists, create it
|
||||
if exists := db.Migrator().HasTable(&user.Device{}); !exists {
|
||||
if err := db.Migrator().CreateTable(&user.Device{}); err != nil {
|
||||
logger.Errorw("Migrate 01705 failed", logger.Field("action", "create user_device table"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
logger.Infow("Migrate 01705 success", logger.Field("action", "create user_device table"))
|
||||
}
|
||||
// check user_table exists and imei column exists, if exists, update imei column name to identifier
|
||||
if exists := db.Migrator().HasColumn(&user.Device{}, "imei"); exists {
|
||||
if err := db.Migrator().RenameColumn(&user.Device{}, "imei", "identifier"); err != nil {
|
||||
logger.Errorw("Migrate 01705 failed", logger.Field("action", "rename imei column to identifier"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
logger.Infow("Migrate 01705 success", logger.Field("action", "rename imei column to identifier"))
|
||||
}
|
||||
|
||||
// update system config
|
||||
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
|
||||
logger.Errorw("Migrate 01705 failed", logger.Field("step", "2"), logger.Field("action", "update system config"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initMobileConfig(db *gorm.DB) error {
|
||||
cfg := new(auth.AlibabaCloudConfig)
|
||||
mobileConfig := auth.MobileAuthConfig{
|
||||
Platform: sms.AlibabaCloud.String(),
|
||||
PlatformConfig: cfg.Marshal(),
|
||||
EnableWhitelist: false,
|
||||
Whitelist: make([]string, 0),
|
||||
}
|
||||
authMethod := auth.Auth{
|
||||
Method: "mobile",
|
||||
Config: mobileConfig.Marshal(),
|
||||
}
|
||||
if err := db.Save(&authMethod).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initEmailConfig(db *gorm.DB) error {
|
||||
enable := true
|
||||
smtpConfig := new(auth.SMTPConfig)
|
||||
|
||||
emailConfig := auth.EmailAuthConfig{
|
||||
Platform: "smtp",
|
||||
PlatformConfig: smtpConfig.Marshal(),
|
||||
EnableVerify: false,
|
||||
EnableDomainSuffix: false,
|
||||
DomainSuffixList: "",
|
||||
VerifyEmailTemplate: email.DefaultEmailVerifyTemplate,
|
||||
ExpirationEmailTemplate: email.DefaultExpirationEmailTemplate,
|
||||
MaintenanceEmailTemplate: email.DefaultMaintenanceEmailTemplate,
|
||||
}
|
||||
authMethod := auth.Auth{
|
||||
Method: "email",
|
||||
Config: emailConfig.Marshal(),
|
||||
Enabled: &enable,
|
||||
}
|
||||
return db.Save(&authMethod).Error
|
||||
}
|
||||
|
||||
func initTrialConfig(tx *gorm.DB) error {
|
||||
configs := []system.System{
|
||||
{
|
||||
Category: "register",
|
||||
Key: "TrialSubscribe",
|
||||
Value: "",
|
||||
Type: "int",
|
||||
Desc: "Trial subscription",
|
||||
},
|
||||
{
|
||||
Category: "register",
|
||||
Key: "TrialTime",
|
||||
Value: "24",
|
||||
Type: "int",
|
||||
Desc: "Trial time",
|
||||
},
|
||||
{
|
||||
Category: "register",
|
||||
Key: "TrialTimeUnit",
|
||||
Value: "Hour",
|
||||
Type: "string",
|
||||
Desc: "Trial time unit",
|
||||
},
|
||||
}
|
||||
return tx.Model(&system.System{}).Save(&configs).Error
|
||||
}
|
||||
|
||||
func addAuthMethodWithDevice(tx *gorm.DB) error {
|
||||
return tx.Model(&auth.Auth{}).Save(&auth.Auth{
|
||||
Method: "device",
|
||||
}).Error
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
package patch
|
||||
|
||||
import (
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/ads"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/application"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/auth"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/order"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/payment"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/server"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/system"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/user"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Migrate02000(db *gorm.DB) error {
|
||||
version := "0.2.0(02000)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := initDeviceConfig(tx); err != nil {
|
||||
logMigrationError("Setting Device Config", err)
|
||||
return err
|
||||
}
|
||||
logMigrationSuccess("Setting Device Config")
|
||||
|
||||
if !tx.Migrator().HasTable(&ads.Ads{}) {
|
||||
if err := createAdsTable(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := updatePaymentTable(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Migrator().AutoMigrate(&order.Order{}); err != nil {
|
||||
logMigrationError("Auto Migrate Order", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return updateSystemVersion(tx, version)
|
||||
})
|
||||
}
|
||||
|
||||
func Migrate02001(db *gorm.DB) error {
|
||||
version := "0.2.0(02001)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if tx.Model(&system.System{}).Where("`category` = 'site' AND `key` = 'CustomData'").Find(&system.System{}).RowsAffected == 0 {
|
||||
if err := tx.Save(&system.System{
|
||||
Category: "site",
|
||||
Key: "CustomData",
|
||||
Value: "{\"website\":\"\",\"contacts\":{\"email\":\"\",\"telephone\":\"\",\"address\":\"\"},\"community\":{\"telegram\":\"\",\"twitter\":\"\",\"discord\":\"\",\"instagram\":\"\",\"linkedin\":\"\",\"facebook\":\"\",\"github\":\"\"}}",
|
||||
Type: "string",
|
||||
Desc: "Custom data",
|
||||
}).Error; err != nil {
|
||||
logMigrationError("create custom data system config", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return updateSystemVersion(tx, version)
|
||||
})
|
||||
}
|
||||
|
||||
func Migrate02002(db *gorm.DB) error {
|
||||
version := "0.2.0(02002)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&system.System{}).Where("`category` = 'site' AND `key` = 'CustomData'").UpdateColumn("type", "string").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return updateSystemVersion(tx, version)
|
||||
})
|
||||
}
|
||||
|
||||
func Migrate02003(db *gorm.DB) error {
|
||||
version := "0.2.0(02003)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := addColumnIfNotExists(tx, &order.Order{}, "payment_id"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addColumnIfNotExists(tx, &payment.Payment{}, "platform"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dropColumnIfExists(tx, &payment.Payment{}, "mark"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addColumnIfNotExists(tx, &payment.Payment{}, "description"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addColumnIfNotExists(tx, &payment.Payment{}, "token"); err != nil {
|
||||
return err
|
||||
}
|
||||
return updateSystemVersion(tx, version)
|
||||
})
|
||||
}
|
||||
|
||||
func Migrate02007(db *gorm.DB) error {
|
||||
version := "0.2.0(02007)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := recreateTable(tx, &server.RuleGroup{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return updateSystemVersion(tx, version)
|
||||
})
|
||||
}
|
||||
|
||||
func Migrate02008(db *gorm.DB) error {
|
||||
version := "0.2.0(02008)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if exists := tx.Migrator().HasColumn(&application.ApplicationConfig{}, "invitation_link"); !exists {
|
||||
if err := tx.Migrator().AddColumn(&application.ApplicationConfig{}, "invitation_link"); err != nil {
|
||||
logger.Errorw("Migrate 02008 failed", logger.Field("action", "add invitation_link column"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
logger.Infow("Migrate 02008 success", logger.Field("action", "add invitation_link column"))
|
||||
}
|
||||
|
||||
if exists := tx.Migrator().HasTable(&user.DeviceOnlineRecord{}); !exists {
|
||||
if err := tx.Migrator().CreateTable(&user.DeviceOnlineRecord{}); err != nil {
|
||||
logger.Errorw("Migrate 02008 failed", logger.Field("action", "create device_online_record table"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error
|
||||
})
|
||||
}
|
||||
|
||||
func Migrate02009(db *gorm.DB) error {
|
||||
version := "0.2.0(02009)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := addColumnIfNotExists(tx, &user.Subscribe{}, "finished_at"); err != nil {
|
||||
logger.Errorw("Migrate 02009 failed", logger.Field("action", "subscribe table add finished_at column"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
return updateSystemVersion(tx, version)
|
||||
})
|
||||
}
|
||||
|
||||
func Migrate02010(db *gorm.DB) error {
|
||||
version := "0.2.0(02010)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := addColumnIfNotExists(tx, &application.ApplicationConfig{}, "kr_website_id"); err != nil {
|
||||
logger.Errorw("Migrate 02010 failed", logger.Field("action", "application_config table add kr_website_id column"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
return updateSystemVersion(tx, version)
|
||||
})
|
||||
}
|
||||
|
||||
func Migrate02011(db *gorm.DB) error {
|
||||
version := "0.2.0(02011)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := addColumnIfNotExists(tx, &user.Subscribe{}, "used_period"); err != nil {
|
||||
logger.Errorw("Migrate 02011 failed", logger.Field("action", "user.Subscribe table add used_period column"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
if err := addColumnIfNotExists(tx, &user.Subscribe{}, "total_period"); err != nil {
|
||||
logger.Errorw("Migrate 02011 failed", logger.Field("action", "user.Subscribe table add total_period column"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
return updateSystemVersion(tx, version)
|
||||
})
|
||||
}
|
||||
|
||||
func initDeviceConfig(db *gorm.DB) error {
|
||||
cfg := new(auth.DeviceConfig)
|
||||
return db.Model(&auth.Auth{}).Where("method = ?", "device").Update("config", cfg.Marshal()).Error
|
||||
}
|
||||
|
||||
func createAdsTable(tx *gorm.DB) error {
|
||||
if err := tx.Migrator().CreateTable(&ads.Ads{}); err != nil {
|
||||
logMigrationError("Create Table Ads", err)
|
||||
return err
|
||||
}
|
||||
logMigrationSuccess("Create Table Ads")
|
||||
return tx.Model(&system.System{}).Save(&system.System{
|
||||
Category: "ad",
|
||||
Key: "WebAD",
|
||||
Value: "false",
|
||||
Type: "bool",
|
||||
Desc: "Display ad on the web",
|
||||
}).Error
|
||||
}
|
||||
|
||||
func updatePaymentTable(tx *gorm.DB) error {
|
||||
if err := tx.Exec("DROP TABLE IF EXISTS `payment`").Error; err != nil {
|
||||
logMigrationError("Drop Payment Table", err)
|
||||
}
|
||||
if err := tx.AutoMigrate(&payment.Payment{}); err != nil {
|
||||
logMigrationError("Auto Migrate Payment", err)
|
||||
return err
|
||||
}
|
||||
enable := true
|
||||
return tx.Model(&payment.Payment{}).Create(&payment.Payment{
|
||||
Id: -1,
|
||||
Name: "",
|
||||
Platform: "balance",
|
||||
Icon: "",
|
||||
Domain: "",
|
||||
Config: "",
|
||||
FeeMode: 0,
|
||||
FeePercent: 0,
|
||||
FeeAmount: 0,
|
||||
Enable: &enable,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func updateSystemVersion(tx *gorm.DB, version string) error {
|
||||
return tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error
|
||||
}
|
||||
|
||||
func addColumnIfNotExists(tx *gorm.DB, model interface{}, columnName string) error {
|
||||
if exists := tx.Migrator().HasColumn(model, columnName); !exists {
|
||||
if err := tx.Migrator().AddColumn(model, columnName); err != nil {
|
||||
logMigrationError("add "+columnName+" column", err)
|
||||
return err
|
||||
}
|
||||
logMigrationSuccess("add " + columnName + " column")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func dropColumnIfExists(tx *gorm.DB, model interface{}, columnName string) error {
|
||||
if exists := tx.Migrator().HasColumn(model, columnName); exists {
|
||||
if err := tx.Migrator().DropColumn(model, columnName); err != nil {
|
||||
logMigrationError("del "+columnName+" column", err)
|
||||
return err
|
||||
}
|
||||
logMigrationSuccess("del " + columnName + " column")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func recreateTable(tx *gorm.DB, model interface{}) error {
|
||||
if exists := tx.Migrator().HasTable(model); exists {
|
||||
if err := tx.Migrator().DropTable(model); err != nil {
|
||||
logMigrationError("drop table", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tx.Migrator().CreateTable(model); err != nil {
|
||||
logMigrationError("create table", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func logMigrationError(action string, err error) {
|
||||
logger.Errorw("Migration failed", logger.Field("action", action), logger.Field("error", err.Error()))
|
||||
}
|
||||
|
||||
func logMigrationSuccess(action string) {
|
||||
logger.Infow("Migration success", logger.Field("action", action))
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package patch
|
||||
|
||||
import (
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/application"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/user"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Migrate03001(db *gorm.DB) error {
|
||||
version := "0.3.0(1)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := addColumnIfNotExists(tx, &user.Subscribe{}, "finished_at"); err != nil {
|
||||
logger.Errorw("Migrate 03001 failed", logger.Field("action", "user.Subscribe table add finished_at column"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
return updateSystemVersion(tx, version)
|
||||
})
|
||||
}
|
||||
|
||||
func Migrate03002(db *gorm.DB) error {
|
||||
version := "0.3.0(2)"
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := addColumnIfNotExists(tx, &application.ApplicationConfig{}, "kr_website_id"); err != nil {
|
||||
logger.Errorw("Migrate 03002 failed", logger.Field("action", "application.Config table add kr_website_id column"), logger.Field("error", err.Error()))
|
||||
return err
|
||||
}
|
||||
return updateSystemVersion(tx, version)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ExecuteSQLFile 执行嵌入的 SQL 文件,去除注释
|
||||
func ExecuteSQLFile(tx *gorm.DB, path string) error {
|
||||
// 读取 SQL 文件内容
|
||||
sqlContent, err := sqlFiles.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read embedded SQL file: %v", err)
|
||||
}
|
||||
|
||||
// 清除注释内容
|
||||
cleanedSQL := removeComments(string(sqlContent))
|
||||
|
||||
// 将清除注释后的内容按分号分割成多个 SQL 语句
|
||||
sqlStatements := splitSQLStatements(cleanedSQL)
|
||||
|
||||
// 遍历 SQL 语句并执行
|
||||
for _, stmt := range sqlStatements {
|
||||
stmt = strings.TrimSpace(stmt)
|
||||
if stmt == "" {
|
||||
continue
|
||||
}
|
||||
// 执行 SQL 语句
|
||||
if err := tx.Exec(stmt).Error; err != nil {
|
||||
return fmt.Errorf("failed to execute SQL statement: %v \nSQL: %s", err.Error(), stmt)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeComments 去除 SQL 代码中的注释
|
||||
func removeComments(sql string) string {
|
||||
var result strings.Builder
|
||||
inSingleLineComment := false
|
||||
inMultiLineComment := false
|
||||
length := len(sql)
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
// 处理 -- 单行注释
|
||||
if !inMultiLineComment && !inSingleLineComment && i+1 < length && sql[i] == '-' && sql[i+1] == '-' {
|
||||
inSingleLineComment = true
|
||||
i++ // 跳过 '-'
|
||||
continue
|
||||
}
|
||||
|
||||
// 结束单行注释(支持 \r\n 和 \n)
|
||||
if inSingleLineComment && (sql[i] == '\n' || sql[i] == '\r') {
|
||||
inSingleLineComment = false
|
||||
}
|
||||
|
||||
// 处理 /* 多行注释 */
|
||||
if !inSingleLineComment && !inMultiLineComment && i+1 < length && sql[i] == '/' && sql[i+1] == '*' {
|
||||
inMultiLineComment = true
|
||||
i++ // 跳过 '*'
|
||||
continue
|
||||
}
|
||||
|
||||
// 结束多行注释
|
||||
if inMultiLineComment && i+1 < length && sql[i] == '*' && sql[i+1] == '/' {
|
||||
inMultiLineComment = false
|
||||
i++ // 跳过 '/'
|
||||
continue
|
||||
}
|
||||
|
||||
// 不是注释内容时,将字符添加到结果中
|
||||
if !inSingleLineComment && !inMultiLineComment {
|
||||
result.WriteByte(sql[i])
|
||||
}
|
||||
}
|
||||
|
||||
// 去除多余的空白行
|
||||
lines := strings.Split(result.String(), "\n")
|
||||
var cleanedLines []string
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed != "" {
|
||||
cleanedLines = append(cleanedLines, trimmed)
|
||||
}
|
||||
}
|
||||
return strings.Join(cleanedLines, "\n")
|
||||
}
|
||||
|
||||
// splitSQLStatements 更安全地分割 SQL 语句
|
||||
func splitSQLStatements(sql string) []string {
|
||||
statements := strings.Split(sql, ";")
|
||||
var results []string
|
||||
for _, stmt := range statements {
|
||||
trimmed := strings.TrimSpace(stmt)
|
||||
if trimmed != "" {
|
||||
results = append(results, trimmed)
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/internal/config"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/auth"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/tool"
|
||||
)
|
||||
|
||||
func Mobile(ctx *svc.ServiceContext) {
|
||||
logger.Debug("Mobile config initialization")
|
||||
method, err := ctx.AuthModel.FindOneByMethod(context.Background(), "mobile")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var cfg config.MobileConfig
|
||||
var mobileConfig auth.MobileAuthConfig
|
||||
if err := mobileConfig.Unmarshal(method.Config); err != nil {
|
||||
panic(fmt.Sprintf("failed to unmarshal mobile auth config: %v", err.Error()))
|
||||
}
|
||||
tool.DeepCopy(&cfg, mobileConfig)
|
||||
cfg.Enable = *method.Enabled
|
||||
value, _ := json.Marshal(mobileConfig.PlatformConfig)
|
||||
cfg.PlatformConfig = string(value)
|
||||
ctx.Config.Mobile = cfg
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"github.com/perfect-panel/ppanel-server/initialize/migrate"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
)
|
||||
|
||||
func Mysql(ctx *svc.ServiceContext) {
|
||||
migrate.Migrate(ctx)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/internal/config"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/system"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/nodeMultiplier"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/tool"
|
||||
)
|
||||
|
||||
func Node(ctx *svc.ServiceContext) {
|
||||
logger.Debug("Node config initialization")
|
||||
configs, err := ctx.SystemModel.GetNodeConfig(context.Background())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var nodeConfig config.NodeConfig
|
||||
tool.SystemConfigSliceReflectToStruct(configs, &nodeConfig)
|
||||
ctx.Config.Node = nodeConfig
|
||||
|
||||
// Manager initialization
|
||||
if ctx.DB.Model(&system.System{}).Where("`key` = ?", "NodeMultiplierConfig").Find(&system.System{}).RowsAffected == 0 {
|
||||
if err := ctx.DB.Model(&system.System{}).Create(&system.System{
|
||||
Key: "NodeMultiplierConfig",
|
||||
Value: "[]",
|
||||
Type: "string",
|
||||
Desc: "Node Multiplier Config",
|
||||
Category: "server",
|
||||
}).Error; err != nil {
|
||||
logger.Errorf("Create Node Multiplier Config Error: %s", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
nodeMultiplierData, err := ctx.SystemModel.FindNodeMultiplierConfig(context.Background())
|
||||
if err != nil {
|
||||
|
||||
logger.Error("Get Node Multiplier Config Error: ", logger.Field("error", err.Error()))
|
||||
return
|
||||
}
|
||||
var periods []nodeMultiplier.TimePeriod
|
||||
if err := json.Unmarshal([]byte(nodeMultiplierData.Value), &periods); err != nil {
|
||||
logger.Error("Unmarshal Node Multiplier Config Error: ", logger.Field("error", err.Error()), logger.Field("value", nodeMultiplierData.Value))
|
||||
}
|
||||
ctx.NodeMultiplierManager = nodeMultiplier.NewNodeMultiplierManager(periods)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
)
|
||||
|
||||
func OAuth(svc *svc.ServiceContext) {
|
||||
logger.Debug("OAuth config initialization")
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/internal/config"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/tool"
|
||||
)
|
||||
|
||||
func Register(ctx *svc.ServiceContext) {
|
||||
logger.Debug("Register config initialization")
|
||||
configs, err := ctx.SystemModel.GetRegisterConfig(context.Background())
|
||||
if err != nil {
|
||||
logger.Errorf("[Init Register Config] Get Register Config Error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
var registerConfig config.RegisterConfig
|
||||
tool.SystemConfigSliceReflectToStruct(configs, ®isterConfig)
|
||||
ctx.Config.Register = registerConfig
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/internal/config"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/tool"
|
||||
)
|
||||
|
||||
func Site(ctx *svc.ServiceContext) {
|
||||
logger.Debug("initialize site config")
|
||||
configs, err := ctx.SystemModel.GetSiteConfig(context.Background())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var siteConfig config.SiteConfig
|
||||
tool.SystemConfigSliceReflectToStruct(configs, &siteConfig)
|
||||
ctx.Config.Site = siteConfig
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/cache"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
)
|
||||
|
||||
func TrafficDataToRedis(svcCtx *svc.ServiceContext) {
|
||||
ctx := context.Background()
|
||||
// 统计昨天的节点流量数据排行榜前10
|
||||
nodeData, err := svcCtx.TrafficLogModel.TopServersTrafficByDay(ctx, time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day()-1, 0, 0, 0, 0, time.Local), 10)
|
||||
if err != nil {
|
||||
logger.Errorw("统计昨天的流量数据失败", logger.Field("error", err.Error()))
|
||||
}
|
||||
var nodeCacheData []cache.NodeTodayTrafficRank
|
||||
for _, node := range nodeData {
|
||||
serverInfo, err := svcCtx.ServerModel.FindOne(ctx, node.ServerId)
|
||||
if err != nil {
|
||||
logger.Errorw("查询节点信息失败", logger.Field("error", err.Error()))
|
||||
continue
|
||||
}
|
||||
nodeCacheData = append(nodeCacheData, cache.NodeTodayTrafficRank{
|
||||
ID: node.ServerId,
|
||||
Name: serverInfo.Name,
|
||||
Upload: node.Upload,
|
||||
Download: node.Download,
|
||||
Total: node.Upload + node.Download,
|
||||
})
|
||||
}
|
||||
// 写入缓存
|
||||
if err = svcCtx.NodeCache.UpdateYesterdayNodeTotalTrafficRank(ctx, nodeCacheData); err != nil {
|
||||
logger.Errorw("写入昨天的流量数据到缓存失败", logger.Field("error", err.Error()))
|
||||
}
|
||||
// 统计昨天的用户流量数据排行榜前10
|
||||
userData, err := svcCtx.TrafficLogModel.TopUsersTrafficByDay(ctx, time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day()-1, 0, 0, 0, 0, time.Local), 10)
|
||||
if err != nil {
|
||||
logger.Errorw("统计昨天的流量数据失败", logger.Field("error", err.Error()))
|
||||
}
|
||||
var userCacheData []cache.UserTodayTrafficRank
|
||||
for _, user := range userData {
|
||||
userCacheData = append(userCacheData, cache.UserTodayTrafficRank{
|
||||
SID: user.SubscribeId,
|
||||
Upload: user.Upload,
|
||||
Download: user.Download,
|
||||
Total: user.Upload + user.Download,
|
||||
})
|
||||
}
|
||||
// 写入缓存
|
||||
if err = svcCtx.NodeCache.UpdateYesterdayUserTotalTrafficRank(ctx, userCacheData); err != nil {
|
||||
logger.Errorw("写入昨天的流量数据到缓存失败", logger.Field("error", err.Error()))
|
||||
}
|
||||
logger.Infow("初始化昨天的流量数据到缓存成功")
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/internal/config"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/tool"
|
||||
)
|
||||
|
||||
func Subscribe(svc *svc.ServiceContext) {
|
||||
logger.Debug("Subscribe config initialization")
|
||||
configs, err := svc.SystemModel.GetSubscribeConfig(context.Background())
|
||||
if err != nil {
|
||||
logger.Error("[Init Subscribe Config] Get Subscribe Config Error: ", logger.Field("error", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
var subscribeConfig config.SubscribeConfig
|
||||
tool.SystemConfigSliceReflectToStruct(configs, &subscribeConfig)
|
||||
svc.Config.Subscribe = subscribeConfig
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
"github.com/perfect-panel/ppanel-server/internal/config"
|
||||
"github.com/perfect-panel/ppanel-server/internal/logic/telegram"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/auth"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/tool"
|
||||
)
|
||||
|
||||
func Telegram(svc *svc.ServiceContext) {
|
||||
|
||||
method, err := svc.AuthModel.FindOneByMethod(context.Background(), "telegram")
|
||||
if err != nil {
|
||||
logger.Errorf("[Init Telegram Config] Get Telegram Config Error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
var tg config.Telegram
|
||||
|
||||
tgConfig := new(auth.TelegramAuthConfig)
|
||||
if err = tgConfig.Unmarshal(method.Config); err != nil {
|
||||
logger.Errorf("[Init Telegram Config] Unmarshal Telegram Config Error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if tgConfig.BotToken == "" {
|
||||
logger.Debug("[Init Telegram Config] Telegram Token is empty")
|
||||
return
|
||||
}
|
||||
|
||||
bot, err := tgbotapi.NewBotAPI(tg.BotToken)
|
||||
if err != nil {
|
||||
logger.Error("[Init Telegram Config] New Bot API Error: ", logger.Field("error", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if tgConfig.WebHookDomain == "" || svc.Config.Debug {
|
||||
// set Long Polling mode
|
||||
updateConfig := tgbotapi.NewUpdate(0)
|
||||
updateConfig.Timeout = 60
|
||||
updates := bot.GetUpdatesChan(updateConfig)
|
||||
go func() {
|
||||
for update := range updates {
|
||||
if update.Message != nil {
|
||||
ctx := context.Background()
|
||||
l := telegram.NewTelegramLogic(ctx, svc)
|
||||
l.TelegramLogic(&update)
|
||||
}
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
wh, err := tgbotapi.NewWebhook(fmt.Sprintf("%s/v1/telegram/webhook?secret=%s", tgConfig.WebHookDomain, tool.Md5Encode(tgConfig.BotToken, false)))
|
||||
if err != nil {
|
||||
logger.Errorf("[Init Telegram Config] New Webhook Error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
_, err = bot.Request(wh)
|
||||
if err != nil {
|
||||
logger.Errorf("[Init Telegram Config] Request Webhook Error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
user, err := bot.GetMe()
|
||||
if err != nil {
|
||||
logger.Error("[Init Telegram Config] Get Bot Info Error: ", logger.Field("error", err.Error()))
|
||||
return
|
||||
}
|
||||
svc.Config.Telegram.BotID = user.ID
|
||||
svc.Config.Telegram.BotName = user.UserName
|
||||
svc.Config.Telegram.EnableNotify = tg.EnableNotify
|
||||
svc.Config.Telegram.WebHookDomain = tg.WebHookDomain
|
||||
svc.TelegramBot = bot
|
||||
|
||||
logger.Info("[Init Telegram Config] Webhook set success")
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- 头部内容 -->
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PPanel - Application Initialization</title>
|
||||
<!-- 引入 Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- Tailwind CSS 配置 -->
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: ["class"],
|
||||
theme: {
|
||||
container: { center: true, padding: "2rem" },
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))" },
|
||||
destructive: { DEFAULT: "hsl(var(--destructive))", foreground: "hsl(var(--destructive-foreground))" },
|
||||
},
|
||||
borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)" },
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<!-- 自定义样式 -->
|
||||
<style>
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 221.2 83.2% 53.3%;
|
||||
--radius: 0.5rem;
|
||||
--primary: 221.2 83.2% 53.3%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--destructive: 346.8 77.2% 49.8%;
|
||||
--destructive-foreground: 355.7 100% 97.3%;
|
||||
}
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
}
|
||||
body {
|
||||
background-color: hsl(var(--background));
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen font-sans antialiased">
|
||||
<div id="appContainer" class="container mx-auto p-4 flex flex-col items-center min-h-screen">
|
||||
<div class="w-full max-w-2xl space-y-6">
|
||||
<!-- 设置卡片 -->
|
||||
<div id="setupCard" class="p-6 space-y-6 bg-white rounded-lg border">
|
||||
<div class="flex justify-between items-center">
|
||||
<h1 id="title" class="text-3xl font-bold">Welcome to PPanel Setup</h1>
|
||||
<button onclick="switchLanguage()" id="langSwitch" class="h-10 px-4 py-2 border rounded-md text-sm font-medium bg-background hover:bg-accent hover:text-accent-foreground">中文</button>
|
||||
</div>
|
||||
<p id="description" class="text-gray-600 !mt-0">Let's get your PPanel application up and running. Please provide the necessary information below.</p>
|
||||
|
||||
<form id="setupForm" class="space-y-6" onsubmit="handleSubmit(event)">
|
||||
<!-- 管理员详情 -->
|
||||
<div class="space-y-4">
|
||||
<h2 id="adminInfoTitle" class="font-bold text-xl">Administrator Details</h2>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="adminEmail" id="labelAdminEmail" class="text-sm font-medium">Administrator Email</label>
|
||||
<input type="email" id="adminEmail" name="adminEmail" required class="input-field mt-1 block w-full rounded-md border px-3 py-2" placeholder="Enter administrator email" data-placeholder-en="Enter administrator email" data-placeholder-zh="请输入管理员邮箱" oninput="validateInput(this)">
|
||||
<span id="adminEmailError" class="text-red-500 text-sm mt-1 hidden">Invalid email address.</span>
|
||||
</div>
|
||||
<div>
|
||||
<label for="adminPassword" id="labelAdminPassword" class="text-sm font-medium">Administrator Password</label>
|
||||
<input type="password" id="adminPassword" name="adminPassword" required minlength="6" class="input-field mt-1 block w-full rounded-md border px-3 py-2" placeholder="Enter administrator password" data-placeholder-en="Enter administrator password" data-placeholder-zh="请输入管理员密码" oninput="validateInput(this)">
|
||||
<span id="adminPasswordError" class="text-red-500 text-sm mt-1 hidden">Password must be at least 6 characters.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MySQL 数据库设置 -->
|
||||
<div class="space-y-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 id="mysqlInfoTitle" class="font-bold text-xl">MySQL Database Setup</h2>
|
||||
<button type="button" onclick="testConnection('mysql')" id="testMySQLButton" class="h-10 px-4 py-2 border rounded-md text-sm font-medium bg-background hover:bg-accent hover:text-accent-foreground">Test MySQL Connection</button>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="mysqlHost" id="labelMysqlHost" class="text-sm font-medium">Database Host</label>
|
||||
<input type="text" id="mysqlHost" name="mysqlHost" required class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="localhost" placeholder="Enter database host" data-placeholder-en="Enter database host" data-placeholder-zh="请输入数据库主机">
|
||||
<span id="mysqlHostError" class="text-red-500 text-sm mt-1 hidden">Please enter a valid host.</span>
|
||||
</div>
|
||||
<div>
|
||||
<label for="mysqlPort" id="labelMysqlPort" class="text-sm font-medium">Database Port</label>
|
||||
<input type="number" id="mysqlPort" name="mysqlPort" required min="1" max="65535" class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="3306" placeholder="Enter database port" data-placeholder-en="Enter database port" data-placeholder-zh="请输入数据库端口">
|
||||
<span id="mysqlPortError" class="text-red-500 text-sm mt-1 hidden">Please enter a valid port number.</span>
|
||||
</div>
|
||||
<div>
|
||||
<label for="mysqlUser" id="labelMysqlUser" class="text-sm font-medium">Database User</label>
|
||||
<input type="text" id="mysqlUser" name="mysqlUser" required class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="root" placeholder="Enter database user" data-placeholder-en="Enter database user" data-placeholder-zh="请输入数据库用户名">
|
||||
<span id="mysqlUserError" class="text-red-500 text-sm mt-1 hidden">Please enter a username.</span>
|
||||
</div>
|
||||
<div>
|
||||
<label for="mysqlPassword" id="labelMysqlPassword" class="text-sm font-medium">Database Password</label>
|
||||
<input type="password" id="mysqlPassword" name="mysqlPassword" class="input-field mt-1 block w-full rounded-md border px-3 py-2" placeholder="Enter database password" data-placeholder-en="Enter database password" data-placeholder-zh="请输入数据库密码">
|
||||
</div>
|
||||
<div>
|
||||
<label for="mysqlDatabase" id="labelMysqlDatabase" class="text-sm font-medium">Database Name</label>
|
||||
<input type="text" id="mysqlDatabase" name="mysqlDatabase" required class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="ppanel_db" placeholder="Enter database name" data-placeholder-en="Enter database name" data-placeholder-zh="请输入数据库名称">
|
||||
<span id="mysqlDatabaseError" class="text-red-500 text-sm mt-1 hidden">Please enter a database name.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Redis 缓存设置 -->
|
||||
<div class="space-y-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 id="redisInfoTitle" class="font-bold text-xl">Redis Cache Setup</h2>
|
||||
<button type="button" onclick="testConnection('redis')" id="testRedisButton" class="h-10 px-4 py-2 border rounded-md text-sm font-medium bg-background hover:bg-accent hover:text-accent-foreground">Test Redis Connection</button>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="redisHost" id="labelRedisHost" class="text-sm font-medium">Redis Host</label>
|
||||
<input type="text" id="redisHost" name="redisHost" required class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="127.0.0.1" placeholder="Enter Redis host" data-placeholder-en="Enter Redis host" data-placeholder-zh="请输入 Redis 主机">
|
||||
<span id="redisHostError" class="text-red-500 text-sm mt-1 hidden">Please enter a valid host.</span>
|
||||
</div>
|
||||
<div>
|
||||
<label for="redisPort" id="labelRedisPort" class="text-sm font-medium">Redis Port</label>
|
||||
<input type="number" id="redisPort" name="redisPort" required min="1" max="65535" class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="6379" placeholder="Enter Redis port" data-placeholder-en="Enter Redis port" data-placeholder-zh="请输入 Redis 端口">
|
||||
<span id="redisPortError" class="text-red-500 text-sm mt-1 hidden">Please enter a valid port number.</span>
|
||||
</div>
|
||||
<div>
|
||||
<label for="redisPassword" id="labelRedisPassword" class="text-sm font-medium">Redis Password</label>
|
||||
<input type="password" id="redisPassword" name="redisPassword" class="input-field mt-1 block w-full rounded-md border px-3 py-2" placeholder="Enter Redis password (optional)" data-placeholder-en="Enter Redis password (optional)" data-placeholder-zh="请输入 Redis 密码(可选)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<button type="submit" id="submitButton" disabled class="w-full h-10 px-4 py-2 bg-primary text-white rounded-md opacity-50 cursor-not-allowed">Start Initialization</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 安装中卡片 -->
|
||||
<div id="installingCard" class="fixed inset-0 flex items-center justify-center bg-white p-6 hidden">
|
||||
<div class="w-full max-w-md space-y-6 text-center">
|
||||
<h1 id="installingTitle" class="text-3xl font-bold">Installing...</h1>
|
||||
<p id="installingMessage">Please wait while we initialize your application.</p>
|
||||
<div class="flex justify-center">
|
||||
<svg class="animate-spin h-10 w-10 text-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 安装完成卡片 -->
|
||||
<div id="installationCompleteCard" class="fixed inset-0 flex items-center justify-center bg-white p-6 hidden">
|
||||
<div class="w-full max-w-md space-y-6 text-center">
|
||||
<h1 id="installationCompleteTitle" class="text-3xl font-bold">Installation Complete</h1>
|
||||
<p id="installationCompleteMessage">You can now close this page.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 已初始化页面 -->
|
||||
<div id="alreadyInitializedCard" class="fixed inset-0 flex items-center justify-center bg-white p-6 hidden">
|
||||
<div class="w-full max-w-md space-y-6 text-center">
|
||||
<h1 id="alreadyInitializedTitle" class="text-3xl font-bold">Already Initialized</h1>
|
||||
<p id="alreadyInitializedMessage">Your PPanel application has already been initialized.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 对话框 -->
|
||||
<div id="dialog" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
|
||||
<div class="bg-white rounded-lg p-6 w-96">
|
||||
<h2 id="dialogTitle" class="text-xl font-bold mb-4"></h2>
|
||||
<p id="dialogMessage" class="mb-4"></p>
|
||||
<div class="flex justify-end">
|
||||
<button onclick="closeDialog()" class="h-10 px-4 py-2 bg-primary text-white rounded-md">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JavaScript -->
|
||||
<script>
|
||||
let currentLang = 'en';
|
||||
const translations = {
|
||||
en: {
|
||||
// 英文翻译内容
|
||||
title: 'Welcome to PPanel Setup',
|
||||
description: "Let's get your PPanel application up and running. Please provide the necessary information below.",
|
||||
adminInfoTitle: 'Administrator Details',
|
||||
mysqlInfoTitle: 'MySQL Database Setup',
|
||||
redisInfoTitle: 'Redis Cache Setup',
|
||||
adminEmail: 'Administrator Email',
|
||||
adminPassword: 'Administrator Password',
|
||||
mysqlHost: 'Database Host',
|
||||
mysqlPort: 'Database Port',
|
||||
mysqlUser: 'Database User',
|
||||
mysqlPassword: 'Database Password',
|
||||
mysqlDatabase: 'Database Name',
|
||||
redisHost: 'Redis Host',
|
||||
redisPort: 'Redis Port',
|
||||
redisPassword: 'Redis Password',
|
||||
submit: 'Start Initialization',
|
||||
switchLang: '中文',
|
||||
testMySQLConnection: 'Test MySQL Connection',
|
||||
testRedisConnection: 'Test Redis Connection',
|
||||
connectionSuccess: 'Connection Successful',
|
||||
connectionError: 'Connection Failed',
|
||||
initializationError: 'Initialization Error',
|
||||
initializationErrorMessage: 'An error occurred during initialization. Please try again.',
|
||||
installingTitle: 'Installing...',
|
||||
installingMessage: 'Please wait while we initialize your application.',
|
||||
installationCompleteTitle: 'Installation Complete',
|
||||
installationCompleteMessage: 'You can now close this page.',
|
||||
proceedButton: 'Go to Application',
|
||||
mysqlConnectionSuccess: 'Successfully connected to the MySQL database.',
|
||||
mysqlConnectionError: 'Failed to connect to the MySQL database. Please check your settings.',
|
||||
redisConnectionSuccess: 'Successfully connected to the Redis server.',
|
||||
redisConnectionError: 'Failed to connect to the Redis server. Please check your settings.',
|
||||
alreadyInitializedTitle: 'Already Initialized',
|
||||
alreadyInitializedMessage: 'Your PPanel application has already been initialized.',
|
||||
invalidEmail: 'Invalid email address.',
|
||||
invalidPassword: 'Password must be at least 6 characters.',
|
||||
},
|
||||
zh: {
|
||||
// 中文翻译内容
|
||||
title: '欢迎使用 PPanel 设置向导',
|
||||
description: '让我们一起启动您的 PPanel 应用程序。请提供以下必要信息。',
|
||||
adminInfoTitle: '管理员详情',
|
||||
mysqlInfoTitle: 'MySQL 数据库设置',
|
||||
redisInfoTitle: 'Redis 缓存设置',
|
||||
adminEmail: '管理员邮箱',
|
||||
adminPassword: '管理员密码',
|
||||
mysqlHost: '数据库主机',
|
||||
mysqlPort: '数据库端口',
|
||||
mysqlUser: '数据库用户',
|
||||
mysqlPassword: '数据库密码',
|
||||
mysqlDatabase: '数据库名称',
|
||||
redisHost: 'Redis 主机',
|
||||
redisPort: 'Redis 端口',
|
||||
redisPassword: 'Redis 密码',
|
||||
submit: '开始初始化',
|
||||
switchLang: 'English',
|
||||
testMySQLConnection: '测试 MySQL 连接',
|
||||
testRedisConnection: '测试 Redis 连接',
|
||||
connectionSuccess: '连接成功',
|
||||
connectionError: '连接失败',
|
||||
initializationError: '初始化错误',
|
||||
initializationErrorMessage: '初始化过程中发生错误。请重试。',
|
||||
installingTitle: '正在安装...',
|
||||
installingMessage: '请稍候,我们正在初始化您的应用程序。',
|
||||
installationCompleteTitle: '安装完成',
|
||||
installationCompleteMessage: '您现在可以关闭此页面。',
|
||||
proceedButton: '前往应用程序',
|
||||
mysqlConnectionSuccess: '成功连接到 MySQL 数据库。',
|
||||
mysqlConnectionError: '无法连接到 MySQL 数据库。请检查您的设置。',
|
||||
redisConnectionSuccess: '成功连接到 Redis 服务器。',
|
||||
redisConnectionError: '无法连接到 Redis 服务器。请检查您的设置。',
|
||||
alreadyInitializedTitle: '已初始化',
|
||||
alreadyInitializedMessage: '您的 PPanel 应用程序已被初始化。',
|
||||
invalidEmail: '无效的邮箱地址。',
|
||||
invalidPassword: '密码至少需要6个字符。',
|
||||
}
|
||||
};
|
||||
|
||||
function switchLanguage() {
|
||||
currentLang = currentLang === 'en' ? 'zh' : 'en';
|
||||
updateTexts();
|
||||
}
|
||||
|
||||
function updateTexts() {
|
||||
const t = translations[currentLang];
|
||||
document.getElementById('title').textContent = t.title;
|
||||
document.getElementById('description').textContent = t.description;
|
||||
document.getElementById('adminInfoTitle').textContent = t.adminInfoTitle;
|
||||
document.getElementById('mysqlInfoTitle').textContent = t.mysqlInfoTitle;
|
||||
document.getElementById('redisInfoTitle').textContent = t.redisInfoTitle;
|
||||
document.getElementById('labelAdminEmail').textContent = t.adminEmail;
|
||||
document.getElementById('labelAdminPassword').textContent = t.adminPassword;
|
||||
document.getElementById('labelMysqlHost').textContent = t.mysqlHost;
|
||||
document.getElementById('labelMysqlPort').textContent = t.mysqlPort;
|
||||
document.getElementById('labelMysqlUser').textContent = t.mysqlUser;
|
||||
document.getElementById('labelMysqlPassword').textContent = t.mysqlPassword;
|
||||
document.getElementById('labelMysqlDatabase').textContent = t.mysqlDatabase;
|
||||
document.getElementById('labelRedisHost').textContent = t.redisHost;
|
||||
document.getElementById('labelRedisPort').textContent = t.redisPort;
|
||||
document.getElementById('labelRedisPassword').textContent = t.redisPassword;
|
||||
document.getElementById('submitButton').textContent = t.submit;
|
||||
document.getElementById('langSwitch').textContent = t.switchLang;
|
||||
document.getElementById('testMySQLButton').textContent = t.testMySQLConnection;
|
||||
document.getElementById('testRedisButton').textContent = t.testRedisConnection;
|
||||
document.getElementById('installingTitle').textContent = t.installingTitle;
|
||||
document.getElementById('installingMessage').textContent = t.installingMessage;
|
||||
document.getElementById('installationCompleteTitle').textContent = t.installationCompleteTitle;
|
||||
document.getElementById('installationCompleteMessage').textContent = t.installationCompleteMessage;
|
||||
document.getElementById('alreadyInitializedTitle').textContent = t.alreadyInitializedTitle;
|
||||
document.getElementById('alreadyInitializedMessage').textContent = t.alreadyInitializedMessage;
|
||||
|
||||
// 更新 placeholder 文本
|
||||
const inputs = document.querySelectorAll('.input-field');
|
||||
inputs.forEach((input) => {
|
||||
const placeholder = input.getAttribute(`data-placeholder-${currentLang}`);
|
||||
if (placeholder) {
|
||||
input.setAttribute('placeholder', placeholder);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function checkInitialization() {
|
||||
const isInitialized = false; // 需要从服务器获取实际状态
|
||||
if (isInitialized) {
|
||||
document.getElementById('appContainer').classList.add('hidden');
|
||||
document.getElementById('alreadyInitializedCard').classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function testConnection(type) {
|
||||
const t = translations[currentLang];
|
||||
let data = {};
|
||||
let url = '';
|
||||
if (type === 'mysql') {
|
||||
data = {
|
||||
host: document.getElementById('mysqlHost').value,
|
||||
port: document.getElementById('mysqlPort').value,
|
||||
user: document.getElementById('mysqlUser').value,
|
||||
password: document.getElementById('mysqlPassword').value,
|
||||
database: document.getElementById('mysqlDatabase').value,
|
||||
};
|
||||
url = '/init/mysql/test';
|
||||
} else if (type === 'redis') {
|
||||
data = {
|
||||
host: document.getElementById('redisHost').value,
|
||||
port: document.getElementById('redisPort').value,
|
||||
password: document.getElementById('redisPassword').value,
|
||||
};
|
||||
url = '/init/redis/test';
|
||||
}
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
console.log(result);
|
||||
if (result.code === 200 && result.status) {
|
||||
showDialog(t.connectionSuccess, result.msg || (type === 'mysql' ? t.mysqlConnectionSuccess : t.redisConnectionSuccess));
|
||||
} else {
|
||||
showDialog(t.connectionError, result.msg || (type === 'mysql' ? t.mysqlConnectionError : t.redisConnectionError));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showDialog(t.connectionError, t.initializationErrorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
const form = document.getElementById('setupForm');
|
||||
if (!form.checkValidity()) {
|
||||
form.reportValidity();
|
||||
return;
|
||||
}
|
||||
const t = translations[currentLang];
|
||||
const formData = new FormData(form);
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
|
||||
// 显示安装中界面
|
||||
document.getElementById('setupCard').classList.add('hidden');
|
||||
document.getElementById('installingCard').classList.remove('hidden');
|
||||
|
||||
// 发送初始化请求
|
||||
fetch('/init/config', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
document.getElementById('installingCard').classList.add('hidden');
|
||||
if (result.code === 200 && result.status) {
|
||||
document.getElementById('installationCompleteCard').classList.remove('hidden');
|
||||
} else {
|
||||
showDialog(t.initializationError, result.msg || t.initializationErrorMessage);
|
||||
document.getElementById('setupCard').classList.remove('hidden');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
document.getElementById('installingCard').classList.add('hidden');
|
||||
showDialog(t.initializationError, t.initializationErrorMessage);
|
||||
document.getElementById('setupCard').classList.remove('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
function showDialog(title, message) {
|
||||
document.getElementById('dialogTitle').textContent = title;
|
||||
document.getElementById('dialogMessage').textContent = message;
|
||||
document.getElementById('dialog').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
document.getElementById('dialog').classList.add('hidden');
|
||||
}
|
||||
|
||||
// 实时表单校验
|
||||
function validateInput(input) {
|
||||
const errorSpan = document.getElementById(input.id + 'Error');
|
||||
|
||||
if (input.type === 'email') {
|
||||
// 邮箱格式验证
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(input.value)) {
|
||||
errorSpan.textContent = translations[currentLang].invalidEmail;
|
||||
errorSpan.classList.remove('hidden');
|
||||
input.setCustomValidity(translations[currentLang].invalidEmail);
|
||||
} else {
|
||||
input.setCustomValidity('');
|
||||
errorSpan.classList.add('hidden');
|
||||
}
|
||||
} else if (input.validity.valid) {
|
||||
errorSpan.classList.add('hidden');
|
||||
input.setCustomValidity('');
|
||||
} else {
|
||||
errorSpan.classList.remove('hidden');
|
||||
}
|
||||
|
||||
updateSubmitButtonState();
|
||||
}
|
||||
|
||||
function updateSubmitButtonState() {
|
||||
const form = document.getElementById('setupForm');
|
||||
const submitButton = document.getElementById('submitButton');
|
||||
if (form.checkValidity()) {
|
||||
submitButton.disabled = false;
|
||||
submitButton.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||
} else {
|
||||
submitButton.disabled = true;
|
||||
submitButton.classList.add('opacity-50', 'cursor-not-allowed');
|
||||
}
|
||||
}
|
||||
|
||||
// 添加事件监听器
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const inputs = document.querySelectorAll('.input-field');
|
||||
inputs.forEach((input) => {
|
||||
input.addEventListener('input', () => validateInput(input));
|
||||
});
|
||||
updateSubmitButtonState();
|
||||
updateTexts();
|
||||
checkInitialization();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,48 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/internal/config"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/tool"
|
||||
)
|
||||
|
||||
type verifyConfig struct {
|
||||
TurnstileSiteKey string
|
||||
TurnstileSecret string
|
||||
EnableLoginVerify bool
|
||||
EnableRegisterVerify bool
|
||||
EnableResetPasswordVerify bool
|
||||
}
|
||||
|
||||
func Verify(svc *svc.ServiceContext) {
|
||||
logger.Debug("Verify config initialization")
|
||||
configs, err := svc.SystemModel.GetVerifyConfig(context.Background())
|
||||
if err != nil {
|
||||
logger.Error("[Init Verify Config] Get Verify Config Error: ", logger.Field("error", err.Error()))
|
||||
return
|
||||
}
|
||||
var verify verifyConfig
|
||||
tool.SystemConfigSliceReflectToStruct(configs, &verify)
|
||||
svc.Config.Verify = config.Verify{
|
||||
TurnstileSiteKey: verify.TurnstileSiteKey,
|
||||
TurnstileSecret: verify.TurnstileSecret,
|
||||
LoginVerify: verify.EnableLoginVerify,
|
||||
RegisterVerify: verify.EnableRegisterVerify,
|
||||
ResetPasswordVerify: verify.EnableResetPasswordVerify,
|
||||
}
|
||||
|
||||
logger.Debug("Verify code config initialization")
|
||||
|
||||
var verifyCodeConfig config.VerifyCode
|
||||
cfg, err := svc.SystemModel.GetVerifyCodeConfig(context.Background())
|
||||
if err != nil {
|
||||
logger.Errorf("[Init Verify Config] Get Verify Code Config Error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
tool.SystemConfigSliceReflectToStruct(cfg, &verifyCodeConfig)
|
||||
svc.Config.VerifyCode = verifyCodeConfig
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/initialize/migrate/patch"
|
||||
"github.com/perfect-panel/ppanel-server/internal/model/system"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/constant"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/tool"
|
||||
)
|
||||
|
||||
func VerifyVersion(ctx *svc.ServiceContext) {
|
||||
var configVersion system.System
|
||||
err := ctx.DB.Transaction(func(db *gorm.DB) error {
|
||||
db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").First(&configVersion)
|
||||
if configVersion.Value != constant.Version {
|
||||
// Version eg: 1.0.0(10000)
|
||||
current := tool.ExtractVersionNumber(constant.Version)
|
||||
sqlVersion := tool.ExtractVersionNumber(configVersion.Value)
|
||||
logger.Infof("Verify System Version, current version: %d, datebase version: %d", current, sqlVersion)
|
||||
if current > sqlVersion {
|
||||
// Migrate to Milestone Version
|
||||
//
|
||||
// Migrate SQL to 0.1.7(01703)
|
||||
if sqlVersion < 1705 {
|
||||
if err := migrate01701(db, sqlVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// 重新执行2000版本的迁移
|
||||
if sqlVersion == 2002 {
|
||||
sqlVersion = 2000
|
||||
}
|
||||
// Migrate SQL to 0.2.0(02000)
|
||||
if sqlVersion < 2009 {
|
||||
if err := migrate02000(db, sqlVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Migrate SQL to 0.3.0(03000)
|
||||
if sqlVersion < 3002 {
|
||||
if err := migrate03000(db, sqlVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic("update system version error:" + err.Error())
|
||||
}
|
||||
}
|
||||
func migrate01701(db *gorm.DB, sqlVersion int) error {
|
||||
migrations := map[int]func(*gorm.DB) error{
|
||||
1200: patch.Migrate01200,
|
||||
1201: patch.Migrate01201,
|
||||
1202: patch.Migrate01202,
|
||||
1203: patch.Migrate01203,
|
||||
1204: patch.Migrate01204,
|
||||
1205: patch.Migrate01205,
|
||||
1301: patch.Migrate01301,
|
||||
1602: patch.Migrate01602,
|
||||
1701: patch.Migrate01701,
|
||||
1702: patch.Migrate01702,
|
||||
1703: patch.Migrate01703,
|
||||
1704: patch.Migrate01704,
|
||||
1705: patch.Migrate01705,
|
||||
}
|
||||
|
||||
for v, migrate := range migrations {
|
||||
if sqlVersion < v {
|
||||
if err := migrate(db); err != nil {
|
||||
return fmt.Errorf("migrator %d version error: %w", v, err)
|
||||
}
|
||||
logger.Infof(fmt.Sprintf("Migrate %d version success", v))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrate02000(db *gorm.DB, sqlVersion int) error {
|
||||
migrations := map[int]func(*gorm.DB) error{
|
||||
2000: patch.Migrate02000,
|
||||
2001: patch.Migrate02001,
|
||||
2002: patch.Migrate02002,
|
||||
2003: patch.Migrate02003,
|
||||
2007: patch.Migrate02007,
|
||||
2008: patch.Migrate02008,
|
||||
2009: patch.Migrate02009,
|
||||
2010: patch.Migrate02010,
|
||||
2011: patch.Migrate02011,
|
||||
}
|
||||
|
||||
for v, migrate := range migrations {
|
||||
if sqlVersion < v {
|
||||
if err := migrate(db); err != nil {
|
||||
return fmt.Errorf("migrator %d version error: %w", v, err)
|
||||
}
|
||||
logger.Infof(fmt.Sprintf("Migrate %d version success", v))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrate03000(db *gorm.DB, sqlVersion int) error {
|
||||
migrations := map[int]func(*gorm.DB) error{
|
||||
3001: patch.Migrate03001,
|
||||
3002: patch.Migrate03002,
|
||||
}
|
||||
|
||||
for v, migrate := range migrations {
|
||||
if sqlVersion < v {
|
||||
if err := migrate(db); err != nil {
|
||||
return fmt.Errorf("migrator %d version error: %w", v, err)
|
||||
}
|
||||
logger.Infof(fmt.Sprintf("Migrate %d version success", v))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user