diff --git a/asset/domain/repository/repository.go b/asset/domain/repository/repository.go new file mode 100644 index 00000000..fa69f781 --- /dev/null +++ b/asset/domain/repository/repository.go @@ -0,0 +1,31 @@ +package repository + +import ( + "context" + "io" + + "github.com/reearth/reearthx/asset/domain" +) + +type Reader interface { + Read(ctx context.Context, id domain.ID) (*domain.Asset, error) + List(ctx context.Context) ([]*domain.Asset, error) +} + +type Writer interface { + Create(ctx context.Context, asset *domain.Asset) error + Update(ctx context.Context, asset *domain.Asset) error + Delete(ctx context.Context, id domain.ID) error +} + +type FileOperator interface { + Upload(ctx context.Context, id domain.ID, content io.Reader) error + Download(ctx context.Context, id domain.ID) (io.ReadCloser, error) + GetUploadURL(ctx context.Context, id domain.ID) (string, error) +} + +type Repository interface { + Reader + Writer + FileOperator +} diff --git a/asset/gcs/repository.go b/asset/gcs/repository.go deleted file mode 100644 index 8e991c79..00000000 --- a/asset/gcs/repository.go +++ /dev/null @@ -1,175 +0,0 @@ -package gcs - -import ( - "context" - "fmt" - "io" - "path" - "time" - - "cloud.google.com/go/storage" - "github.com/reearth/reearthx/asset" - "google.golang.org/api/iterator" -) - -type Repository struct { - bucket *storage.BucketHandle - bucketName string - basePath string -} - -var _ asset.Repository = (*Repository)(nil) - -func NewRepository(ctx context.Context, bucketName string) (*Repository, error) { - client, err := storage.NewClient(ctx) - if err != nil { - return nil, fmt.Errorf("failed to create client: %w", err) - } - - return &Repository{ - bucket: client.Bucket(bucketName), - bucketName: bucketName, - basePath: "assets", - }, nil -} - -func (r *Repository) Create(ctx context.Context, asset *asset.Asset) error { - obj := r.bucket.Object(r.objectPath(asset.ID)) - attrs := storage.ObjectAttrs{ - Metadata: map[string]string{ - "name": asset.Name, - "content_type": asset.ContentType, - }, - } - - if _, err := obj.Attrs(ctx); err == nil { - return fmt.Errorf("asset already exists: %s", asset.ID) - } - - writer := obj.NewWriter(ctx) - writer.ObjectAttrs = attrs - return writer.Close() -} - -func (r *Repository) getObject(id asset.ID) *storage.ObjectHandle { - return r.bucket.Object(r.objectPath(id)) -} - -func (r *Repository) handleNotFound(err error, id asset.ID) error { - if err == storage.ErrObjectNotExist { - return fmt.Errorf("asset not found: %s", id) - } - return fmt.Errorf("failed to get asset: %w", err) -} - -func (r *Repository) Read(ctx context.Context, id asset.ID) (*asset.Asset, error) { - attrs, err := r.getObject(id).Attrs(ctx) - if err != nil { - return nil, r.handleNotFound(err, id) - } - - return &asset.Asset{ - ID: id, - Name: attrs.Metadata["name"], - Size: attrs.Size, - ContentType: attrs.ContentType, - CreatedAt: attrs.Created, - UpdatedAt: attrs.Updated, - }, nil -} - -func (r *Repository) Update(ctx context.Context, asset *asset.Asset) error { - obj := r.bucket.Object(r.objectPath(asset.ID)) - update := storage.ObjectAttrsToUpdate{ - Metadata: map[string]string{ - "name": asset.Name, - "content_type": asset.ContentType, - }, - } - - if _, err := obj.Update(ctx, update); err != nil { - return fmt.Errorf("failed to update asset: %w", err) - } - return nil -} - -func (r *Repository) Delete(ctx context.Context, id asset.ID) error { - obj := r.bucket.Object(r.objectPath(id)) - if err := obj.Delete(ctx); err != nil { - if err == storage.ErrObjectNotExist { - return nil - } - return fmt.Errorf("failed to delete asset: %w", err) - } - return nil -} - -func (r *Repository) List(ctx context.Context) ([]*asset.Asset, error) { - var assets []*asset.Asset - it := r.bucket.Objects(ctx, &storage.Query{Prefix: r.basePath}) - - for { - attrs, err := it.Next() - if err == iterator.Done { - break - } - if err != nil { - return nil, fmt.Errorf("failed to list assets: %w", err) - } - - assets = append(assets, &asset.Asset{ - ID: asset.ID(path.Base(attrs.Name)), - Name: attrs.Metadata["name"], - Size: attrs.Size, - ContentType: attrs.ContentType, - CreatedAt: attrs.Created, - UpdatedAt: attrs.Updated, - }) - } - - return assets, nil -} - -func (r *Repository) Upload(ctx context.Context, id asset.ID, file io.Reader) error { - obj := r.bucket.Object(r.objectPath(id)) - writer := obj.NewWriter(ctx) - - if _, err := io.Copy(writer, file); err != nil { - writer.Close() - return fmt.Errorf("failed to upload file: %w", err) - } - - if err := writer.Close(); err != nil { - return fmt.Errorf("failed to close writer: %w", err) - } - return nil -} - -func (r *Repository) FetchFile(ctx context.Context, id asset.ID) (io.ReadCloser, error) { - obj := r.bucket.Object(r.objectPath(id)) - reader, err := obj.NewReader(ctx) - if err != nil { - if err == storage.ErrObjectNotExist { - return nil, fmt.Errorf("asset not found: %s", id) - } - return nil, fmt.Errorf("failed to read file: %w", err) - } - return reader, nil -} - -func (r *Repository) GetUploadURL(ctx context.Context, id asset.ID) (string, error) { - opts := &storage.SignedURLOptions{ - Method: "PUT", - Expires: time.Now().Add(15 * time.Minute), - } - - url, err := r.bucket.SignedURL(r.objectPath(id), opts) - if err != nil { - return "", fmt.Errorf("failed to generate upload URL: %w", err) - } - return url, nil -} - -func (r *Repository) objectPath(id asset.ID) string { - return path.Join(r.basePath, id.String()) -} diff --git a/asset/infrastructure/gcs/repository.go b/asset/infrastructure/gcs/repository.go index 8949d628..8ae7a03d 100644 --- a/asset/infrastructure/gcs/repository.go +++ b/asset/infrastructure/gcs/repository.go @@ -9,6 +9,7 @@ import ( "cloud.google.com/go/storage" "github.com/reearth/reearthx/asset/domain" + "github.com/reearth/reearthx/asset/domain/repository" "google.golang.org/api/iterator" ) @@ -18,7 +19,7 @@ type Repository struct { basePath string } -var _ domain.Repository = (*Repository)(nil) +var _ repository.Repository = (*Repository)(nil) func NewRepository(ctx context.Context, bucketName string) (*Repository, error) { client, err := storage.NewClient(ctx)