package storage import ( "context" "errors" "io" "net/url" "strings" "time" "filefast/backend/internal/config" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) type MinIOClient struct { client *minio.Client bucket string presignExpiry time.Duration enabled bool } func NewMinIOClient(cfg config.MinIOConfig) (*MinIOClient, error) { if cfg.Endpoint == "" || cfg.AccessKey == "" || cfg.SecretKey == "" { return &MinIOClient{ bucket: cfg.Bucket, presignExpiry: cfg.PresignExpiry, enabled: false, }, nil } client, err := minio.New(cfg.Endpoint, &minio.Options{ Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""), Secure: cfg.UseSSL, }) if err != nil { return nil, err } return &MinIOClient{ client: client, bucket: cfg.Bucket, presignExpiry: cfg.PresignExpiry, enabled: true, }, nil } func (c *MinIOClient) Enabled() bool { return c != nil && c.enabled } func (c *MinIOClient) EnsureBucket(ctx context.Context) error { if !c.Enabled() { return nil } exists, err := c.client.BucketExists(ctx, c.bucket) if err != nil { return err } if exists { return nil } return c.client.MakeBucket(ctx, c.bucket, minio.MakeBucketOptions{}) } func (c *MinIOClient) PresignUpload(ctx context.Context, objectKey string) (*url.URL, error) { if !c.Enabled() { return nil, errors.New("minio is disabled") } return c.client.PresignedPutObject(ctx, c.bucket, objectKey, c.presignExpiry) } func (c *MinIOClient) PresignDownload(ctx context.Context, objectKey string) (*url.URL, error) { return c.PresignDownloadWithFilename(ctx, objectKey, "") } func (c *MinIOClient) PresignDownloadWithFilename(ctx context.Context, objectKey, filename string) (*url.URL, error) { if !c.Enabled() { return nil, errors.New("minio is disabled") } var reqParams url.Values if filename = sanitizeDownloadFilename(filename); filename != "" { reqParams = make(url.Values) reqParams.Set("response-content-disposition", `attachment; filename="`+filename+`"`) } return c.client.PresignedGetObject(ctx, c.bucket, objectKey, c.presignExpiry, reqParams) } func (c *MinIOClient) UploadObject(ctx context.Context, objectKey string, reader io.Reader, size int64, contentType string) error { if !c.Enabled() { return errors.New("minio is disabled") } options := minio.PutObjectOptions{ ContentType: contentType, } _, err := c.client.PutObject(ctx, c.bucket, objectKey, reader, size, options) return err } func (c *MinIOClient) OpenObject(ctx context.Context, objectKey string) (*minio.Object, minio.ObjectInfo, error) { if !c.Enabled() { return nil, minio.ObjectInfo{}, errors.New("minio is disabled") } object, err := c.client.GetObject(ctx, c.bucket, objectKey, minio.GetObjectOptions{}) if err != nil { return nil, minio.ObjectInfo{}, err } info, err := object.Stat() if err != nil { _ = object.Close() return nil, minio.ObjectInfo{}, err } return object, info, nil } func (c *MinIOClient) RemoveObject(ctx context.Context, objectKey string) error { if !c.Enabled() { return nil } return c.client.RemoveObject(ctx, c.bucket, objectKey, minio.RemoveObjectOptions{}) } func sanitizeDownloadFilename(filename string) string { filename = strings.TrimSpace(filename) if filename == "" { return "" } filename = strings.ReplaceAll(filename, `"`, "") filename = strings.ReplaceAll(filename, "\r", "") filename = strings.ReplaceAll(filename, "\n", "") return filename }