Files
2026-07-10 00:56:03 +03:30

79 lines
2.6 KiB
Go

package admin
import (
"context"
"database/sql"
"encoding/base64"
"os"
"path/filepath"
"regexp"
"strings"
)
// برخی ابزارها (مثلِ خروجیِ AI) یک PNG را داخلِ یک <svg> با base64 قرار می‌دهند.
// flutter_svg نمی‌تواند این «SVGِ رستری» را رندر کند، پس آن را به PNG واقعی تبدیل
// می‌کنیم.
var rasterInSVG = regexp.MustCompile(`data:image/(png|jpe?g);base64,([A-Za-z0-9+/=\s]+)`)
// extractEmbeddedRaster اگر محتوای SVG صرفاً یک تصویرِ رستریِ base64 باشد، آن را
// استخراج می‌کند (بایت‌های خام + پسوند). برای SVGهای واقعیِ برداری false برمی‌گرداند.
func extractEmbeddedRaster(svg []byte) (data []byte, ext string, ok bool) {
m := rasterInSVG.FindSubmatch(svg)
if m == nil {
return nil, "", false
}
ext = "png"
if strings.HasPrefix(string(m[1]), "jp") {
ext = "jpg"
}
b64 := regexp.MustCompile(`\s`).ReplaceAll(m[2], nil)
raw, err := base64.StdEncoding.DecodeString(string(b64))
if err != nil || len(raw) == 0 {
return nil, "", false
}
return raw, ext, true
}
// convertRasterAvatars فایل‌های آواتارِ svg را که در واقع رستر هستند به png/jpg
// تبدیل می‌کند و img_ext را در DB به‌روزرسانی می‌کند. یک‌بار در startup اجرا می‌شود
// (idempotent). برای رفعِ آواتارهایی که قبلاً به‌صورتِ «svgِ رستری» آپلود شده‌اند.
func convertRasterAvatars(ctx context.Context, db *sql.DB, dir string) {
if dir == "" {
return
}
rows, err := db.QueryContext(ctx, `SELECT id FROM avatars WHERE img_ext = 'svg'`)
if err != nil {
return
}
var ids []string
for rows.Next() {
var id string
if rows.Scan(&id) == nil {
ids = append(ids, id)
}
}
rows.Close()
for _, id := range ids {
svgPath := filepath.Join(dir, id+".svg")
content, err := os.ReadFile(svgPath)
if err != nil {
continue
}
raw, ext, ok := extractEmbeddedRaster(content)
if !ok {
continue // SVGِ واقعیِ برداری — دست نزن.
}
if err := os.WriteFile(filepath.Join(dir, id+"."+ext), raw, 0o644); err != nil {
continue
}
_ = os.Remove(svgPath)
_, _ = db.ExecContext(ctx, `UPDATE avatars SET img_ext = ? WHERE id = ?`, ext, id)
}
}
// ConvertRasterAvatars نسخه‌ی عمومی برای فراخوانی از main در startup.
func ConvertRasterAvatars(ctx context.Context, db *sql.DB, dir string) {
convertRasterAvatars(ctx, db, dir)
}