package service import ( "fmt" "os" "path/filepath" "github.com/google/uuid" ) // PhotoStorage اینترفیس ذخیره‌سازی فایل عکس پروفایل. // برای اتصال به یک storage واقعی (S3، Arvan Object Storage و...) کافیه یک // پیاده‌سازی جدید از این اینترفیس بسازی و در main.go جایگزین نسخه‌ی local کنی؛ // بقیه‌ی کد (ProfileService و...) نیازی به تغییر ندارد. type PhotoStorage interface { // Save فایل رو ذخیره می‌کنه و URL قابل‌دسترسی (نسبی یا کامل) رو برمی‌گردونه. Save(userID uint, ext string, data []byte) (url string, err error) // Delete فایل رو با URL برگردونده‌شده از Save حذف می‌کنه. Delete(url string) error } // LocalPhotoStorage فایل‌ها رو روی دیسک محلی سرور ذخیره می‌کنه و از طریق // مسیر استاتیک /uploads سرو می‌شن. برای پروداکشن با تعداد کاربر بالا، // بهتره با یک object storage واقعی جایگزین بشه (فایل‌ها روی دیسک سرور API // می‌مونن و بین چند instance به اشتراک گذاشته نمی‌شن). type LocalPhotoStorage struct { rootDir string // مسیر روی دیسک، مثلاً "./uploads/photos" urlPrefix string // پیشوند URL عمومی، مثلاً "/uploads/photos" } func NewLocalPhotoStorage(rootDir, urlPrefix string) *LocalPhotoStorage { return &LocalPhotoStorage{rootDir: rootDir, urlPrefix: urlPrefix} } func (s *LocalPhotoStorage) Save(userID uint, ext string, data []byte) (string, error) { userDir := filepath.Join(s.rootDir, fmt.Sprintf("%d", userID)) if err := os.MkdirAll(userDir, 0o755); err != nil { return "", err } filename := uuid.NewString() + ext fullPath := filepath.Join(userDir, filename) if err := os.WriteFile(fullPath, data, 0o644); err != nil { return "", err } return fmt.Sprintf("%s/%d/%s", s.urlPrefix, userID, filename), nil } func (s *LocalPhotoStorage) Delete(url string) error { rel, err := filepath.Rel(s.urlPrefix, url) if err != nil { return err } fullPath := filepath.Join(s.rootDir, rel) err = os.Remove(fullPath) if os.IsNotExist(err) { return nil } return err }