Add photo content-hash dedup and local↔S3 library sync.

Uploads skip per-gallery duplicates, checksums can be backfilled, and STORAGE_BACKEND plus sync tooling make switching storage backends safe.
This commit is contained in:
Michilis
2026-08-05 05:41:12 +00:00
parent dafa3711f8
commit 498d7d8a7d
31 changed files with 2216 additions and 64 deletions
+17 -2
View File
@@ -7,6 +7,7 @@ package storage
import (
"context"
"errors"
"fmt"
"io"
"time"
@@ -17,16 +18,30 @@ import (
// callers then stream the object through the API instead.
var ErrNoPresign = errors.New("presigned URLs not supported")
// ErrNotExist is what Stat reports for a missing object on either backend.
var ErrNotExist = errors.New("object does not exist")
type Storage interface {
Put(ctx context.Context, key string, r io.Reader, size int64, contentType string) error
Open(ctx context.Context, key string) (io.ReadCloser, int64, error)
// Stat returns the object's size without reading it, or ErrNotExist.
Stat(ctx context.Context, key string) (int64, error)
Delete(ctx context.Context, key string) error
PresignGet(ctx context.Context, key, downloadFilename, contentType string, expiry time.Duration) (string, error)
}
// New builds the backend that serves requests (see config.S3Enabled).
func New(cfg config.Config) (Storage, error) {
if cfg.S3Enabled() {
return newS3(cfg)
return NewS3(cfg)
}
return newLocal(cfg.StoragePath)
return NewLocal(cfg.StoragePath)
}
// Name describes a backend for log lines.
func Name(cfg config.Config, s Storage) string {
if _, ok := s.(*s3Store); ok {
return fmt.Sprintf("S3 bucket %s at %s", cfg.S3Bucket, cfg.S3Endpoint)
}
return fmt.Sprintf("local disk at %s", cfg.StoragePath)
}