2025-11-23 22:49:46 +07:00

188 lines
4.3 KiB
Go

// 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)
}