191 lines
6.4 KiB
Go
191 lines
6.4 KiB
Go
package services
|
||
|
||
import (
|
||
configs "ares/config"
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gofiber/fiber/v3"
|
||
"github.com/h2non/bimg"
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
// ImageOptions defines the parameters for image processing
|
||
type ImageOptions struct {
|
||
Width int
|
||
Height int
|
||
Quality int
|
||
Format string // "avif", "webp", "png", "jpg"
|
||
Folder string // e.g. "settings", "heroes"
|
||
}
|
||
|
||
// ProcessAndSaveImage handles the file upload, processing, and saving
|
||
func ProcessAndSaveImage(c fiber.Ctx, fieldName string, opts ImageOptions) (string, error) {
|
||
// 1. Get file from form
|
||
file, err := c.FormFile(fieldName)
|
||
if err != nil {
|
||
// If no file uploaded, return empty string (not an error)
|
||
configs.Logger.Debug("no file uploaded", zap.Error(err), zap.String("field", fieldName))
|
||
return "", nil
|
||
}
|
||
|
||
// 2. Open file
|
||
f, err := file.Open()
|
||
if err != nil {
|
||
configs.Logger.Error("failed to open uploaded file", zap.Error(err), zap.String("filename", file.Filename), zap.String("field", fieldName))
|
||
return "", err
|
||
}
|
||
defer f.Close()
|
||
|
||
// Read bytes
|
||
buffer := make([]byte, file.Size)
|
||
n, err := f.Read(buffer)
|
||
if err != nil {
|
||
configs.Logger.Error("failed to read uploaded file bytes", zap.Error(err), zap.String("filename", file.Filename), zap.Int64("size_expected", file.Size))
|
||
return "", err
|
||
}
|
||
configs.Logger.Debug("read uploaded file bytes", zap.String("filename", file.Filename), zap.Int("read_bytes", n), zap.Int64("size_expected", file.Size))
|
||
|
||
// 3. Process with bimg
|
||
options := bimg.Options{
|
||
Width: opts.Width,
|
||
Height: opts.Height,
|
||
Quality: opts.Quality,
|
||
}
|
||
|
||
// If both Width and Height are set, use Smart Crop (Cover)
|
||
if opts.Width > 0 && opts.Height > 0 {
|
||
options.Crop = true
|
||
options.Gravity = bimg.GravitySmart
|
||
}
|
||
|
||
// Allow enlarging smaller images to requested dimensions and strip metadata
|
||
options.Enlarge = true
|
||
options.StripMetadata = true
|
||
|
||
newImage, err := bimg.NewImage(buffer).Process(options)
|
||
if err != nil {
|
||
configs.Logger.Error("image processing failed", zap.Error(err), zap.Any("options", options))
|
||
return "", fmt.Errorf("resim işleme hatası: %v", err)
|
||
}
|
||
|
||
// 4. Convert Format (if requested)
|
||
// Default to AVIF if not specified
|
||
targetFormat := bimg.AVIF
|
||
ext := ".avif"
|
||
|
||
if opts.Format != "" {
|
||
switch strings.ToLower(opts.Format) {
|
||
case "webp":
|
||
targetFormat = bimg.WEBP
|
||
ext = ".webp"
|
||
case "png":
|
||
targetFormat = bimg.PNG
|
||
ext = ".png"
|
||
case "jpg", "jpeg":
|
||
targetFormat = bimg.JPEG
|
||
ext = ".jpg"
|
||
case "avif":
|
||
targetFormat = bimg.AVIF
|
||
ext = ".avif"
|
||
}
|
||
}
|
||
|
||
if newImage, err = bimg.NewImage(newImage).Convert(targetFormat); err != nil {
|
||
configs.Logger.Error("format conversion failed", zap.Error(err), zap.String("target_format", strings.ToLower(opts.Format)), zap.String("ext", ext))
|
||
return "", fmt.Errorf("format dönüştürme hatası: %v", err)
|
||
}
|
||
|
||
// 5. Generate Filename and Path
|
||
filename := fmt.Sprintf("%d%s", time.Now().UnixNano(), ext)
|
||
|
||
// Ensure uploads directory exists
|
||
// We save to ./uploads/{folder} (root uploads, served by main.go handler)
|
||
uploadPath := filepath.Join("uploads", opts.Folder)
|
||
if err := os.MkdirAll(uploadPath, 0755); err != nil {
|
||
configs.Logger.Error("failed to create upload directory", zap.Error(err), zap.String("upload_path", uploadPath))
|
||
return "", fmt.Errorf("dizin oluşturma hatası: %v", err)
|
||
}
|
||
|
||
fullPath := filepath.Join(uploadPath, filename)
|
||
|
||
// 6. Save File using bimg
|
||
if err := bimg.Write(fullPath, newImage); err != nil {
|
||
configs.Logger.Error("failed to write image to disk", zap.Error(err), zap.String("full_path", fullPath))
|
||
return "", fmt.Errorf("dosya kaydetme hatası: %v", err)
|
||
}
|
||
|
||
configs.Logger.Info("image saved", zap.String("path", fullPath), zap.String("url", fmt.Sprintf("/uploads/%s/%s", opts.Folder, filename)), zap.Int("width", opts.Width), zap.Int("height", opts.Height), zap.String("format", opts.Format), zap.Int("quality", opts.Quality))
|
||
|
||
// Return relative path for DB (e.g. /uploads/heroes/123.avif)
|
||
return fmt.Sprintf("/uploads/%s/%s", opts.Folder, filename), nil
|
||
}
|
||
|
||
// ProcessAndSaveImageFromBytes processes raw image bytes and saves the file similar to ProcessAndSaveImage
|
||
func ProcessAndSaveImageFromBytes(buffer []byte, opts ImageOptions) (string, error) {
|
||
configs.Logger.Debug("ProcessAndSaveImageFromBytes called", zap.Int("input_bytes", len(buffer)), zap.Any("options", opts))
|
||
|
||
// 1. Process with bimg
|
||
options := bimg.Options{
|
||
Width: opts.Width,
|
||
Height: opts.Height,
|
||
Quality: opts.Quality,
|
||
}
|
||
if opts.Width > 0 && opts.Height > 0 {
|
||
options.Crop = true
|
||
options.Gravity = bimg.GravitySmart
|
||
}
|
||
options.Enlarge = true
|
||
options.StripMetadata = true
|
||
|
||
newImage, err := bimg.NewImage(buffer).Process(options)
|
||
if err != nil {
|
||
configs.Logger.Error("image processing from bytes failed", zap.Error(err), zap.Any("options", options))
|
||
return "", fmt.Errorf("resim işleme hatası: %v", err)
|
||
}
|
||
|
||
// Convert format
|
||
targetFormat := bimg.AVIF
|
||
ext := ".avif"
|
||
if opts.Format != "" {
|
||
switch strings.ToLower(opts.Format) {
|
||
case "webp":
|
||
targetFormat = bimg.WEBP
|
||
ext = ".webp"
|
||
case "png":
|
||
targetFormat = bimg.PNG
|
||
ext = ".png"
|
||
case "jpg", "jpeg":
|
||
targetFormat = bimg.JPEG
|
||
ext = ".jpg"
|
||
case "avif":
|
||
targetFormat = bimg.AVIF
|
||
ext = ".avif"
|
||
}
|
||
}
|
||
|
||
if newImage, err = bimg.NewImage(newImage).Convert(targetFormat); err != nil {
|
||
configs.Logger.Error("format conversion from bytes failed", zap.Error(err), zap.String("target_format", strings.ToLower(opts.Format)), zap.String("ext", ext))
|
||
return "", fmt.Errorf("format dönüştürme hatası: %v", err)
|
||
}
|
||
|
||
filename := fmt.Sprintf("%d%s", time.Now().UnixNano(), ext)
|
||
uploadPath := filepath.Join("uploads", opts.Folder)
|
||
if err := os.MkdirAll(uploadPath, 0755); err != nil {
|
||
configs.Logger.Error("failed to create upload directory (from bytes)", zap.Error(err), zap.String("upload_path", uploadPath))
|
||
return "", fmt.Errorf("dizin oluşturma hatası: %v", err)
|
||
}
|
||
fullPath := filepath.Join(uploadPath, filename)
|
||
if err := bimg.Write(fullPath, newImage); err != nil {
|
||
configs.Logger.Error("failed to write image to disk (from bytes)", zap.Error(err), zap.String("full_path", fullPath))
|
||
return "", fmt.Errorf("dosya kaydetme hatası: %v", err)
|
||
}
|
||
|
||
configs.Logger.Info("image saved from bytes", zap.String("path", fullPath), zap.String("url", fmt.Sprintf("/uploads/%s/%s", opts.Folder, filename)), zap.Int("width", opts.Width), zap.Int("height", opts.Height), zap.String("format", opts.Format), zap.Int("quality", opts.Quality))
|
||
|
||
return fmt.Sprintf("/uploads/%s/%s", opts.Folder, filename), nil
|
||
}
|