Browse Source

feat(subscription): implement SQLite support for SubscriptionPlan table creation and migration
#2823

CaIon 1 tuần trước cách đây
mục cha
commit
f1e6c1bf77
1 tập tin đã thay đổi với 100 bổ sung6 xóa
  1. 100 6
      model/main.go

+ 100 - 6
model/main.go

@@ -271,7 +271,6 @@ func migrateDB() error {
 		&TwoFA{},
 		&TwoFABackupCode{},
 		&Checkin{},
-		&SubscriptionPlan{},
 		&SubscriptionOrder{},
 		&UserSubscription{},
 		&SubscriptionPreConsumeRecord{},
@@ -279,6 +278,15 @@ func migrateDB() error {
 	if err != nil {
 		return err
 	}
+	if common.UsingSQLite {
+		if err := ensureSubscriptionPlanTableSQLite(); err != nil {
+			return err
+		}
+	} else {
+		if err := DB.AutoMigrate(&SubscriptionPlan{}); err != nil {
+			return err
+		}
+	}
 	return nil
 }
 
@@ -309,7 +317,6 @@ func migrateDBFast() error {
 		{&TwoFA{}, "TwoFA"},
 		{&TwoFABackupCode{}, "TwoFABackupCode"},
 		{&Checkin{}, "Checkin"},
-		{&SubscriptionPlan{}, "SubscriptionPlan"},
 		{&SubscriptionOrder{}, "SubscriptionOrder"},
 		{&UserSubscription{}, "UserSubscription"},
 		{&SubscriptionPreConsumeRecord{}, "SubscriptionPreConsumeRecord"},
@@ -337,6 +344,15 @@ func migrateDBFast() error {
 			return err
 		}
 	}
+	if common.UsingSQLite {
+		if err := ensureSubscriptionPlanTableSQLite(); err != nil {
+			return err
+		}
+	} else {
+		if err := DB.AutoMigrate(&SubscriptionPlan{}); err != nil {
+			return err
+		}
+	}
 	common.SysLog("database migrated")
 	return nil
 }
@@ -349,9 +365,91 @@ func migrateLOGDB() error {
 	return nil
 }
 
+type sqliteColumnDef struct {
+	Name string
+	DDL  string
+}
+
+func ensureSubscriptionPlanTableSQLite() error {
+	if !common.UsingSQLite {
+		return nil
+	}
+	tableName := "subscription_plans"
+	if !DB.Migrator().HasTable(tableName) {
+		createSQL := `CREATE TABLE ` + "`" + tableName + "`" + ` (
+` + "`id`" + ` integer,
+` + "`title`" + ` varchar(128) NOT NULL,
+` + "`subtitle`" + ` varchar(255) DEFAULT '',
+` + "`price_amount`" + ` decimal(10,6) NOT NULL,
+` + "`currency`" + ` varchar(8) NOT NULL DEFAULT 'USD',
+` + "`duration_unit`" + ` varchar(16) NOT NULL DEFAULT 'month',
+` + "`duration_value`" + ` integer NOT NULL DEFAULT 1,
+` + "`custom_seconds`" + ` bigint NOT NULL DEFAULT 0,
+` + "`enabled`" + ` numeric DEFAULT 1,
+` + "`sort_order`" + ` integer DEFAULT 0,
+` + "`stripe_price_id`" + ` varchar(128) DEFAULT '',
+` + "`creem_product_id`" + ` varchar(128) DEFAULT '',
+` + "`max_purchase_per_user`" + ` integer DEFAULT 0,
+` + "`upgrade_group`" + ` varchar(64) DEFAULT '',
+` + "`total_amount`" + ` bigint NOT NULL DEFAULT 0,
+` + "`quota_reset_period`" + ` varchar(16) DEFAULT 'never',
+` + "`quota_reset_custom_seconds`" + ` bigint DEFAULT 0,
+` + "`created_at`" + ` bigint,
+` + "`updated_at`" + ` bigint,
+PRIMARY KEY (` + "`id`" + `)
+)`
+		return DB.Exec(createSQL).Error
+	}
+	var cols []struct {
+		Name string `gorm:"column:name"`
+	}
+	if err := DB.Raw("PRAGMA table_info(`" + tableName + "`)").Scan(&cols).Error; err != nil {
+		return err
+	}
+	existing := make(map[string]struct{}, len(cols))
+	for _, c := range cols {
+		existing[c.Name] = struct{}{}
+	}
+	required := []sqliteColumnDef{
+		{Name: "title", DDL: "`title` varchar(128) NOT NULL"},
+		{Name: "subtitle", DDL: "`subtitle` varchar(255) DEFAULT ''"},
+		{Name: "price_amount", DDL: "`price_amount` decimal(10,6) NOT NULL"},
+		{Name: "currency", DDL: "`currency` varchar(8) NOT NULL DEFAULT 'USD'"},
+		{Name: "duration_unit", DDL: "`duration_unit` varchar(16) NOT NULL DEFAULT 'month'"},
+		{Name: "duration_value", DDL: "`duration_value` integer NOT NULL DEFAULT 1"},
+		{Name: "custom_seconds", DDL: "`custom_seconds` bigint NOT NULL DEFAULT 0"},
+		{Name: "enabled", DDL: "`enabled` numeric DEFAULT 1"},
+		{Name: "sort_order", DDL: "`sort_order` integer DEFAULT 0"},
+		{Name: "stripe_price_id", DDL: "`stripe_price_id` varchar(128) DEFAULT ''"},
+		{Name: "creem_product_id", DDL: "`creem_product_id` varchar(128) DEFAULT ''"},
+		{Name: "max_purchase_per_user", DDL: "`max_purchase_per_user` integer DEFAULT 0"},
+		{Name: "upgrade_group", DDL: "`upgrade_group` varchar(64) DEFAULT ''"},
+		{Name: "total_amount", DDL: "`total_amount` bigint NOT NULL DEFAULT 0"},
+		{Name: "quota_reset_period", DDL: "`quota_reset_period` varchar(16) DEFAULT 'never'"},
+		{Name: "quota_reset_custom_seconds", DDL: "`quota_reset_custom_seconds` bigint DEFAULT 0"},
+		{Name: "created_at", DDL: "`created_at` bigint"},
+		{Name: "updated_at", DDL: "`updated_at` bigint"},
+	}
+	for _, col := range required {
+		if _, ok := existing[col.Name]; ok {
+			continue
+		}
+		if err := DB.Exec("ALTER TABLE `" + tableName + "` ADD COLUMN " + col.DDL).Error; err != nil {
+			return err
+		}
+	}
+	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() {
+	// SQLite doesn't support ALTER COLUMN, and its type affinity handles this automatically
+	// Skip early to avoid GORM parsing the existing table DDL which may cause issues
+	if common.UsingSQLite {
+		return
+	}
+
 	tableName := "subscription_plans"
 	columnName := "price_amount"
 
@@ -387,10 +485,6 @@ func migrateSubscriptionPlanPriceAmount() {
 		}
 		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
 	}