225 lines
6.5 KiB
Go
225 lines
6.5 KiB
Go
package economy
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
|
|
"hakemsho/internal/auth"
|
|
"hakemsho/internal/httpx"
|
|
)
|
|
|
|
// Handler endpointهای فروشگاه و کیفپول (همه پشت JWT).
|
|
type Handler struct {
|
|
svc *Service
|
|
}
|
|
|
|
func NewHandler(svc *Service) *Handler { return &Handler{svc: svc} }
|
|
|
|
func uid(r *http.Request) (int64, bool) { return auth.UserID(r.Context()) }
|
|
|
|
// Wallet — GET /api/wallet
|
|
func (h *Handler) Wallet(w http.ResponseWriter, r *http.Request) {
|
|
id, ok := uid(r)
|
|
if !ok {
|
|
httpx.Error(w, http.StatusUnauthorized, "unauthorized")
|
|
return
|
|
}
|
|
p, err := h.svc.GetProfile(r.Context(), id)
|
|
if err != nil {
|
|
httpx.Error(w, http.StatusNotFound, "user not found")
|
|
return
|
|
}
|
|
httpx.JSON(w, http.StatusOK, p)
|
|
}
|
|
|
|
// Shop — GET /api/shop
|
|
func (h *Handler) Shop(w http.ResponseWriter, r *http.Request) {
|
|
id, ok := uid(r)
|
|
if !ok {
|
|
httpx.Error(w, http.StatusUnauthorized, "unauthorized")
|
|
return
|
|
}
|
|
owned, err := h.svc.OwnedCards(r.Context(), id)
|
|
if err != nil {
|
|
httpx.Error(w, http.StatusInternalServerError, "server error")
|
|
return
|
|
}
|
|
p, _ := h.svc.GetProfile(r.Context(), id)
|
|
selected := "simple"
|
|
if p != nil {
|
|
selected = p.SelectedCard
|
|
}
|
|
httpx.JSON(w, http.StatusOK, map[string]any{
|
|
"catalog": h.svc.Catalog(),
|
|
"owned_cards": owned,
|
|
"selected_card": selected,
|
|
})
|
|
}
|
|
|
|
type cardReq struct {
|
|
CardID string `json:"card_id"`
|
|
}
|
|
|
|
// BuyCard — POST /api/shop/buy-card
|
|
func (h *Handler) BuyCard(w http.ResponseWriter, r *http.Request) {
|
|
id, ok := uid(r)
|
|
if !ok {
|
|
httpx.Error(w, http.StatusUnauthorized, "unauthorized")
|
|
return
|
|
}
|
|
var req cardReq
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
httpx.Error(w, http.StatusBadRequest, "invalid body")
|
|
return
|
|
}
|
|
switch err := h.svc.BuyCard(r.Context(), id, req.CardID); {
|
|
case err == nil:
|
|
httpx.JSON(w, http.StatusOK, map[string]string{"message": "ok"})
|
|
case errors.Is(err, ErrNotFound):
|
|
httpx.Error(w, http.StatusNotFound, "card not found")
|
|
case errors.Is(err, ErrAlreadyOwned):
|
|
httpx.Error(w, http.StatusConflict, "already owned")
|
|
case errors.Is(err, ErrInsufficient):
|
|
httpx.Error(w, http.StatusPaymentRequired, "insufficient coins")
|
|
default:
|
|
httpx.Error(w, http.StatusInternalServerError, "server error")
|
|
}
|
|
}
|
|
|
|
// SelectCard — POST /api/shop/select-card
|
|
func (h *Handler) SelectCard(w http.ResponseWriter, r *http.Request) {
|
|
id, ok := uid(r)
|
|
if !ok {
|
|
httpx.Error(w, http.StatusUnauthorized, "unauthorized")
|
|
return
|
|
}
|
|
var req cardReq
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
httpx.Error(w, http.StatusBadRequest, "invalid body")
|
|
return
|
|
}
|
|
switch err := h.svc.SelectCard(r.Context(), id, req.CardID); {
|
|
case err == nil:
|
|
httpx.JSON(w, http.StatusOK, map[string]string{"message": "ok"})
|
|
case errors.Is(err, ErrNotFound):
|
|
httpx.Error(w, http.StatusNotFound, "card not found")
|
|
case errors.Is(err, ErrNotOwned):
|
|
httpx.Error(w, http.StatusForbidden, "card not owned")
|
|
default:
|
|
httpx.Error(w, http.StatusInternalServerError, "server error")
|
|
}
|
|
}
|
|
|
|
// Purchase — POST /api/shop/purchase (تأیید خرید IAP)
|
|
func (h *Handler) Purchase(w http.ResponseWriter, r *http.Request) {
|
|
id, ok := uid(r)
|
|
if !ok {
|
|
httpx.Error(w, http.StatusUnauthorized, "unauthorized")
|
|
return
|
|
}
|
|
var req PurchaseRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
httpx.Error(w, http.StatusBadRequest, "invalid body")
|
|
return
|
|
}
|
|
switch err := h.svc.VerifyPurchase(r.Context(), id, req); {
|
|
case err == nil:
|
|
p, _ := h.svc.GetProfile(r.Context(), id)
|
|
httpx.JSON(w, http.StatusOK, map[string]any{"message": "ok", "wallet": p})
|
|
case errors.Is(err, ErrAlreadyOwned):
|
|
httpx.Error(w, http.StatusConflict, "already processed")
|
|
case errors.Is(err, ErrIAPInvalid):
|
|
httpx.Error(w, http.StatusBadRequest, "purchase not valid")
|
|
case errors.Is(err, ErrNotFound):
|
|
httpx.Error(w, http.StatusNotFound, "package not found")
|
|
default:
|
|
httpx.Error(w, http.StatusInternalServerError, "server error")
|
|
}
|
|
}
|
|
|
|
// Stats — GET /api/stats (آمار پروفایل؛ نمایشِ کامل ویژهی کاربران VIP)
|
|
func (h *Handler) Stats(w http.ResponseWriter, r *http.Request) {
|
|
id, ok := uid(r)
|
|
if !ok {
|
|
httpx.Error(w, http.StatusUnauthorized, "unauthorized")
|
|
return
|
|
}
|
|
p, err := h.svc.GetProfile(r.Context(), id)
|
|
if err != nil {
|
|
httpx.Error(w, http.StatusNotFound, "user not found")
|
|
return
|
|
}
|
|
resp := map[string]any{"vip": p.VIP}
|
|
if p.VIP {
|
|
st, err := h.svc.GetStats(r.Context(), id)
|
|
if err != nil {
|
|
httpx.Error(w, http.StatusInternalServerError, "server error")
|
|
return
|
|
}
|
|
resp["stats"] = st
|
|
}
|
|
httpx.JSON(w, http.StatusOK, resp)
|
|
}
|
|
|
|
// TablesInfo — GET /api/tables/info (باقیماندهی میزهای خصوصیِ رایگان)
|
|
func (h *Handler) TablesInfo(w http.ResponseWriter, r *http.Request) {
|
|
id, ok := uid(r)
|
|
if !ok {
|
|
httpx.Error(w, http.StatusUnauthorized, "unauthorized")
|
|
return
|
|
}
|
|
remaining, unlimited := h.svc.PrivateTableInfo(r.Context(), id)
|
|
httpx.JSON(w, http.StatusOK, map[string]any{
|
|
"remaining": remaining,
|
|
"unlimited": unlimited,
|
|
})
|
|
}
|
|
|
|
// Daily — POST /api/rewards/daily
|
|
func (h *Handler) Daily(w http.ResponseWriter, r *http.Request) {
|
|
id, ok := uid(r)
|
|
if !ok {
|
|
httpx.Error(w, http.StatusUnauthorized, "unauthorized")
|
|
return
|
|
}
|
|
switch amount, err := h.svc.ClaimDaily(r.Context(), id); {
|
|
case err == nil:
|
|
httpx.JSON(w, http.StatusOK, map[string]any{"message": "ok", "amount": amount})
|
|
case errors.Is(err, ErrTooSoon):
|
|
httpx.Error(w, http.StatusTooManyRequests, "already claimed today")
|
|
default:
|
|
httpx.Error(w, http.StatusInternalServerError, "server error")
|
|
}
|
|
}
|
|
|
|
type adReq struct {
|
|
Token string `json:"token"`
|
|
}
|
|
|
|
// AdReward — POST /api/rewards/ad (سکه رایگان پس از دیدن کامل تبلیغ)
|
|
func (h *Handler) AdReward(w http.ResponseWriter, r *http.Request) {
|
|
id, ok := uid(r)
|
|
if !ok {
|
|
httpx.Error(w, http.StatusUnauthorized, "unauthorized")
|
|
return
|
|
}
|
|
var req adReq
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
httpx.Error(w, http.StatusBadRequest, "invalid body")
|
|
return
|
|
}
|
|
switch amount, err := h.svc.ClaimAdReward(r.Context(), id, req.Token); {
|
|
case err == nil:
|
|
httpx.JSON(w, http.StatusOK, map[string]any{"message": "ok", "amount": amount})
|
|
case errors.Is(err, ErrAdNotVerified):
|
|
httpx.Error(w, http.StatusBadRequest, "ad not verified")
|
|
case errors.Is(err, ErrAdDuplicate):
|
|
httpx.Error(w, http.StatusConflict, "already claimed")
|
|
case errors.Is(err, ErrAdCap):
|
|
httpx.Error(w, http.StatusTooManyRequests, "daily ad cap reached")
|
|
default:
|
|
httpx.Error(w, http.StatusInternalServerError, "server error")
|
|
}
|
|
}
|