Skip to content
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(server): associate project asset #1410

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

hexaforce
Copy link
Contributor

@hexaforce hexaforce commented Feb 5, 2025

Overview

Add the association of the project to the asset.

What I've done

I have added the project parameter to the asset.
Added updateAsset, which allows you to change the project.
You can also specify null to make it a workspace file.
The changes to the API are as follows:

type Asset implements Node {
  id: ID!
  createdAt: DateTime!
  teamId: ID!
+ projectId: ID
  name: String!
  size: FileSize!
  url: String!
  contentType: String!
  team: Team
  coreSupport: Boolean!
}

input CreateAssetInput {
  teamId: ID!
+ projectId: ID
  coreSupport: Boolean!
  file: Upload!
}

+input UpdateAssetInput {
+  assetId: ID!
+  projectId: ID
+}

+type UpdateAssetPayload {
+  assetId: ID!
+  projectId: ID
+}

extend type Query {
  assets(
    teamId: ID!
+  projectId: ID
    pagination: Pagination
    keyword: String
    sort: AssetSort
  ): AssetConnection!
}

extend type Mutation {
  createAsset(input: CreateAssetInput!): CreateAssetPayload
+  updateAsset(input: UpdateAssetInput!): UpdateAssetPayload
  removeAsset(input: RemoveAssetInput!): RemoveAssetPayload
}

The projectId parameter for createAsset is optional, while teamId remains required as before.
teamId is necessary because, if the owner is not present, the data would remain as unmanaged.

For the Query, when both teamId and projectId are specified, the search will be performed based on projectId (and teamId will be ignored).

The data model for the asset is as shown in the diagram below.
スクリーンショット 2025-02-05 19 39 49

What I haven't done

How I tested

e2e test

https://github.com/reearth/reearth-visualizer/pull/1410/files#diff-be135cdb0bc1a5b8a184c356c9664cee68bcc8220f9d2097ad6b5348ac25c425R131

Which point I want you to review particularly

Memo

Fix the front-end query here

web/src/services/gql/queries/asset.ts

import { gql } from "@reearth/services/gql/__gen__";

export const GET_ASSETS = gql(`
-  query GetAssets($teamId: ID!, $pagination: Pagination, $keyword: String, $sort: AssetSort) {
-    assets(teamId: $teamId, pagination: $pagination, keyword: $keyword, sort: $sort) {
+  query GetAssets($teamId: ID!, $projectId: ID, $pagination: Pagination, $keyword: String, $sort: AssetSort) {
+    assets(teamId: $teamId, projectId: $projectId, pagination: $pagination, keyword: $keyword, sort: $sort) {
      edges {
        cursor
        node {
          id
          teamId
+          projectId
          name
          size
          url
          createdAt
          contentType
          coreSupport
        }
      }
      nodes {
        id
        teamId
+       projectId
        name
        size
        url
        createdAt
        contentType
        coreSupport
      }
      pageInfo {
        endCursor
        hasNextPage
        hasPreviousPage
        startCursor
      }
      totalCount
    }
  }
`);

export const CREATE_ASSET = gql(`
-  mutation CreateAsset($teamId: ID!, $file: Upload!, $coreSupport: Boolean!) {
-    createAsset(input: { teamId: $teamId, file: $file, coreSupport: $coreSupport }) {
+  mutation CreateAsset($teamId: ID!, $projectId: ID, $file: Upload!, $coreSupport: Boolean!) {
+    createAsset(input: { teamId: $teamId, projectId: $projectId, file: $file, coreSupport: $coreSupport }) {
      asset {
        id
        teamId
 +       projectId
        name
        size
        url
        contentType
      }
    }
  }
`);

+export const UPDATE_ASSET = gql(`
+  mutation UpdateAsset($assetId: ID!, $projectId: ID) {
+    updateAsset(input: { assetId: $assetId projectId: $projectId }) {
+      assetId
+      projectId
+      __typename
+    }
+  }
+`);

export const REMOVE_ASSET = gql(`
  mutation RemoveAsset($assetId: ID!) {
    removeAsset(input: { assetId: $assetId }) {
      assetId
    }
  }
`);

Summary by CodeRabbit

  • New Features

    • Enhanced asset management with new update capabilities and project filtering, enabling assets to be associated with and retrieved by project.
    • Introduced optional projectId parameter in various asset-related API methods for improved project association.
  • Refactor / Removed Features

    • Streamlined the GraphQL API by removing legacy endpoints and complex models for datasets, clusters, tags, and infoboxes.
    • Simplified scene and property data structures through the removal of obsolete fields and redundant logic.
    • Updated API and mutation signatures to improve clarity and performance.
    • Removed encoding functionality from scene and layer management, simplifying the export process.

Copy link

coderabbitai bot commented Feb 5, 2025

Walkthrough

This pull request introduces extensive modifications across multiple parts of the codebase. Notable changes include the addition of a new Makefile target for resetting services, significant updates in end-to-end tests and utility functions, and widespread removals and refactoring in GraphQL schemas, resolvers, data loaders, and conversion functions. New project ID parameters have been added to asset-related functionality while dataset, tag, cluster, and infobox management components have been removed or simplified. Infrastructure and use case layers have been updated to support these changes, and similar adjustments are reflected in the web API files.

Changes

Files (Grouped) Change Summaries
server/Makefile Added new run-reset target to stop/reset Docker containers and update .PHONY declarations.
server/e2e/ Introduced new JSON equality functions and enhanced handling of HTTP responses; several test files (e.g., dataset_export_test.go, gql_layer_test.go) were removed; minor modifications to test logic (e.g., added nil parameter for geojson tests).
server/gql/ Modified GraphQL schemas: added optional projectId fields and arguments to asset types and queries; removed types and fields related to clusters, datasets, tags, and layers; updated queries and mutations (e.g., updateAsset mutation added).
server/internal/adapter/gql/ Removed multiple data loader implementations, conversion functions, and resolver functions related to datasets, tags, layers, clusters, and property field links; updated resolver methods to accept new project ID parameters and removed obsolete case handling.
server/internal/infrastructure/ Updated FindByWorkspace methods in both memory and Mongo implementations to support project filtering; added a Project field in asset document definitions with corresponding conversion logic.
server/internal/usecase/ Updated asset interactor methods to include project ID parameters (e.g., FindByWorkspace, UploadAssetFile), and added a new Update method for changing an asset’s project; removed cluster handling in scene import and dataset-related functionality.
server/pkg/asset/ Added a new field for project ID along with getter and setter methods; extended the Builder with a new method to set a project ID; new type aliases and helper variables for ProjectID were introduced.
server/pkg/layer/ Removed the Encoder field from the Exporter struct, effectively disabling certain encoding logic; simplified layer export control flow.
server/pkg/scene/builder/ Removed the encoder field and associated logic from the Builder and sceneJSON methods; eliminated layers, tags, and clusters from the scene structure; deleted related test files and commented out unused type definitions.
web/src/ Updated API utilities by removing the “actualValue” property from merged types; adjusted scene API query mapping to no longer include rootLayerId; deleted the dataset GraphQL fragment file.

Sequence Diagram(s)

sequenceDiagram
    participant C as Client
    participant R as GraphQL Resolver
    participant I as Asset Interactor
    participant Repo as Asset Repository
    participant DB as Database (MongoDB)
    
    C->>R: CreateAsset(input with optional projectId)
    R->>I: Call CreateAsset with projectID and asset details
    I->>Repo: Upload asset file and create asset record\n(with project association)
    Repo->>DB: Insert asset document (includes Project field)
    DB-->>Repo: Confirmation
    Repo-->>I: Return created asset details
    I-->>R: Pass asset details back
    R-->>C: Respond with Asset Created result
Loading

Suggested reviewers

  • pyshx

Poem

In a burrow deep, I hop with glee,
Changes abound from A to Z.
New targets reset, and code refined,
Asset projects now neatly aligned.
With nimble paws and ASCII art so true,
I toast these changes – a hop, skip, and a dew!
🐰✨

Tip

🌐 Web search-backed reviews and chat
  • We have enabled web search-based reviews and chat for all users. This feature allows CodeRabbit to access the latest documentation and information on the web.
  • You can disable this feature by setting web_search: false in the knowledge_base settings.
  • Please share any feedback in the Discord discussion.
✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

netlify bot commented Feb 5, 2025

Deploy Preview for reearth-web canceled.

Name Link
🔨 Latest commit 76da26f
🔍 Latest deploy log https://app.netlify.com/sites/reearth-web/deploys/67a453c606f06900081fa4c3

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🔭 Outside diff range comments (2)
web/src/beta/features/AssetsManager/hooks.ts (2)

184-184: Pass the projectId parameter to FindByWorkspace.

The projectId parameter should be passed to the FindByWorkspace function to enable filtering assets by project.

Apply this diff to fix the issue:

-  assets, _, err := usecases(ctx).Asset.FindByWorkspace(ctx, prj.Workspace(), nil, nil, sort, page, getOperator(ctx))
+  assets, _, err := usecases(ctx).Asset.FindByWorkspace(ctx, prj.Workspace(), &projectId, nil, sort, page, getOperator(ctx))

202-213: Add missing dependency to useCallback hook.

The projectId parameter is used within the callback but is missing from the dependency array.

Apply this diff to fix the issue:

  const handleAssetsCreate = useCallback(
    async (files?: FileList) => {
      if (!files) return;
      await useCreateAssets({
        teamId: workspaceId ?? "",
        projectId: projectId ?? "",
        file: files,
        coreSupport: true
      });
    },
-   [workspaceId, useCreateAssets]
+   [workspaceId, projectId, useCreateAssets]
  );
🧰 Tools
🪛 GitHub Check: ci-web / ci

[failure] 212-212:
React Hook useCallback has a missing dependency: 'projectId'. Either include it or remove the dependency array

🪛 GitHub Actions: ci

[error] 212-212: React Hook useCallback has a missing dependency: 'projectId'. Either include it or remove the dependency array.

🧹 Nitpick comments (12)
server/pkg/scene/builder/builder.go (2)

