package model import ( "context" "time" "gorm.io/gorm" "spark-server/internal/module/client/entity" ) type Repo struct { db *gorm.DB } func NewRepo(db *gorm.DB) *Repo { if db == nil { return nil } return &Repo{db: db} } type legalRow struct { DocKey string `gorm:"column:doc_key;primaryKey"` Lang string `gorm:"column:lang;primaryKey"` Title string `gorm:"column:title"` Content string `gorm:"column:content"` Version string `gorm:"column:version"` UpdatedAt time.Time `gorm:"column:updated_at"` } func (legalRow) TableName() string { return "t_legal_documents" } type feedbackRow struct { ID int64 `gorm:"column:id;primaryKey;autoIncrement"` UserID *int64 `gorm:"column:user_id"` Email string `gorm:"column:email"` Content string `gorm:"column:content"` AppVersion string `gorm:"column:app_version"` Language string `gorm:"column:language"` Status string `gorm:"column:status"` CreatedAt time.Time `gorm:"column:created_at"` } func (feedbackRow) TableName() string { return "t_feedback" } type planRow struct { PlanID string `gorm:"column:plan_id;primaryKey"` Lang string `gorm:"column:lang;primaryKey"` Title string `gorm:"column:title"` Subtitle string `gorm:"column:subtitle"` PriceDisplay string `gorm:"column:price_display"` PriceCents int `gorm:"column:price_cents"` Currency string `gorm:"column:currency"` PeriodDays int `gorm:"column:period_days"` Badge string `gorm:"column:badge"` SortOrder int `gorm:"column:sort_order"` Enabled bool `gorm:"column:enabled"` ProductID string `gorm:"column:product_id"` UpdatedAt time.Time `gorm:"column:updated_at"` } func (planRow) TableName() string { return "t_subscription_plans" } type passwordRow struct { UserID int64 `gorm:"column:user_id;primaryKey"` PasswordHash string `gorm:"column:password_hash"` UpdatedAt time.Time `gorm:"column:updated_at"` } func (passwordRow) TableName() string { return "t_user_passwords" } func (r *Repo) GetLegal(ctx context.Context, lang string) (privacy, terms *entity.LegalDocument, err error) { var rows []legalRow if err = r.db.WithContext(ctx).Where("lang = ?", lang).Find(&rows).Error; err != nil { return nil, nil, err } for i := range rows { d := &entity.LegalDocument{ Key: rows[i].DocKey, Lang: rows[i].Lang, Title: rows[i].Title, Content: rows[i].Content, Version: rows[i].Version, } switch rows[i].DocKey { case entity.DocPrivacy: privacy = d case entity.DocTerms: terms = d } } return privacy, terms, nil } func (r *Repo) UpsertLegal(ctx context.Context, d entity.LegalDocument) error { row := legalRow{ DocKey: d.Key, Lang: d.Lang, Title: d.Title, Content: d.Content, Version: d.Version, UpdatedAt: time.Now(), } if row.Version == "" { row.Version = "1.0" } return r.db.WithContext(ctx).Save(&row).Error } func (r *Repo) InsertFeedback(ctx context.Context, f *entity.Feedback) error { status := f.Status if status == "" { status = "open" } row := feedbackRow{ UserID: f.UserID, Email: f.Email, Content: f.Content, AppVersion: f.AppVersion, Language: f.Language, Status: status, CreatedAt: f.CreatedAt, } if err := r.db.WithContext(ctx).Create(&row).Error; err != nil { return err } f.ID = row.ID f.Status = status return nil } func (r *Repo) ListFeedback(ctx context.Context, page, size int) ([]entity.Feedback, int64, error) { if page < 0 { page = 0 } if size <= 0 || size > 200 { size = 50 } var total int64 if err := r.db.WithContext(ctx).Model(&feedbackRow{}).Count(&total).Error; err != nil { return nil, 0, err } var rows []feedbackRow err := r.db.WithContext(ctx).Order("id DESC").Offset(page * size).Limit(size).Find(&rows).Error if err != nil { return nil, 0, err } out := make([]entity.Feedback, 0, len(rows)) for _, row := range rows { out = append(out, entity.Feedback{ ID: row.ID, UserID: row.UserID, Email: row.Email, Content: row.Content, AppVersion: row.AppVersion, Language: row.Language, Status: row.Status, CreatedAt: row.CreatedAt, }) } return out, total, nil } func (r *Repo) UpdateFeedbackStatus(ctx context.Context, id int64, status string) error { return r.db.WithContext(ctx).Model(&feedbackRow{}).Where("id = ?", id).Update("status", status).Error } func (r *Repo) ListPlans(ctx context.Context, lang string) ([]entity.SubscriptionPlan, error) { var rows []planRow err := r.db.WithContext(ctx). Where("lang = ? AND enabled = 1", lang). Order("sort_order ASC, plan_id ASC"). Find(&rows).Error if err != nil { return nil, err } return mapPlans(rows), nil } func (r *Repo) ListPlansAdmin(ctx context.Context, lang string) ([]entity.SubscriptionPlan, error) { q := r.db.WithContext(ctx).Order("lang ASC, sort_order ASC, plan_id ASC") if lang != "" { q = q.Where("lang = ?", lang) } var rows []planRow if err := q.Find(&rows).Error; err != nil { return nil, err } return mapPlans(rows), nil } func mapPlans(rows []planRow) []entity.SubscriptionPlan { out := make([]entity.SubscriptionPlan, 0, len(rows)) for _, row := range rows { out = append(out, entity.SubscriptionPlan{ PlanID: row.PlanID, Lang: row.Lang, Title: row.Title, Subtitle: row.Subtitle, PriceDisplay: row.PriceDisplay, PriceCents: row.PriceCents, Currency: row.Currency, PeriodDays: row.PeriodDays, Badge: row.Badge, SortOrder: row.SortOrder, Enabled: row.Enabled, ProductID: row.ProductID, }) } return out } func (r *Repo) UpsertPlan(ctx context.Context, p entity.SubscriptionPlan) error { row := planRow{ PlanID: p.PlanID, Lang: p.Lang, Title: p.Title, Subtitle: p.Subtitle, PriceDisplay: p.PriceDisplay, PriceCents: p.PriceCents, Currency: p.Currency, PeriodDays: p.PeriodDays, Badge: p.Badge, SortOrder: p.SortOrder, Enabled: p.Enabled, ProductID: p.ProductID, UpdatedAt: time.Now(), } if row.Currency == "" { row.Currency = "CNY" } return r.db.WithContext(ctx).Save(&row).Error } func (r *Repo) GetPasswordHash(ctx context.Context, userID int64) (string, error) { var row passwordRow err := r.db.WithContext(ctx).Where("user_id = ?", userID).Take(&row).Error if err == gorm.ErrRecordNotFound { return "", nil } if err != nil { return "", err } return row.PasswordHash, nil } func (r *Repo) SetPasswordHash(ctx context.Context, userID int64, hash string) error { row := passwordRow{UserID: userID, PasswordHash: hash, UpdatedAt: time.Now()} return r.db.WithContext(ctx).Save(&row).Error }