-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(asset): add asset management functionality and validation #60
base: main
Are you sure you want to change the base?
Changes from all commits
7c1213b
db6529c
fb30047
221c638
f024592
6fcbb9f
13fd52d
a6e465c
2dce8db
01cd883
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
.env | ||
.env.* | ||
.idea |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package decompress | ||
|
||
import ( | ||
"archive/zip" | ||
"context" | ||
"github.com/reearth/reearthx/asset" | ||
) | ||
|
||
type ZipDecompressor struct { | ||
assetService *asset.Service | ||
} | ||
|
||
func NewZipDecompressor(assetService *asset.Service) *ZipDecompressor { | ||
return &ZipDecompressor{ | ||
assetService: assetService, | ||
} | ||
} | ||
|
||
func (d *ZipDecompressor) DecompressAsync(ctx context.Context, assetID asset.ID) error { | ||
// Get the zip file from asset service | ||
zipFile, err := d.assetService.GetFile(ctx, assetID) | ||
if err != nil { | ||
return err | ||
} | ||
defer zipFile.Close() | ||
|
||
// Create a temporary file to store the zip content | ||
// Implementation of async zip extraction | ||
return nil | ||
} | ||
|
||
func (d *ZipDecompressor) processZipFile(ctx context.Context, zipReader *zip.Reader) error { | ||
// Process each file in the zip | ||
// Create new assets for each file | ||
return nil | ||
} | ||
Comment on lines
+32
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Implement the processZipFile method or remove if unused. The Either implement the method with proper ZIP file processing logic or remove it if not needed. Consider: func (d *ZipDecompressor) processZipFile(ctx context.Context, zipReader *zip.Reader) error {
for _, file := range zipReader.File {
select {
case <-ctx.Done():
return ctx.Err()
default:
if err := d.processZipEntry(ctx, file); err != nil {
return fmt.Errorf("failed to process %s: %w", file.Name, err)
}
}
}
return nil
} 🧰 Tools🪛 golangci-lint (1.62.2)32-32: func (unused) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package pubsub | ||
|
||
import ( | ||
"context" | ||
"github.com/reearth/reearthx/asset" | ||
) | ||
|
||
type AssetEvent struct { | ||
Type string `json:"type"` | ||
AssetID asset.ID `json:"asset_id"` | ||
} | ||
|
||
type Publisher interface { | ||
Publish(ctx context.Context, topic string, msg interface{}) error | ||
} | ||
|
||
type AssetPubSub struct { | ||
publisher Publisher | ||
topic string | ||
} | ||
|
||
func NewAssetPubSub(publisher Publisher, topic string) *AssetPubSub { | ||
return &AssetPubSub{ | ||
publisher: publisher, | ||
topic: topic, | ||
} | ||
} | ||
|
||
func (p *AssetPubSub) PublishAssetEvent(ctx context.Context, eventType string, assetID asset.ID) error { | ||
event := AssetEvent{ | ||
Type: eventType, | ||
AssetID: assetID, | ||
} | ||
return p.publisher.Publish(ctx, p.topic, event) | ||
} | ||
Comment on lines
+29
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add validation for eventType parameter. The func (p *AssetPubSub) PublishAssetEvent(ctx context.Context, eventType string, assetID asset.ID) error {
+ if eventType == "" {
+ return errors.New("event type cannot be empty")
+ }
event := AssetEvent{
Type: eventType,
AssetID: assetID,
}
return p.publisher.Publish(ctx, p.topic, event)
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package asset | ||
|
||
import ( | ||
"context" | ||
"io" | ||
) | ||
|
||
type Repository interface { | ||
Fetch(ctx context.Context, id ID) (*Asset, error) | ||
FetchFile(ctx context.Context, id ID) (io.ReadCloser, error) | ||
Save(ctx context.Context, asset *Asset) error | ||
Remove(ctx context.Context, id ID) error | ||
Upload(ctx context.Context, id ID, file io.Reader) error | ||
GetUploadURL(ctx context.Context, id ID) (string, error) | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,76 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
package asset | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"context" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"io" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"time" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type Service struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
repo Repository | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func NewService(repo Repository) *Service { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return &Service{repo: repo} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (s *Service) Create(ctx context.Context, input CreateAssetInput) (*Asset, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
asset := &Asset{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ID: ID(generateID()), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Name: input.Name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Size: input.Size, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ContentType: input.ContentType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
CreatedAt: time.Now(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
UpdatedAt: time.Now(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err := s.repo.Save(ctx, asset); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return asset, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+17
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Enhance error handling and validation in Create method. The Create method needs additional validation and error wrapping:
func (s *Service) Create(ctx context.Context, input CreateAssetInput) (*Asset, error) {
+ if err := input.Validate(); err != nil {
+ return nil, fmt.Errorf("invalid input: %w", err)
+ }
+
+ id, err := generateID()
+ if err != nil {
+ return nil, fmt.Errorf("failed to generate ID: %w", err)
+ }
asset := &Asset{
- ID: ID(generateID()),
+ ID: ID(id),
Name: input.Name,
Size: input.Size,
ContentType: input.ContentType,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if err := s.repo.Save(ctx, asset); err != nil {
- return nil, err
+ return nil, fmt.Errorf("failed to save asset: %w", err)
}
return asset, nil
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (s *Service) Update(ctx context.Context, id ID, input UpdateAssetInput) (*Asset, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
asset, err := s.repo.Fetch(ctx, id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if input.Name != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
asset.Name = *input.Name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if input.URL != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
asset.URL = *input.URL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if input.ContentType != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
asset.ContentType = *input.ContentType | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
asset.UpdatedAt = time.Now() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err := s.repo.Save(ctx, asset); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return nil, err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return asset, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (s *Service) Delete(ctx context.Context, id ID) error { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return s.repo.Remove(ctx, id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (s *Service) Get(ctx context.Context, id ID) (*Asset, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return s.repo.Fetch(ctx, id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (s *Service) GetFile(ctx context.Context, id ID) (io.ReadCloser, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return s.repo.FetchFile(ctx, id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (s *Service) Upload(ctx context.Context, id ID, file io.Reader) error { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return s.repo.Upload(ctx, id, file) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+70
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add file validation in Upload method. The Upload method should validate the file before uploading:
func (s *Service) Upload(ctx context.Context, id ID, file io.Reader) error {
+ // Wrap reader with size limitation
+ limitedReader := io.LimitReader(file, MaxFileSize)
+
+ // Optionally wrap with progress tracking
+ progressReader := &ProgressReader{Reader: limitedReader}
+
- return s.repo.Upload(ctx, id, file)
+ return s.repo.Upload(ctx, id, progressReader)
}
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (s *Service) GetUploadURL(ctx context.Context, id ID) (string, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return s.repo.GetUploadURL(ctx, id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package asset | ||
|
||
import ( | ||
"time" | ||
) | ||
|
||
type ID string | ||
|
||
type Asset struct { | ||
ID ID | ||
Name string | ||
Size int64 | ||
URL string | ||
ContentType string | ||
CreatedAt time.Time | ||
UpdatedAt time.Time | ||
} | ||
|
||
type CreateAssetInput struct { | ||
Name string | ||
Size int64 | ||
ContentType string | ||
} | ||
|
||
type UpdateAssetInput struct { | ||
Name *string | ||
URL *string | ||
ContentType *string | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package asset | ||
|
||
import ( | ||
"github.com/google/uuid" | ||
) | ||
|
||
func generateID() string { | ||
return uuid.New().String() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implementation incomplete and missing error handling.
The
DecompressAsync
method:Consider implementing the missing functionality: