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:
@@ -20,6 +20,11 @@ type Config struct {
|
||||
// JWT_SECRET during the Better Auth migration.
|
||||
ViewTokenSecret string
|
||||
|
||||
// StorageBackend selects the active backend: "auto" (S3 when it is
|
||||
// configured, local otherwise), "local" or "s3". Explicit values let both
|
||||
// backends stay configured — required to run `photo-api sync`, and the
|
||||
// one-line switch after a sync.
|
||||
StorageBackend string
|
||||
StoragePath string
|
||||
S3Endpoint string
|
||||
S3Region string
|
||||
@@ -34,12 +39,26 @@ type Config struct {
|
||||
HeicConverter string // optional explicit converter command; autodetected when empty
|
||||
}
|
||||
|
||||
// S3Enabled mirrors backend/src/lib/storage.ts: S3 is active when both
|
||||
// S3_ENDPOINT and S3_BUCKET are set.
|
||||
func (c Config) S3Enabled() bool {
|
||||
// S3Configured reports whether the S3 credentials are present at all,
|
||||
// mirroring backend/src/lib/storage.ts: both S3_ENDPOINT and S3_BUCKET set.
|
||||
// Kept separate from S3Enabled so a configured-but-inactive S3 backend can
|
||||
// still be reached by `photo-api sync`.
|
||||
func (c Config) S3Configured() bool {
|
||||
return c.S3Endpoint != "" && c.S3Bucket != ""
|
||||
}
|
||||
|
||||
// S3Enabled reports whether S3 is the backend serving requests.
|
||||
func (c Config) S3Enabled() bool {
|
||||
switch c.StorageBackend {
|
||||
case "s3":
|
||||
return true
|
||||
case "local":
|
||||
return false
|
||||
default: // auto
|
||||
return c.S3Configured()
|
||||
}
|
||||
}
|
||||
|
||||
func Load() (Config, error) {
|
||||
loadDotenv(".env")
|
||||
|
||||
@@ -48,6 +67,7 @@ func Load() (Config, error) {
|
||||
DBType: strings.ToLower(env("DB_TYPE", "sqlite")),
|
||||
DatabaseURL: env("DATABASE_URL", ""),
|
||||
ViewTokenSecret: env("PHOTO_VIEW_SECRET", env("JWT_SECRET", "")),
|
||||
StorageBackend: strings.ToLower(env("STORAGE_BACKEND", "auto")),
|
||||
StoragePath: env("STORAGE_PATH", "./data/photos"),
|
||||
S3Endpoint: env("S3_ENDPOINT", ""),
|
||||
S3Region: env("S3_REGION", "auto"),
|
||||
@@ -70,6 +90,14 @@ func Load() (Config, error) {
|
||||
if cfg.ViewTokenSecret == "" {
|
||||
return cfg, fmt.Errorf("PHOTO_VIEW_SECRET is required (or legacy JWT_SECRET as fallback)")
|
||||
}
|
||||
switch cfg.StorageBackend {
|
||||
case "auto", "local", "s3":
|
||||
default:
|
||||
return cfg, fmt.Errorf("STORAGE_BACKEND must be auto, local or s3, got %q", cfg.StorageBackend)
|
||||
}
|
||||
if cfg.StorageBackend == "s3" && !cfg.S3Configured() {
|
||||
return cfg, fmt.Errorf("STORAGE_BACKEND=s3 requires S3_ENDPOINT and S3_BUCKET")
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user