From 85525c76b5497242b3f9b903021587a40773eb45 Mon Sep 17 00:00:00 2001 From: Bram Verhulst Date: Sat, 22 Aug 2026 18:32:20 +0200 Subject: [PATCH] fix stuff --- .env.example | 1 + backend/cmd/server/main.go | 14 ++++--- backend/internal/config/config.go | 50 +++++++++++++------------ backend/internal/storage/storage.go | 57 +++++++++++++++++++---------- docker-compose.yml | 1 + 5 files changed, 74 insertions(+), 49 deletions(-) diff --git a/.env.example b/.env.example index be368c2..edd62b2 100644 --- a/.env.example +++ b/.env.example @@ -12,6 +12,7 @@ SESSION_SECRET=local-development-session-secret-change-me COOKIE_SECURE=false STORAGE_ENDPOINT=localhost:9000 +STORAGE_PUBLIC_ENDPOINT= STORAGE_ACCESS_KEY=minioadmin STORAGE_SECRET_KEY=minioadmin STORAGE_BUCKET=gallery-media diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index b6e7036..f5bb1a3 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -45,12 +45,14 @@ func main() { defer database.Close() objectStorage, err := storage.NewMinIO(storage.Config{ - Endpoint: cfg.StorageEndpoint, - AccessKey: cfg.StorageAccessKey, - SecretKey: cfg.StorageSecretKey, - Bucket: cfg.StorageBucket, - UseSSL: cfg.StorageUseSSL, - CORSOrigins: cfg.CORSOrigin, + Endpoint: cfg.StorageEndpoint, + PublicEndpoint: cfg.StoragePublicEndpoint, + AccessKey: cfg.StorageAccessKey, + SecretKey: cfg.StorageSecretKey, + Bucket: cfg.StorageBucket, + UseSSL: cfg.StorageUseSSL, + PublicUseSSL: true, + CORSOrigins: cfg.CORSOrigin, }) if err != nil { log.Fatalf("storage unavailable: %v", err) diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index 55f879b..beeec0a 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -10,18 +10,19 @@ import ( // Config contains the small set of process-level settings needed by the API. type Config struct { - DBDriver string - Port string - DatabaseURL string - SQLitePath string - CORSOrigin string - SessionSecret string - CookieSecure bool - StorageEndpoint string - StorageAccessKey string - StorageSecretKey string - StorageBucket string - StorageUseSSL bool + DBDriver string + Port string + DatabaseURL string + SQLitePath string + CORSOrigin string + SessionSecret string + CookieSecure bool + StorageEndpoint string + StoragePublicEndpoint string + StorageAccessKey string + StorageSecretKey string + StorageBucket string + StorageUseSSL bool } func Load() Config { @@ -32,18 +33,19 @@ func Load() Config { } return Config{ - DBDriver: envOrDefault("DB_DRIVER", "postgres"), - Port: envOrDefault("PORT", "8080"), - DatabaseURL: envOrDefault("DATABASE_URL", "postgres://surprise:surprise_dev_password@localhost:5432/surprise?sslmode=disable"), - SQLitePath: envOrDefault("SQLITE_PATH", "./data/surprise.db"), - CORSOrigin: envOrDefault("CORS_ORIGIN", "http://localhost:5173,http://127.0.0.1:5173"), - SessionSecret: envOrDefault("SESSION_SECRET", "local-development-session-secret-change-me"), - CookieSecure: parseBoolEnv("COOKIE_SECURE", false), - StorageEndpoint: envOrDefault("STORAGE_ENDPOINT", "localhost:9000"), - StorageAccessKey: envOrDefault("STORAGE_ACCESS_KEY", "minioadmin"), - StorageSecretKey: envOrDefault("STORAGE_SECRET_KEY", "minioadmin"), - StorageBucket: envOrDefault("STORAGE_BUCKET", "gallery-media"), - StorageUseSSL: parseBoolEnv("STORAGE_USE_SSL", false), + DBDriver: envOrDefault("DB_DRIVER", "postgres"), + Port: envOrDefault("PORT", "8080"), + DatabaseURL: envOrDefault("DATABASE_URL", "postgres://surprise:surprise_dev_password@localhost:5432/surprise?sslmode=disable"), + SQLitePath: envOrDefault("SQLITE_PATH", "./data/surprise.db"), + CORSOrigin: envOrDefault("CORS_ORIGIN", "http://localhost:5173,http://127.0.0.1:5173"), + SessionSecret: envOrDefault("SESSION_SECRET", "local-development-session-secret-change-me"), + CookieSecure: parseBoolEnv("COOKIE_SECURE", false), + StorageEndpoint: envOrDefault("STORAGE_ENDPOINT", "localhost:9000"), + StoragePublicEndpoint: envOrDefault("STORAGE_PUBLIC_ENDPOINT", ""), + StorageAccessKey: envOrDefault("STORAGE_ACCESS_KEY", "minioadmin"), + StorageSecretKey: envOrDefault("STORAGE_SECRET_KEY", "minioadmin"), + StorageBucket: envOrDefault("STORAGE_BUCKET", "gallery-media"), + StorageUseSSL: parseBoolEnv("STORAGE_USE_SSL", false), } } diff --git a/backend/internal/storage/storage.go b/backend/internal/storage/storage.go index 041acde..8118696 100644 --- a/backend/internal/storage/storage.go +++ b/backend/internal/storage/storage.go @@ -13,12 +13,14 @@ import ( ) type Config struct { - Endpoint string - AccessKey string - SecretKey string - Bucket string - UseSSL bool - CORSOrigins string + Endpoint string + PublicEndpoint string + AccessKey string + SecretKey string + Bucket string + UseSSL bool + PublicUseSSL bool + CORSOrigins string } type ObjectInfo struct { @@ -39,9 +41,10 @@ type Storage interface { } type MinIO struct { - client *minio.Client - bucket string - corsOrigins string + client *minio.Client + publicClient *minio.Client + bucket string + corsOrigins string } func NewMinIO(config Config) (*MinIO, error) { @@ -55,7 +58,24 @@ func NewMinIO(config Config) (*MinIO, error) { if config.Bucket == "" { return nil, fmt.Errorf("object storage bucket is required") } - return &MinIO{client: client, bucket: config.Bucket, corsOrigins: config.CORSOrigins}, nil + + publicClient := client + if config.PublicEndpoint != "" { + publicClient, err = minio.New(config.PublicEndpoint, &minio.Options{ + Creds: credentials.NewStaticV4(config.AccessKey, config.SecretKey, ""), + Secure: config.PublicUseSSL, + }) + if err != nil { + return nil, fmt.Errorf("create public object storage client: %w", err) + } + } + + return &MinIO{ + client: client, + publicClient: publicClient, + bucket: config.Bucket, + corsOrigins: config.CORSOrigins, + }, nil } func (s *MinIO) EnsureBucket(ctx context.Context) error { @@ -63,13 +83,12 @@ func (s *MinIO) EnsureBucket(ctx context.Context) error { if err != nil { return fmt.Errorf("check object storage bucket: %w", err) } - if exists { - return nil - } - if err := s.client.MakeBucket(ctx, s.bucket, minio.MakeBucketOptions{}); err != nil { - response := minio.ToErrorResponse(err) - if response.Code != "BucketAlreadyExists" && response.Code != "BucketAlreadyOwnedByYou" { - return fmt.Errorf("create object storage bucket: %w", err) + if !exists { + if err := s.client.MakeBucket(ctx, s.bucket, minio.MakeBucketOptions{}); err != nil { + response := minio.ToErrorResponse(err) + if response.Code != "BucketAlreadyExists" && response.Code != "BucketAlreadyOwnedByYou" { + return fmt.Errorf("create object storage bucket: %w", err) + } } } origins := make([]string, 0) @@ -95,7 +114,7 @@ func (s *MinIO) EnsureBucket(ctx context.Context) error { } func (s *MinIO) CreateUploadURL(ctx context.Context, key, _ string, expiry time.Duration) (string, error) { - url, err := s.client.PresignedPutObject(ctx, s.bucket, key, expiry) + url, err := s.publicClient.PresignedPutObject(ctx, s.bucket, key, expiry) if err != nil { return "", fmt.Errorf("create upload URL: %w", err) } @@ -103,7 +122,7 @@ func (s *MinIO) CreateUploadURL(ctx context.Context, key, _ string, expiry time. } func (s *MinIO) CreateDownloadURL(ctx context.Context, key string, expiry time.Duration) (string, error) { - url, err := s.client.PresignedGetObject(ctx, s.bucket, key, expiry, nil) + url, err := s.publicClient.PresignedGetObject(ctx, s.bucket, key, expiry, nil) if err != nil { return "", fmt.Errorf("create download URL: %w", err) } diff --git a/docker-compose.yml b/docker-compose.yml index f9682a1..3f64973 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,6 +49,7 @@ services: SESSION_SECRET: "${SESSION_SECRET:-docker-compose-session-secret-change-me}" COOKIE_SECURE: "${COOKIE_SECURE:-false}" STORAGE_ENDPOINT: "minio:9000" + STORAGE_PUBLIC_ENDPOINT: "" STORAGE_ACCESS_KEY: "${STORAGE_ACCESS_KEY:-minioadmin}" STORAGE_SECRET_KEY: "${STORAGE_SECRET_KEY:-minioadmin}" STORAGE_BUCKET: "${STORAGE_BUCKET:-gallery-media}"