feat: add characters

This commit is contained in:
2026-07-10 00:56:03 +03:30
parent da6df0328f
commit fe0b63083c
19 changed files with 635 additions and 92 deletions
+58 -2
View File
@@ -33,11 +33,12 @@ type Handler struct {
tpl *template.Template
cardsDir string // پوشه‌ی دیسکیِ آپلودِ اسکین‌ها (همان مسیری که static از آن سرو می‌کند)
carpetsDir string // پوشه‌ی دیسکیِ آپلودِ تصاویرِ فرش
avatarsDir string // پوشه‌ی دیسکیِ آپلودِ تصاویرِ شخصیتِ آواتار
}
func New(db *sql.DB, eco *economy.Service, cardsDir, carpetsDir string) *Handler {
func New(db *sql.DB, eco *economy.Service, cardsDir, carpetsDir, avatarsDir string) *Handler {
tpl := template.Must(template.ParseFS(tplFS, "templates/*.html"))
return &Handler{db: db, eco: eco, tpl: tpl, cardsDir: cardsDir, carpetsDir: carpetsDir}
return &Handler{db: db, eco: eco, tpl: tpl, cardsDir: cardsDir, carpetsDir: carpetsDir, avatarsDir: avatarsDir}
}
// Routes زیرروترِ /admin را با Basic Auth برمی‌گرداند.
@@ -537,6 +538,61 @@ func (h *Handler) carpetUpload(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/admin/carpets?saved=1", http.StatusSeeOther)
}
// avatarUpload یک تصویرِ png یا svg را برای یک شخصیتِ آواتار آپلود و با نامِ
// <id>.<ext> ذخیره می‌کند و img_ext را در دیتابیس به‌روزرسانی می‌کند. نوعِ فایل از
// پسوندِ نامِ آپلودشده تشخیص داده می‌شود (svg → svg، وگرنه png). png برای شفافیت.
func (h *Handler) avatarUpload(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.avatarsDir == "" {
http.Error(w, "AVATARS_DIR تنظیم نشده است", http.StatusInternalServerError)
return
}
if err := r.ParseMultipartForm(16 << 20); err != nil {
http.Error(w, "bad upload", http.StatusBadRequest)
return
}
file, hdr, err := r.FormFile("image")
if err != nil {
http.Error(w, "image لازم است", http.StatusBadRequest)
return
}
defer file.Close()
buf, err := io.ReadAll(io.LimitReader(file, 16<<20))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
ext := "png"
if strings.HasSuffix(strings.ToLower(hdr.Filename), ".svg") {
ext = "svg"
// «SVGِ رستری» (PNG داخلِ svg) را به PNG واقعی تبدیل کن تا اپ رندرش کند.
if raw, rext, ok := extractEmbeddedRaster(buf); ok {
buf, ext = raw, rext
}
}
if err := os.MkdirAll(h.avatarsDir, 0o755); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// نسخه‌های قبلی با پسوندِ دیگر را پاک کن تا چند نسخه نماند.
for _, e := range []string{"png", "jpg", "svg"} {
if e != ext {
_ = os.Remove(filepath.Join(h.avatarsDir, id+"."+e))
}
}
if err := os.WriteFile(filepath.Join(h.avatarsDir, id+"."+ext), buf, 0o644); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, _ = h.db.ExecContext(r.Context(), `UPDATE avatars SET img_ext = ? WHERE id = ?`, ext, id)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"ok":true}`))
}
// --- مدیریتِ بسته‌های چت و پیام‌ها ---
func (h *Handler) chatAdmin(w http.ResponseWriter, r *http.Request) {