Files
warmbly/internal/infrastructure/storage/store.go
T
Matthew Meszaros 368c1f7886 infra(storage): Store interface + Filesystem impl + S3-compatible factory
One Store interface (Get/Put/Delete/Has/PresignedGetURL/Name) covers
both the AWS S3 client (now exposes high-level methods alongside the
legacy embedded *s3.Client) and a new FilesystemStore for self-hosters.

FilesystemStore writes atomically via temp-file + rename, rejects '..'
in keys before path normalization, returns ErrUnsupported from
PresignedGetURL.

The S3 impl works against AWS S3, MinIO, Cloudflare R2, Backblaze B2,
and Hetzner Object Storage via standard AWS endpoint-URL config.

Factory NewFromEnv selects via BLOB_PROVIDER (defaults to s3).

14 tests cover round-trip, ErrNotFound, atomic-write cleanup, traversal
rejection, factory env-selection paths.
2026-05-27 14:41:40 +00:00

40 lines
1.6 KiB
Go

package storage
import (
"context"
"io"
"time"
)
// Store is the abstraction over blob storage. One implementation suffices for
// AWS S3, MinIO, Cloudflare R2, Backblaze B2, and Hetzner Object Storage — all
// speak the S3 protocol. A separate Filesystem implementation covers
// self-hosters who don't want any object-storage service at all.
//
// Keys are opaque; implementations may translate to filesystem paths, S3
// object keys, etc. Callers should not assume a particular separator.
type Store interface {
// Get returns the object's body. The caller must close the reader.
// Returns ErrNotFound when the key doesn't exist.
Get(ctx context.Context, key string) (io.ReadCloser, error)
// Put writes the object. Existing objects at the same key are overwritten.
// contentType may be empty.
Put(ctx context.Context, key string, body io.Reader, contentType string) error
// Delete removes the object. Deleting a missing key is not an error.
Delete(ctx context.Context, key string) error
// Has reports whether the key has an object. (Not named Exists because the
// legacy S3 Client.Exists already takes a bucket argument.)
Has(ctx context.Context, key string) (bool, error)
// PresignedGetURL returns a time-limited URL for a third party (typically
// the browser) to fetch the object directly. Implementations that can't
// produce one (filesystem, in particular) should return ErrUnsupported.
PresignedGetURL(ctx context.Context, key string, ttl time.Duration) (string, error)
// Name returns the implementation identifier for admin UI / audit logs.
Name() string
}