// internal/utils/image_handler.go package utils import ( "errors" "fmt" "image" "image/jpeg" "image/png" "io" "mime/multipart" "os" "path/filepath" "strings" "time" "github.com/nfnt/resize" ) // ImageHandler handles image upload and processing type ImageHandler struct { uploadPath string maxSize int64 allowedTypes []string maxWidth uint maxHeight uint } // NewImageHandler creates a new image handler func NewImageHandler(uploadPath string) *ImageHandler { return &ImageHandler{ uploadPath: uploadPath, maxSize: 10 * 1024 * 1024, // 10MB allowedTypes: []string{"image/jpeg", "image/jpg", "image/png"}, maxWidth: 1920, maxHeight: 1080, } } // UploadImage uploads and processes an image func (h *ImageHandler) UploadImage(file *multipart.FileHeader, subfolder string) (string, error) { // Check file size if file.Size > h.maxSize { return "", errors.New("file size exceeds maximum allowed size") } // Check file type if !h.isAllowedType(file.Header.Get("Content-Type")) { return "", errors.New("file type not allowed") } // Open uploaded file src, err := file.Open() if err != nil { return "", err } defer src.Close() // Generate unique filename ext := filepath.Ext(file.Filename) filename := fmt.Sprintf("%d_%s%s", time.Now().Unix(), generateRandomString(8), ext) // Create upload directory if not exists uploadDir := filepath.Join(h.uploadPath, subfolder) if err := os.MkdirAll(uploadDir, os.ModePerm); err != nil { return "", err } // Full file path filePath := filepath.Join(uploadDir, filename) // Decode image img, format, err := image.Decode(src) if err != nil { return "", errors.New("invalid image file") } // Resize if necessary if uint(img.Bounds().Dx()) > h.maxWidth || uint(img.Bounds().Dy()) > h.maxHeight { img = resize.Thumbnail(h.maxWidth, h.maxHeight, img, resize.Lanczos3) } // Create destination file dst, err := os.Create(filePath) if err != nil { return "", err } defer dst.Close() // Encode and save image switch format { case "jpeg", "jpg": if err := jpeg.Encode(dst, img, &jpeg.Options{Quality: 90}); err != nil { return "", err } case "png": if err := png.Encode(dst, img); err != nil { return "", err } default: return "", errors.New("unsupported image format") } // Return relative path return filepath.Join(subfolder, filename), nil } // UploadImageSimple uploads image without processing func (h *ImageHandler) UploadImageSimple(file *multipart.FileHeader, subfolder string) (string, error) { // Check file size if file.Size > h.maxSize { return "", errors.New("file size exceeds maximum allowed size") } // Check file type if !h.isAllowedType(file.Header.Get("Content-Type")) { return "", errors.New("file type not allowed") } // Open uploaded file src, err := file.Open() if err != nil { return "", err } defer src.Close() // Generate unique filename ext := filepath.Ext(file.Filename) filename := fmt.Sprintf("%d_%s%s", time.Now().Unix(), generateRandomString(8), ext) // Create upload directory if not exists uploadDir := filepath.Join(h.uploadPath, subfolder) if err := os.MkdirAll(uploadDir, os.ModePerm); err != nil { return "", err } // Full file path filePath := filepath.Join(uploadDir, filename) // Create destination file dst, err := os.Create(filePath) if err != nil { return "", err } defer dst.Close() // Copy file if _, err := io.Copy(dst, src); err != nil { return "", err } // Return relative path return filepath.Join(subfolder, filename), nil } // DeleteImage deletes an image file func (h *ImageHandler) DeleteImage(relativePath string) error { if relativePath == "" { return nil } filePath := filepath.Join(h.uploadPath, relativePath) if _, err := os.Stat(filePath); os.IsNotExist(err) { return nil // File doesn't exist, no error } return os.Remove(filePath) } // isAllowedType checks if file type is allowed func (h *ImageHandler) isAllowedType(contentType string) bool { for _, allowed := range h.allowedTypes { if strings.EqualFold(contentType, allowed) { return true } } return false } // generateRandomString generates a random string func generateRandomString(length int) string { const charset = "abcdefghijklmnopqrstuvwxyz0123456789" b := make([]byte, length) for i := range b { b[i] = charset[time.Now().UnixNano()%int64(len(charset))] } return string(b) }