From 4c4729fac2115860aac49d63aaa95b766de34aba Mon Sep 17 00:00:00 2001 From: CSCITech Date: Sat, 11 Jul 2026 01:15:03 +0800 Subject: [PATCH] v1.0.4-preview.3 --- controller/user.go | 8 ++--- controller/user_setting_test.go | 11 ++++--- model/main.go | 55 +++++++++++++++++++++++++++++++++ model/user.go | 10 +++--- 4 files changed, 70 insertions(+), 14 deletions(-) diff --git a/controller/user.go b/controller/user.go index 8ee30d4..c1259c7 100644 --- a/controller/user.go +++ b/controller/user.go @@ -35,7 +35,7 @@ var ( errOriginalPasswordFail = errors.New("original password is incorrect") ) -const maxUserQuotaValue = 1<<31 - 1 +const maxUserQuotaValue = int64(^uint64(0) >> 1) func Login(c *gin.Context) { if !common.PasswordLoginEnabled { @@ -996,14 +996,14 @@ type ManageRequest struct { } func isValidQuotaOverride(value int) bool { - return value >= 0 && value <= maxUserQuotaValue + return value >= 0 && int64(value) <= maxUserQuotaValue } func isValidQuotaAddition(current int, delta int) bool { - if delta <= 0 || delta > maxUserQuotaValue { + if delta <= 0 || int64(delta) > maxUserQuotaValue { return false } - return current <= maxUserQuotaValue-delta + return int64(current) <= maxUserQuotaValue-int64(delta) } // ManageUser Only admin user can do this diff --git a/controller/user_setting_test.go b/controller/user_setting_test.go index 52dff8b..156a725 100644 --- a/controller/user_setting_test.go +++ b/controller/user_setting_test.go @@ -217,13 +217,14 @@ func TestRegisterConsumesEmailVerificationCode(t *testing.T) { } func TestQuotaBoundsValidation(t *testing.T) { + legacyInt32Max := 1<<31 - 1 + aboveLegacyInt32Max := legacyInt32Max + 1 + require.True(t, isValidQuotaOverride(0)) - require.True(t, isValidQuotaOverride(maxUserQuotaValue)) + require.True(t, isValidQuotaOverride(aboveLegacyInt32Max)) require.False(t, isValidQuotaOverride(-1)) - require.False(t, isValidQuotaOverride(maxUserQuotaValue+1)) - require.True(t, isValidQuotaAddition(maxUserQuotaValue-1, 1)) - require.False(t, isValidQuotaAddition(maxUserQuotaValue, 1)) - require.False(t, isValidQuotaAddition(0, maxUserQuotaValue+1)) + require.True(t, isValidQuotaAddition(aboveLegacyInt32Max, 1)) + require.True(t, isValidQuotaAddition(0, aboveLegacyInt32Max)) require.False(t, isValidQuotaAddition(0, 0)) } diff --git a/model/main.go b/model/main.go index b0524b4..e2183bf 100644 --- a/model/main.go +++ b/model/main.go @@ -278,6 +278,9 @@ func migrateDB() error { if err := migrateTokenModelLimitsToText(); err != nil { return err } + if err := migrateUserQuotaColumnsToBigInt(); err != nil { + return err + } err := DB.AutoMigrate( &Channel{}, @@ -331,6 +334,9 @@ func migrateDB() error { } func migrateDBFast() error { + if err := migrateUserQuotaColumnsToBigInt(); err != nil { + return err + } var wg sync.WaitGroup @@ -660,6 +666,55 @@ func migrateTokenModelLimitsToText() error { return nil } +func migrateUserQuotaColumnsToBigInt() error { + if DB == nil || common.UsingSQLite || !DB.Migrator().HasTable(&User{}) { + return nil + } + + tableName := "users" + columnNames := []string{"quota", "used_quota", "aff_quota", "aff_history"} + + for _, columnName := range columnNames { + if !DB.Migrator().HasColumn(&User{}, columnName) { + continue + } + + var alterSQL string + if common.UsingPostgreSQL { + var dataType string + if err := DB.Raw(`SELECT data_type FROM information_schema.columns + WHERE table_schema = current_schema() AND table_name = ? AND column_name = ?`, + tableName, columnName).Scan(&dataType).Error; err != nil { + common.SysLog(fmt.Sprintf("Warning: failed to query metadata for %s.%s: %v", tableName, columnName, err)) + } else if strings.EqualFold(dataType, "bigint") { + continue + } + alterSQL = fmt.Sprintf(`ALTER TABLE "%s" ALTER COLUMN "%s" TYPE bigint USING "%s"::bigint`, + tableName, columnName, columnName) + } else if common.UsingMySQL { + var columnType string + if err := DB.Raw(`SELECT COLUMN_TYPE FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?`, + tableName, columnName).Scan(&columnType).Error; err != nil { + common.SysLog(fmt.Sprintf("Warning: failed to query metadata for %s.%s: %v", tableName, columnName, err)) + } else if strings.HasPrefix(strings.ToLower(columnType), "bigint") { + continue + } + alterSQL = fmt.Sprintf("ALTER TABLE `%s` MODIFY COLUMN `%s` bigint DEFAULT 0", tableName, columnName) + } + + if alterSQL == "" { + continue + } + if err := DB.Exec(alterSQL).Error; err != nil { + return fmt.Errorf("failed to migrate %s.%s to bigint: %w", tableName, columnName, err) + } + common.SysLog(fmt.Sprintf("Successfully migrated %s.%s to bigint", tableName, columnName)) + } + + return nil +} + // migrateSubscriptionPlanPriceAmount migrates price_amount column from float/double to decimal(10,6) // This is safe to run multiple times - it checks the column type first func migrateSubscriptionPlanPriceAmount() { diff --git a/model/user.go b/model/user.go index fb9fc76..8775229 100644 --- a/model/user.go +++ b/model/user.go @@ -64,14 +64,14 @@ type User struct { TelegramId string `json:"telegram_id" gorm:"column:telegram_id;index"` VerificationCode string `json:"verification_code" gorm:"-:all"` // this field is only for Email verification, don't save it to database! AccessToken *string `json:"-" gorm:"type:char(32);column:access_token;uniqueIndex"` // this token is for system management - Quota int `json:"quota" gorm:"type:int;default:0"` - UsedQuota int `json:"used_quota" gorm:"type:int;default:0;column:used_quota"` // used quota - RequestCount int `json:"request_count" gorm:"type:int;default:0;"` // request number + Quota int `json:"quota" gorm:"type:bigint;default:0"` + UsedQuota int `json:"used_quota" gorm:"type:bigint;default:0;column:used_quota"` // used quota + RequestCount int `json:"request_count" gorm:"type:int;default:0;"` // request number Group string `json:"group" gorm:"type:varchar(64);default:'default'"` AffCode string `json:"aff_code" gorm:"type:varchar(32);column:aff_code;uniqueIndex"` AffCount int `json:"aff_count" gorm:"type:int;default:0;column:aff_count"` - AffQuota int `json:"aff_quota" gorm:"type:int;default:0;column:aff_quota"` // 邀请剩余额度 - AffHistoryQuota int `json:"aff_history_quota" gorm:"type:int;default:0;column:aff_history"` // 邀请历史额度 + AffQuota int `json:"aff_quota" gorm:"type:bigint;default:0;column:aff_quota"` // 邀请剩余额度 + AffHistoryQuota int `json:"aff_history_quota" gorm:"type:bigint;default:0;column:aff_history"` // 邀请历史额度 InviterId int `json:"inviter_id" gorm:"type:int;column:inviter_id;index"` DeletedAt gorm.DeletedAt `gorm:"index"` LinuxDOId string `json:"linux_do_id" gorm:"column:linux_do_id;index"`