149 lines
4.5 KiB
Go
149 lines
4.5 KiB
Go
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/admin"
|
|
"hakemsho/internal/auth"
|
|
"hakemsho/internal/config"
|
|
"hakemsho/internal/economy"
|
|
"hakemsho/internal/httpx"
|
|
"hakemsho/internal/static"
|
|
"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)
|
|
|
|
// اقتصاد و فروشگاه. تأییدِ خرید: مایکت (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)
|
|
}
|
|
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)
|
|
|
|
// تصاویرِ کارت (هر اسکین یک پوشه) — از backend سرویس میشود تا اپ سبک بماند.
|
|
r.Handle("/cards/*", static.CardsHandler(cfg.CardsDir))
|
|
// تصاویرِ فرش (بهجای میز) — از پنل ادمین آپلود میشوند.
|
|
r.Handle("/carpets/*", static.CarpetsHandler(cfg.CarpetsDir))
|
|
|
|
// پنل ادمین (HTML سرورساید، پشت Basic Auth)
|
|
r.Mount("/admin", admin.New(st.DB, eco, cfg.CardsDir, cfg.CarpetsDir).Routes(cfg.AdminUser, cfg.AdminPass))
|
|
|
|
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.Post("/profile", authH.UpdateProfile)
|
|
|
|
// کیفپول و فروشگاه
|
|
r.Get("/wallet", ecoH.Wallet)
|
|
r.Get("/stats", ecoH.Stats)
|
|
r.Get("/leaderboard", ecoH.Leaderboard)
|
|
r.Get("/tables/info", ecoH.TablesInfo)
|
|
r.Get("/shop", ecoH.Shop)
|
|
r.Post("/shop/buy-card", ecoH.BuyCard)
|
|
r.Post("/shop/select-card", ecoH.SelectCard)
|
|
r.Get("/chat-packs", ecoH.ChatPacks)
|
|
r.Post("/shop/buy-chat-pack", ecoH.BuyChatPack)
|
|
r.Get("/carpets", ecoH.Carpets)
|
|
r.Post("/shop/buy-carpet", ecoH.BuyCarpet)
|
|
r.Post("/shop/select-carpet", ecoH.SelectCarpet)
|
|
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)
|
|
}
|
|
}
|