package storage import ( "bytes" "context" "fmt" "time" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" "github.com/rjNemo/rentease/internal/config" "github.com/rjNemo/rentease/internal/service/booking" ) type MinioInvoiceStorage struct { bucket string client *minio.Client } func NewMinioInvoiceStorage(cfg *config.Config) (*MinioInvoiceStorage, error) { if cfg.MinIOEndpoint == "" && cfg.MinIOAccessKey == "" && cfg.MinIOSecretKey == "" && cfg.MinIOBucket == "" { return nil, nil } switch { case cfg.MinIOEndpoint == "": return nil, fmt.Errorf("minio endpoint is required") case cfg.MinIOAccessKey == "": return nil, fmt.Errorf("minio access key is required") case cfg.MinIOSecretKey == "": return nil, fmt.Errorf("minio secret key is required") case cfg.MinIOBucket == "": return nil, fmt.Errorf("minio bucket is required") } client, err := minio.New(cfg.MinIOEndpoint, &minio.Options{ Creds: credentials.NewStaticV4(cfg.MinIOAccessKey, cfg.MinIOSecretKey, ""), Secure: cfg.MinIOUseSSL, }) if err != nil { return nil, fmt.Errorf("create minio client: %w", err) } return &MinioInvoiceStorage{ bucket: cfg.MinIOBucket, client: client, }, nil } func (s *MinioInvoiceStorage) StoreInvoice( ctx context.Context, objectKey string, file booking.GeneratedFile, shareURLTTL time.Duration, ) (*booking.StoredInvoiceFile, error) { if err := s.ensureBucket(ctx); err != nil { return nil, err } _, err := s.client.PutObject( ctx, s.bucket, objectKey, bytes.NewReader(file.Data), int64(len(file.Data)), minio.PutObjectOptions{ ContentType: file.ContentType, }, ) if err != nil { return nil, fmt.Errorf("upload object %q: %w", objectKey, err) } shareURL, err := s.client.PresignedGetObject(ctx, s.bucket, objectKey, shareURLTTL, nil) if err != nil { return nil, fmt.Errorf("presign object %q: %w", objectKey, err) } return &booking.StoredInvoiceFile{ ObjectKey: objectKey, ShareURL: shareURL.String(), ShareURLExpiresAt: time.Now().UTC().Add(shareURLTTL), }, nil } func (s *MinioInvoiceStorage) ensureBucket(ctx context.Context) error { exists, err := s.client.BucketExists(ctx, s.bucket) if err != nil { return fmt.Errorf("check bucket %q: %w", s.bucket, err) } if exists { return nil } if err := s.client.MakeBucket(ctx, s.bucket, minio.MakeBucketOptions{}); err != nil { return fmt.Errorf("create bucket %q: %w", s.bucket, err) } return nil }