spark-server/internal/module/client/model/repo.go
DESKTOP-L8OUL9F\gfl0034 1edcb959b7 feat: 补好友拒绝、客户端配置 API 与 OpenAPI/popadmin
为 Spark Messenger 主路径补齐 POST /v1/friends/requests/reject,并落地法律协议/反馈/订阅/密码登录及管理页,同步 OpenAPI 文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-24 14:01:46 +08:00

217 lines
6.5 KiB
Go

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
}