chore: custom error

This commit is contained in:
2026-03-02 13:39:34 +01:00
parent abfddca518
commit 19b9532f21
21 changed files with 364 additions and 61 deletions
+72 -3
View File
@@ -3,10 +3,20 @@ package images
import (
"bytes"
"context"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"hash/fnv"
"image"
"image/jpeg"
_ "image/jpeg"
_ "image/png"
"io"
"strings"
"time"
"github.com/valkey-io/valkey-go"
)
var ErrNotAnImage = errors.New("not an image")
@@ -24,7 +34,7 @@ func (s *Service) getMime(path string) (mimeType string, err error) {
return
}
func (s *Service) GetImage(ctx context.Context, uid string) (img io.Reader, mimeType string, err error) {
func (s *Service) getImage(uid string) (file []byte, mimeType string, err error) {
path, err := PathFromUID(uid)
if err != nil {
err = fmt.Errorf("could not get path: %w", err)
@@ -40,10 +50,69 @@ func (s *Service) GetImage(ctx context.Context, uid string) (img io.Reader, mime
return
}
file, err := s.fs.Read(path)
file, err = s.fs.Read(path)
if err != nil {
err = fmt.Errorf("image file could not be fetched")
}
return bytes.NewBuffer(file), mimeType, err
return
}
func (s *Service) GetImage(ctx context.Context, uid string, options Options) (img io.Reader, mimeType string, err error) {
rawImage, mimeType, err := s.getImage(uid)
if err != nil {
return
}
hash, err := hash(rawImage, options)
if err != nil {
return
}
if imgBytes, err := s.cache.Do(ctx, s.cache.B().Get().Key("img:"+hash).Build()).AsBytes(); err == nil {
return bytes.NewBuffer(imgBytes), mimeType, err
}
decImage, _, err := image.Decode(bytes.NewBuffer(rawImage))
if err != nil {
return
}
decImage = scale(decImage, options)
outBuff := bytes.NewBuffer(make([]byte, 0, 1024))
if options.Quality == 0 {
options.Quality = 80
}
err = jpeg.Encode(outBuff, decImage, &jpeg.Options{Quality: options.Quality})
if err != nil {
return
}
if err = s.cache.Do(ctx, s.cache.B().Set().Key("img:"+hash).Value(valkey.BinaryString(outBuff.Bytes())).Ex(time.Hour*48).Build()).Error(); err == nil {
return outBuff, "image/jpeg", err
}
return outBuff, "image/jpeg", err
}
func hash(bytes []byte, options Options) (hash string, err error) {
h := fnv.New128()
if _, err = h.Write(bytes); err != nil {
return
}
if err = binary.Write(h, binary.LittleEndian, int64(options.Height)); err != nil {
return
}
if err = binary.Write(h, binary.LittleEndian, int64(options.Width)); err != nil {
return
}
if err = binary.Write(h, binary.LittleEndian, int64(options.Quality)); err != nil {
return
}
res := h.Sum(make([]byte, 0, 128/8))
return base64.RawStdEncoding.EncodeToString(res), nil
}
+17 -5
View File
@@ -1,14 +1,26 @@
package images
import "os"
import (
"os"
type Service struct {
fs fileSystem
"github.com/valkey-io/valkey-go"
)
type Options struct {
Width int // max width, 0 is unlimited
Height int // max Height, 0 is unlimited
Quality int // quality of the output jpeg
}
func New(fs fileSystem) *Service {
type Service struct {
fs fileSystem
cache valkey.Client
}
func New(fs fileSystem, cache valkey.Client) *Service {
return &Service{
fs: fs,
fs: fs,
cache: cache,
}
}
+55
View File
@@ -0,0 +1,55 @@
package images
import (
"image"
"math"
"golang.org/x/image/draw"
)
func scale(src image.Image, options Options) image.Image {
// Set the expected size that you want:
dst := image.NewRGBA(
calculateFit(src.Bounds(), options),
)
// Resize:
draw.BiLinear.Scale(dst, dst.Rect, src, src.Bounds(), draw.Over, nil)
// Encode to `output`:
return dst
}
// calculateFit returns the new dimensions (width, height) that fit within the
// provided Options without upscaling and while maintaining the aspect ratio.
func calculateFit(bounds image.Rectangle, opt Options) image.Rectangle {
// If no limits are set, or the image is already smaller than the limits,
// return original dimensions (No Upscaling).
if (opt.Width == 0 || bounds.Max.X <= opt.Width) && (opt.Height == 0 || bounds.Max.Y <= opt.Height) {
return bounds
}
// Calculate ratios for width and height
ratioW := float64(opt.Width) / float64(bounds.Max.X)
ratioH := float64(opt.Height) / float64(bounds.Max.Y)
// Determine the scale factor
var scale float64
if opt.Width > 0 && opt.Height > 0 {
// Use the smaller ratio to ensure it fits in both bounds
scale = math.Min(ratioW, ratioH)
} else if opt.Width > 0 {
scale = ratioW
} else if opt.Height > 0 {
scale = ratioH
} else {
return bounds
}
return image.Rect(
0,
0,
int(math.Round(float64(bounds.Max.X)*scale)),
int(math.Round(float64(bounds.Max.Y)*scale)),
)
}