diff --git a/internal/middleware/objects/middleware.go b/internal/middleware/objects/middleware.go index c2ec077..a843079 100644 --- a/internal/middleware/objects/middleware.go +++ b/internal/middleware/objects/middleware.go @@ -16,15 +16,14 @@ import ( ent "github.com/theopenlane/core/internal/ent/generated" "github.com/theopenlane/core/pkg/middleware/transaction" "github.com/theopenlane/core/pkg/objects" - "github.com/theopenlane/core/pkg/objects/storage" ) // Upload is the object that handles the file upload process type Upload struct { // ObjectStorage is the object storage configuration ObjectStorage *objects.Objects - // Storage is the storage type to use, in this case, S3 - Storage *storage.S3Store + // Storage is the storage type to use, this can be S3 or Disk + Storage objects.Storage } // FileUpload is the object that holds the file information @@ -338,10 +337,10 @@ func (u *Upload) createFile(ctx context.Context, f FileUpload) (*ent.File, error DetectedContentType: contentType, Md5Hash: &md5Hash, StoreKey: &f.Key, - StorageScheme: &u.Storage.Scheme, + StorageScheme: u.Storage.GetScheme(), } - // get file contents + // get file contents to store in the database contents, err := objects.StreamToByte(f.File) if err != nil { log.Error().Err(err).Str("file", f.Filename).Msg("failed to read file contents") diff --git a/pkg/objects/io.go b/pkg/objects/io.go index a717255..07d9f7b 100644 --- a/pkg/objects/io.go +++ b/pkg/objects/io.go @@ -9,16 +9,24 @@ import ( "os" "github.com/gabriel-vasile/mimetype" + "github.com/rs/zerolog/log" ) // StreamToByte function reads the content of the provided io.Reader and returns it as a byte slice -func StreamToByte(stream io.Reader) ([]byte, error) { +func StreamToByte(stream io.ReadSeeker) ([]byte, error) { buf := new(bytes.Buffer) if _, err := buf.ReadFrom(stream); err != nil { return nil, err } + // reset the file to the beginning + if _, err := stream.Seek(0, io.SeekStart); err != nil { + log.Error().Err(err).Msg("failed to reset file") + + return nil, err + } + return buf.Bytes(), nil } diff --git a/pkg/objects/mocks/mock_Storage.go b/pkg/objects/mocks/mock_Storage.go index 90ede36..9327197 100644 --- a/pkg/objects/mocks/mock_Storage.go +++ b/pkg/objects/mocks/mock_Storage.go @@ -195,6 +195,53 @@ func (_c *MockStorage_GetPresignedURL_Call) RunAndReturn(run func(context.Contex return _c } +// GetScheme provides a mock function with given fields: +func (_m *MockStorage) GetScheme() *string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetScheme") + } + + var r0 *string + if rf, ok := ret.Get(0).(func() *string); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*string) + } + } + + return r0 +} + +// MockStorage_GetScheme_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetScheme' +type MockStorage_GetScheme_Call struct { + *mock.Call +} + +// GetScheme is a helper method to define mock.On call +func (_e *MockStorage_Expecter) GetScheme() *MockStorage_GetScheme_Call { + return &MockStorage_GetScheme_Call{Call: _e.mock.On("GetScheme")} +} + +func (_c *MockStorage_GetScheme_Call) Run(run func()) *MockStorage_GetScheme_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockStorage_GetScheme_Call) Return(_a0 *string) *MockStorage_GetScheme_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockStorage_GetScheme_Call) RunAndReturn(run func() *string) *MockStorage_GetScheme_Call { + _c.Call.Return(run) + return _c +} + // ManagerUpload provides a mock function with given fields: _a0, _a1 func (_m *MockStorage) ManagerUpload(_a0 context.Context, _a1 [][]byte) error { ret := _m.Called(_a0, _a1) diff --git a/pkg/objects/objects.go b/pkg/objects/objects.go index 5b13f17..8e841c2 100644 --- a/pkg/objects/objects.go +++ b/pkg/objects/objects.go @@ -16,6 +16,8 @@ type Storage interface { Download(context.Context, string, *DownloadFileOptions) (*DownloadFileMetadata, io.ReadCloser, error) // GetPresignedURL is used to get a presigned URL for a file in the storage backend GetPresignedURL(context.Context, string) (string, error) + // GetScheme returns the scheme of the storage backend + GetScheme() *string io.Closer } diff --git a/pkg/objects/storage/disk.go b/pkg/objects/storage/disk.go index 1bcade5..817e138 100644 --- a/pkg/objects/storage/disk.go +++ b/pkg/objects/storage/disk.go @@ -16,6 +16,9 @@ type Disk struct { Scheme string } +// ensure Disk satisfies the Storage interface +var _ objects.Storage = &Disk{} + func NewDiskStorage(folder string) (*Disk, error) { if len(strings.TrimSpace(folder)) == 0 { return nil, fmt.Errorf("%w: please provide a valid folder path", ErrInvalidFolderPath) @@ -45,3 +48,26 @@ func (d *Disk) Upload(ctx context.Context, r io.Reader, opts *objects.UploadFile Key: opts.FileName, }, err } + +// GetScheme returns the scheme of the storage backend +func (d *Disk) GetScheme() *string { + return &d.Scheme +} + +// ManagerUpload uploads multiple files to disk +// TODO: Implement this method +func (d *Disk) ManagerUpload(ctx context.Context, files [][]byte) error { + return nil +} + +// Download is used to download a file from the storage backend +// TODO: Implement this method +func (d *Disk) Download(ctx context.Context, key string, opts *objects.DownloadFileOptions) (*objects.DownloadFileMetadata, io.ReadCloser, error) { + return nil, nil, nil +} + +// GetPresignedURL is used to get a presigned URL for a file in the storage backend +// TODO: Implement this method +func (d *Disk) GetPresignedURL(ctx context.Context, key string) (string, error) { + return "", nil +} diff --git a/pkg/objects/storage/s3.go b/pkg/objects/storage/s3.go index 614b802..2fd2e86 100644 --- a/pkg/objects/storage/s3.go +++ b/pkg/objects/storage/s3.go @@ -21,6 +21,9 @@ var ( presignedURLTimeout = 15 * time.Minute ) +// ensure S3Store satisfies the Storage interface +var _ objects.Storage = &S3Store{} + // S3Options is used to configure the S3Store type S3Options struct { // Bucket to store objects in @@ -185,3 +188,8 @@ func (s *S3Store) GetPresignedURLWithCustomDuration(ctx context.Context, key str return presignURL.URL, nil } + +// GetScheme returns the scheme of the storage backend +func (s *S3Store) GetScheme() *string { + return &s.Scheme +}