fix: add frame

This commit is contained in:
2026-07-08 13:47:50 +03:30
parent b5420bac21
commit da6df0328f
5 changed files with 106 additions and 7 deletions
+43 -4
View File
@@ -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) {
+31 -2
View File
@@ -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
+6 -1
View File
@@ -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);