122 lines
3.3 KiB
Go
122 lines
3.3 KiB
Go
// Package storage provides MinIO file storage operations.
|
|
package storage
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/minio/minio-go/v7"
|
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
|
)
|
|
|
|
// Config holds MinIO connection settings.
|
|
type Config struct {
|
|
Endpoint string
|
|
AccessKey string
|
|
SecretKey string
|
|
Bucket string
|
|
UseSSL bool
|
|
Region string
|
|
}
|
|
|
|
// Storage wraps MinIO client operations.
|
|
type Storage struct {
|
|
client *minio.Client
|
|
bucket string
|
|
}
|
|
|
|
// Connect creates a new MinIO storage client.
|
|
func Connect(ctx context.Context, cfg Config) (*Storage, error) {
|
|
client, err := minio.New(cfg.Endpoint, &minio.Options{
|
|
Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""),
|
|
Secure: cfg.UseSSL,
|
|
Region: cfg.Region,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating minio client: %w", err)
|
|
}
|
|
|
|
// Ensure bucket exists with versioning
|
|
exists, err := client.BucketExists(ctx, cfg.Bucket)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("checking bucket: %w", err)
|
|
}
|
|
if !exists {
|
|
if err := client.MakeBucket(ctx, cfg.Bucket, minio.MakeBucketOptions{
|
|
Region: cfg.Region,
|
|
}); err != nil {
|
|
return nil, fmt.Errorf("creating bucket: %w", err)
|
|
}
|
|
// Enable versioning
|
|
if err := client.EnableVersioning(ctx, cfg.Bucket); err != nil {
|
|
return nil, fmt.Errorf("enabling versioning: %w", err)
|
|
}
|
|
}
|
|
|
|
return &Storage{client: client, bucket: cfg.Bucket}, nil
|
|
}
|
|
|
|
// PutResult contains the result of a put operation.
|
|
type PutResult struct {
|
|
Key string
|
|
VersionID string
|
|
Size int64
|
|
Checksum string
|
|
}
|
|
|
|
// Put uploads a file to storage.
|
|
func (s *Storage) Put(ctx context.Context, key string, reader io.Reader, size int64, contentType string) (*PutResult, error) {
|
|
info, err := s.client.PutObject(ctx, s.bucket, key, reader, size, minio.PutObjectOptions{
|
|
ContentType: contentType,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("uploading object: %w", err)
|
|
}
|
|
|
|
return &PutResult{
|
|
Key: key,
|
|
VersionID: info.VersionID,
|
|
Size: info.Size,
|
|
Checksum: info.ChecksumSHA256,
|
|
}, nil
|
|
}
|
|
|
|
// Get downloads a file from storage.
|
|
func (s *Storage) Get(ctx context.Context, key string) (io.ReadCloser, error) {
|
|
obj, err := s.client.GetObject(ctx, s.bucket, key, minio.GetObjectOptions{})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("getting object: %w", err)
|
|
}
|
|
return obj, nil
|
|
}
|
|
|
|
// GetVersion downloads a specific version of a file.
|
|
func (s *Storage) GetVersion(ctx context.Context, key, versionID string) (io.ReadCloser, error) {
|
|
obj, err := s.client.GetObject(ctx, s.bucket, key, minio.GetObjectOptions{
|
|
VersionID: versionID,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("getting object version: %w", err)
|
|
}
|
|
return obj, nil
|
|
}
|
|
|
|
// Delete removes a file from storage.
|
|
func (s *Storage) Delete(ctx context.Context, key string) error {
|
|
if err := s.client.RemoveObject(ctx, s.bucket, key, minio.RemoveObjectOptions{}); err != nil {
|
|
return fmt.Errorf("removing object: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FileKey generates a storage key for an item file.
|
|
func FileKey(partNumber string, revision int) string {
|
|
return fmt.Sprintf("items/%s/rev%d.FCStd", partNumber, revision)
|
|
}
|
|
|
|
// ThumbnailKey generates a storage key for a thumbnail.
|
|
func ThumbnailKey(partNumber string, revision int) string {
|
|
return fmt.Sprintf("thumbnails/%s/rev%d.png", partNumber, revision)
|
|
}
|