Add S3-compatible storage backend (B2, MinIO, AWS S3)

Introduces a StorageBackend interface with local filesystem and S3
implementations. The StorageService delegates raw I/O to the backend
while keeping validation, encryption, and URL generation unchanged.

Backend selection is config-driven: set B2_ENDPOINT + B2_KEY_ID +
B2_APP_KEY + B2_BUCKET_NAME for S3 mode, or STORAGE_UPLOAD_DIR for
local mode. STORAGE_USE_SSL=false for in-cluster MinIO (HTTP).

All existing tests pass unchanged — the local backend preserves
identical behavior to the previous direct-filesystem implementation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-03-30 21:31:24 -05:00
parent 34553f3bec
commit 2e10822e5a
8 changed files with 359 additions and 132 deletions

View File

@@ -122,19 +122,13 @@ func main() {
Msg("Email service not configured - emails will not be sent")
}
// Initialize storage service for file uploads
// Initialize storage service for file uploads (local filesystem or S3-compatible)
var storageService *services.StorageService
if cfg.Storage.UploadDir != "" {
if cfg.Storage.UploadDir != "" || cfg.Storage.IsS3() {
storageService, err = services.NewStorageService(&cfg.Storage)
if err != nil {
log.Warn().Err(err).Msg("Failed to initialize storage service - uploads disabled")
} else {
log.Info().
Str("upload_dir", cfg.Storage.UploadDir).
Str("base_url", cfg.Storage.BaseURL).
Int64("max_file_size", cfg.Storage.MaxFileSize).
Msg("Storage service initialized")
// Initialize file encryption at rest if configured
if cfg.Storage.EncryptionKey != "" {
encSvc, encErr := services.NewEncryptionService(cfg.Storage.EncryptionKey)