// Package static سرویس‌دهیِ تصاویرِ کارت (هر اسکین یک پوشه) را انجام می‌دهد. // دو منبع: ۱) پوشه‌ی دیسکیِ ماندگار (diskDir) برای اسکین‌هایی که از پنل ادمین // آپلود می‌شوند، ۲) فایل‌سیستمِ embed‌شده برای اسکین‌های پیش‌فرض (simple/epic). // ابتدا دیسک بررسی می‌شود، سپس embed؛ پس آپلودها بدونِ کامپایلِ دوباره سرو می‌شوند. // مسیر: GET /cards//.png و /cards//back.jpg package static import ( "embed" "io/fs" "net/http" "os" "path/filepath" "strings" ) //go:embed cards var cardsFS embed.FS // CardsHandler یک http.Handler برای سرویس‌دهیِ فایل‌های کارت زیر /cards/ است. // diskDir پوشه‌ی اسکین‌های آپلودی است (می‌تواند خالی/ناموجود باشد). // CORS باز است تا نسخهٔ وب (Flutter web / canvaskit) هم بتواند تصاویر را بکشد. func CardsHandler(diskDir string) http.Handler { sub, err := fs.Sub(cardsFS, "cards") if err != nil { panic(err) } embedded := http.StripPrefix("/cards/", http.FileServer(http.FS(sub))) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Cache-Control", "public, max-age=604800") // یک هفته کش // تلاشِ اول: پوشه‌ی دیسکی (اسکین‌های آپلودشده از پنل ادمین). if diskDir != "" { rel := strings.TrimPrefix(r.URL.Path, "/cards/") rel = filepath.Clean("/" + rel) // جلوگیری از path traversal full := filepath.Join(diskDir, rel) if st, err := os.Stat(full); err == nil && !st.IsDir() { http.ServeFile(w, r, full) return } } // تلاشِ دوم: اسکین‌های embed‌شده (simple/epic). embedded.ServeHTTP(w, r) }) }