Files
back-hokm/internal/economy/chat.go
T
2026-06-25 23:34:41 +03:30

130 lines
3.7 KiB
Go

package economy
import "context"
// ChatPack یک بسته‌ی پیام/شکلکِ چت. Owned یعنی کاربر می‌تواند از آن استفاده کند
// (رایگان، یا VIP برای بسته‌ی vip، یا خریداری‌شده).
type ChatPack struct {
ID string `json:"id"`
Title string `json:"title"`
Kind string `json:"kind"` // text | emoji
PriceCoins int64 `json:"price_coins"`
VIP bool `json:"vip"` // برای VIP رایگان است
Owned bool `json:"owned"`
Messages []string `json:"messages"`
}
// ChatPacks بسته‌های فعال را همراه پیام‌ها و وضعیتِ مالکیتِ کاربر برمی‌گرداند.
func (s *Service) ChatPacks(ctx context.Context, userID int64) ([]ChatPack, error) {
rows, err := s.db.QueryContext(ctx,
`SELECT id, title, kind, price_coins, vip FROM chat_packs WHERE enabled = 1 ORDER BY sort`)
if err != nil {
return nil, err
}
defer rows.Close()
var packs []ChatPack
for rows.Next() {
var p ChatPack
var vip int
if err := rows.Scan(&p.ID, &p.Title, &p.Kind, &p.PriceCoins, &vip); err != nil {
return nil, err
}
p.VIP = vip == 1
packs = append(packs, p)
}
if err := rows.Err(); err != nil {
return nil, err
}
owned, err := s.ownedChatPacks(ctx, userID)
if err != nil {
return nil, err
}
isVIP := s.isVIP(ctx, userID)
for i := range packs {
p := &packs[i]
// رایگان (بدون قیمت و غیرِ vip)، یا VIP برای بسته‌ی vip، یا خریداری‌شده.
p.Owned = (p.PriceCoins == 0 && !p.VIP) || (p.VIP && isVIP) || owned[p.ID]
msgs, err := s.packMessages(ctx, p.ID)
if err != nil {
return nil, err
}
p.Messages = msgs
}
return packs, nil
}
func (s *Service) packMessages(ctx context.Context, packID string) ([]string, error) {
rows, err := s.db.QueryContext(ctx,
`SELECT body FROM chat_messages WHERE pack_id = ? ORDER BY sort, id`, packID)
if err != nil {
return nil, err
}
defer rows.Close()
msgs := []string{}
for rows.Next() {
var b string
if err := rows.Scan(&b); err != nil {
return nil, err
}
msgs = append(msgs, b)
}
return msgs, rows.Err()
}
func (s *Service) ownedChatPacks(ctx context.Context, userID int64) (map[string]bool, error) {
rows, err := s.db.QueryContext(ctx,
`SELECT pack_id FROM user_chat_packs WHERE user_id = ?`, userID)
if err != nil {
return nil, err
}
defer rows.Close()
owned := map[string]bool{}
for rows.Next() {
var id string
if err := rows.Scan(&id); err != nil {
return nil, err
}
owned[id] = true
}
return owned, rows.Err()
}
// BuyChatPack یک بسته‌ی چت را با سکه باز می‌کند. بسته‌های رایگان/قبلاً‌مالک خطا
// می‌دهند؛ بسته‌ی VIP برای کاربرِ VIP خودبه‌خود در دسترس است (نیازی به خرید نیست).
func (s *Service) BuyChatPack(ctx context.Context, userID int64, packID string) error {
var price int64
var vip, enabled int
err := s.db.QueryRowContext(ctx,
`SELECT price_coins, vip, enabled FROM chat_packs WHERE id = ?`, packID).
Scan(&price, &vip, &enabled)
if err != nil || enabled != 1 {
return ErrNotFound
}
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
var exists int
_ = tx.QueryRowContext(ctx,
`SELECT 1 FROM user_chat_packs WHERE user_id = ? AND pack_id = ?`, userID, packID).Scan(&exists)
if exists == 1 {
return ErrAlreadyOwned
}
if price > 0 {
if err := adjustTx(ctx, tx, userID, CurrencyCoin, -price, "buy_chat_pack", packID); err != nil {
return err
}
}
if _, err := tx.ExecContext(ctx,
`INSERT INTO user_chat_packs (user_id, pack_id) VALUES (?, ?)`, userID, packID); err != nil {
return err
}
return tx.Commit()
}