46-55: Consider documenting the exporter configuration.

The exporter setup is crucial for layer processing but lacks documentation about its role and configuration. Consider adding comments explaining:

  • The merger's role in layer/property loading
  • The sealer's role in dataset/tag processing

133-159: Improve error message consistency and clarity.

The error messages in BuildResult could be more descriptive and consistent:

  1. Error prefixes vary between "Fail" and "Fail build"
  2. Error messages use inconsistent separators

Consider applying this diff to improve error messages:

-			return nil, errors.New("Fail buildScene :" + err.Error())
+			return nil, fmt.Errorf("failed to build scene: %w", err)
-			return nil, errors.New("Fail buildStory :" + err.Error())
+			return nil, fmt.Errorf("failed to build story: %w", err)
-			return nil, errors.New("Fail buildNLSLayers :" + err.Error())
+			return nil, fmt.Errorf("failed to build NLS layers: %w", err)
-			return nil, errors.New("Fail buildLayerStyles :" + err.Error())
+			return nil, fmt.Errorf("failed to build layer styles: %w", err)

Also add the following import:

+	"fmt"
server/e2e/gql_asset_test.go (2)

29-83: Repeated JSON checks hint at code duplication.

Lines creating assets and verifying JSON structures look repetitive. You might consider factoring out a helper function that wraps creation of the asset plus validation of its fields to avoid duplication and reduce potential for inconsistencies.


216-243: Synchronized logic for file data creation.

createAssetFromFileData mirrors createAsset. Consider deduplicating these if possible—using a shared helper for request building and file handling avoids divergences.

server/internal/adapter/gql/gqlmodel/convert_asset.go (1)

12-16: Consider a more concise implementation.

While the current implementation is correct, it could be simplified for better readability.

-	var pid *ID
-	if project := a.Project(); project != nil {
-		pidValue := IDFrom(*a.Project())
-		pid = &pidValue
-	}
+	var pid *ID
+	if p := a.Project(); p != nil {
+		id := IDFrom(*p)
+		pid = &id
+	}

Also applies to: 22-22

server/internal/adapter/gql/resolver_mutation_asset.go (1)

19-26: Improve error variable naming for consistency.

The implementation is correct, but consider improving error variable naming for consistency with the rest of the file.

