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/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{}) 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) // پنل ادمین (HTML سرور‌ساید، پشت Basic Auth) r.Mount("/admin", admin.New(st.DB, eco).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("/tables/info", ecoH.TablesInfo) 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) } }