fix: add frame
This commit is contained in:
+43
-4
@@ -39,6 +39,9 @@ func (h *Handler) APIRoutes() http.Handler {
|
|||||||
r.Post("/frames", h.apiFrameUpsert)
|
r.Post("/frames", h.apiFrameUpsert)
|
||||||
r.Delete("/frames", h.apiFrameDelete)
|
r.Delete("/frames", h.apiFrameDelete)
|
||||||
|
|
||||||
|
r.Get("/rank-tiers", h.apiRankTiers)
|
||||||
|
r.Post("/rank-tiers", h.apiRankTierUpsert)
|
||||||
|
|
||||||
r.Get("/chat", h.apiChat)
|
r.Get("/chat", h.apiChat)
|
||||||
r.Post("/chat/pack", h.apiChatPackUpsert)
|
r.Post("/chat/pack", h.apiChatPackUpsert)
|
||||||
r.Delete("/chat/pack", h.apiChatPackDelete)
|
r.Delete("/chat/pack", h.apiChatPackDelete)
|
||||||
@@ -291,8 +294,8 @@ func (h *Handler) apiCarpetDelete(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (h *Handler) apiFrames(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) apiFrames(w http.ResponseWriter, r *http.Request) {
|
||||||
httpx.JSON(w, http.StatusOK, map[string]any{
|
httpx.JSON(w, http.StatusOK, map[string]any{
|
||||||
"frames": h.rows(r.Context(),
|
"frames": h.rows(r.Context(),
|
||||||
`SELECT id,title,price_coins,vip,sort,enabled FROM avatar_frames ORDER BY sort`,
|
`SELECT id,title,price_coins,vip,c1,c2,sort,enabled FROM avatar_frames ORDER BY sort`,
|
||||||
"id", "title", "price_coins", "vip", "sort", "enabled"),
|
"id", "title", "price_coins", "vip", "c1", "c2", "sort", "enabled"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,6 +305,8 @@ func (h *Handler) apiFrameUpsert(w http.ResponseWriter, r *http.Request) {
|
|||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
PriceCoins int64 `json:"price_coins"`
|
PriceCoins int64 `json:"price_coins"`
|
||||||
VIP bool `json:"vip"`
|
VIP bool `json:"vip"`
|
||||||
|
C1 string `json:"c1"`
|
||||||
|
C2 string `json:"c2"`
|
||||||
Sort int `json:"sort"`
|
Sort int `json:"sort"`
|
||||||
Enabled *bool `json:"enabled"`
|
Enabled *bool `json:"enabled"`
|
||||||
}
|
}
|
||||||
@@ -317,9 +322,11 @@ func (h *Handler) apiFrameUpsert(w http.ResponseWriter, r *http.Request) {
|
|||||||
if b.Enabled != nil && !*b.Enabled {
|
if b.Enabled != nil && !*b.Enabled {
|
||||||
enabled = 0
|
enabled = 0
|
||||||
}
|
}
|
||||||
|
// رنگها را نرمال کن (حذفِ # و فاصله).
|
||||||
|
clean := func(s string) string { return strings.TrimPrefix(strings.TrimSpace(s), "#") }
|
||||||
if _, err := h.db.ExecContext(r.Context(),
|
if _, err := h.db.ExecContext(r.Context(),
|
||||||
`INSERT OR REPLACE INTO avatar_frames (id,title,price_coins,vip,sort,enabled) VALUES (?,?,?,?,?,?)`,
|
`INSERT OR REPLACE INTO avatar_frames (id,title,price_coins,vip,c1,c2,sort,enabled) VALUES (?,?,?,?,?,?,?,?)`,
|
||||||
b.ID, b.Title, b.PriceCoins, vip, b.Sort, enabled); err != nil {
|
b.ID, b.Title, b.PriceCoins, vip, clean(b.C1), clean(b.C2), b.Sort, enabled); err != nil {
|
||||||
httpx.Error(w, http.StatusInternalServerError, err.Error())
|
httpx.Error(w, http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -331,6 +338,38 @@ func (h *Handler) apiFrameDelete(w http.ResponseWriter, r *http.Request) {
|
|||||||
httpx.JSON(w, http.StatusOK, map[string]any{"ok": true})
|
httpx.JSON(w, http.StatusOK, map[string]any{"ok": true})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- رتبهها (رنگ/برچسب؛ مجموعه ثابت است، فقط ویرایش) ---
|
||||||
|
|
||||||
|
func (h *Handler) apiRankTiers(w http.ResponseWriter, r *http.Request) {
|
||||||
|
httpx.JSON(w, http.StatusOK, map[string]any{
|
||||||
|
"rank_tiers": h.rows(r.Context(),
|
||||||
|
`SELECT id,label,c1,c2,sort FROM rank_tiers ORDER BY sort`,
|
||||||
|
"id", "label", "c1", "c2", "sort"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) apiRankTierUpsert(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var b struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
C1 string `json:"c1"`
|
||||||
|
C2 string `json:"c2"`
|
||||||
|
Sort int `json:"sort"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&b); err != nil || strings.TrimSpace(b.ID) == "" {
|
||||||
|
httpx.Error(w, http.StatusBadRequest, "invalid body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clean := func(s string) string { return strings.TrimPrefix(strings.TrimSpace(s), "#") }
|
||||||
|
if _, err := h.db.ExecContext(r.Context(),
|
||||||
|
`INSERT OR REPLACE INTO rank_tiers (id,label,c1,c2,sort) VALUES (?,?,?,?,?)`,
|
||||||
|
b.ID, b.Label, clean(b.C1), clean(b.C2), b.Sort); err != nil {
|
||||||
|
httpx.Error(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpx.JSON(w, http.StatusOK, map[string]any{"ok": true})
|
||||||
|
}
|
||||||
|
|
||||||
// --- چت ---
|
// --- چت ---
|
||||||
|
|
||||||
func (h *Handler) apiChat(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) apiChat(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -8,13 +8,42 @@ type Frame struct {
|
|||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
PriceCoins int64 `json:"price_coins"`
|
PriceCoins int64 `json:"price_coins"`
|
||||||
VIP bool `json:"vip"`
|
VIP bool `json:"vip"`
|
||||||
|
C1 string `json:"c1"` // رنگِ گرادیان (hex بدونِ #)؛ خالی ⇒ قابِ رتبه
|
||||||
|
C2 string `json:"c2"`
|
||||||
Owned bool `json:"owned"`
|
Owned bool `json:"owned"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RankTierStyle رنگ و برچسبِ یک رتبه (برنز..پادشاه). سمتِ کلاینت hardcode نیست.
|
||||||
|
type RankTierStyle struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
C1 string `json:"c1"`
|
||||||
|
C2 string `json:"c2"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RankTiers رنگ/برچسبِ همهی رتبهها را برمیگرداند (برای کشِ کلاینت).
|
||||||
|
func (s *Service) RankTiers(ctx context.Context) ([]RankTierStyle, error) {
|
||||||
|
rows, err := s.db.QueryContext(ctx,
|
||||||
|
`SELECT id, label, c1, c2 FROM rank_tiers ORDER BY sort`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var tiers []RankTierStyle
|
||||||
|
for rows.Next() {
|
||||||
|
var t RankTierStyle
|
||||||
|
if err := rows.Scan(&t.ID, &t.Label, &t.C1, &t.C2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tiers = append(tiers, t)
|
||||||
|
}
|
||||||
|
return tiers, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
// Frames قابهای فعال را با وضعیتِ مالکیتِ کاربر برمیگرداند.
|
// Frames قابهای فعال را با وضعیتِ مالکیتِ کاربر برمیگرداند.
|
||||||
func (s *Service) Frames(ctx context.Context, userID int64) ([]Frame, error) {
|
func (s *Service) Frames(ctx context.Context, userID int64) ([]Frame, error) {
|
||||||
rows, err := s.db.QueryContext(ctx,
|
rows, err := s.db.QueryContext(ctx,
|
||||||
`SELECT id, title, price_coins, vip FROM avatar_frames WHERE enabled = 1 ORDER BY sort`)
|
`SELECT id, title, price_coins, vip, c1, c2 FROM avatar_frames WHERE enabled = 1 ORDER BY sort`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -24,7 +53,7 @@ func (s *Service) Frames(ctx context.Context, userID int64) ([]Frame, error) {
|
|||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var f Frame
|
var f Frame
|
||||||
var vip int
|
var vip int
|
||||||
if err := rows.Scan(&f.ID, &f.Title, &f.PriceCoins, &vip); err != nil {
|
if err := rows.Scan(&f.ID, &f.Title, &f.PriceCoins, &vip, &f.C1, &f.C2); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
f.VIP = vip == 1
|
f.VIP = vip == 1
|
||||||
|
|||||||
@@ -171,7 +171,12 @@ func (h *Handler) Frames(w http.ResponseWriter, r *http.Request) {
|
|||||||
if p, _ := h.svc.GetProfile(r.Context(), id); p != nil {
|
if p, _ := h.svc.GetProfile(r.Context(), id); p != nil {
|
||||||
selected = p.SelectedFrame
|
selected = p.SelectedFrame
|
||||||
}
|
}
|
||||||
httpx.JSON(w, http.StatusOK, map[string]any{"frames": frames, "selected": selected})
|
tiers, _ := h.svc.RankTiers(r.Context())
|
||||||
|
httpx.JSON(w, http.StatusOK, map[string]any{
|
||||||
|
"frames": frames,
|
||||||
|
"selected": selected,
|
||||||
|
"rank_tiers": tiers,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuyFrame — POST /api/shop/buy-frame
|
// BuyFrame — POST /api/shop/buy-frame
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
-- رنگِ گرادیانِ هر قاب سمتِ سرور تعریف میشود (hex بدونِ #)، تا ادمین بتواند قابِ
|
||||||
|
-- جدید با رنگِ دلخواه بسازد بدونِ بهروزرسانیِ اپ. خالی ⇒ قابِ رتبه.
|
||||||
|
ALTER TABLE avatar_frames ADD COLUMN c1 TEXT NOT NULL DEFAULT '';
|
||||||
|
ALTER TABLE avatar_frames ADD COLUMN c2 TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
UPDATE avatar_frames SET c1='F3D27A', c2='B8860B' WHERE id='gold';
|
||||||
|
UPDATE avatar_frames SET c1='FF9A3C', c2='B81D2A' WHERE id='fire';
|
||||||
|
UPDATE avatar_frames SET c1='7BEAA5', c2='1B7A4A' WHERE id='emerald';
|
||||||
|
UPDATE avatar_frames SET c1='8FD3F0', c2='1E5FA8' WHERE id='ocean';
|
||||||
|
UPDATE avatar_frames SET c1='F7A8C4', c2='A83060' WHERE id='rose';
|
||||||
|
UPDATE avatar_frames SET c1='CE9BEA', c2='6A2FA0' WHERE id='royal';
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
-- رنگ و برچسبِ رتبهها از سرور میآید (تا هیچ رنگی سمتِ کلاینت hardcode نباشد).
|
||||||
|
CREATE TABLE IF NOT EXISTS rank_tiers (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
label TEXT NOT NULL,
|
||||||
|
c1 TEXT NOT NULL,
|
||||||
|
c2 TEXT NOT NULL,
|
||||||
|
sort INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO rank_tiers (id, label, c1, c2, sort) VALUES
|
||||||
|
('bronze', 'برنز', 'CD7F32', '8C5A2B', 1),
|
||||||
|
('silver', 'نقره', 'BFC6CC', '8A949B', 2),
|
||||||
|
('gold', 'طلا', 'FFD54F', 'B8860B', 3),
|
||||||
|
('diamond', 'الماس', '5BE0E6', '1E8A99', 4),
|
||||||
|
('king', 'پادشاه', 'B388FF', '6A1B9A', 5);
|
||||||
Reference in New Issue
Block a user