package storage import ( "context" "fmt" "io" "time" "github.com/google/uuid" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) type Storage struct { Client *minio.Client Bucket string } func New(ctx context.Context, endpoint, accessKey, secretKey, bucket string, useSSL bool) (*Storage, error) { client, err := minio.New(endpoint, &minio.Options{ Creds: credentials.NewStaticV4(accessKey, secretKey, ""), Secure: useSSL, }) if err != nil { return nil, fmt.Errorf("minio: %w", err) } exists, err := client.BucketExists(ctx, bucket) if err != nil { return nil, fmt.Errorf("bucket exists check: %w", err) } if !exists { if err := client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{}); err != nil { return nil, fmt.Errorf("make bucket: %w", err) } } return &Storage{Client: client, Bucket: bucket}, nil } // UploadAvatar — загружает аватар в MinIO и возвращает публичный URL // (для MinIO нужен bucket policy или presigned URL — пока делаем простой PUT с публичным доступом через наш backend). func (s *Storage) UploadAvatar(ctx context.Context, userID uuid.UUID, contentType string, data io.Reader, size int64) (string, error) { key := fmt.Sprintf("avatars/%s/%s-%d", userID.String(), uuid.NewString(), time.Now().Unix()) _, err := s.Client.PutObject(ctx, s.Bucket, key, data, size, minio.PutObjectOptions{ ContentType: contentType, }) if err != nil { return "", err } // возвращаем относительный URL — бэкенд сам раздаёт через nginx (proxy_pass /storage → MinIO) return "/storage/" + key, nil }