feat(subscription): validate price amount and migrate database column type
- Add validation to ensure subscription plan price amount is non-negative and does not exceed 9999. - Migrate the price_amount column from float/double to decimal(10,6) in the database for improved precision. - Update SubscriptionPlan model to reflect the new decimal type for price_amount.
This commit is contained in:
@@ -118,6 +118,14 @@ func AdminCreateSubscriptionPlan(c *gin.Context) {
|
|||||||
common.ApiErrorMsg(c, "套餐标题不能为空")
|
common.ApiErrorMsg(c, "套餐标题不能为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if req.Plan.PriceAmount < 0 {
|
||||||
|
common.ApiErrorMsg(c, "价格不能为负数")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.Plan.PriceAmount > 9999 {
|
||||||
|
common.ApiErrorMsg(c, "价格不能超过9999")
|
||||||
|
return
|
||||||
|
}
|
||||||
if req.Plan.Currency == "" {
|
if req.Plan.Currency == "" {
|
||||||
req.Plan.Currency = "USD"
|
req.Plan.Currency = "USD"
|
||||||
}
|
}
|
||||||
@@ -172,6 +180,14 @@ func AdminUpdateSubscriptionPlan(c *gin.Context) {
|
|||||||
common.ApiErrorMsg(c, "套餐标题不能为空")
|
common.ApiErrorMsg(c, "套餐标题不能为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if req.Plan.PriceAmount < 0 {
|
||||||
|
common.ApiErrorMsg(c, "价格不能为负数")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.Plan.PriceAmount > 9999 {
|
||||||
|
common.ApiErrorMsg(c, "价格不能超过9999")
|
||||||
|
return
|
||||||
|
}
|
||||||
req.Plan.Id = id
|
req.Plan.Id = id
|
||||||
if req.Plan.Currency == "" {
|
if req.Plan.Currency == "" {
|
||||||
req.Plan.Currency = "USD"
|
req.Plan.Currency = "USD"
|
||||||
|
|||||||
@@ -248,6 +248,9 @@ func InitLogDB() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func migrateDB() error {
|
func migrateDB() error {
|
||||||
|
// Migrate price_amount column from float/double to decimal for existing tables
|
||||||
|
migrateSubscriptionPlanPriceAmount()
|
||||||
|
|
||||||
err := DB.AutoMigrate(
|
err := DB.AutoMigrate(
|
||||||
&Channel{},
|
&Channel{},
|
||||||
&Token{},
|
&Token{},
|
||||||
@@ -346,6 +349,61 @@ func migrateLOGDB() error {
|
|||||||
return nil
|
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() {
|
||||||
|
tableName := "subscription_plans"
|
||||||
|
columnName := "price_amount"
|
||||||
|
|
||||||
|
// Check if table exists first
|
||||||
|
if !DB.Migrator().HasTable(tableName) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if column exists
|
||||||
|
if !DB.Migrator().HasColumn(&SubscriptionPlan{}, columnName) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var alterSQL string
|
||||||
|
if common.UsingPostgreSQL {
|
||||||
|
// PostgreSQL: Check if already decimal/numeric
|
||||||
|
var dataType string
|
||||||
|
DB.Raw(`SELECT data_type FROM information_schema.columns
|
||||||
|
WHERE table_name = ? AND column_name = ?`, tableName, columnName).Scan(&dataType)
|
||||||
|
if dataType == "numeric" {
|
||||||
|
return // Already decimal/numeric
|
||||||
|
}
|
||||||
|
alterSQL = fmt.Sprintf(`ALTER TABLE %s ALTER COLUMN %s TYPE decimal(10,6) USING %s::decimal(10,6)`,
|
||||||
|
tableName, columnName, columnName)
|
||||||
|
} else if common.UsingMySQL {
|
||||||
|
// MySQL: Check if already decimal
|
||||||
|
var columnType string
|
||||||
|
DB.Raw(`SELECT COLUMN_TYPE FROM information_schema.columns
|
||||||
|
WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?`,
|
||||||
|
tableName, columnName).Scan(&columnType)
|
||||||
|
if strings.HasPrefix(strings.ToLower(columnType), "decimal") {
|
||||||
|
return // Already decimal
|
||||||
|
}
|
||||||
|
alterSQL = fmt.Sprintf("ALTER TABLE %s MODIFY COLUMN %s decimal(10,6) NOT NULL DEFAULT 0",
|
||||||
|
tableName, columnName)
|
||||||
|
} else if common.UsingSQLite {
|
||||||
|
// SQLite doesn't support ALTER COLUMN, but its type affinity handles this automatically
|
||||||
|
// The column will accept decimal values without modification
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if alterSQL != "" {
|
||||||
|
if err := DB.Exec(alterSQL).Error; err != nil {
|
||||||
|
common.SysLog(fmt.Sprintf("Warning: failed to migrate %s.%s to decimal: %v", tableName, columnName, err))
|
||||||
|
} else {
|
||||||
|
common.SysLog(fmt.Sprintf("Successfully migrated %s.%s to decimal(10,6)", tableName, columnName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func closeDB(db *gorm.DB) error {
|
func closeDB(db *gorm.DB) error {
|
||||||
sqlDB, err := db.DB()
|
sqlDB, err := db.DB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ type SubscriptionPlan struct {
|
|||||||
Subtitle string `json:"subtitle" gorm:"type:varchar(255);default:''"`
|
Subtitle string `json:"subtitle" gorm:"type:varchar(255);default:''"`
|
||||||
|
|
||||||
// Display money amount (follow existing code style: float64 for money)
|
// Display money amount (follow existing code style: float64 for money)
|
||||||
PriceAmount float64 `json:"price_amount" gorm:"type:double;not null;default:0"`
|
PriceAmount float64 `json:"price_amount" gorm:"type:decimal(10,6);not null;default:0"`
|
||||||
Currency string `json:"currency" gorm:"type:varchar(8);not null;default:'USD'"`
|
Currency string `json:"currency" gorm:"type:varchar(8);not null;default:'USD'"`
|
||||||
|
|
||||||
DurationUnit string `json:"duration_unit" gorm:"type:varchar(16);not null;default:'month'"`
|
DurationUnit string `json:"duration_unit" gorm:"type:varchar(16);not null;default:'month'"`
|
||||||
|
|||||||
Reference in New Issue
Block a user