diff --git a/server/e2e/proto_project_export_test.go b/server/e2e/proto_project_export_test.go index c100586b22..50d8c0564d 100644 --- a/server/e2e/proto_project_export_test.go +++ b/server/e2e/proto_project_export_test.go @@ -1,7 +1,10 @@ package e2e import ( + "archive/zip" + "bytes" "context" + "encoding/json" "testing" pb "github.com/reearth/reearth/server/internal/adapter/internalapi/schemas/internalapi/v1" @@ -88,7 +91,8 @@ func TestInternalAPI_export(t *testing.T) { resp.Header("Content-Type").Contains("application/zip") body := resp.Body().Raw() - assert.Greater(t, len(body), 0) + assert.Greater(t, len(body), 4) + assert.Equal(t, "PK\x03\x04", string(body[:4])) // check zip file hedder // private => Error! exp, err = client.ExportProject(ctx, &pb.ExportProjectRequest{ @@ -131,7 +135,40 @@ func TestInternalAPI_export(t *testing.T) { resp.Header("Content-Type").Contains("application/zip") body := resp.Body().Raw() - assert.Greater(t, len(body), 0) + assert.Greater(t, len(body), 4) + assert.Equal(t, "PK\x03\x04", string(body[:4])) // zip header + + data := []byte(body) + + r, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) + require.NoError(t, err) + + var pjBytes []byte + for _, f := range r.File { + if f.Name == "project.json" { + rc, err := f.Open() + require.NoError(t, err) + + var buf bytes.Buffer + _, err = buf.ReadFrom(rc) + rc.Close() + require.NoError(t, err) + + pjBytes = buf.Bytes() + break + } + } + require.NotNil(t, pjBytes, "project.json should exist in the zip") + assert.True(t, json.Valid(pjBytes), "project.json must be valid JSON") + + var doc map[string]any + require.NoError(t, json.Unmarshal(pjBytes, &doc)) + + _, ok := doc["project"].(map[string]any) + assert.True(t, ok, "`project` must exist and be an object") + + _, ok = doc["scene"].(map[string]any) + assert.True(t, ok, "`scene` must exist and be an object") }) } diff --git a/server/internal/app/repo.go b/server/internal/app/repo.go index 4409dea06d..5f4128ee94 100644 --- a/server/internal/app/repo.go +++ b/server/internal/app/repo.go @@ -167,6 +167,7 @@ func initReposAndGateways(ctx context.Context, conf *config.Config, debug bool) func initFile(ctx context.Context, conf *config.Config) (fileRepo gateway.File) { var err error if conf.GCS.IsConfigured() { + log.Infofc(ctx, "[Storage] GCS storage is used: %s", conf.GCS.BucketName) isFake := conf.GCS.IsFake fileRepo, err = gcs.NewFile(isFake, conf.GCS.BucketName, conf.AssetBaseURL, conf.GCS.PublicationCacheControl) if err != nil { @@ -177,7 +178,7 @@ func initFile(ctx context.Context, conf *config.Config) (fileRepo gateway.File) } if conf.S3.IsConfigured() { - log.Infofc(ctx, "file: S3 storage is used: %s\n", conf.S3.BucketName) + log.Infofc(ctx, "[Storage] S3 storage is used: %s", conf.S3.BucketName) fileRepo, err = s3.NewS3(ctx, conf.S3.BucketName, conf.AssetBaseURL, conf.S3.PublicationCacheControl) if err != nil { log.Warnf("file: failed to init S3 storage: %s\n", err.Error()) @@ -185,7 +186,7 @@ func initFile(ctx context.Context, conf *config.Config) (fileRepo gateway.File) return } - log.Infof("file: local storage is used") + log.Infof("[Storage] local afero storage is used") afs := afero.NewBasePathFs(afero.NewOsFs(), "tmp/afero") fileRepo, err = fs.NewFile(afs, conf.AssetBaseURL) if err != nil { diff --git a/server/internal/infrastructure/gcs/file.go b/server/internal/infrastructure/gcs/file.go index 94e9c65e1d..9fa2effe04 100644 --- a/server/internal/infrastructure/gcs/file.go +++ b/server/internal/infrastructure/gcs/file.go @@ -427,17 +427,51 @@ func (f *fileRepo) read(ctx context.Context, filename string) (io.ReadCloser, er return resp.Body, nil } - reader, err := bucket.Object(filename).NewReader(ctx) + obj := bucket.Object(filename) + + attrs, err := obj.Attrs(ctx) + if err != nil { + if errors.Is(err, storage.ErrObjectNotExist) { + return nil, rerror.ErrNotFound + } + log.Errorfc(ctx, "gcs: attrs err: %+v\n", err) + return nil, rerror.ErrInternalByWithContext(ctx, err) + } + + reader, err := obj.Generation(attrs.Generation).NewReader(ctx) if err != nil { if errors.Is(err, storage.ErrObjectNotExist) { + if OK := f.exists(ctx, bucket, filename); OK { + return nil, errors.New("no read permission") + } return nil, rerror.ErrNotFound } - return nil, visualizer.ErrorWithCallerLogging(ctx, "gcs: read object err", rerror.ErrInternalByWithContext(ctx, err)) + log.Errorfc(ctx, "gcs: generation read err: %+v\n", err) + return nil, rerror.ErrInternalByWithContext(ctx, err) } return reader, nil } +func (f *fileRepo) exists(ctx context.Context, bucket *storage.BucketHandle, filename string) bool { + fmt.Printf("Checking existence: %s\n", filename) + + obj := bucket.Object(filename) + attrs, err := obj.Attrs(ctx) + if err != nil { + if errors.Is(err, storage.ErrObjectNotExist) { + fmt.Printf("File does NOT exist: %s\n", filename) + return false + } + fmt.Printf("Error checking existence: %+v\n", err) + return false + } + + fmt.Printf("OK! File exist: %s , Updated: %v (Size: %d bytes, Generation: %d)\n", + filename, attrs.Updated, attrs.Size, attrs.Generation) + return true +} + func (f *fileRepo) upload(ctx context.Context, filename string, content io.Reader) (int64, error) { if filename == "" { return 0, gateway.ErrInvalidFile diff --git a/server/internal/usecase/interactor/common.go b/server/internal/usecase/interactor/common.go index 9f5aef1d37..4f886d74b5 100644 --- a/server/internal/usecase/interactor/common.go +++ b/server/internal/usecase/interactor/common.go @@ -269,10 +269,10 @@ func (d ProjectDeleter) Delete(ctx context.Context, prj *project.Project, force } func IsCurrentHostAssets(ctx context.Context, u string) bool { - if strings.HasPrefix(u, "assets/") || strings.HasPrefix(u, "/assets") { + if strings.HasPrefix(u, "assets/") && strings.HasPrefix(u, adapter.CurrentHost(ctx)) { return true } - return strings.HasPrefix(u, adapter.CurrentHost(ctx)) + return false } func ReplaceToCurrentHost(ctx context.Context, urlString string) string {