This commit is contained in:
2026-06-15 16:53:25 +03:30
commit 390ef96e7e
34 changed files with 4463 additions and 0 deletions
+111
View File
@@ -0,0 +1,111 @@
package main
import (
"context"
"log/slog"
"net/http"
"os"
"strconv"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"hakemsho/internal/auth"
"hakemsho/internal/config"
"hakemsho/internal/economy"
"hakemsho/internal/httpx"
"hakemsho/internal/store"
"hakemsho/internal/user"
"hakemsho/internal/ws"
)
func main() {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, nil)))
cfg := config.Load()
st, err := store.Open(cfg.DBPath)
if err != nil {
slog.Error("open db", "err", err)
os.Exit(1)
}
defer st.Close()
// لایه‌های دامنه
users := user.NewRepo(st.DB)
otp := auth.NewOTPStore(st.DB, cfg.OTPTTL)
kave := auth.NewKavenegar(cfg.KaveAPIKey, cfg.KaveTemple)
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{})
ecoH := economy.NewHandler(eco)
// هاب WebSocket برای بازی realtime
hub := ws.NewHub(func(token string) (int64, string, error) {
id, err := jwt.Verify(token)
if err != nil {
return 0, "", err
}
name := "بازیکن " + strconv.FormatInt(id, 10)
if u, err := users.FindByID(context.Background(), id); err == nil {
if u.FirstName != nil && *u.FirstName != "" {
name = *u.FirstName
} else {
name = u.Mobile
}
}
return id, name, nil
})
hub.SetSettler(gameSettler{eco: eco})
go hub.Run()
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(15 * time.Second))
r.Get("/health", func(w http.ResponseWriter, _ *http.Request) {
httpx.JSON(w, http.StatusOK, map[string]string{"status": "ok"})
})
// WebSocket بازی (احراز هویت با توکن در ?token= یا هدر Authorization)
r.Get("/ws", hub.ServeWS)
r.Route("/api", func(r chi.Router) {
r.Route("/auth", func(r chi.Router) {
r.Post("/login-otp", authH.LoginOTP)
r.Post("/check-otp", authH.CheckOTP)
})
// مسیرهای محافظت‌شده با JWT
r.Group(func(r chi.Router) {
r.Use(jwt.Middleware)
r.Get("/me", authH.Me)
// کیف‌پول و فروشگاه
r.Get("/wallet", ecoH.Wallet)
r.Get("/shop", ecoH.Shop)
r.Post("/shop/buy-card", ecoH.BuyCard)
r.Post("/shop/select-card", ecoH.SelectCard)
r.Post("/shop/purchase", ecoH.Purchase)
r.Post("/rewards/daily", ecoH.Daily)
r.Post("/rewards/ad", ecoH.AdReward)
})
})
srv := &http.Server{
Addr: cfg.Addr,
Handler: r,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
slog.Info("server starting", "addr", cfg.Addr)
if err := srv.ListenAndServe(); err != nil {
slog.Error("server", "err", err)
os.Exit(1)
}
}
+48
View File
@@ -0,0 +1,48 @@
package main
import (
"context"
"log/slog"
"hakemsho/internal/economy"
)
// gameSettler اینترفیس ws.Settler را با سرویس اقتصاد پیاده می‌کند.
// tier را به ورودی/جایزه/XP/جام نگاشت کرده و تراکنش سکه را انجام می‌دهد.
type gameSettler struct {
eco *economy.Service
}
func (g gameSettler) ChargeEntry(userID int64, tier string) error {
t := economy.FindTier(tier)
if t == nil {
return nil // tier ناشناخته ⇒ بدون ورودی
}
return g.eco.ChargeEntry(context.Background(), userID, t.Entry, "tier:"+tier)
}
func (g gameSettler) Refund(userID int64, tier string) {
t := economy.FindTier(tier)
if t == nil {
return
}
if err := g.eco.Refund(context.Background(), userID, t.Entry, "tier:"+tier); err != nil {
slog.Error("refund", "user", userID, "err", err)
}
}
func (g gameSettler) AwardWinner(userID int64, tier string) {
t := economy.FindTier(tier)
if t == nil {
return
}
if err := g.eco.AwardWinner(context.Background(), userID, t.Prize, t.XP, t.Trophy, "tier:"+tier); err != nil {
slog.Error("award winner", "user", userID, "err", err)
}
}
func (g gameSettler) RecordGame(room, playersJSON string, winnerTeam int, kot bool) {
if err := g.eco.RecordGame(context.Background(), room, playersJSON, winnerTeam, kot); err != nil {
slog.Error("record game", "room", room, "err", err)
}
}