spark-server/internal/module/client/model/repo.go
DESKTOP-L8OUL9F\gfl0034 082f420347 feat: 意见反馈支持登录与游客分流
可选 Bearer 写入 auth_type/uid,游客必填 contact;Admin 与 popadmin 可按类型筛选。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-24 16:24:47 +08:00

243 lines
7.2 KiB
Go

package model
import (
"context"
"errors"
"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"`
AuthType string `gorm:"column:auth_type"`
Email string `gorm:"column:email"`
Contact string `gorm:"column:contact"`
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 {
if d == nil {
return errors.New("client: nil legal document")
}
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"
}
authType := f.AuthType
if authType == "" {
authType = entity.FeedbackAuthGuest
}
row := feedbackRow{
UserID: f.UserID, AuthType: authType, Email: f.Email, Contact: f.Contact,
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
f.AuthType = authType
return nil
}
func (r *Repo) ListFeedback(ctx context.Context, page, size int, authType string) ([]entity.Feedback, int64, error) {
if page < 0 {
page = 0
}
if size <= 0 || size > 200 {
size = 50
}
q := r.db.WithContext(ctx).Model(&feedbackRow{})
if authType == entity.FeedbackAuthLogin || authType == entity.FeedbackAuthGuest {
q = q.Where("auth_type = ?", authType)
}
var total int64
if err := q.Count(&total).Error; err != nil {
return nil, 0, err
}
var rows []feedbackRow
listQ := r.db.WithContext(ctx).Order("id DESC").Offset(page * size).Limit(size)
if authType == entity.FeedbackAuthLogin || authType == entity.FeedbackAuthGuest {
listQ = listQ.Where("auth_type = ?", authType)
}
err := listQ.Find(&rows).Error
if err != nil {
return nil, 0, err
}
out := make([]entity.Feedback, 0, len(rows))
for i := range rows {
row := &rows[i]
out = append(out, entity.Feedback{
ID: row.ID, UserID: row.UserID, AuthType: row.AuthType, Email: row.Email,
Contact: row.Contact, 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 i := range rows {
row := &rows[i]
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 {
if p == nil {
return errors.New("client: nil subscription plan")
}
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
}