diff --git a/cmd/server/main.go b/cmd/server/main.go index 3911b95..4c72bde 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -40,9 +40,23 @@ func main() { jwt := auth.NewJWT(cfg.JWTSecret, cfg.JWTTTL) authH := auth.NewHandler(users, otp, kave, jwt, cfg.AdminMobile, cfg.AdminOTP) - // اقتصاد و فروشگاه. تأییدکنندهها فعلاً نسخهی توسعهاند؛ آداپتر واقعی - // تپسل (تبلیغ) و بازار/مایکت (IAP) با تنظیم creds جایگزین میشوند. - eco := economy.New(st.DB, economy.DevAdVerifier{}, economy.DevIAPVerifier{}) + // اقتصاد و فروشگاه. تأییدِ خرید: مایکت (X-Access-Token) و کافهبازار (امضای RSA). + // اگر کلیدها تنظیم نشده باشند، به تأییدکنندهی توسعه برمیگردیم (dev fallback). + iap := economy.StoreRouter{DevFallback: true} + if cfg.MyketAccessToken != "" && cfg.IAPPackage != "" { + iap.Myket = economy.NewMyketVerifier(cfg.IAPPackage, cfg.MyketAccessToken) + slog.Info("IAP myket configured", "package", cfg.IAPPackage) + } + if cfg.BazaarRSAKey != "" { + bv, err := economy.NewBazaarRSAVerifier(cfg.BazaarRSAKey) + if err != nil { + slog.Error("bazaar rsa key", "err", err) + os.Exit(1) + } + iap.Bazaar = bv + slog.Info("IAP bazaar (RSA) configured") + } + eco := economy.New(st.DB, economy.DevAdVerifier{}, iap) if err := eco.LoadCatalog(context.Background()); err != nil { slog.Error("load catalog", "err", err) os.Exit(1) diff --git a/internal/admin/admin.go b/internal/admin/admin.go index 2d5260f..5265be5 100644 --- a/internal/admin/admin.go +++ b/internal/admin/admin.go @@ -5,6 +5,7 @@ package admin import ( "archive/zip" "context" + "crypto/rand" "database/sql" "embed" "fmt" @@ -15,6 +16,7 @@ import ( "path/filepath" "strconv" "strings" + "time" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" @@ -99,16 +101,16 @@ func (h *Handler) shop(w http.ResponseWriter, r *http.Request) { ctx := r.Context() data := map[string]any{ "Nav": "shop", - "Coins": h.rows(ctx, `SELECT id,title,coins,vip_days,price_toman,bonus_pct,sort,enabled FROM coin_packages ORDER BY sort`, - "id", "title", "coins", "vip_days", "price_toman", "bonus_pct", "sort", "enabled"), - "Tickets": h.rows(ctx, `SELECT id,title,tickets,price_toman,sort,enabled FROM ticket_packages ORDER BY sort`, - "id", "title", "tickets", "price_toman", "sort", "enabled"), + "Coins": h.rows(ctx, `SELECT id,title,coins,vip_days,price_toman,bonus_pct,sku,sort,enabled FROM coin_packages ORDER BY sort`, + "id", "title", "coins", "vip_days", "price_toman", "bonus_pct", "sku", "sort", "enabled"), + "Tickets": h.rows(ctx, `SELECT id,title,tickets,price_toman,sku,sort,enabled FROM ticket_packages ORDER BY sort`, + "id", "title", "tickets", "price_toman", "sku", "sort", "enabled"), "Cards": h.rows(ctx, `SELECT id,title,price_coins,sort,enabled FROM card_skins ORDER BY sort`, "id", "title", "price_coins", "sort", "enabled"), - "Boosters": h.rows(ctx, `SELECT id,title,multiplier,hours,price_toman,sort,enabled FROM boosters ORDER BY sort`, - "id", "title", "multiplier", "hours", "price_toman", "sort", "enabled"), - "VIP": h.rows(ctx, `SELECT id,title,months,price_toman,sort,enabled FROM vip_packages ORDER BY sort`, - "id", "title", "months", "price_toman", "sort", "enabled"), + "Boosters": h.rows(ctx, `SELECT id,title,multiplier,hours,price_toman,sku,sort,enabled FROM boosters ORDER BY sort`, + "id", "title", "multiplier", "hours", "price_toman", "sku", "sort", "enabled"), + "VIP": h.rows(ctx, `SELECT id,title,months,price_toman,sku,sort,enabled FROM vip_packages ORDER BY sort`, + "id", "title", "months", "price_toman", "sku", "sort", "enabled"), "Tiers": h.rows(ctx, `SELECT id,title,hands,entry,prize,xp,trophy,rank_reward,sort,enabled FROM table_tiers ORDER BY sort`, "id", "title", "hands", "entry", "prize", "xp", "trophy", "rank_reward", "sort", "enabled"), "Saved": r.URL.Query().Get("saved") == "1", @@ -181,25 +183,26 @@ func (h *Handler) addItem(w http.ResponseWriter, r *http.Request) { if r.FormValue("enabled") == "on" { en = 1 } + sku := newUUID() // شناسهی محصولِ فروشگاه (uuid) خودکار تولید میشود var q string var args []any switch kind { case "coin": - q = `INSERT INTO coin_packages (id,title,coins,vip_days,price_toman,bonus_pct,sort,enabled) VALUES (?,?,?,?,?,?,?,?)` - args = []any{id, r.FormValue("title"), f("coins"), f("vip_days"), f("price_toman"), f("bonus_pct"), f("sort"), en} + q = `INSERT INTO coin_packages (id,title,coins,vip_days,price_toman,bonus_pct,sku,sort,enabled) VALUES (?,?,?,?,?,?,?,?,?)` + args = []any{id, r.FormValue("title"), f("coins"), f("vip_days"), f("price_toman"), f("bonus_pct"), sku, f("sort"), en} case "ticket": - q = `INSERT INTO ticket_packages (id,title,tickets,price_toman,sort,enabled) VALUES (?,?,?,?,?,?)` - args = []any{id, r.FormValue("title"), f("tickets"), f("price_toman"), f("sort"), en} + q = `INSERT INTO ticket_packages (id,title,tickets,price_toman,sku,sort,enabled) VALUES (?,?,?,?,?,?,?)` + args = []any{id, r.FormValue("title"), f("tickets"), f("price_toman"), sku, f("sort"), en} case "card": q = `INSERT INTO card_skins (id,title,price_coins,sort,enabled) VALUES (?,?,?,?,?)` args = []any{id, r.FormValue("title"), f("price_coins"), f("sort"), en} case "booster": - q = `INSERT INTO boosters (id,title,multiplier,hours,price_toman,sort,enabled) VALUES (?,?,?,?,?,?)` - args = []any{id, r.FormValue("title"), f("multiplier"), f("hours"), f("price_toman"), f("sort"), en} + q = `INSERT INTO boosters (id,title,multiplier,hours,price_toman,sku,sort,enabled) VALUES (?,?,?,?,?,?,?)` + args = []any{id, r.FormValue("title"), f("multiplier"), f("hours"), f("price_toman"), sku, f("sort"), en} case "vip": - q = `INSERT INTO vip_packages (id,title,months,price_toman,sort,enabled) VALUES (?,?,?,?,?,?)` - args = []any{id, r.FormValue("title"), f("months"), f("price_toman"), f("sort"), en} + q = `INSERT INTO vip_packages (id,title,months,price_toman,sku,sort,enabled) VALUES (?,?,?,?,?,?,?)` + args = []any{id, r.FormValue("title"), f("months"), f("price_toman"), sku, f("sort"), en} case "tier": q = `INSERT INTO table_tiers (id,title,hands,entry,prize,xp,trophy,rank_reward,sort,enabled) VALUES (?,?,?,?,?,?,?,?,?,?)` args = []any{id, r.FormValue("title"), f("hands"), f("entry"), f("prize"), f("xp"), f("trophy"), f("rank_reward"), f("sort"), en} @@ -643,6 +646,17 @@ func (h *Handler) chatMessageDelete(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/admin/chat?saved=1", http.StatusSeeOther) } +// newUUID یک UUIDv4 برای شناسهی محصولِ فروشگاه (SKU) تولید میکند. +func newUUID() string { + var b [16]byte + if _, err := rand.Read(b[:]); err != nil { + return fmt.Sprintf("sku-%d", time.Now().UnixNano()) + } + b[6] = (b[6] & 0x0f) | 0x40 // نسخه ۴ + b[8] = (b[8] & 0x3f) | 0x80 // variant + return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]) +} + func (h *Handler) rows(ctx context.Context, query string, cols ...string) []map[string]any { rs, err := h.db.QueryContext(ctx, query) if err != nil { diff --git a/internal/admin/templates/shop.html b/internal/admin/templates/shop.html index 27758d3..abd5297 100644 --- a/internal/admin/templates/shop.html +++ b/internal/admin/templates/shop.html @@ -10,7 +10,7 @@
| شناسه | عنوان | سکه | VIP (روز) | قیمت (تومان) | بونوس٪ | ترتیب | فعال | |||
|---|---|---|---|---|---|---|---|---|---|---|
| شناسه | عنوان | سکه | VIP (روز) | قیمت (تومان) | بونوس٪ | SKU (مایکت/کافه) | ترتیب | فعال | ||