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 }