-		pidValue, err := gqlmodel.ToID[id.Project](*input.ProjectID)
-		if err != nil {
+		pidValue, err2 := gqlmodel.ToID[id.Project](*input.ProjectID)
+		if err2 != nil {

Note: This matches the error variable naming pattern used in the RemoveAsset function (line 48).

Also applies to: 30-30

server/internal/infrastructure/memory/asset.go (1)

49-59: Consider extracting the filter condition for better maintainability.

The implementation is correct, but the filter conditions could be extracted into a separate function to improve readability and maintainability.

 func (r *Asset) FindByWorkspace(_ context.Context, wid accountdomain.WorkspaceID, pid *id.ProjectID, filter repo.AssetFilter) ([]*asset.Asset, *usecasex.PageInfo, error) {
+    filterAsset := func(v *asset.Asset) bool {
+        baseCondition := v.CoreSupport() && (filter.Keyword == nil || strings.Contains(v.Name(), *filter.Keyword))
+        if pid != nil {
+            return v.Project() != nil && *v.Project() == *pid && baseCondition
+        }
+        return v.Workspace() == wid && baseCondition
+    }
+
     result := r.data.FindAll(func(k id.AssetID, v *asset.Asset) bool {
-        if pid != nil {
-            return v.Project() != nil && *v.Project() == *pid && v.CoreSupport() && (filter.Keyword == nil || strings.Contains(v.Name(), *filter.Keyword))
-        }
-        return v.Workspace() == wid && v.CoreSupport() && (filter.Keyword == nil || strings.Contains(v.Name(), *filter.Keyword))
+        return filterAsset(v)
     })
web/src/services/api/assetsApi.ts (1)

116-116: Consider implementing error tracking.

The TODO comment indicates missing error tracking implementation.

Would you like me to help implement error tracking using a standard error tracking library?

server/e2e/common.go (2)

288-293: Consider handling additional JSON value types.

The function only handles map[string]interface{} but could be enhanced to support arrays and primitive types.

Apply this diff to improve type handling:

 func JSONEqRegexpValue(t *testing.T, actual *httpexpect.Value, expected string) bool {
-  if actualData, ok := actual.Raw().(map[string]interface{}); ok {
+  switch actualData := actual.Raw().(type) {
+  case map[string]interface{}, []interface{}, string, float64, bool:
     return JSONEqRegexpInterface(t, actualData, expected)
+  default:
+    return false
   }
-  return false
 }

305-319: Enhance error handling in ValueDump function.

Consider logging errors when JSON marshaling fails and handling nil values.

Apply this diff to improve error handling:

 func ValueDump(val *httpexpect.Value) {
+  if val == nil {
+    fmt.Println("Value is nil")
+    return
+  }
   raw := val.Raw()
   switch data := raw.(type) {
   case map[string]interface{}:
     if text, err := json.MarshalIndent(data, "", "  "); err == nil {
       fmt.Println(string(text))
+    } else {
+      fmt.Printf("Error marshaling map: %v\n", err)
     }
   case []interface{}:
     if text, err := json.MarshalIndent(data, "", "  "); err == nil {
       fmt.Println(string(text))
+    } else {
+      fmt.Printf("Error marshaling array: %v\n", err)
     }
   default:
     fmt.Println("Unsupported type:", reflect.TypeOf(raw))
   }
 }
server/internal/adapter/gql/resolver_mutation_project.go (2)

262-272: Improve error message clarity.

The error messages could be more descriptive to help with debugging.

Apply this diff to improve error messages:

   projectData, _ := unmarshalProject(tempData)
   prj, tx, err := usecases(ctx).Project.ImportProject(ctx, workspace.String(), projectData)
   if err != nil {
-    return nil, errors.New("Fail ImportProject :" + err.Error())
+    return nil, fmt.Errorf("failed to import project (workspace: %s): %w", workspace.String(), err)
   }
   defer func() {
     if err2 := tx.End(ctx); err == nil && err2 != nil {
       err = err2
     }
   }()

287-292: Improve error handling for asset upload.

The error handling could be more descriptive and consistent with other error messages.

Apply this diff to improve error handling:

   pid, err := id.ProjectIDFrom(prj.ID().String())
   if err != nil {
-    return nil, errors.New("Fail UploadAssetFile :" + err.Error())
+    return nil, fmt.Errorf("failed to parse project ID %s: %w", prj.ID().String(), err)
   }
   url, _, err := usecases(ctx).Asset.UploadAssetFile(ctx, realName, file, workspace, &pid)
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c9312c and e876288.

⛔ Files ignored due to path filters (3)
  • web/src/services/gql/__gen__/fragmentMatcher.json is excluded by !**/__gen__/**
  • web/src/services/gql/__gen__/gql.ts is excluded by !**/__gen__/**
  • web/src/services/gql/__gen__/graphql.ts is excluded by !**/__gen__/**
📒 Files selected for processing (82)
  • server/Makefile (3 hunks)
  • server/e2e/common.go (3 hunks)
  • server/e2e/dataset_export_test.go (0 hunks)
  • server/e2e/gql_asset_test.go (7 hunks)
  • server/e2e/gql_custom_property_test.go (1 hunks)
  • server/e2e/gql_layer_test.go (0 hunks)
  • server/e2e/gql_nlslayer_test.go (0 hunks)
  • server/e2e/gql_project_import_test.go (1 hunks)
  • server/e2e/gql_storytelling_test.go (2 hunks)
  • server/e2e/gql_validate_geojson_test.go (1 hunks)
  • server/e2e/seeder.go (1 hunks)
  • server/gql/asset.graphql (3 hunks)
  • server/gql/cluster.graphql (0 hunks)
  • server/gql/dataset.graphql (0 hunks)
  • server/gql/layer.graphql (0 hunks)
  • server/gql/plugin.graphql (2 hunks)
  • server/gql/property.graphql (0 hunks)
  • server/gql/scene.graphql (1 hunks)
  • server/gql/tag.graphql (0 hunks)
  • server/gql/workspace.graphql (2 hunks)
  • server/gqlgen.yml (0 hunks)
  • server/internal/adapter/gql/context.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/dataloader.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/datasetloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/datasetschemaloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/taggrouploader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/tagitemloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/tagloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_asset.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_dataset.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_layer.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_plugin.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_scene.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_tag.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/models.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/models_gen.go (6 hunks)
  • server/internal/adapter/gql/loader.go (0 hunks)
  • server/internal/adapter/gql/loader_asset.go (2 hunks)
  • server/internal/adapter/gql/loader_dataset.go (0 hunks)
  • server/internal/adapter/gql/loader_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_dataset.go (0 hunks)
  • server/internal/adapter/gql/resolver_dataset_schema.go (0 hunks)
  • server/internal/adapter/gql/resolver_layer.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_asset.go (2 hunks)
  • server/internal/adapter/gql/resolver_mutation_dataset.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_layer.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_project.go (3 hunks)
  • server/internal/adapter/gql/resolver_mutation_scene.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_property.go (0 hunks)
  • server/internal/adapter/gql/resolver_property_test.go (0 hunks)
  • server/internal/adapter/gql/resolver_query.go (1 hunks)
  • server/internal/adapter/gql/resolver_scene.go (0 hunks)
  • server/internal/adapter/gql/resolver_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_team.go (1 hunks)
  • server/internal/infrastructure/memory/asset.go (1 hunks)
  • server/internal/infrastructure/mongo/asset.go (2 hunks)
  • server/internal/infrastructure/mongo/mongodoc/asset.go (3 hunks)
  • server/internal/usecase/interactor/asset.go (4 hunks)
  • server/internal/usecase/interactor/layer.go (0 hunks)
  • server/internal/usecase/interactor/scene.go (0 hunks)
  • server/internal/usecase/interactor/scene_test.go (0 hunks)
  • server/internal/usecase/interfaces/asset.go (2 hunks)
  • server/internal/usecase/repo/asset.go (1 hunks)
  • server/pkg/asset/asset.go (2 hunks)
  • server/pkg/asset/builder.go (1 hunks)
  • server/pkg/asset/id.go (1 hunks)
  • server/pkg/layer/encoding/exporter.go (1 hunks)
  • server/pkg/layer/layerops/processor_test.go (0 hunks)
  • server/pkg/scene/builder/builder.go (1 hunks)
  • server/pkg/scene/builder/builder_test.go (0 hunks)
  • server/pkg/scene/builder/encoder.go (1 hunks)
  • server/pkg/scene/builder/encoder_test.go (0 hunks)
  • server/pkg/scene/builder/scene.go (1 hunks)
  • web/src/beta/features/AssetsManager/hooks.ts (2 hunks)
  • web/src/beta/features/AssetsManager/index.tsx (3 hunks)
  • web/src/beta/features/Dashboard/ContentsContainer/Assets/index.tsx (2 hunks)
  • web/src/services/api/assetsApi.ts (2 hunks)
  • web/src/services/api/propertyApi/utils.ts (1 hunks)
  • web/src/services/api/sceneApi.ts (0 hunks)
  • web/src/services/gql/fragments/dataset.ts (0 hunks)
⛔ Files not processed due to max files limit (5)
  • web/src/services/gql/fragments/index.ts
  • web/src/services/gql/fragments/layer.ts
  • web/src/services/gql/fragments/property.ts
  • web/src/services/gql/queries/asset.ts
  • web/src/services/gql/queries/scene.ts
💤 Files with no reviewable changes (45)
  • server/internal/adapter/gql/context.go
  • server/internal/adapter/gql/gqlmodel/convert_scene.go
  • server/internal/adapter/gql/gqldataloader/dataloader.go
  • web/src/services/gql/fragments/dataset.ts
  • server/internal/adapter/gql/gqlmodel/convert.go
  • server/internal/usecase/interactor/scene_test.go
  • server/pkg/scene/builder/builder_test.go
  • web/src/services/api/sceneApi.ts
  • server/pkg/layer/layerops/processor_test.go
  • server/internal/adapter/gql/gqlmodel/convert_plugin.go
  • server/gql/property.graphql
  • server/internal/adapter/gql/resolver_property_test.go
  • server/pkg/scene/builder/encoder_test.go
  • server/internal/usecase/interactor/scene.go
  • server/gqlgen.yml
  • server/internal/adapter/gql/gqlmodel/convert_layer.go
  • server/gql/cluster.graphql
  • server/internal/adapter/gql/resolver_scene.go
  • server/e2e/gql_layer_test.go
  • server/internal/adapter/gql/resolver_layer.go
  • server/internal/adapter/gql/gqlmodel/convert_tag.go
  • server/e2e/gql_nlslayer_test.go
  • server/e2e/dataset_export_test.go
  • server/internal/usecase/interactor/layer.go
  • server/gql/dataset.graphql
  • server/internal/adapter/gql/gqlmodel/convert_dataset.go
  • server/internal/adapter/gql/gqlmodel/models.go
  • server/internal/adapter/gql/resolver_mutation_scene.go
  • server/internal/adapter/gql/resolver_mutation_tag.go
  • server/internal/adapter/gql/loader.go
  • server/internal/adapter/gql/resolver_mutation_layer.go
  • server/internal/adapter/gql/resolver_dataset.go
  • server/internal/adapter/gql/resolver_mutation_dataset.go
  • server/internal/adapter/gql/resolver_dataset_schema.go
  • server/internal/adapter/gql/gqldataloader/taggrouploader_gen.go
  • server/internal/adapter/gql/gqldataloader/tagloader_gen.go
  • server/internal/adapter/gql/gqldataloader/tagitemloader_gen.go
  • server/internal/adapter/gql/resolver_tag.go
  • server/gql/tag.graphql
  • server/internal/adapter/gql/resolver_property.go
  • server/internal/adapter/gql/gqldataloader/datasetloader_gen.go
  • server/gql/layer.graphql
  • server/internal/adapter/gql/loader_tag.go
  • server/internal/adapter/gql/gqldataloader/datasetschemaloader_gen.go
  • server/internal/adapter/gql/loader_dataset.go
✅ Files skipped from review due to trivial changes (1)
  • server/pkg/scene/builder/encoder.go
🧰 Additional context used
📓 Learnings (1)
server/internal/adapter/gql/resolver_mutation_project.go (1)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:215-217
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project import functionality, the file names are unique and can be safely replaced uniformly using `bytes.Replace`.
🪛 GitHub Actions: ci
web/src/beta/features/AssetsManager/hooks.ts

[error] 212-212: React Hook useCallback has a missing dependency: 'projectId'. Either include it or remove the dependency array.

server/e2e/seeder.go

[warning] 28-28: var sID is unused.

🪛 golangci-lint (1.62.2)
server/e2e/seeder.go

28-28: var sID is unused

(unused)

🪛 GitHub Check: ci-server / ci-server-lint
server/e2e/seeder.go

[failure] 28-28:
var sID is unused (unused)

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Redirect rules - reearth-web
  • GitHub Check: Header rules - reearth-web
  • GitHub Check: Pages changed - reearth-web
🔇 Additional comments (55)
server/pkg/scene/builder/scene.go (2)

11-25: LGTM! Clean removal of deprecated fields.

The removal of Layers, Tags, and Clusters fields from the sceneJSON struct aligns with the broader refactoring effort to simplify the data model.


27-40: LGTM! Method signature updated correctly.

The sceneJSON method has been updated to remove the layers parameter while maintaining all essential functionality.

server/e2e/gql_project_import_test.go (1)

80-82: LGTM! Clean formatting adjustment.

The formatting change improves code readability without affecting functionality.

server/gql/scene.graphql (2)

1-17: LGTM! Scene type simplified effectively.

The removal of deprecated fields (rootLayerId, rootLayer, datasetSchemas, tagIds, tags, clusters) streamlines the Scene type while maintaining all essential functionality.


50-52: LGTM! Query formatting improved.

The formatting change in the Query type extension improves readability.

server/gql/plugin.graphql (2)

20-30: LGTM! Removed deprecated extension type.

The removal of the Cluster extension type is consistent with the broader refactoring effort.


99-109: LGTM! Improved formatting.

The formatting changes in the Query and Mutation type extensions improve code readability.

server/e2e/seeder.go (1)

1-91: Verify alignment with PR objectives.

The changes in this file primarily involve removing dataset-related code, but the PR objectives focus on adding project asset association functionality. Please verify if additional test setup code is needed to cover the new project asset association feature.

Would you like me to help generate test setup code for the new project asset association feature?

🧰 Tools
🪛 golangci-lint (1.62.2)

28-28: var sID is unused

(unused)

🪛 GitHub Check: ci-server / ci-server-lint

[failure] 28-28:
var sID is unused (unused)

🪛 GitHub Actions: ci

[warning] 28-28: var sID is unused.

server/internal/adapter/gql/gqlmodel/models_gen.go (5)

138-138: Ensure optional field is properly handled.
The addition of ProjectID as an optional field aligns with the PR objectives for associating assets with projects. Please verify that usage in resolvers or services properly accounts for the possibility of a nil project reference.


186-186: Confirm validation for projectId in creation flow.
Adding projectId to CreateAssetInput is consistent with the new association feature. Ensure relevant logic (e.g., create mutation) properly validates when projectId is omitted or points to a non-existent project.


581-588: Struct changes look good.
These newly introduced fields in MergedPropertyField seem consistent with the merged property concept. No issues detected.


919-921: New fields appear consistent.
These new fields in PropertyFieldLink correctly reflect optional vs. required attributes.


1834-1834: Enum coverage update looks valid.
Including PluginExtensionTypeInfoboxBlock in the IsValid switch branch is appropriate.

server/pkg/scene/builder/builder.go (1)

176-181: Verify layer export functionality after encoder removal.

The removal of the encoder and direct usage of the exporter changes how layers are processed. While the code looks correct, we should verify that:

  1. All layer data is properly exported via ExportLayerByID
  2. The scene JSON generation receives all necessary layer information through the context
✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Search for any direct encoder usage that might have been missed
rg -A 5 "encoder\." server/pkg/scene/

# Check for any layer-related tests that might need updates
fd -e go -e test.go -x rg -l "TestBuild.*Scene|TestExport.*Layer" {} \;

Length of output: 154


🏁 Script executed:

#!/bin/bash
# Check the definition of ExportLayerByID in the repository to inspect how it handles layer data.
rg -A 20 "func.*ExportLayerByID" -n .

# Check the implementation of sceneJSON within the Builder to verify that it processes the exported layer data correctly.
rg -A 20 "func (b *Builder) sceneJSON" -n server/pkg/scene/builder/builder.go

Length of output: 1450


Layer export functionality verified post-encoder removal.

The exported layer data is now processed via b.exporter.ExportLayerByID, which internally merges and encodes the layer correctly. No residual usage of the encoder was found, and the existing tests (e.g., in server/pkg/property/builder_test.go) confirm that scene construction via sceneJSON receives the necessary layer data.

server/e2e/gql_asset_test.go (7)

16-18: Rename is appropriate but consider clarifying doc comments.

Renaming the test function to TestCoreSupportGetAssets aligns better with its purpose of testing core support for created assets. Consider adding a short comment describing the test's high-level objective for future maintainers.


113-113: Enforce domain constraints on workspace searching.

When calling FindByWorkspace() with a nil project filter, ensure the rest of the domain logic still enforces correctness (like workspace membership, valid statuses, etc.).


121-122: Verify consistency of coreSupport in results.

The test strictly checks coreSupport == true for every returned asset. If some assets are intended to have false, confirm this query or filter is correct; otherwise, the test might mask unexpected data.


129-171: Test covers multiple project associations thoroughly.

The TestAssociateProjectGetAssets function offers good coverage for assets without a project and with different projects. Consider adding error-path checks (e.g., invalid project IDs) to bolster reliability.


173-190: GraphQL mutation structure looks correct.

Adding projectId to CreateAssetInput is consistent with the PR goal. Confirm all references to this mutation in the codebase or frontend are updated to avoid signature mismatches.


192-214: Optional projectId parameter is handled properly.

Allowing nil for projectId is logical for workspace-level assets. Validating it in the create function can prevent invalid references (e.g., unknown project).


245-308: Query is comprehensive but watch performance.

The getAssets query includes pagination, filtering, and many fields. While thorough, ensure performance is acceptable under large data loads; you might consider smaller queries or only requesting necessary fields in certain contexts.

server/e2e/gql_custom_property_test.go (1)

347-762: Extensive multi-fragment GraphQL query is well-structured.

Using fragments for PropertyFragment, WidgetAlignSystemFragment, etc., helps keep the query organized. However, it's quite large. If performance or clarity issues arise, consider splitting queries by domain area or introducing smaller resolvers.

server/e2e/gql_storytelling_test.go (2)

735-741: Refactored configuration for test initialization.

Separating configuration into a variable c before calling StartServerAndRepos improves readability and maintainability. Ensure any additional test config toggles remain consistent across all tests.


770-840: Confirm published JSON matches updated schema.

The large JSON structure checks many fields, including new properties like coreSupport. If your schema evolves, remember to adjust these expected values to prevent brittle tests. Also, consider verifying the final published file includes all required fields (e.g., property.tiles).

server/internal/usecase/repo/asset.go (1)

20-20: LGTM! The interface change aligns with PR objectives.

The updated FindByWorkspace method signature correctly adds project filtering capability while maintaining backward compatibility with an optional project ID parameter.

server/pkg/layer/encoding/exporter.go (2)

37-42: Verify the intentional removal of encoding functionality.

The Encode method now returns nil without performing any operations. This could lead to silent failures as callers expect encoding to happen.

Please confirm:

  1. Is this change intentional?
  2. Should we add a warning log or error return to indicate that encoding is not implemented?
  3. Should we update the method documentation to reflect this change?

10-13: Consider documenting the removal of encoding functionality.

The Encoder field has been removed from the Exporter struct, significantly changing its behavior.

Let's verify if any callers of this struct need to be updated:

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Search for usages of Exporter struct and Encode method
rg -A 5 "Exporter.*{|Encode\(" --type go

Length of output: 156


🏁 Script executed:

#!/bin/bash
# Search for the Exporter struct definition to confirm its current fields
rg -A 5 "type Exporter struct" --type go

# Search for any calls to an Encode method (which might be referencing the removed Encoder functionality)
rg -A 5 "\.Encode\(" --type go

# Additionally, search for any references to the removed 'Encoder' field in usages of Exporter
rg -A 5 "Exporter.*Encoder" --type go

Length of output: 5000


No caller updates are necessary since the new Encode method is properly used and there are no references to the previously removed Encoder field.

  • The grep output confirms that the Exporter struct now only contains the Merger and Sealer fields.
  • All invocations of encoding functionality directly call the Encode method on the Exporter instance.
  • There are no lingering references to an Encoder field anywhere in the codebase.

It would still be beneficial to add a documentation note describing the removal of the Encoder field and how encoding is now handled.

server/pkg/asset/id.go (1)

10-10: LGTM! Well-structured ID type definitions.

The new ProjectID type and associated helper functions follow consistent naming patterns and maintain code organization.

Also applies to: 14-14, 18-18, 22-22, 26-26

web/src/beta/features/Dashboard/ContentsContainer/Assets/index.tsx (1)

7-10: LGTM! Clean implementation of project association in the UI.

The changes correctly implement project filtering while maintaining backward compatibility with the optional projectId prop.

Also applies to: 12-12, 29-31

server/pkg/asset/asset.go (1)

18-18: LGTM! Clean implementation of project association.

The optional project field and its getter method are well-implemented, following the established patterns in the codebase.

Also applies to: 34-36

server/internal/usecase/interfaces/asset.go (1)

28-28: Verify implementations of the updated interface methods.

The interface changes look good, but please ensure all implementations of the Asset interface have been updated to match these changes:

  • FindByWorkspace with new ProjectID parameter
  • UploadAssetFile with new ProjectID parameter

Also applies to: 39-39, 42-42

server/internal/adapter/gql/resolver_team.go (1)

27-34: LGTM! The changes correctly implement project filtering for team assets.

The updated Assets method signature and implementation properly handle the optional project ID parameter, aligning with the PR objectives.

server/pkg/asset/builder.go (1)

57-60: LGTM! The Project method follows the builder pattern consistently.

The implementation maintains the fluent interface pattern used throughout the builder, correctly handling the optional project ID.

server/internal/infrastructure/mongo/mongodoc/asset.go (3)

17-17: LGTM! Project field added to AssetDocument.

The field is correctly defined as an optional string pointer, consistent with MongoDB document patterns.


35-40: LGTM! Project ID conversion is properly handled in NewAsset.

The implementation correctly handles the optional project ID conversion to string format for MongoDB storage.


65-72: LGTM! Model method properly handles project ID conversion.

The implementation includes proper error handling for project ID conversion from MongoDB format.

server/internal/adapter/gql/loader_asset.go (2)

39-54: LGTM! Project ID handling is properly implemented.

The implementation includes proper error handling for project ID conversion and maintains consistency with other ID handling patterns.


60-66: Nice optimization of edge creation!

Pre-allocating the edges slice and using an indexed loop improves performance by avoiding slice growth operations.

server/e2e/gql_validate_geojson_test.go (1)

186-186: LGTM! Test updated correctly for new API signature.

The test has been properly updated to include the new projectId parameter as nil, maintaining backward compatibility.

web/src/services/api/assetsApi.ts (1)

74-76: LGTM! Correctly added projectId to asset creation.

The projectId parameter has been properly integrated into the asset creation flow.

server/internal/usecase/interactor/asset.go (1)

39-51: Consider adding project ID validation.

The FindByWorkspace implementation should validate that the project belongs to the workspace when a project ID is provided.

server/internal/infrastructure/mongo/asset.go (2)

67-67: LGTM! Method signature updated to support project-based filtering.

The addition of projectId parameter aligns with the PR objective of associating assets with projects.


72-80: LGTM! Filter construction handles both project and workspace scenarios.

The implementation correctly:

  • Uses project ID for filtering when provided
  • Falls back to workspace ID when project ID is not provided
server/internal/adapter/gql/resolver_query.go (1)

15-16: LGTM! GraphQL resolver updated to support project-based asset filtering.

The changes correctly propagate the projectId parameter to the asset loader.

web/src/beta/features/AssetsManager/index.tsx (2)

24-24: LGTM! Type definition updated to support project association.

The optional projectId prop allows the component to be used in both project-specific and workspace-wide contexts.


37-37: LGTM! Component correctly handles the projectId prop.

The implementation properly:

  • Destructures projectId from props
  • Passes it to useHooks for handling asset operations

Also applies to: 69-69

web/src/beta/features/AssetsManager/hooks.ts (1)

30-31: LGTM!

The optional projectId parameter has been correctly added to both the function signature and type definition.

Also applies to: 38-39

server/gql/asset.graphql (3)

5-5: LGTM!

The optional projectId field has been correctly added to the Asset type.


24-24: LGTM!

The optional projectId field has been correctly added to the CreateAssetInput type.


65-65: LGTM!

The optional projectId parameter has been correctly added to the assets query.

server/gql/workspace.graphql (3)

8-14: Enhance Asset Query with Optional 'projectId' Parameter
The assets field now accepts an optional projectId parameter, which will enable filtering assets by their associated project. Please ensure that the corresponding resolver logic is updated to handle this parameter.


15-21: Consistent Formatting for Projects Field
The parameters for the projects field have been reformatted for improved readability. There are no functional changes, so this update is acceptable.


121-123: Refactored Mutation Field for removeMemberFromTeam
The formatting for the removeMemberFromTeam mutation has been updated to a multi-line structure. This change improves readability while preserving functionality.

server/Makefile (2)

85-90: New 'run-reset' Target Added
The run-reset target has been introduced to reset the MongoDB environment by stopping the container (reearth-visualizer-reearth-mongo-1), removing old data directories, and restarting the database along with initializing a mock user. Please verify that the Docker container name is consistent with your deployment configuration. As a future improvement, consider adding error handling (e.g., checking if the container is running before attempting to stop it) to make the process more robust.


112-112: Updated .PHONY Declaration
Including run-reset in the .PHONY declaration ensures that the target is always executed correctly. This update is correct and helps prevent potential issues with file conflicts.

server/e2e/seeder.go Outdated Show resolved Hide resolved
server/internal/usecase/interactor/asset.go Show resolved Hide resolved
web/src/services/api/propertyApi/utils.ts Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (4)
web/src/services/gql/queries/asset.ts (1)

58-66: Consider adding error handling to the UPDATE_ASSET mutation.

The new mutation looks good, but it would be beneficial to include error information in the response.

 export const UPDATE_ASSET = gql(`
   mutation UpdateAsset($assetId: ID!, $projectId: ID) {
     updateAsset(input: { assetId: $assetId projectId: $projectId }) {
       assetId
       projectId
+      errors {
+        message
+        path
+      }
       __typename
     }
   }
 `);
server/e2e/common.go (2)

288-293: Enhance error handling in JSONEqRegexpValue.

The function could be more robust by handling additional types and providing better error feedback.

 func JSONEqRegexpValue(t *testing.T, actual *httpexpect.Value, expected string) bool {
+    if actual == nil {
+        t.Error("actual value is nil")
+        return false
+    }
     if actualData, ok := actual.Raw().(map[string]interface{}); ok {
         return JSONEqRegexpInterface(t, actualData, expected)
     }
+    t.Errorf("expected map[string]interface{}, got %T", actual.Raw())
     return false
 }

305-319: Improve error handling in ValueDump.

The function silently ignores marshaling errors and could provide better feedback.

 func ValueDump(val *httpexpect.Value) {
+    if val == nil {
+        fmt.Println("Error: nil value")
+        return
+    }
     raw := val.Raw()
     switch data := raw.(type) {
     case map[string]interface{}:
         if text, err := json.MarshalIndent(data, "", "  "); err == nil {
             fmt.Println(string(text))
+        } else {
+            fmt.Printf("Error marshaling map: %v\n", err)
         }
     case []interface{}:
         if text, err := json.MarshalIndent(data, "", "  "); err == nil {
             fmt.Println(string(text))
+        } else {
+            fmt.Printf("Error marshaling array: %v\n", err)
         }
     default:
         fmt.Println("Unsupported type:", reflect.TypeOf(raw))
     }
 }
server/e2e/gql_asset_test.go (1)

268-271: Remove commented code.

The commented code block is unnecessary and should be removed for better code cleanliness.

-            // "input": map[string]interface{}{
-            // 	"assetId": assetId,
-            // },
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e876288 and 7ff5ecd.

⛔ Files ignored due to path filters (2)
  • web/src/services/gql/__gen__/gql.ts is excluded by !**/__gen__/**
  • web/src/services/gql/__gen__/graphql.ts is excluded by !**/__gen__/**
📒 Files selected for processing (11)
  • server/e2e/common.go (4 hunks)
  • server/e2e/gql_asset_test.go (7 hunks)
  • server/gql/asset.graphql (5 hunks)
  • server/internal/adapter/gql/gqlmodel/models_gen.go (7 hunks)
  • server/internal/adapter/gql/resolver_mutation_asset.go (3 hunks)
  • server/internal/usecase/interactor/asset.go (6 hunks)
  • server/internal/usecase/interfaces/asset.go (2 hunks)
  • server/pkg/asset/asset.go (2 hunks)
  • web/src/beta/features/AssetsManager/hooks.ts (2 hunks)
  • web/src/beta/ui/fields/AssetField/useAssetUpload.ts (1 hunks)
  • web/src/services/gql/queries/asset.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/beta/features/AssetsManager/hooks.ts
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Redirect rules - reearth-web
  • GitHub Check: Header rules - reearth-web
  • GitHub Check: Pages changed - reearth-web
🔇 Additional comments (14)
server/internal/adapter/gql/gqlmodel/models_gen.go (5)

906-915: Use a consistent type for ID in PropertyField.

The ID field in the PropertyField struct is declared as string, while other ID fields use the custom ID type. For consistency and type safety, consider changing it to use the ID type.


138-138: Addition of ProjectID to Asset struct.

The optional ProjectID field has been added to the Asset struct to associate assets with projects. Ensure that this addition aligns with the overall data model and that any serialization or validation logic accounts for this new field.


186-186: Update CreateAssetInput with optional projectId.

The CreateAssetInput struct now includes an optional projectId field. This allows for the creation of assets associated with a specific project. Confirm that all GraphQL mutations using this input handle the new field correctly and that appropriate validations are in place.


1330-1333: Introduce UpdateAssetInput with optional projectId.

A new UpdateAssetInput type with an optional projectId field has been added. This enables updating an asset's associated project. Verify that the update operations correctly handle changes to the projectId and maintain data integrity.


581-589: Verify the correctness of added fields in MergedPropertyField.

New fields have been added to the MergedPropertyField struct, including SchemaID, FieldID, Value, Type, Links, Overridden, Schema, and Field. Ensure that these fields are properly initialized and integrated within the system's property merging logic.

server/pkg/asset/asset.go (2)

18-18: Addition of optional project field to Asset struct.

The Asset struct now includes an optional project field of type *ProjectID, allowing assets to be associated with a project. Verify that this new field is correctly handled throughout the codebase, especially during asset creation and serialization.


34-40: Implement Project() and SetProject() methods in Asset.

The new methods Project() and SetProject() provide access to the project field. Ensure that these methods are used appropriately and that they do not introduce unintended side effects. Additionally, confirm that any concurrency considerations are addressed if the asset may be accessed by multiple goroutines.

server/internal/usecase/interfaces/asset.go (2)

28-28: Include ProjectID in CreateAssetParam.

The CreateAssetParam struct now includes a ProjectID field. Ensure that this parameter is correctly utilized in asset creation workflows and that necessary validations are in place when associating an asset with a project.


41-41: Add Update method to Asset interface.

A new Update method has been added to the Asset interface to allow updating an asset's ProjectID. Ensure that this method is implemented consistently across all implementations of the interface and that it properly handles concurrent access and error conditions.

web/src/services/gql/queries/asset.ts (1)

4-5: LGTM! The query parameters have been updated correctly.

The GET_ASSETS query has been enhanced with an optional projectId parameter, allowing for project-specific asset filtering.

server/internal/adapter/gql/resolver_mutation_asset.go (1)

19-26: Add validation for project-workspace relationship.

The CreateAsset resolver should verify that the provided project belongs to the workspace before creating the asset.

server/internal/usecase/interactor/asset.go (1)

108-127: Consider adding transaction rollback on error.

The Update method uses a transaction but doesn't explicitly handle rollback on error. While the transaction wrapper might handle this, it would be good to verify the behavior.

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Check how transactions are handled in the codebase
rg "Transaction\(\)" -A 10 --type go

Length of output: 7950


Transaction Rollback Is Handled by the Transaction Wrapper

The transaction infrastructure (using Usecase().Transaction() in combination with Run2) automatically rolls back the transaction upon error. This behavior is confirmed by tests in server/internal/usecase/interactor/usecase_test.go where error cases assert that the transaction is not committed.

server/e2e/gql_asset_test.go (1)

131-195: LGTM! Comprehensive test coverage for project association.

The test cases thoroughly cover:

  • Asset creation with project association
  • Asset retrieval by project
  • Asset movement between projects
  • Asset removal from projects
  • Asset deletion
server/gql/asset.graphql (1)

1-87: LGTM! Well-structured GraphQL schema changes.

The changes consistently implement project association across all relevant types and operations:

  • Asset type includes optional projectId
  • Input types properly handle projectId
  • Query and mutation operations support project filtering

server/internal/usecase/interfaces/asset.go Show resolved Hide resolved
server/internal/usecase/interfaces/asset.go Show resolved Hide resolved
web/src/beta/ui/fields/AssetField/useAssetUpload.ts Outdated Show resolved Hide resolved
server/internal/usecase/interactor/asset.go Outdated Show resolved Hide resolved
@hexaforce hexaforce force-pushed the feat/associate-project-asset branch from d6b5ab7 to ae2ce12 Compare February 6, 2025 04:52
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
server/internal/adapter/gql/gqlmodel/models_gen.go (1)

906-915: 🛠️ Refactor suggestion

Use consistent type for ID in PropertyField

The ID field in the PropertyField struct is declared as string, whereas other ID fields use the custom ID type. For consistency and improved type safety, consider changing the ID field to use the ID type.

Apply this diff to align the ID type:

-	ID       string               `json:"id"`
+	ID       ID                   `json:"id"`
🧹 Nitpick comments (6)
server/internal/adapter/gql/resolver_team.go (1)

27-34: Consider documenting pagination parameters.

The implementation correctly handles the new projectID parameter, but the pagination parameters (first, last, after, before) could benefit from documentation explaining their usage and limitations.

server/internal/infrastructure/mongo/asset.go (1)

67-103: Improve filter construction for better maintainability.

The current implementation has separate branches for project and team filtering. Consider using a more maintainable approach by building the filter dynamically.

 func (r *Asset) FindByWorkspace(ctx context.Context, id accountdomain.WorkspaceID, projectId *id.ProjectID, uFilter repo.AssetFilter) ([]*asset.Asset, *usecasex.PageInfo, error) {
 	if !r.f.CanRead(id) {
 		return nil, usecasex.EmptyPageInfo(), nil
 	}

-	filter := bson.M{
-		"coresupport": true,
-	}
-
-	if projectId != nil {
-		filter["project"] = projectId.String()
-	} else {
-		filter["team"] = id.String()
-	}
+	filter := bson.M{"coresupport": true}
+	
+	// Build filter based on project or team
+	if projectId != nil {
+		filter["project"] = projectId.String()
+		filter["team"] = id.String() // Always include team check for security
+	} else {
+		filter["team"] = id.String()
+	}
server/e2e/common.go (2)

288-294: Enhance JSON comparison to handle more types.

The function only handles map[string]interface{} but should support arrays and other JSON types for better test coverage.

 func JSONEqRegexpValue(t *testing.T, actual *httpexpect.Value, expected string) bool {
-	if actualData, ok := actual.Raw().(map[string]interface{}); ok {
-		return JSONEqRegexpInterface(t, actualData, expected)
+	switch actualData := actual.Raw().(type) {
+	case map[string]interface{}, []interface{}:
+		return JSONEqRegexpInterface(t, actualData, expected)
+	default:
+		t.Errorf("Unsupported type for JSON comparison: %T", actualData)
+		return false
 	}
-	return false
 }

305-319: Reduce code duplication in JSON marshaling.

The JSON marshaling logic is duplicated for map and slice types. Consider extracting it to a helper function.

+func marshalAndPrint(data interface{}) {
+	if text, err := json.MarshalIndent(data, "", "  "); err == nil {
+		fmt.Println(string(text))
+	}
+}
+
 func ValueDump(val *httpexpect.Value) {
 	raw := val.Raw()
 	switch data := raw.(type) {
 	case map[string]interface{}:
-		if text, err := json.MarshalIndent(data, "", "  "); err == nil {
-			fmt.Println(string(text))
-		}
+		marshalAndPrint(data)
 	case []interface{}:
-		if text, err := json.MarshalIndent(data, "", "  "); err == nil {
-			fmt.Println(string(text))
-		}
+		marshalAndPrint(data)
 	default:
 		fmt.Println("Unsupported type:", reflect.TypeOf(raw))
 	}
 }
server/internal/adapter/gql/resolver_mutation_project.go (1)

287-292: Consider enhancing error message.

The error message could be more descriptive to help with debugging.

-			return nil, errors.New("Fail UploadAssetFile :" + err.Error())
+			return nil, errors.New("Failed to parse project ID during asset upload: " + err.Error())
server/internal/adapter/gql/gqlmodel/models_gen.go (1)

1844-1845: Ensure consistent string values in PluginExtensionType constants

The new PluginExtensionType constants have mixed-case string values (e.g., "Story", "StoryPage"), while existing constants use uppercase (e.g., "PRIMITIVE", "WIDGET"). For consistency and to prevent potential issues during serialization, consider updating the string values of the new constants to uppercase.

Apply this diff to standardize the string values:

-	PluginExtensionTypeStory        PluginExtensionType = "Story"
-	PluginExtensionTypeStoryPage    PluginExtensionType = "StoryPage"
-	PluginExtensionTypeStoryBlock   PluginExtensionType = "StoryBlock"
-	PluginExtensionTypeInfoboxBlock PluginExtensionType = "InfoboxBlock"
+	PluginExtensionTypeStory        PluginExtensionType = "STORY"
+	PluginExtensionTypeStoryPage    PluginExtensionType = "STORYPAGE"
+	PluginExtensionTypeStoryBlock   PluginExtensionType = "STORYBLOCK"
+	PluginExtensionTypeInfoboxBlock PluginExtensionType = "INFOBOXBLOCK"

Also, update the IsValid method and any references to these constants accordingly.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ff5ecd and 5e732cf.

📒 Files selected for processing (23)
  • server/e2e/common.go (4 hunks)
  • server/e2e/gql_asset_test.go (7 hunks)
  • server/e2e/gql_validate_geojson_test.go (1 hunks)
  • server/e2e/seeder.go (0 hunks)
  • server/gql/asset.graphql (5 hunks)
  • server/gql/workspace.graphql (2 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_asset.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/models_gen.go (7 hunks)
  • server/internal/adapter/gql/loader_asset.go (2 hunks)
  • server/internal/adapter/gql/resolver_mutation_asset.go (3 hunks)
  • server/internal/adapter/gql/resolver_mutation_project.go (3 hunks)
  • server/internal/adapter/gql/resolver_query.go (1 hunks)
  • server/internal/adapter/gql/resolver_team.go (1 hunks)
  • server/internal/infrastructure/memory/asset.go (1 hunks)
  • server/internal/infrastructure/mongo/asset.go (2 hunks)
  • server/internal/infrastructure/mongo/mongodoc/asset.go (3 hunks)
  • server/internal/usecase/interactor/asset.go (5 hunks)
  • server/internal/usecase/interfaces/asset.go (2 hunks)
  • server/internal/usecase/repo/asset.go (1 hunks)
  • server/pkg/asset/asset.go (2 hunks)
  • server/pkg/asset/builder.go (1 hunks)
  • server/pkg/asset/id.go (1 hunks)
  • server/pkg/layer/layerops/processor_test.go (0 hunks)
💤 Files with no reviewable changes (2)
  • server/pkg/layer/layerops/processor_test.go
  • server/e2e/seeder.go
🚧 Files skipped from review as they are similar to previous changes (9)
  • server/internal/adapter/gql/gqlmodel/convert_asset.go
  • server/pkg/asset/builder.go
  • server/internal/usecase/repo/asset.go
  • server/internal/infrastructure/memory/asset.go
  • server/pkg/asset/asset.go
  • server/gql/asset.graphql
  • server/internal/infrastructure/mongo/mongodoc/asset.go
  • server/e2e/gql_validate_geojson_test.go
  • server/internal/adapter/gql/loader_asset.go
🧰 Additional context used
📓 Learnings (1)
server/internal/adapter/gql/resolver_mutation_project.go (1)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:215-217
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project import functionality, the file names are unique and can be safely replaced uniformly using `bytes.Replace`.
🔇 Additional comments (19)
server/pkg/asset/id.go (1)

10-10: LGTM! Well-structured ID type implementation.

The new ProjectID type and its associated functions follow the established patterns consistently.

Also applies to: 14-14, 18-18, 22-22, 26-26

server/internal/usecase/interfaces/asset.go (2)

28-29: LGTM! ProjectID field added to CreateAssetParam.

The addition follows the struct's existing pattern.


39-39: Verify interface implementations for updated method signatures.

The Asset interface has been updated with ProjectID parameters. Ensure all implementations (mongo, memory, etc.) are updated accordingly.

Also applies to: 41-41, 43-43

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Search for Asset interface implementations
ast-grep --pattern 'type $_ struct {
  $$$
}

func (r $_) FindByWorkspace(ctx context.Context, wid accountdomain.WorkspaceID, pid *id.ProjectID, $$$) ($$$) {
  $$$
}'

Length of output: 168


I'm going to run an additional search using rg to list all occurrences of FindByWorkspace method definitions in the repository. This should help us verify if the implementations have been updated to the new signature with the ProjectID parameter.


🏁 Script executed:

#!/bin/bash
# Search for all FindByWorkspace definitions with context.
rg "func .*FindByWorkspace\(.*\)" -A10 -B10 .

Length of output: 16701


Interface implementations for FindByWorkspace have been updated correctly.

  • The memory implementation in server/internal/infrastructure/memory/asset.go accepts the new *id.ProjectID parameter.
  • The Mongo implementation in server/internal/infrastructure/mongo/asset.go similarly reflects the updated signature.
  • The usecase and gateway layers (e.g., in server/internal/usecase/interactor/asset.go and the GraphQL loader) also pass the parameter as expected.

All implementations appear to be consistent with the new interface signature.

server/internal/adapter/gql/resolver_mutation_asset.go (1)

41-62: LGTM! UpdateAsset implementation with validation.

The implementation includes proper validation of project-workspace relationship as suggested in past reviews.

server/internal/usecase/interactor/asset.go (3)

39-51: LGTM! Method signature updated to support project filtering.

The changes correctly implement project-based filtering for assets by passing the project ID to the repository layer.


88-92: LGTM! Asset creation updated to include project association.

The asset builder chain now correctly includes the project ID when creating new assets.


154-182: Add validation for project-workspace relationship.

The UploadAssetFile method should verify that the provided project belongs to the workspace before creating the asset.

 func (i *Asset) UploadAssetFile(ctx context.Context, name string, zipFile *zip.File, workspace idx.ID[accountdomain.Workspace], project *id.ProjectID) (*url.URL, int64, error) {
+	if project != nil {
+		p, err := i.repos.Project.FindByID(ctx, *project)
+		if err != nil {
+			return nil, 0, fmt.Errorf("failed to validate project: %w", err)
+		}
+		if p.Workspace() != workspace {
+			return nil, 0, fmt.Errorf("project does not belong to workspace")
+		}
+	}
server/internal/adapter/gql/resolver_query.go (1)

15-17: LGTM! GraphQL resolver updated to support project filtering.

The Assets query resolver correctly handles the new projectId parameter.

server/e2e/gql_asset_test.go (4)

16-17: LGTM! Clear test description.

The test description in the comment clearly indicates the test's purpose.


131-195: Well-structured test coverage for project association.

The test comprehensively covers:

  • Asset creation with project association
  • Asset retrieval by project
  • Moving assets between projects
  • Removing project association
  • Asset deletion

197-214: LGTM! GraphQL mutation updated correctly.

The CreateAssetMutation has been properly updated to include the projectId parameter.


240-256: LGTM! Clear implementation of asset update mutation.

The updateAsset function is well-implemented with proper error handling.

server/internal/adapter/gql/resolver_mutation_project.go (1)

262-271: LGTM! Improved transaction handling.

The deferred transaction handling ensures proper cleanup in case of errors.

server/gql/workspace.graphql (2)

8-14: LGTM! Well-structured assets query.

The assets query has been properly updated to include the optional projectId parameter, maintaining backward compatibility.


15-21: LGTM! Consistent formatting.

The projects query formatting matches the assets query, maintaining consistency.

server/internal/adapter/gql/gqlmodel/models_gen.go (4)

138-138: Verify integration of the new ProjectID field in Asset and CreateAssetInput structs

The addition of the optional ProjectID field to the Asset and CreateAssetInput structs appears to be correctly implemented. Ensure that all parts of the codebase that create or manipulate assets handle this new field appropriately. This includes validation, serialization, and updates to any API clients that interact with these structs.

Also applies to: 186-186


1330-1337: Ensure correct implementation of UpdateAssetInput and UpdateAssetPayload structs

The introduction of UpdateAssetInput and UpdateAssetPayload structs with an optional ProjectID field extends the asset update functionality. Verify that the mutation resolvers correctly utilize these structs and that the ProjectID field is properly handled during asset updates.


1135-1149: Verify handling of new fields in the Scene struct

The addition of multiple fields to the Scene struct, such as ProjectID, TeamID, Widgets, Plugins, NewLayers, Stories, and Styles, enhances the scene's capabilities. Ensure that these fields are correctly initialized, managed, and serialized throughout the codebase. Also, verify that any associated business logic and database migrations are updated to accommodate these changes.


581-588: Check compatibility of using any as a type

The MergedPropertyField struct uses any (an alias for interface{} introduced in Go 1.18) for the Value field. If the project aims to maintain compatibility with versions of Go prior to 1.18, consider replacing any with interface{}. Ensure consistency across all structs where any is used.

Apply this diff if compatibility with earlier Go versions is required:

-	Value      any                  `json:"value,omitempty"`
+	Value      interface{}          `json:"value,omitempty"`
✅ Verification successful

I'm going to start by checking the Go version required by the project (as specified in the go.mod file) to determine whether using Go 1.18's any alias is acceptable. This will help us know if we need backward compatibility.


🏁 Script executed:

#!/bin/bash
# Check the Go version specified in go.mod
grep "^go " go.mod

Length of output: 60


Let's get more context on the Go version used by the project by checking for Dockerfiles that might specify a Go version.


🏁 Script executed:

#!/bin/bash
# Check for indications of the Go version used in Dockerfiles.
grep -R "FROM golang:" .

Length of output: 81


Compatibility of any is acceptable given the project's Go version

  • The Dockerfile specifies golang:1.23.5-alpine, ensuring the project is built on Go 1.23.5, which fully supports the any alias.
  • No backward compatibility concerns arise as Go versions prior to 1.18 are not targeted.
  • The use of any in the MergedPropertyField struct is consistent with the project's environment.

Comment on lines +19 to +26
var pid *idx.ID[id.Project]
if input.ProjectID != nil {
pidValue, err := gqlmodel.ToID[id.Project](*input.ProjectID)
if err != nil {
return nil, err
}
pid = &pidValue
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add project-workspace validation in CreateAsset.

Similar to the validation added in UpdateAsset (from past reviews), the CreateAsset resolver should also validate the project-workspace relationship.

 var pid *idx.ID[id.Project]
 if input.ProjectID != nil {
     pidValue, err := gqlmodel.ToID[id.Project](*input.ProjectID)
     if err != nil {
         return nil, err
     }
+    // Validate project exists and belongs to the workspace
+    project, err := usecases(ctx).Project.FindByID(ctx, pidValue)
+    if err != nil {
+        return nil, err
+    }
+    if project.Workspace() != tid {
+        return nil, fmt.Errorf("project does not belong to specified workspace")
+    }
     pid = &pidValue
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var pid *idx.ID[id.Project]
if input.ProjectID != nil {
pidValue, err := gqlmodel.ToID[id.Project](*input.ProjectID)
if err != nil {
return nil, err
}
pid = &pidValue
}
var pid *idx.ID[id.Project]
if input.ProjectID != nil {
pidValue, err := gqlmodel.ToID[id.Project](*input.ProjectID)
if err != nil {
return nil, err
}
// Validate project exists and belongs to the workspace
project, err := usecases(ctx).Project.FindByID(ctx, pidValue)
if err != nil {
return nil, err
}
if project.Workspace() != tid {
return nil, fmt.Errorf("project does not belong to specified workspace")
}
pid = &pidValue
}

Comment on lines +108 to +127
func (i *Asset) Update(ctx context.Context, aid id.AssetID, pid *id.ProjectID, operator *usecase.Operator) (id.AssetID, *id.ProjectID, error) {
return Run2(
ctx, operator, i.repos,
Usecase().Transaction(),
func(ctx context.Context) (id.AssetID, *id.ProjectID, error) {
asset, err := i.repos.Asset.FindByID(ctx, aid)
if err != nil {
return aid, pid, err
}

if ok := operator.IsWritableWorkspace(asset.Workspace()); !ok {
return aid, pid, interfaces.ErrOperationDenied
}

asset.SetProject(pid)

return aid, pid, i.repos.Asset.Save(ctx, asset)
},
)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add validation for project-workspace relationship.

The Update method should verify that the provided project belongs to the workspace before updating the asset's project association.

 func (i *Asset) Update(ctx context.Context, aid id.AssetID, pid *id.ProjectID, operator *usecase.Operator) (id.AssetID, *id.ProjectID, error) {
 	return Run2(
 		ctx, operator, i.repos,
 		Usecase().Transaction(),
 		func(ctx context.Context) (id.AssetID, *id.ProjectID, error) {
 			asset, err := i.repos.Asset.FindByID(ctx, aid)
 			if err != nil {
 				return aid, pid, err
 			}

 			if ok := operator.IsWritableWorkspace(asset.Workspace()); !ok {
 				return aid, pid, interfaces.ErrOperationDenied
 			}

+			if pid != nil {
+				project, err := i.repos.Project.FindByID(ctx, *pid)
+				if err != nil {
+					return aid, pid, fmt.Errorf("failed to validate project: %w", err)
+				}
+				if project.Workspace() != asset.Workspace() {
+					return aid, pid, fmt.Errorf("project does not belong to asset's workspace")
+				}
+			}
+
 			asset.SetProject(pid)

 			return aid, pid, i.repos.Asset.Save(ctx, asset)
 		},
 	)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (i *Asset) Update(ctx context.Context, aid id.AssetID, pid *id.ProjectID, operator *usecase.Operator) (id.AssetID, *id.ProjectID, error) {
return Run2(
ctx, operator, i.repos,
Usecase().Transaction(),
func(ctx context.Context) (id.AssetID, *id.ProjectID, error) {
asset, err := i.repos.Asset.FindByID(ctx, aid)
if err != nil {
return aid, pid, err
}
if ok := operator.IsWritableWorkspace(asset.Workspace()); !ok {
return aid, pid, interfaces.ErrOperationDenied
}
asset.SetProject(pid)
return aid, pid, i.repos.Asset.Save(ctx, asset)
},
)
}
func (i *Asset) Update(ctx context.Context, aid id.AssetID, pid *id.ProjectID, operator *usecase.Operator) (id.AssetID, *id.ProjectID, error) {
return Run2(
ctx, operator, i.repos,
Usecase().Transaction(),
func(ctx context.Context) (id.AssetID, *id.ProjectID, error) {
asset, err := i.repos.Asset.FindByID(ctx, aid)
if err != nil {
return aid, pid, err
}
if ok := operator.IsWritableWorkspace(asset.Workspace()); !ok {
return aid, pid, interfaces.ErrOperationDenied
}
if pid != nil {
project, err := i.repos.Project.FindByID(ctx, *pid)
if err != nil {
return aid, pid, fmt.Errorf("failed to validate project: %w", err)
}
if project.Workspace() != asset.Workspace() {
return aid, pid, fmt.Errorf("project does not belong to asset's workspace")
}
}
asset.SetProject(pid)
return aid, pid, i.repos.Asset.Save(ctx, asset)
},
)
}

@hexaforce hexaforce force-pushed the feat/associate-project-asset branch from 5e732cf to c76e941 Compare February 6, 2025 05:46
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
server/internal/adapter/gql/gqlmodel/models_gen.go (1)

906-915: 🛠️ Refactor suggestion

Inconsistent type for 'ID' in 'PropertyField' struct.

The ID field is declared as string, whereas other identifier fields use the custom ID type. For consistency and type safety, consider changing ID to use the ID type.

Apply this diff to fix the inconsistency:

-ID       string               `json:"id"`
+ID       ID                   `json:"id"`
server/internal/usecase/interactor/asset.go (1)

108-127: ⚠️ Potential issue

Add missing project-workspace validation.

The project-workspace validation is missing from the Update method.

As previously suggested in past reviews, add the validation before updating the asset:

 if ok := operator.IsWritableWorkspace(asset.Workspace()); !ok {
   return aid, pid, interfaces.ErrOperationDenied
 }

+if pid != nil {
+  project, err := i.repos.Project.FindByID(ctx, *pid)
+  if err != nil {
+    return aid, pid, fmt.Errorf("failed to validate project: %w", err)
+  }
+  if project.Workspace() != asset.Workspace() {
+    return aid, pid, fmt.Errorf("project does not belong to asset's workspace")
+  }
+}
+
 asset.SetProject(pid)
🧹 Nitpick comments (3)
server/internal/adapter/gql/resolver_mutation_project.go (1)

287-292: Improve error message specificity.

The current error message "Fail UploadAssetFile" doesn't indicate whether the failure was due to project ID conversion or the actual file upload.

-			return nil, errors.New("Fail UploadAssetFile :" + err.Error())
+			return nil, errors.New("Failed to convert project ID during asset upload: " + err.Error())
server/e2e/common.go (1)

305-319: Improve error handling in ValueDump.

While the type handling has been improved, the function silently ignores JSON marshaling errors.

Consider this enhancement:

 func ValueDump(val *httpexpect.Value) {
     raw := val.Raw()
     switch data := raw.(type) {
     case map[string]interface{}:
         if text, err := json.MarshalIndent(data, "", "  "); err == nil {
             fmt.Println(string(text))
+        } else {
+            fmt.Printf("Error marshaling map: %v\n", err)
         }
     case []interface{}:
         if text, err := json.MarshalIndent(data, "", "  "); err == nil {
             fmt.Println(string(text))
+        } else {
+            fmt.Printf("Error marshaling array: %v\n", err)
         }
     default:
         fmt.Println("Unsupported type:", reflect.TypeOf(raw))
     }
 }
server/internal/adapter/gql/gqlmodel/models_gen.go (1)

1147-1149: Consider renaming 'NewLayers' field in 'Scene' struct for clarity.

The field NewLayers may cause confusion regarding its purpose. If it represents the primary collection of layers in a scene, consider renaming it to Layers for consistency. If it coexists with an existing Layers field, a more descriptive name like UpdatedLayers or AdditionalLayers might enhance clarity.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e732cf and 76da26f.

📒 Files selected for processing (24)
  • server/e2e/common.go (4 hunks)
  • server/e2e/gql_asset_test.go (7 hunks)
  • server/e2e/gql_validate_geojson_test.go (1 hunks)
  • server/e2e/seeder.go (0 hunks)
  • server/gql/asset.graphql (5 hunks)
  • server/gql/workspace.graphql (2 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_asset.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/models_gen.go (7 hunks)
  • server/internal/adapter/gql/loader_asset.go (2 hunks)
  • server/internal/adapter/gql/resolver_mutation_asset.go (3 hunks)
  • server/internal/adapter/gql/resolver_mutation_project.go (3 hunks)
  • server/internal/adapter/gql/resolver_query.go (1 hunks)
  • server/internal/adapter/gql/resolver_team.go (1 hunks)
  • server/internal/infrastructure/memory/asset.go (1 hunks)
  • server/internal/infrastructure/mongo/asset.go (2 hunks)
  • server/internal/infrastructure/mongo/mongodoc/asset.go (3 hunks)
  • server/internal/usecase/interactor/asset.go (5 hunks)
  • server/internal/usecase/interfaces/asset.go (2 hunks)
  • server/internal/usecase/repo/asset.go (1 hunks)
  • server/pkg/asset/asset.go (2 hunks)
  • server/pkg/asset/builder.go (1 hunks)
  • server/pkg/asset/id.go (1 hunks)
  • server/pkg/layer/layerops/processor_test.go (0 hunks)
  • server/pkg/scene/builder_test.go (0 hunks)
💤 Files with no reviewable changes (3)
  • server/pkg/scene/builder_test.go
  • server/pkg/layer/layerops/processor_test.go
  • server/e2e/seeder.go
🚧 Files skipped from review as they are similar to previous changes (14)
  • server/internal/adapter/gql/gqlmodel/convert_asset.go
  • server/internal/usecase/repo/asset.go
  • server/internal/adapter/gql/resolver_team.go
  • server/e2e/gql_validate_geojson_test.go
  • server/pkg/asset/builder.go
  • server/pkg/asset/id.go
  • server/gql/asset.graphql
  • server/internal/infrastructure/memory/asset.go
  • server/gql/workspace.graphql
  • server/internal/adapter/gql/loader_asset.go
  • server/pkg/asset/asset.go
  • server/internal/infrastructure/mongo/mongodoc/asset.go
  • server/internal/infrastructure/mongo/asset.go
  • server/internal/adapter/gql/resolver_mutation_asset.go
🧰 Additional context used
📓 Learnings (1)
server/internal/adapter/gql/resolver_mutation_project.go (1)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:215-217
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project import functionality, the file names are unique and can be safely replaced uniformly using `bytes.Replace`.
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: ci-server / ci-server-lint
  • GitHub Check: ci-web / ci
  • GitHub Check: ci-server / ci-server-test
🔇 Additional comments (17)
server/internal/usecase/interfaces/asset.go (4)

26-31: LGTM! The ProjectID field is well-defined.

The optional ProjectID field is correctly implemented as a pointer type, allowing assets to be created with or without project association.


39-39: LGTM! The FindByWorkspace method signature is updated correctly.

The method now accepts a *id.ProjectID parameter, enabling filtering of assets by project.


41-41: LGTM! The Update method is well-designed.

The method returns both AssetID and ProjectID, providing clear feedback about the update operation's result.


43-43: LGTM! The UploadAssetFile method signature is updated correctly.

The method now accepts a *id.ProjectID parameter, allowing project association during file upload.

server/internal/adapter/gql/resolver_query.go (1)

15-16: LGTM! The Assets resolver is updated correctly.

The resolver now accepts and forwards the optional projectId parameter to the asset loader, enabling project-based filtering.

server/e2e/gql_asset_test.go (3)

18-127: LGTM! The core support test cases are comprehensive.

The test cases thoroughly validate asset creation and retrieval with core support flags, including proper handling of the new projectId field.


131-194: LGTM! The project association test cases are thorough.

The test suite comprehensively validates the project association functionality:

  • Asset creation with project association
  • Project-specific asset retrieval
  • Moving assets between projects
  • Removing project associations
  • Asset removal

215-365: LGTM! The helper functions are well-implemented.

The helper functions provide comprehensive support for testing asset management:

  • Asset creation with project association
  • Asset retrieval with project filtering
  • Project association updates
  • Asset removal
server/internal/adapter/gql/resolver_mutation_project.go (1)

262-271: LGTM! Proper transaction handling.

The transaction management is well-implemented with proper cleanup and error propagation.

server/internal/adapter/gql/gqlmodel/models_gen.go (6)

138-138: Addition of 'ProjectID' field to 'Asset' struct is appropriate.

The ProjectID field allows assets to be associated with projects, enhancing asset management functionality as per the PR objectives. The field is optional and correctly handled using a pointer, and the JSON tag follows the correct naming convention.


186-186: Proper inclusion of 'ProjectID' in 'CreateAssetInput' struct.

Adding ProjectID to CreateAssetInput enables associating assets with projects during creation. The implementation correctly uses a pointer for the optional field, and the JSON tag is appropriately named.


581-588: Fields added to 'MergedPropertyField' struct are properly defined.

The addition of fields such as SchemaID, FieldID, Value, Type, Links, Overridden, Schema, and Field enhances the functionality of MergedPropertyField. The types are correctly specified, and the use of any for the Value field is appropriate.


1331-1332: 'ProjectID' correctly added to 'UpdateAssetInput' struct.

Including ProjectID in UpdateAssetInput allows updating the association between an asset and a project. The optional nature of the field is properly handled, and the JSON tag follows conventions.


1336-1337: 'ProjectID' correctly added to 'UpdateAssetPayload' struct.

This ensures the response after updating an asset includes the ProjectID, reflecting any changes made to the asset's project association.


1844-1844: Updated 'PluginExtensionType.IsValid' method to include new extension types.

The addition of new extension types like PluginExtensionTypeStory, PluginExtensionTypeStoryPage, PluginExtensionTypeStoryBlock, and PluginExtensionTypeInfoboxBlock to the validation ensures these types are recognized as valid, aligning with the new functionalities introduced.

server/internal/usecase/interactor/asset.go (2)

39-44: LGTM! Well-structured project filtering implementation.

The addition of project ID filtering in FindByWorkspace is clean and consistent with the existing filtering pattern.


88-92: 🛠️ Refactor suggestion

Add project-workspace validation before asset creation.

While setting the project ID is implemented correctly, you should validate that the project belongs to the workspace before creating the asset.

Add this validation after the workspace check:

 if !operator.IsWritableWorkspace(ws.ID()) {
   return nil, interfaces.ErrOperationDenied
 }
+
+if inp.ProjectID != nil {
+  project, err := i.repos.Project.FindByID(ctx, *inp.ProjectID)
+  if err != nil {
+    return nil, fmt.Errorf("failed to validate project: %w", err)
+  }
+  if project.Workspace() != ws.ID() {
+    return nil, fmt.Errorf("project does not belong to workspace")
+  }
+}

@@ -181,7 +181,7 @@ func (r *mutationResolver) ExportProject(ctx context.Context, input gqlmodel.Exp
Key: "id",
Desc: true,
}
assets, _, err := usecases(ctx).Asset.FindByWorkspace(ctx, prj.Workspace(), nil, sort, page, getOperator(ctx))
assets, _, err := usecases(ctx).Asset.FindByWorkspace(ctx, prj.Workspace(), nil, nil, sort, page, getOperator(ctx))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Filter assets by project ID during export.

The current implementation exports all assets in the workspace. Consider filtering assets by project ID to ensure only relevant assets are exported.

-	assets, _, err := usecases(ctx).Asset.FindByWorkspace(ctx, prj.Workspace(), nil, nil, sort, page, getOperator(ctx))
+	assets, _, err := usecases(ctx).Asset.FindByWorkspace(ctx, prj.Workspace(), &prj.ID(), nil, sort, page, getOperator(ctx))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assets, _, err := usecases(ctx).Asset.FindByWorkspace(ctx, prj.Workspace(), nil, nil, sort, page, getOperator(ctx))
assets, _, err := usecases(ctx).Asset.FindByWorkspace(ctx, prj.Workspace(), &prj.ID(), nil, sort, page, getOperator(ctx))

Comment on lines +288 to +293
func JSONEqRegexpValue(t *testing.T, actual *httpexpect.Value, expected string) bool {
if actualData, ok := actual.Raw().(map[string]interface{}); ok {
return JSONEqRegexpInterface(t, actualData, expected)
}
return false
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance error handling and type support in JSONEqRegexpValue.

The function has limited type support and lacks error handling:

  1. Only handles map types, but could receive arrays or other JSON-compatible values
  2. No error handling for JSON marshaling failures
  3. Missing documentation explaining the function's purpose

Consider this improved implementation:

+// JSONEqRegexpValue compares the JSON representation of an httpexpect.Value against a regexp pattern.
+// Returns true if the pattern matches, false otherwise.
 func JSONEqRegexpValue(t *testing.T, actual *httpexpect.Value, expected string) bool {
-    if actualData, ok := actual.Raw().(map[string]interface{}); ok {
+    actualData := actual.Raw()
+    if actualData == nil {
+        t.Error("actual value is nil")
+        return false
+    }
+    switch actualData.(type) {
+    case map[string]interface{}, []interface{}, string, float64, bool:
         return JSONEqRegexpInterface(t, actualData, expected)
+    default:
+        t.Errorf("unsupported type for JSON comparison: %T", actualData)
+        return false
     }
-    return false
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func JSONEqRegexpValue(t *testing.T, actual *httpexpect.Value, expected string) bool {
if actualData, ok := actual.Raw().(map[string]interface{}); ok {
return JSONEqRegexpInterface(t, actualData, expected)
}
return false
}
// JSONEqRegexpValue compares the JSON representation of an httpexpect.Value against a regexp pattern.
// Returns true if the pattern matches, false otherwise.
func JSONEqRegexpValue(t *testing.T, actual *httpexpect.Value, expected string) bool {
actualData := actual.Raw()
if actualData == nil {
t.Error("actual value is nil")
return false
}
switch actualData.(type) {
case map[string]interface{}, []interface{}, string, float64, bool:
return JSONEqRegexpInterface(t, actualData, expected)
default:
t.Errorf("unsupported type for JSON comparison: %T", actualData)
return false
}
}

@@ -129,7 +151,7 @@ func (i *Asset) Remove(ctx context.Context, aid id.AssetID, operator *usecase.Op
)
}

func (i *Asset) UploadAssetFile(ctx context.Context, name string, zipFile *zip.File, workspace idx.ID[accountdomain.Workspace]) (*url.URL, int64, error) {
func (i *Asset) UploadAssetFile(ctx context.Context, name string, zipFile *zip.File, workspace idx.ID[accountdomain.Workspace], project *id.ProjectID) (*url.URL, int64, error) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add project-workspace validation in UploadAssetFile.

Similar to other methods, project-workspace validation is needed before creating the asset.

Add the validation before creating the asset:

 func (i *Asset) UploadAssetFile(ctx context.Context, name string, zipFile *zip.File, workspace idx.ID[accountdomain.Workspace], project *id.ProjectID) (*url.URL, int64, error) {
+  if project != nil {
+    p, err := i.repos.Project.FindByID(ctx, *project)
+    if err != nil {
+      return nil, 0, fmt.Errorf("failed to validate project: %w", err)
+    }
+    if p.Workspace() != workspace {
+      return nil, 0, fmt.Errorf("project does not belong to workspace")
+    }
+  }

Also applies to: 178-182

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant