153 lines
4.3 KiB
Go
153 lines
4.3 KiB
Go
package economy
|
|
|
|
import "context"
|
|
|
|
// Frame یک قابِ آواتار (کازمتیک). Owned یعنی قابلِ انتخاب. سبکِ رنگی سمتِ کلاینت است.
|
|
type Frame struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
PriceCoins int64 `json:"price_coins"`
|
|
VIP bool `json:"vip"`
|
|
C1 string `json:"c1"` // رنگِ گرادیان (hex بدونِ #)؛ خالی ⇒ قابِ رتبه
|
|
C2 string `json:"c2"`
|
|
Owned bool `json:"owned"`
|
|
}
|
|
|
|
// RankTierStyle رنگ و برچسبِ یک رتبه (برنز..پادشاه). سمتِ کلاینت hardcode نیست.
|
|
type RankTierStyle struct {
|
|
ID string `json:"id"`
|
|
Label string `json:"label"`
|
|
C1 string `json:"c1"`
|
|
C2 string `json:"c2"`
|
|
}
|
|
|
|
// RankTiers رنگ/برچسبِ همهی رتبهها را برمیگرداند (برای کشِ کلاینت).
|
|
func (s *Service) RankTiers(ctx context.Context) ([]RankTierStyle, error) {
|
|
rows, err := s.db.QueryContext(ctx,
|
|
`SELECT id, label, c1, c2 FROM rank_tiers ORDER BY sort`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var tiers []RankTierStyle
|
|
for rows.Next() {
|
|
var t RankTierStyle
|
|
if err := rows.Scan(&t.ID, &t.Label, &t.C1, &t.C2); err != nil {
|
|
return nil, err
|
|
}
|
|
tiers = append(tiers, t)
|
|
}
|
|
return tiers, rows.Err()
|
|
}
|
|
|
|
// Frames قابهای فعال را با وضعیتِ مالکیتِ کاربر برمیگرداند.
|
|
func (s *Service) Frames(ctx context.Context, userID int64) ([]Frame, error) {
|
|
rows, err := s.db.QueryContext(ctx,
|
|
`SELECT id, title, price_coins, vip, c1, c2 FROM avatar_frames WHERE enabled = 1 ORDER BY sort`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var frames []Frame
|
|
for rows.Next() {
|
|
var f Frame
|
|
var vip int
|
|
if err := rows.Scan(&f.ID, &f.Title, &f.PriceCoins, &vip, &f.C1, &f.C2); err != nil {
|
|
return nil, err
|
|
}
|
|
f.VIP = vip == 1
|
|
frames = append(frames, f)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
owned, err := s.ownedFrames(ctx, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
isVIP := s.isVIP(ctx, userID)
|
|
for i := range frames {
|
|
f := &frames[i]
|
|
f.Owned = (f.PriceCoins == 0 && !f.VIP) || (f.VIP && isVIP) || owned[f.ID]
|
|
}
|
|
return frames, nil
|
|
}
|
|
|
|
func (s *Service) ownedFrames(ctx context.Context, userID int64) (map[string]bool, error) {
|
|
rows, err := s.db.QueryContext(ctx,
|
|
`SELECT frame_id FROM user_frames 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()
|
|
}
|
|
|
|
// BuyFrame یک قاب را با سکه باز میکند.
|
|
func (s *Service) BuyFrame(ctx context.Context, userID int64, frameID string) error {
|
|
var price int64
|
|
var vip, enabled int
|
|
err := s.db.QueryRowContext(ctx,
|
|
`SELECT price_coins, vip, enabled FROM avatar_frames WHERE id = ?`, frameID).
|
|
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_frames WHERE user_id = ? AND frame_id = ?`, userID, frameID).Scan(&exists)
|
|
if exists == 1 {
|
|
return ErrAlreadyOwned
|
|
}
|
|
if price > 0 {
|
|
if err := adjustTx(ctx, tx, userID, CurrencyCoin, -price, "buy_frame", frameID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if _, err := tx.ExecContext(ctx,
|
|
`INSERT INTO user_frames (user_id, frame_id) VALUES (?, ?)`, userID, frameID); err != nil {
|
|
return err
|
|
}
|
|
// خرید = مالکیت + انتخابِ خودکار.
|
|
if _, err := tx.ExecContext(ctx,
|
|
`UPDATE users SET selected_frame = ? WHERE id = ?`, frameID, userID); err != nil {
|
|
return err
|
|
}
|
|
return tx.Commit()
|
|
}
|
|
|
|
// SelectFrame قابِ انتخابیِ کاربر را تنظیم میکند (باید مالکش باشد یا رایگان/VIP).
|
|
func (s *Service) SelectFrame(ctx context.Context, userID int64, frameID string) error {
|
|
frames, err := s.Frames(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, f := range frames {
|
|
if f.ID == frameID {
|
|
if !f.Owned {
|
|
return ErrNotOwned
|
|
}
|
|
_, err := s.db.ExecContext(ctx,
|
|
`UPDATE users SET selected_frame = ? WHERE id = ?`, frameID, userID)
|
|
return err
|
|
}
|
|
}
|
|
return ErrNotFound
|
|
}
|