Files
Tony 3bb4ab494e feat: implement database and storage abstractions
- Added `internal/db/store.go` to define the Store interface for database operations, including user, organization, app, channel, release, patch, and artifact management.
- Introduced `internal/models/models.go` changes to reflect updated organization and artifact response structures.
- Created `internal/storage/factory.go`, `local.go`, and `s3.go` to implement storage backends for local filesystem and S3-compatible services.
- Removed deprecated `internal/storage/storage.go` and refactored storage interface into `internal/storage/store.go`.
- Updated frontend JavaScript in `web/js/app.js` to display server health information, including storage and database backend details.
2026-06-12 08:45:01 +08:00

80 lines
2.4 KiB
Go

package storage
import (
"context"
"fmt"
"io"
"net/url"
"time"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
// S3Store implements Store backed by MinIO or any S3-compatible service.
type S3Store struct {
client *minio.Client
releaseBucket string
patchBucket string
}
// NewS3Store creates a new S3-compatible storage backend.
func NewS3Store(endpoint, accessKeyID, secretAccessKey, releaseBucket, patchBucket string, useSSL bool) (*S3Store, error) {
client, err := minio.New(endpoint, &minio.Options{
Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
Secure: useSSL,
})
if err != nil {
return nil, fmt.Errorf("failed to create minio client: %w", err)
}
return &S3Store{client: client, releaseBucket: releaseBucket, patchBucket: patchBucket}, nil
}
func (s *S3Store) BackendName() string { return "S3/MinIO" }
func (s *S3Store) GeneratePresignedUploadURL(ctx context.Context, objectKey string, isPublic bool, expiry time.Duration) (string, error) {
bucket := s.releaseBucket
if isPublic {
bucket = s.patchBucket
}
u, err := s.client.PresignedPutObject(ctx, bucket, objectKey, expiry)
if err != nil {
return "", fmt.Errorf("failed to generate presigned URL: %w", err)
}
return u.String(), nil
}
func (s *S3Store) GeneratePublicDownloadURL(ctx context.Context, objectKey string) (string, error) {
endpoint := s.client.EndpointURL()
return fmt.Sprintf("%s/%s/%s", endpoint.String(), s.patchBucket, objectKey), nil
}
func (s *S3Store) UploadObject(ctx context.Context, objectKey string, reader io.Reader, size int64, contentType string, isPublic bool) error {
bucket := s.releaseBucket
if isPublic {
bucket = s.patchBucket
}
_, err := s.client.PutObject(ctx, bucket, objectKey, reader, size, minio.PutObjectOptions{ContentType: contentType})
if err != nil {
return fmt.Errorf("failed to upload object: %w", err)
}
return nil
}
func (s *S3Store) GetObject(ctx context.Context, objectKey string, isPublic bool) (io.ReadCloser, error) {
bucket := s.releaseBucket
if isPublic {
bucket = s.patchBucket
}
obj, err := s.client.GetObject(ctx, bucket, objectKey, minio.GetObjectOptions{})
if err != nil {
return nil, fmt.Errorf("failed to get object: %w", err)
}
return obj, nil
}
// PresignedURL parses a presigned URL string.
func PresignedURL(raw string) (*url.URL, error) {
return url.Parse(raw)
}