feat: add carpet

This commit is contained in:
2026-07-03 21:58:28 +03:30
parent d290ba86bb
commit 413fca68cc
20 changed files with 421 additions and 16 deletions
+109 -6
View File
@@ -26,15 +26,16 @@ import (
var tplFS embed.FS
type Handler struct {
db *sql.DB
eco *economy.Service
tpl *template.Template
cardsDir string // پوشه‌ی دیسکیِ آپلودِ اسکین‌ها (همان مسیری که static از آن سرو می‌کند)
db *sql.DB
eco *economy.Service
tpl *template.Template
cardsDir string // پوشه‌ی دیسکیِ آپلودِ اسکین‌ها (همان مسیری که static از آن سرو می‌کند)
carpetsDir string // پوشه‌ی دیسکیِ آپلودِ تصاویرِ فرش
}
func New(db *sql.DB, eco *economy.Service, cardsDir string) *Handler {
func New(db *sql.DB, eco *economy.Service, cardsDir, carpetsDir string) *Handler {
tpl := template.Must(template.ParseFS(tplFS, "templates/*.html"))
return &Handler{db: db, eco: eco, tpl: tpl, cardsDir: cardsDir}
return &Handler{db: db, eco: eco, tpl: tpl, cardsDir: cardsDir, carpetsDir: carpetsDir}
}
// Routes زیرروترِ /admin را با Basic Auth برمی‌گرداند.
@@ -51,10 +52,15 @@ func (h *Handler) Routes(user, pass string) http.Handler {
r.Post("/users/coins", h.adjustCoins)
r.Post("/users/grant", h.grantToUser)
r.Post("/season/reset", h.resetSeason)
r.Get("/carpets", h.carpetsAdmin)
r.Post("/carpets/add", h.carpetAdd)
r.Post("/carpets/delete", h.carpetDelete)
r.Post("/carpets/{id}/upload", h.carpetUpload)
r.Get("/chat", h.chatAdmin)
r.Post("/chat/pack/add", h.chatPackAdd)
r.Post("/chat/pack/delete", h.chatPackDelete)
r.Post("/chat/message/add", h.chatMessageAdd)
r.Post("/chat/message", h.chatMessageUpdate)
r.Post("/chat/message/delete", h.chatMessageDelete)
return r
}
@@ -443,6 +449,86 @@ func (h *Handler) grantToUser(w http.ResponseWriter, r *http.Request) {
}
// rows یک کوئری را به []map[string]any تبدیل می‌کند (برای رندرِ عمومیِ جدول‌ها).
// --- مدیریتِ فرش‌ها ---
func (h *Handler) carpetsAdmin(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
carpets := h.rows(ctx,
`SELECT id,title,price_coins,vip,sort,enabled FROM carpets ORDER BY sort`,
"id", "title", "price_coins", "vip", "sort", "enabled")
h.render(w, "carpets.html", map[string]any{
"Nav": "carpets",
"Carpets": carpets,
"Saved": r.URL.Query().Get("saved") == "1",
})
}
func (h *Handler) carpetAdd(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
id := r.FormValue("id")
if id == "" || strings.ContainsAny(id, "/\\.") {
http.Error(w, "invalid id", http.StatusBadRequest)
return
}
num := func(k string) int { v, _ := strconv.Atoi(r.FormValue(k)); return v }
vip := 0
if r.FormValue("vip") == "on" {
vip = 1
}
_, err := h.db.ExecContext(r.Context(),
`INSERT OR REPLACE INTO carpets (id,title,price_coins,vip,sort,enabled) VALUES (?,?,?,?,?,1)`,
id, r.FormValue("title"), num("price_coins"), vip, num("sort"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/admin/carpets?saved=1", http.StatusSeeOther)
}
func (h *Handler) carpetDelete(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
_, _ = h.db.ExecContext(r.Context(), `DELETE FROM carpets WHERE id=?`, r.FormValue("id"))
http.Redirect(w, r, "/admin/carpets?saved=1", http.StatusSeeOther)
}
// carpetUpload یک تصویرِ jpg/png را برای یک فرش آپلود و با نامِ <id>.jpg ذخیره می‌کند.
func (h *Handler) carpetUpload(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
if id == "" || strings.ContainsAny(id, "/\\.") {
http.Error(w, "invalid id", http.StatusBadRequest)
return
}
if h.carpetsDir == "" {
http.Error(w, "CARPETS_DIR تنظیم نشده است", http.StatusInternalServerError)
return
}
if err := r.ParseMultipartForm(16 << 20); err != nil {
http.Error(w, "bad upload", http.StatusBadRequest)
return
}
file, _, err := r.FormFile("image")
if err != nil {
http.Error(w, "image لازم است", http.StatusBadRequest)
return
}
defer file.Close()
if err := os.MkdirAll(h.carpetsDir, 0o755); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
out, err := os.Create(filepath.Join(h.carpetsDir, id+".jpg"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer out.Close()
if _, err := io.Copy(out, io.LimitReader(file, 16<<20)); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/admin/carpets?saved=1", http.StatusSeeOther)
}
// --- مدیریتِ بسته‌های چت و پیام‌ها ---
func (h *Handler) chatAdmin(w http.ResponseWriter, r *http.Request) {
@@ -533,6 +619,23 @@ func (h *Handler) chatMessageAdd(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/admin/chat?saved=1", http.StatusSeeOther)
}
func (h *Handler) chatMessageUpdate(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
id, _ := strconv.Atoi(r.FormValue("id"))
body := r.FormValue("body")
if id == 0 || body == "" {
http.Redirect(w, r, "/admin/chat", http.StatusSeeOther)
return
}
sort, _ := strconv.Atoi(r.FormValue("sort"))
if _, err := h.db.ExecContext(r.Context(),
`UPDATE chat_messages SET body=?, sort=? WHERE id=?`, body, sort, id); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/admin/chat?saved=1", http.StatusSeeOther)
}
func (h *Handler) chatMessageDelete(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
id, _ := strconv.Atoi(r.FormValue("id"))