Skip to content

feat(server): add ProjectImportStatus[VIZ-1641] #1614

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

hexaforce
Copy link
Contributor

@hexaforce hexaforce commented May 22, 2025

Overview

Support for Asynchronous Import Projects

What I've done

1. Fixed Issues with File Uploads

・Fixed a problem with file merging
・Fixed a bug where opening large files would cause an error
・Added a policy check process during asset import

2. Added Import Status During the Import Process

・A temporary project is now created during the first chunk (chunk0)
・At this time, the status returns as "PROCESSING"
・Once the import is complete, the status is set to either SUCCESS or FAILED
・For normally created projects, the status returns as NONE or null

3. Removed ProjectImport from GraphQL

extend type Mutation {
  createProject(input: CreateProjectInput!): ProjectPayload
  updateProject(input: UpdateProjectInput!): ProjectPayload
  publishProject(input: PublishProjectInput!): ProjectPayload
  deleteProject(input: DeleteProjectInput!): DeleteProjectPayload
  exportProject(input: ExportProjectInput!): ExportProjectPayload
- importProject(input: ImportProjectInput!): ImportProjectPayload
}

4. Added importStatus to Project

It is necessary to add importStatus on the web side

type Project implements Node {
...
  name: String!
  description: String!
  imageUrl: URL
  visualizer: Visualizer!
  createdAt: DateTime!
  updatedAt: DateTime!
+ importStatus: ProjectImportStatus!
...

}

+enum ProjectImportStatus {
+  NONE
+  PROCESSING
+  FAILE
+  SUCCESS
+}

What I haven't done

How I tested

Which point I want you to review particularly

Memo

Summary by CodeRabbit

  • New Features

    • Projects now display an import status indicating the current state of project imports (none, processing, failed, or success).
    • Chunked file uploads create a new project with processing import status and track import progress asynchronously.
  • Removals

    • The dedicated project import mutation and related input and payload types have been removed.
    • End-to-end tests for project export/import have been removed.
  • Improvements

    • Import status is tracked and updated throughout the import lifecycle for clearer feedback.
    • Project creation and asset import processes have been enhanced to support import status handling.
    • Scene data fetching in tests has been enriched with detailed nested queries for better validation.

Copy link

netlify bot commented May 22, 2025

Deploy Preview for reearth-web canceled.

Name Link
🔨 Latest commit 920b9d6
🔍 Latest deploy log https://app.netlify.com/projects/reearth-web/deploys/682ec27c74fde30008201525

Copy link
Contributor

coderabbitai bot commented May 22, 2025

Walkthrough

This change removes the GraphQL mutation-based project import functionality and its associated test, replacing it with a new project-level import status field and enum. The chunked file upload process is refactored to create a project at the start, track import status, and update it asynchronously as the import progresses, with status persisted in the database.

Changes

File(s) Change Summary
server/e2e/gql_project_export_import_test.go Deleted entire file removing chunked import and export/import end-to-end tests.
server/e2e/gql_scene_test.go Added getScene helper function and large GraphQL query for detailed scene data retrieval.
server/gql/project.graphql Removed importProject mutation and related input/payload types; added importStatus field and ProjectImportStatus enum to Project.
server/internal/adapter/gql/generated.go Removed all code related to importProject mutation and payload; added handling for importStatus field and enum on Project.
server/internal/adapter/gql/gqlmodel/convert_project.go Updated project conversion to include the new ImportStatus field.
server/internal/adapter/gql/gqlmodel/models_gen.go Removed ImportProjectInput and ImportProjectPayload structs; added ImportStatus field and enum to Project.
server/internal/adapter/gql/resolver_mutation_project.go Removed ImportProject mutation resolver and helper function; updated project creation to set ImportStatus.
server/internal/app/file_uploader.go Refactored chunked upload: creates project at first chunk, tracks project ID in session, updates import status, sorts chunks before assembly, triggers async import on completion, and responds with project ID.
server/internal/infrastructure/mongo/mongodoc/project.go Added ImportStatus field to project documents; updated conversion logic to persist and restore this field.
server/internal/usecase/interactor/asset.go Refactored asset import to use a helper method; updated method signatures to include operator; modularized project image update logic.
server/internal/usecase/interactor/project.go Added import status tracking to project creation and import; introduced UpdateImportStatus method; updated import to accept optional project ID.
server/internal/usecase/interfaces/asset.go Updated ImportAssetFiles interface method signature to include operator parameter.
server/internal/usecase/interfaces/project.go Added ImportStatus field to project creation and update params; updated import method signature; added UpdateImportStatus method.
server/pkg/project/builder.go Added builder method for setting project import status.
server/pkg/project/project.go Added importStatus field and getter/setter methods to Project.
server/pkg/status/Import_status.go Introduced ProjectImportStatus type and constants for tracking project import state.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server (UploadManager)
    participant DB
    participant Importer

    Client->>Server (UploadManager): POST /api/split-import (chunk 0)
    Server (UploadManager)->>DB: Create new Project (importStatus: PROCESSING)
    Server (UploadManager)->>Client: Respond with ProjectID

    loop For each chunk
        Client->>Server (UploadManager): POST /api/split-import (chunk N, ProjectID)
        Server (UploadManager)->>DB: Update UploadSession with ProjectID
    end

    Server (UploadManager)->>Importer: On last chunk, assemble file & start async import
    Importer->>DB: Import data into Project (using ProjectID)
    alt Import fails
        Importer->>DB: Update Project importStatus to FAILED
    else Import succeeds
        Importer->>DB: Update Project importStatus to SUCCESS
    end
Loading

Suggested reviewers

  • pyshx

Poem

In the land of code where imports hop,
The old mutation takes a stop.
Now projects track their status true,
From NONE to SUCCESS, or FAILED in blue.
Chunk by chunk, the files arrive—
And rabbits cheer as imports thrive! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @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.

@hexaforce hexaforce changed the title add ProjectImportStatus feat(server): add ProjectImportStatus[VIZ-1641] May 22, 2025
Copy link
Contributor

@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: 6

🔭 Outside diff range comments (3)
server/e2e/gql_project_export_import_test.go (1)

143-173: ⚠️ Potential issue

Update test to match the new import status API

The test is failing because it's still using the removed importProject GraphQL mutation. According to the pipeline failure, the GraphQL API now returns an error: "Cannot query field 'importProject' on type 'Mutation'". This aligns with the PR objectives which state that the importProject mutation was removed and replaced with an importStatus field on the Project type.

You need to update this test to work with the new import API approach that uses status tracking instead of a direct mutation. Consider:

func importProject(t *testing.T, e *httpexpect.Expect, filePath string) *httpexpect.Value {
    file, err := os.Open(filePath)
    if err != nil {
        t.Fatalf("failed to open file: %v", err)
    }
    defer func() {
        if cerr := file.Close(); cerr != nil && err == nil {
            err = cerr
        }
    }()
    requestBody := map[string]interface{}{
-        "operationName": "ImportProject",
+        "operationName": "UploadProjectFile",
        "variables": map[string]interface{}{
            "teamId": wID.String(),
            "file":   nil,
        },
-        "query": `mutation ImportProject($teamId: ID!, $file: Upload!) {  
-            importProject(input: {teamId: $teamId, file: $file}) {    
-                projectData    
-                __typename  
-            } 
-        }`,
+        "query": `mutation UploadProjectFile($teamId: ID!, $file: Upload!) {  
+            uploadProjectFile(input: {teamId: $teamId, file: $file}) {    
+                projectId
+                __typename  
+            } 
+        }`,
    }

    assert.Nil(t, err)
    r := RequestWithMultipart(e, uID.String(), requestBody, filePath)
    
-    projectData := r.Object().Value("data").Object().Value("importProject").Object().Value("projectData")
+    projectId := r.Object().Value("data").Object().Value("uploadProjectFile").Object().Value("projectId").Raw().(string)
+    
+    // Poll until import status is SUCCESS or timeout
+    deadline := time.Now().Add(30 * time.Second)
+    var projectData *httpexpect.Value
+    
+    for time.Now().Before(deadline) {
+        // Query project to check import status
+        projectQuery := GraphQLRequest{
+            OperationName: "GetProject",
+            Query: `query GetProject($projectId: ID!) {
+                node(id: $projectId, type: PROJECT) {
+                    ... on Project {
+                        importStatus
+                        # Add other fields you need
+                    }
+                }
+            }`,
+            Variables: map[string]any{
+                "projectId": projectId,
+            },
+        }
+        
+        projectResp := Request(e, uID.String(), projectQuery)
+        status := projectResp.Path("$.data.node.importStatus").String().Raw()
+        
+        if status == "SUCCESS" {
+            // Fetch the complete project data
+            // ...
+            break
+        } else if status == "FAILED" {
+            t.Fatalf("Project import failed")
+        }
+        
+        time.Sleep(1 * time.Second)
+    }

Note: The actual implementation will depend on your API design. This is just a suggestion based on the changes described in the PR.

server/internal/app/file_uploader.go (1)

320-350: ⚠️ Potential issue

Received counter is incremented even for duplicate chunks

session.Received++ is executed before duplicate detection, so a retransmitted chunk inflates the counter and may signal completion prematurely.

-    session.UpdatedAt = time.Now()
-    session.Received++
-
-    for _, chunk := range session.Chunks {
+    session.UpdatedAt = time.Now()
+
+    for _, chunk := range session.Chunks {
         if chunk == fmt.Sprintf("%s_part_%d", fileID, chunkNum) {
             return session, false, nil
         }
     }
+
+    session.Received++

This keeps Received equal to the distinct number of chunks written.

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

6775-6803: ⚠️ Potential issue

Typo in enum value – FAILE should be FAILED

The enum ProjectImportStatus contains the value FAILE, which looks like a misspelling of FAILED.
Because this file is generated, the typo originates from the .graphql schema (or the Go model). Fixing it there and regenerating will prevent:

• runtime errors when the backend tries to marshal an unknown enum value
• confusion for API consumers and documentation generators

Proposed schema fix:

-enum ProjectImportStatus {
-  NONE
-  PROCESSING
-  FAILE
-  SUCCESS
-}
+enum ProjectImportStatus {
+  NONE
+  PROCESSING
+  FAILED
+  SUCCESS
+}

After correcting the schema, re-run go generate ./... (or the code-gen command you use) to update this file.

🧹 Nitpick comments (5)
server/internal/adapter/gql/resolver_mutation_project.go (1)

196-210: Remove unused function

The replaceOldSceneID function is flagged as unused by the linter. It should be removed since the import project functionality has been refactored.

-func replaceOldSceneID(data *[]byte, newScene *scene.Scene) (string, error) {
-	var d map[string]any
-	if err := json.Unmarshal(*data, &d); err != nil {
-		return "", err
-	}
-	if s, ok := d["scene"].(map[string]any); ok {
-		if oldSceneID, ok := s["id"].(string); ok {
-
-			// Replace new scene id
-			*data = bytes.Replace(*data, []byte(oldSceneID), []byte(newScene.ID().String()), -1)
-			return oldSceneID, nil
-		}
-	}
-	return "", errors.New("scene id not found")
-}
🧰 Tools
🪛 GitHub Check: ci-server / ci-server-lint

[failure] 196-196:
func replaceOldSceneID is unused (unused)

🪛 golangci-lint (1.64.8)

196-196: func replaceOldSceneID is unused

(unused)

🪛 GitHub Actions: ci

[error] 196-196: golangci-lint: func replaceOldSceneID is unused (unused)

server/internal/infrastructure/mongo/mongodoc/project.go (1)

70-74: Minor consistency nit – prefer typed alias over plain string

Every other enum field (Visualizer, PublishmentStatus, …) is stored as its string value but still typed in the document struct when possible.
Storing ImportStatus as status.ProjectImportStatus instead of plain string would give compile-time help when populating the document:

-importStatus string
+importStatus status.ProjectImportStatus

Not mandatory, but keeps the struct symmetrical and prevents accidental typos at the call-site.

Also applies to: 123-124

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

1730-1757: Add helper validation to enum instead of hand-rolled switches

The generated IsValid uses an exhaustive switch.
A simple map[string]struct{} or strings.Contains on a constant slice avoids future maintenance every time a new value is added.

Not urgent (generated file), but worth upstreaming to the generator template.

server/internal/app/file_uploader.go (2)

155-160: Avoid shadowing the imported status package

The parameter status status.ProjectImportStatus hides the imported status package inside UpdateImportStatus, forcing callers to qualify constants elsewhere (status.ProjectImportStatusFaile) but not here.
Rename the parameter, e.g. newStatus, for clarity.

-func UpdateImportStatus(..., pid id.ProjectID, status status.ProjectImportStatus) {
-    _, err := usecases.Project.UpdateImportStatus(ctx, pid, status, op)
+func UpdateImportStatus(..., pid id.ProjectID, newStatus status.ProjectImportStatus) {
+    _, err := usecases.Project.UpdateImportStatus(ctx, pid, newStatus, op)
 }

359-376: Numeric sort helper can panic on unexpected names

getChunkNumber returns 0 on parse errors, so part_foo sorts before part_1.
Safer to return -1 or filter invalid entries out; otherwise you may join chunks in the wrong order silently.

Not critical if producer guarantees well-formed names, but worth guarding in publicly exposed endpoints.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8b41d82 and 1871286.

📒 Files selected for processing (15)
  • server/e2e/gql_project_export_import_test.go (1 hunks)
  • server/gql/project.graphql (2 hunks)
  • server/internal/adapter/gql/generated.go (11 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_project.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/models_gen.go (2 hunks)
  • server/internal/adapter/gql/resolver_mutation_project.go (2 hunks)
  • server/internal/app/file_uploader.go (9 hunks)
  • server/internal/infrastructure/mongo/mongodoc/project.go (3 hunks)
  • server/internal/usecase/interactor/asset.go (7 hunks)
  • server/internal/usecase/interactor/project.go (7 hunks)
  • server/internal/usecase/interfaces/asset.go (1 hunks)
  • server/internal/usecase/interfaces/project.go (4 hunks)
  • server/pkg/project/builder.go (2 hunks)
  • server/pkg/project/project.go (3 hunks)
  • server/pkg/status/Import_status.go (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
server/e2e/gql_project_export_import_test.go (1)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1141
File: server/e2e/gql_import_export_test.go:48-80
Timestamp: 2024-09-13T01:19:11.267Z
Learning: In `server/e2e/gql_import_export_test.go`, test functions like `TestCallImportProject` may have disabled assertions due to problems with GraphQL.
🧬 Code Graph Analysis (6)
server/internal/infrastructure/mongo/mongodoc/project.go (1)
server/pkg/status/Import_status.go (1)
  • ProjectImportStatus (3-3)
server/internal/adapter/gql/resolver_mutation_project.go (2)
server/internal/adapter/gql/gqlmodel/models_gen.go (2)
  • Visualizer (2155-2155)
  • ProjectImportStatusNone (1717-1717)
server/pkg/status/Import_status.go (1)
  • ProjectImportStatusNone (6-6)
server/pkg/status/Import_status.go (1)
server/internal/adapter/gql/gqlmodel/models_gen.go (1)
  • ProjectImportStatus (1714-1714)
server/internal/adapter/gql/gqlmodel/convert_project.go (2)
server/internal/adapter/gql/gqlmodel/models_gen.go (2)
  • Visualizer (2155-2155)
  • ProjectImportStatus (1714-1714)
server/pkg/status/Import_status.go (1)
  • ProjectImportStatus (3-3)
server/internal/usecase/interfaces/project.go (6)
server/internal/adapter/gql/gqlmodel/models_gen.go (4)
  • Visualizer (2155-2155)
  • ProjectImportStatus (1714-1714)
  • Project (673-702)
  • Project (704-704)
server/pkg/status/Import_status.go (1)
  • ProjectImportStatus (3-3)
server/internal/usecase/interactor/project.go (1)
  • Project (37-54)
server/pkg/project/project.go (1)
  • Project (14-43)
server/internal/usecase/repo/project.go (1)
  • Project (19-33)
server/internal/app/file_uploader.go (1)
  • UpdateImportStatus (155-160)
server/internal/adapter/gql/generated.go (4)
server/internal/usecase/interfaces/project.go (1)
  • Project (74-91)
server/pkg/project/project.go (1)
  • Project (14-43)
server/internal/adapter/gql/gqlmodel/models_gen.go (3)
  • Project (673-702)
  • Project (704-704)
  • ProjectImportStatus (1714-1714)
server/pkg/status/Import_status.go (1)
  • ProjectImportStatus (3-3)
🪛 GitHub Actions: ci
server/internal/adapter/gql/resolver_mutation_project.go

[error] 196-196: golangci-lint: func replaceOldSceneID is unused (unused)

server/e2e/gql_project_export_import_test.go

[error] 45-45: TestProjectExportImport failed: unexpected HTTP status 422 Unprocessable Entity. GraphQL error: Cannot query field "importProject" on type "Mutation". Panic: interface conversion: interface {} is nil, not string.

🔇 Additional comments (29)
server/internal/adapter/gql/gqlmodel/convert_project.go (1)

54-54: LGTM: New import status field added to Project conversion

The new field ImportStatus has been correctly added to the ToProject conversion function, ensuring that the import status from the domain model is properly mapped to the GraphQL model.

server/pkg/project/builder.go (2)

7-9: LGTM: Status package import added

The import for the status package is correctly added to support the new import status field.


89-92: LGTM: Builder method for import status added

The new ImportStatus method follows the established builder pattern correctly, allowing for the setting of the import status during project construction.

server/pkg/project/project.go (4)

9-10: LGTM: Status package import added

The import for the status package is correctly added to support the new import status field.


23-23: LGTM: Import status field added to Project struct

The new importStatus field is correctly added to the Project struct.


98-100: LGTM: Getter method for import status added

The new ImportStatus() method follows the established pattern for getter methods in this struct.


160-162: LGTM: Setter method for import status added

The new UpdateImportStatus() method follows the established pattern for setter methods in this struct.

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

42-42: LGTM: Operator parameter added for policy enforcement

Adding the *usecase.Operator parameter to the ImportAssetFiles method is a good change that allows for policy enforcement during asset import, which aligns with the PR objectives of "adding a policy check during asset import."

server/gql/project.graphql (1)

13-13: LGTM: New import status field

Adding the importStatus field to the Project type properly implements the status tracking mechanism described in the PR objectives.

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

31-39: LGTM: Initial import status in CreateProject

Setting the initial import status to status.ProjectImportStatusNone when creating a new project is the correct approach. This aligns with the PR objectives which state that "For projects created normally, the import status is 'NONE' or null."

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

88-90: Double-check the new ImportProjectData signature

ImportProjectData(ctx, teamID string, projectID *string, data *[]byte, op …) places the optional project ID before the payload.
Call-sites have to pass &session.ProjectID, which is easy to mix up with data.

Consider:

ImportProjectData(ctx, teamID, data, &optionalProjectID, op)

to match the natural order “context, identifiers, payload, operator”.

server/internal/app/file_uploader.go (1)

170-177: Race condition: project created only when chunk 0 arrives

If chunks arrive out-of-order (e.g., 1, 2, 0 …), the first call cannot attach a ProjectID to the session, resulting in an empty ProjectID when the background import fires.

Mitigations:

  1. Create the processing project the moment the first chunk for a new fileID is seen (regardless of its index).
  2. Or postpone spawning the import goroutine until session.ProjectID is non-empty.

Leaving as-is risks a nil/empty ID and status update failures.

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

21-24: New import for policy package added.

The policy package import is now being used in the refactored asset creation and import process to enforce workspace policy limits.


58-82: Well-structured refactoring of the Create method.

The method has been refactored to use a new helper function uploadAndSave which improves code organization and reusability. The policy retrieval and enforcement logic has been properly maintained while making the code more modular.


130-156: ImportAssetFiles now accepts operator parameter and adds workspace policy checks.

The method signature has been updated to accept an operator parameter and now retrieves the workspace and policy information before processing assets. This change aligns with the PR objective of adding policy checks during asset import.


164-166: Error handling improved for zip file operations.

Changed from returning an error to logging and continuing when zip files can't be opened. This makes the import process more resilient by allowing it to continue even if some files have issues.


181-190: Project image update logic extracted to a separate method.

The project image update logic has been modularized into the updateProjectImage method, which is good for code organization and maintainability.


203-211: New updateProjectImage method is well-structured.

This new helper method cleanly handles the conditional logic for updating a project's image URL when it matches an imported asset.


213-252: New uploadAndSave helper method improves code organization.

This well-structured helper method:

  1. Uploads the file
  2. Enforces policy limits
  3. Creates and saves the asset entity
  4. Properly handles errors at each step

The method also correctly cleans up uploaded files if policy limits are exceeded, preventing orphaned files.

server/internal/usecase/interactor/project.go (8)

28-33: New import for status package added.

The status package import supports the new ProjectImportStatus functionality being implemented in this PR.


96-106: Create method updated to support import status.

The Create method now passes the ImportStatus field to the createProject function, aligning with the PR objective of implementing an import status tracking mechanism.


222-237: New UpdateImportStatus method correctly implements status updates.

This method provides a clean interface for updating a project's import status, with proper error handling and timestamp updates. This directly supports the PR objective of tracking import status during the import process.


676-716: ImportProjectData updated to support project ID and import status.

The method now accepts an optional projectId parameter and sets the import status to PROCESSING when calling createProject. This aligns with the PR objective of creating a temporary project with PROCESSING status at the start of data import.


700-710: Import status set to PROCESSING during project creation.

The code correctly implements the requirement to set the import status to PROCESSING when importing a project, which aligns with the PR objective of tracking the import process.


748-760: createProjectInput struct updated with new fields.

The struct now includes ProjectID and ImportStatus fields to support the new import status tracking functionality.


800-814: Project ID handling correctly implemented.

The code now supports using a provided project ID or generating a new one if not provided. This allows for creating a project with a specific ID during the import process, which is essential for the chunked upload workflow.


814-815: Import status properly set during project creation.

The code correctly sets the import status when building a new project, which is a key part of implementing the import status tracking mechanism described in the PR objectives.

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

6775-6778: Non-nullable importStatus may crash for legacy projects

importStatus: ProjectImportStatus! is declared non-nullable.
If any existing project record has an empty string or null in MongoDB, the resolver (_Project_importStatus) will hit the must not be null branch (lines 26843-26847) and return a GraphQL error for the entire query.

Please verify that:

  1. All existing documents have importStatus populated—e.g. default "NONE".
  2. New project creation paths set the field explicitly.

If either isn’t true, change the field to nullable or run a data migration.


538-541: Complexity map updated correctly – nothing to add

The new ImportStatus field is added to the complexity struct in line with other scalar fields. Looks good.

Comment on lines 3 to 10
type ProjectImportStatus string

const (
ProjectImportStatusNone ProjectImportStatus = "NONE"
ProjectImportStatusProcessing ProjectImportStatus = "PROCESSING"
ProjectImportStatusFaile ProjectImportStatus = "FAILE"
ProjectImportStatusSuccess ProjectImportStatus = "SUCCESS"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix typo in enum value "FAILE"

There's a typo in the enum value ProjectImportStatusFaile - it should be FAILED instead of FAILE. This could cause confusion for clients consuming the API.

-	ProjectImportStatusFaile      ProjectImportStatus = "FAILE"
+	ProjectImportStatusFailed     ProjectImportStatus = "FAILED"

Additionally, consider adding documentation comments to explain the purpose of each status value.

 type ProjectImportStatus string

 const (
+	// ProjectImportStatusNone indicates no import has been attempted
 	ProjectImportStatusNone       ProjectImportStatus = "NONE"
+	// ProjectImportStatusProcessing indicates an import is currently in progress
 	ProjectImportStatusProcessing ProjectImportStatus = "PROCESSING"
-	ProjectImportStatusFaile      ProjectImportStatus = "FAILE"
+	// ProjectImportStatusFailed indicates an import attempt that failed
+	ProjectImportStatusFailed     ProjectImportStatus = "FAILED"
+	// ProjectImportStatusSuccess indicates a successful import
 	ProjectImportStatusSuccess    ProjectImportStatus = "SUCCESS"
 )
🤖 Prompt for AI Agents
In server/pkg/status/Import_status.go between lines 3 and 10, correct the typo
in the enum value by renaming ProjectImportStatusFaile to
ProjectImportStatusFailed and changing its string value from "FAILE" to
"FAILED". Also, add documentation comments above each constant to clearly
describe the meaning of each import status value for better code clarity and API
consumer understanding.

Comment on lines +24 to +25
ImportStatus string
CoreSupport bool
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Validate ‟ImportStatus” before round-tripping to the domain model

ImportStatus is persisted as a raw string.
If anything other than the four valid enum values (or an empty string) is stored, Model() will still feed that value back into the builder without any validation, silently polluting the domain model with an invalid enum.

Consider normalising the value on load:

@@
-        ImportStatus: string(project.ImportStatus()),
+        ImportStatus: string(project.ImportStatus()),

and in Model():

-        .ImportStatus(status.ProjectImportStatus(d.ImportStatus)).
+        .ImportStatus(func() status.ProjectImportStatus {
+            s := status.ProjectImportStatus(d.ImportStatus)
+            if !s.IsValid() { // add IsValid() to status package if missing
+                s = status.ProjectImportStatusNone
+            }
+            return s
+        }()).

This makes corrupted data self-healing while keeping the DB schema unchanged.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In server/internal/infrastructure/mongo/mongodoc/project.go around lines 24 to
25, the ImportStatus field is stored as a raw string without validation,
allowing invalid enum values to propagate into the domain model. To fix this,
add validation logic when loading ImportStatus from the database to normalize or
default invalid values to a valid enum or empty string. Also, update the Model()
method to validate and normalize ImportStatus before returning the domain model,
ensuring corrupted or unexpected values are corrected and do not pollute the
domain logic.

Comment on lines 22 to 30
type CreateProjectParam struct {
WorkspaceID accountdomain.WorkspaceID
Visualizer visualizer.Visualizer
Name *string
Description *string
CoreSupport *bool
Visibility *string
WorkspaceID accountdomain.WorkspaceID
Visualizer visualizer.Visualizer
Name *string
Description *string
CoreSupport *bool
Visibility *string
ImportStatus status.ProjectImportStatus
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use pointer for ImportStatus to follow existing optional-field convention

All optional inputs (Name, Description, CoreSupport, …) use pointers so the caller can distinguish “unset” from “set to zero value”.
ImportStatus is currently a value – the caller cannot express “leave unchanged” in UpdateProjectParam and cannot omit the field in CreateProjectParam.

-type CreateProjectParam struct {
+type CreateProjectParam struct {
@@
-    ImportStatus status.ProjectImportStatus
+    ImportStatus *status.ProjectImportStatus
 }
@@
-    ImportStatus   status.ProjectImportStatus
+    ImportStatus   *status.ProjectImportStatus

UpdateImportStatus already exists for explicit changes, so keeping this field optional avoids accidental overrides.

Also applies to: 39-40

🤖 Prompt for AI Agents
In server/internal/usecase/interfaces/project.go around lines 22 to 30 and also
lines 39 to 40, change the ImportStatus field in CreateProjectParam and
UpdateProjectParam structs from a value type to a pointer type. This aligns with
the existing convention for optional fields, allowing callers to distinguish
between "unset" and "set to zero value" and preventing accidental overrides by
making ImportStatus optional.

Comment on lines 1714 to 1721
type ProjectImportStatus string

const (
ProjectImportStatusNone ProjectImportStatus = "NONE"
ProjectImportStatusProcessing ProjectImportStatus = "PROCESSING"
ProjectImportStatusFaile ProjectImportStatus = "FAILE"
ProjectImportStatusSuccess ProjectImportStatus = "SUCCESS"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Typo: enum value FAILE should be FAILED

The GraphQL-exposed enum has a misspelling:

enum ProjectImportStatus {
  NONE
  PROCESSING
- FAILE
+ FAILED
  SUCCESS
}

Downstream clients will carry this typo indefinitely once released.
Fix it in the GraphQL schema and regenerate – do not patch the generated file directly.

🤖 Prompt for AI Agents
In server/internal/adapter/gql/gqlmodel/models_gen.go lines 1714 to 1721, the
enum value "FAILE" is a typo and should be corrected to "FAILED". Do not edit
this generated file directly; instead, fix the typo in the original GraphQL
schema definition by changing "FAILE" to "FAILED" in the ProjectImportStatus
enum, then regenerate the gqlmodel code to propagate the fix properly.

Comment on lines +26823 to +26852
func (ec *executionContext) _Project_importStatus(ctx context.Context, field graphql.CollectedField, obj *gqlmodel.Project) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Project_importStatus(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return obj.ImportStatus, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(gqlmodel.ProjectImportStatus)
fc.Result = res
return ec.marshalNProjectImportStatus2githubᚗcomᚋreearthᚋreearthᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐProjectImportStatus(ctx, field.Selections, res)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Resolver returns raw enum string – ensure zero value is NEVER empty

The resolver simply returns obj.ImportStatus. If the zero value ("") slips through, GraphQL marshalling will fail because the empty string doesn’t match any enum literal.
Guard at the domain layer (e.g. make the type an alias with const Unknown = "NONE" default) or add a fallback here:

-       return obj.ImportStatus, nil
+       if obj.ImportStatus == "" {
+               return gqlmodel.ProjectImportStatusNone, nil // or your default
+       }
+       return obj.ImportStatus, nil

(Adjust constant name to match generated enum.)

🤖 Prompt for AI Agents
In server/internal/adapter/gql/generated.go around lines 26823 to 26852, the
resolver returns the raw enum string obj.ImportStatus, which can be an empty
string causing GraphQL marshalling to fail. To fix this, add a guard in the
resolver to check if obj.ImportStatus is empty and return a default fallback
enum value (e.g., "NONE" or an appropriate constant) instead. This ensures the
resolver never returns an invalid empty enum string.

@hexaforce hexaforce enabled auto-merge (squash) May 22, 2025 05:36
@hexaforce hexaforce force-pushed the feat/add-project-import-status branch 3 times, most recently from 734c963 to 3c3b838 Compare May 28, 2025 03:00
Copy link
Contributor

@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/generated.go (1)

26823-26852: ⚠️ Potential issue

Critical issue still exists - resolver returns raw enum string without validation.

The resolver directly returns obj.ImportStatus without checking for empty string values, which can cause GraphQL marshalling failures when the zero value slips through. This is the exact same issue flagged in the previous review.

Apply this fix to handle empty import status values:

-       return obj.ImportStatus, nil
+       if obj.ImportStatus == "" {
+               return gqlmodel.ProjectImportStatusNone, nil
+       }
+       return obj.ImportStatus, nil
🧹 Nitpick comments (1)
server/internal/usecase/interactor/project.go (1)

222-237: Consider adding transaction support for data consistency

The UpdateImportStatus method updates the project's import status and timestamp, but unlike other update methods in this file (e.g., Update, Publish), it doesn't use a database transaction. Consider whether this operation should be wrapped in a transaction for consistency with other project updates.

 func (i *Project) UpdateImportStatus(ctx context.Context, pid id.ProjectID, importStatus status.ProjectImportStatus, operator *usecase.Operator) (*project.Project, error) {
+	tx, err := i.transaction.Begin(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	ctx = tx.Context()
+	defer func() {
+		if err2 := tx.End(ctx); err == nil && err2 != nil {
+			err = err2
+		}
+	}()
+
 	prj, err := i.projectRepo.FindByID(ctx, pid)
 	if err != nil {
 		return nil, err
 	}

 	prj.UpdateImportStatus(importStatus)
 	currentTime := time.Now().UTC()
 	prj.SetUpdatedAt(currentTime)

 	if err := i.projectRepo.Save(ctx, prj); err != nil {
 		return nil, err
 	}
+
+	tx.Commit()
 	return prj, nil
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between decd359 and 3c3b838.

📒 Files selected for processing (18)
  • server/e2e/gql_project_export_import_test.go (0 hunks)
  • server/e2e/gql_scene_test.go (1 hunks)
  • server/gql/project.graphql (2 hunks)
  • server/internal/adapter/gql/generated.go (11 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_project.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_project_test.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/models_gen.go (2 hunks)
  • server/internal/adapter/gql/resolver_mutation_project.go (2 hunks)
  • server/internal/app/file_uploader.go (9 hunks)
  • server/internal/infrastructure/mongo/mongodoc/project.go (3 hunks)
  • server/internal/infrastructure/mongo/project_test.go (2 hunks)
  • server/internal/usecase/interactor/asset.go (7 hunks)
  • server/internal/usecase/interactor/project.go (7 hunks)
  • server/internal/usecase/interfaces/asset.go (1 hunks)
  • server/internal/usecase/interfaces/project.go (4 hunks)
  • server/pkg/project/builder.go (2 hunks)
  • server/pkg/project/project.go (3 hunks)
  • server/pkg/status/Import_status.go (1 hunks)
💤 Files with no reviewable changes (1)
  • server/e2e/gql_project_export_import_test.go
🚧 Files skipped from review as they are similar to previous changes (13)
  • server/pkg/project/builder.go
  • server/pkg/status/Import_status.go
  • server/internal/adapter/gql/gqlmodel/convert_project.go
  • server/internal/usecase/interfaces/asset.go
  • server/pkg/project/project.go
  • server/internal/infrastructure/mongo/mongodoc/project.go
  • server/internal/adapter/gql/resolver_mutation_project.go
  • server/internal/usecase/interfaces/project.go
  • server/e2e/gql_scene_test.go
  • server/internal/app/file_uploader.go
  • server/internal/adapter/gql/gqlmodel/models_gen.go
  • server/gql/project.graphql
  • server/internal/usecase/interactor/asset.go
🧰 Additional context used
🧬 Code Graph Analysis (1)
server/internal/infrastructure/mongo/project_test.go (3)
server/pkg/project/builder.go (1)
  • New (17-19)
server/internal/adapter/gql/gqlmodel/models_gen.go (4)
  • PublishmentStatus (1869-1869)
  • PublishmentStatusPublic (1872-1872)
  • ProjectImportStatusNone (1717-1717)
  • PublishmentStatusLimited (1873-1873)
server/pkg/status/Import_status.go (1)
  • ProjectImportStatusNone (6-6)
🔇 Additional comments (16)
server/internal/usecase/interactor/project.go (7)

28-28: LGTM - Import addition for status package

The import of the status package is necessary for the new ProjectImportStatus type used in the import status functionality.


32-32: LGTM - Import addition for idx package

The idx package import is required for the enhanced project ID handling in the createProject method.


98-105: LGTM - Create method updated for import status support

The Create method correctly forwards the new ImportStatus field to the createProject function, maintaining consistency with the existing parameter passing pattern.


676-676: LGTM - Method signature enhanced for targeted imports

The addition of the optional projectId parameter to ImportProjectData enables importing data into a specific project, which aligns with the asynchronous import workflow described in the PR objectives.


699-710: LGTM - Import data creation with proper status

The ImportProjectData method correctly:

  • Sets the import status to status.ProjectImportStatusProcessing
  • Passes the optional projectId to createProject
  • Maintains existing functionality while adding new import tracking capabilities

This implementation aligns with the PR description where imports start with "PROCESSING" status.


749-760: LGTM - Struct expanded for enhanced project creation

The createProjectInput struct has been appropriately extended with ProjectID and ImportStatus fields to support the new import functionality while maintaining backward compatibility.


800-814: LGTM - Robust project ID handling with proper error management

The project ID assignment logic is well-implemented:

  • Properly parses the provided project ID string with error handling
  • Falls back to generating a new ID when none is provided
  • Uses the new ImportStatus field in project creation

The error handling for ID parsing ensures the system fails gracefully with invalid input.

server/internal/adapter/gql/generated.go (8)

538-538: LGTM - Complexity function added correctly.

The ImportStatus complexity function has been properly added to the Project struct.


3758-3763: LGTM - Complexity case handling implemented correctly.

The complexity case for "Project.importStatus" follows the standard pattern with proper nil check and complexity calculation.


6775-6775: LGTM - Non-nullable field correctly defined.

The importStatus field is properly declared as non-nullable (ProjectImportStatus!), which aligns with the requirement that projects should always have an import status.


6798-6803: LGTM - Enum definition is correct.

The ProjectImportStatus enum is properly defined with all expected values: NONE, PROCESSING, FAILED, and SUCCESS.


26854-26865: LGTM - Field context function implemented correctly.

The field context function for importStatus follows the standard pattern for enum fields.


27809-27810: LGTM - Field context cases added consistently.

The "importStatus" cases have been properly added to all relevant field context switch statements.

Also applies to: 28050-28051, 28152-28153, 34522-34523


51704-51708: LGTM - Field execution properly implemented.

The execution of _Project_importStatus is correctly integrated with proper null handling.


58403-58411: LGTM - Marshalling functions implemented correctly.

The unmarshalling and marshalling functions for ProjectImportStatus follow the standard pattern for GraphQL enums.

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

12-12: LGTM: Import addition is correct.

The addition of the status package import is necessary for using ProjectImportStatusNone constant.

Comment on lines +107 to 109
prj1 := project.New().NewID().Workspace(wid).UpdatedAt(now).Alias("alias").PublishmentStatus(project.PublishmentStatusPublic).ImportStatus(status.ProjectImportStatusNone).MustBuild()
prj2 := project.New().NewID().Workspace(wid).UpdatedAt(now).Alias("aaaaa").PublishmentStatus(project.PublishmentStatusLimited).ImportStatus(status.ProjectImportStatusNone).MustBuild()
prj3 := project.New().NewID().Workspace(wid).UpdatedAt(now).Alias("bbbbb").MustBuild()
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix inconsistent ImportStatus initialization.

While prj1 and prj2 explicitly set ImportStatus(status.ProjectImportStatusNone), prj3 doesn't have this field set. This inconsistency could lead to different behavior during testing, where prj3 might have a different default ImportStatus value.

Apply this diff to ensure consistency:

 	prj1 := project.New().NewID().Workspace(wid).UpdatedAt(now).Alias("alias").PublishmentStatus(project.PublishmentStatusPublic).ImportStatus(status.ProjectImportStatusNone).MustBuild()
 	prj2 := project.New().NewID().Workspace(wid).UpdatedAt(now).Alias("aaaaa").PublishmentStatus(project.PublishmentStatusLimited).ImportStatus(status.ProjectImportStatusNone).MustBuild()
-	prj3 := project.New().NewID().Workspace(wid).UpdatedAt(now).Alias("bbbbb").MustBuild()
+	prj3 := project.New().NewID().Workspace(wid).UpdatedAt(now).Alias("bbbbb").ImportStatus(status.ProjectImportStatusNone).MustBuild()
🤖 Prompt for AI Agents
In server/internal/infrastructure/mongo/project_test.go around lines 107 to 109,
prj3 is missing the ImportStatus initialization that prj1 and prj2 have, causing
inconsistent test behavior. Add ImportStatus(status.ProjectImportStatusNone) to
prj3's builder chain to ensure all three projects have the same ImportStatus set
explicitly.

@@ -91,6 +91,7 @@ func TestToProject(t *testing.T) {
ImageURL: lo.Must(url.Parse("https://example.com/hoge.gif")),
TeamID: IDFrom(wsId),
Visualizer: Visualizer(visualizer.VisualizerCesium),
ImportStatus: "NONE",
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use constant instead of hard-coded string for ImportStatus.

The ImportStatus field uses a hard-coded string "NONE" instead of the defined constant. This reduces maintainability and could lead to inconsistencies if the enum value changes.

Apply this diff to use the constant:

-				ImportStatus:      "NONE",
+				ImportStatus:      string(ProjectImportStatusNone),

Note: You may need to add the appropriate import or ensure ProjectImportStatusNone is accessible in this test file's scope.

📝 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
ImportStatus: "NONE",
ImportStatus: string(ProjectImportStatusNone),
🤖 Prompt for AI Agents
In server/internal/adapter/gql/gqlmodel/convert_project_test.go at line 94,
replace the hard-coded string "NONE" assigned to ImportStatus with the
corresponding constant ProjectImportStatusNone. Ensure that the constant is
properly imported or accessible in the test file to maintain consistency and
improve maintainability.

@hexaforce hexaforce force-pushed the feat/add-project-import-status branch from 3c3b838 to 4430b19 Compare May 28, 2025 09:47
Copy link
Contributor

@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 (1)
server/internal/adapter/gql/generated.go (1)

26823-26852: ⚠️ Potential issue

Address the empty string handling issue (duplicate from past review).

This is the same issue identified in the previous review. The resolver returns obj.ImportStatus directly without checking if it's an empty string, which could cause GraphQL marshaling failures when the zero value slips through.

Apply the previously suggested fix:

-       return obj.ImportStatus, nil
+       if obj.ImportStatus == "" {
+               return gqlmodel.ProjectImportStatusNone, nil
+       }
+       return obj.ImportStatus, nil
🧹 Nitpick comments (1)
server/internal/app/file_uploader.go (1)

359-375: Improve chunk sorting robustness and performance.

The current sorting implementation has some issues:

  1. Performance: Creating a closure for each comparison is inefficient
  2. Error handling: Parse errors result in chunk number 0, which could cause incorrect ordering
  3. Logging: Warning logs in comparison function could be excessive

Here's a more robust implementation:

+	// Pre-compute chunk numbers for efficient sorting
+	type chunkInfo struct {
+		name   string
+		number int
+	}
+	
+	chunks := make([]chunkInfo, len(session.Chunks))
+	for i, chunk := range session.Chunks {
+		parts := strings.Split(chunk, "_part_")
+		if len(parts) != 2 {
+			log.Printf("warning: unexpected chunk name format: %s", chunk)
+			chunks[i] = chunkInfo{name: chunk, number: 0}
+			continue
+		}
+		n, err := strconv.Atoi(parts[1])
+		if err != nil {
+			log.Printf("warning: failed to parse chunk number from %s: %v", chunk, err)
+			chunks[i] = chunkInfo{name: chunk, number: 0}
+			continue
+		}
+		chunks[i] = chunkInfo{name: chunk, number: n}
+	}

-	sort.Slice(session.Chunks, func(i, j int) bool {
-		getChunkNumber := func(s string) int {
-			parts := strings.Split(s, "_part_")
-			if len(parts) != 2 {
-				log.Printf("warning: unexpected chunk name format: %s", s)
-				return 0
-			}
-			n, err := strconv.Atoi(parts[1])
-			if err != nil {
-				log.Printf("warning: failed to parse chunk number from %s: %v", s, err)
-				return 0
-			}
-			return n
-		}
-
-		return getChunkNumber(session.Chunks[i]) < getChunkNumber(session.Chunks[j])
-	})
+	sort.Slice(chunks, func(i, j int) bool {
+		return chunks[i].number < chunks[j].number
+	})
+	
+	// Update session.Chunks with sorted names
+	for i, chunk := range chunks {
+		session.Chunks[i] = chunk.name
+	}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3c3b838 and 4430b19.

📒 Files selected for processing (18)
  • server/e2e/gql_project_export_import_test.go (0 hunks)
  • server/e2e/gql_scene_test.go (1 hunks)
  • server/gql/project.graphql (2 hunks)
  • server/internal/adapter/gql/generated.go (11 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_project.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_project_test.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/models_gen.go (2 hunks)
  • server/internal/adapter/gql/resolver_mutation_project.go (2 hunks)
  • server/internal/app/file_uploader.go (9 hunks)
  • server/internal/infrastructure/mongo/mongodoc/project.go (3 hunks)
  • server/internal/infrastructure/mongo/project_test.go (2 hunks)
  • server/internal/usecase/interactor/asset.go (7 hunks)
  • server/internal/usecase/interactor/project.go (8 hunks)
  • server/internal/usecase/interfaces/asset.go (1 hunks)
  • server/internal/usecase/interfaces/project.go (4 hunks)
  • server/pkg/project/builder.go (2 hunks)
  • server/pkg/project/project.go (3 hunks)
  • server/pkg/status/Import_status.go (1 hunks)
💤 Files with no reviewable changes (1)
  • server/e2e/gql_project_export_import_test.go
🚧 Files skipped from review as they are similar to previous changes (15)
  • server/internal/infrastructure/mongo/project_test.go
  • server/internal/adapter/gql/gqlmodel/convert_project.go
  • server/internal/adapter/gql/gqlmodel/convert_project_test.go
  • server/pkg/project/builder.go
  • server/gql/project.graphql
  • server/pkg/project/project.go
  • server/pkg/status/Import_status.go
  • server/internal/usecase/interfaces/asset.go
  • server/internal/infrastructure/mongo/mongodoc/project.go
  • server/internal/adapter/gql/resolver_mutation_project.go
  • server/e2e/gql_scene_test.go
  • server/internal/usecase/interfaces/project.go
  • server/internal/usecase/interactor/project.go
  • server/internal/adapter/gql/gqlmodel/models_gen.go
  • server/internal/usecase/interactor/asset.go
🔇 Additional comments (12)
server/internal/app/file_uploader.go (4)

15-17: LGTM: Import additions support new functionality.

The new imports are appropriate for the enhanced project import status tracking functionality.

Also applies to: 28-34


48-48: LGTM: ProjectID field addition aligns with new workflow.

Adding ProjectID to the UploadSession struct is necessary to track the project created during the chunked upload process.


320-320: LGTM: Session update logic correctly handles project assignment.

The changes to accept an optional project parameter and store its ID are well-implemented.

Also applies to: 333-335


220-220:

✅ Verification successful

Verify parameter changes in usecase method calls.

The import methods now receive additional parameters (&session.ProjectID and newProject). Ensure these changes are consistent with the updated method signatures in the usecase layer.

Also applies to: 227-227


🏁 Script executed:

#!/bin/bash
# Description: Verify that the usecase methods accept the new parameters
# Expected: Method signatures should match the new parameter usage

# Check ImportProjectData method signature
ast-grep --pattern 'func ($$$) ImportProjectData($$$) ($$$) {
  $$$
}'

# Check ImportAssetFiles method signature  
ast-grep --pattern 'func ($$$) ImportAssetFiles($$$) ($$$) {
  $$$
}'

Length of output: 8669


Import method parameter verification

Verified that both ImportProjectData and ImportAssetFiles in the usecase layer accept the newly added parameters:

  • ImportProjectData(ctx context.Context, workspace string, projectId *string, data *[]byte, op *usecase.Operator) (*project.Project, error)
  • ImportAssetFiles(ctx context.Context, assets map[string]*zip.File, data *[]byte, newProject *project.Project, operator *usecase.Operator) (*[]byte, error)

No further changes are needed.

server/internal/adapter/gql/generated.go (8)

538-538: LGTM! Complexity function properly added.

The ImportStatus complexity function is correctly added to the Project complexity struct, maintaining consistency with other field complexity functions.


3758-3763: LGTM! Complexity calculation properly implemented.

The complexity calculation for Project.importStatus follows the standard pattern with proper nil checking and complexity function invocation.


6775-6775: LGTM! Non-nullable field correctly defined.

The importStatus field is properly defined as non-nullable (ProjectImportStatus!) in the GraphQL schema, which aligns with the business requirement that every project should have an import status.


6798-6803: LGTM! Enum values are well-defined.

The ProjectImportStatus enum includes all necessary states for tracking import progress: NONE (default), PROCESSING (in progress), FAILED (error state), and SUCCESS (completed). The values are clear and follow GraphQL naming conventions.


26854-26865: LGTM! Field context properly implemented.

The field context function for importStatus is correctly implemented with proper error handling for child field access attempts (which should return an error since enums don't have child fields).


27809-27810: LGTM! Field context routing properly added.

The importStatus field context routing is consistently added across all relevant GraphQL execution contexts, maintaining proper field resolution paths.

Also applies to: 28050-28051, 28152-28153, 34522-34523


51704-51708: LGTM! Field execution properly implemented.

The importStatus field execution is correctly added to the Project field resolution with proper null checking and invalid counter incrementing, following the same pattern as other non-nullable fields.


58403-58411: LGTM! Marshaling functions properly implemented.

The unmarshal and marshal functions for ProjectImportStatus are correctly implemented, delegating to the enum's own UnmarshalGQL method and returning the enum value directly for marshaling.

Comment on lines +170 to +177
var prj *project.Project
if chunkNum == 0 {
prj, err = CreateProcessingProject(ctx, usecases, op, teamId)
if err != nil {
return nil, err
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential security vulnerability: Unrestricted project creation.

Creating a project automatically on the first chunk (chunk 0) could enable abuse scenarios:

  1. DoS attacks: Malicious users could send multiple chunk 0 requests to create numerous projects
  2. Resource exhaustion: No apparent rate limiting or validation before project creation

Consider adding validation and rate limiting:

	var prj *project.Project
	if chunkNum == 0 {
+		// Validate team permissions and rate limits before creating project
+		if err := validateProjectCreation(ctx, usecases, op, teamId); err != nil {
+			return nil, echo.NewHTTPError(http.StatusForbidden, "Project creation not allowed")
+		}
		prj, err = CreateProcessingProject(ctx, usecases, op, teamId)
		if err != nil {
			return nil, err
		}
	}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In server/internal/app/file_uploader.go around lines 170 to 177, the code
creates a project automatically when chunkNum is 0 without any validation or
rate limiting, which can lead to DoS attacks and resource exhaustion. To fix
this, add validation checks to ensure the request is authorized and legitimate
before creating a project, and implement rate limiting to restrict how
frequently projects can be created by a user or IP. This will prevent abuse by
controlling project creation frequency and validating inputs.

Comment on lines +128 to +153
func CreateProcessingProject(ctx context.Context, usecases *interfaces.Container, op *usecase.Operator, teamId string) (*project.Project, error) {

visibility := "private"
coreSupport := true
unknown := "It's importing now..."
workspaceID, err := accountdomain.WorkspaceIDFrom(teamId)
if err != nil {
return nil, echo.NewHTTPError(http.StatusBadRequest, "Invalid team id")
}

prj, err := usecases.Project.Create(ctx, interfaces.CreateProjectParam{
WorkspaceID: workspaceID,
Visualizer: visualizer.VisualizerCesium,
Name: &unknown,
Description: &unknown,
CoreSupport: &coreSupport,
Visibility: &visibility,
ImportStatus: status.ProjectImportStatusProcessing, // PROCESSING
}, op)
if err != nil {
return nil, echo.NewHTTPError(http.StatusBadRequest, "Failed to create project")
}

return prj, nil

}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Review hardcoded project configuration and error handling.

The project creation logic has some concerns:

  1. Hardcoded values: Using "It's importing now..." for both name and description may not be user-friendly
  2. Error type inconsistency: Returning echo.NewHTTPError from a non-HTTP handler function is inappropriate

Consider this refactor:

func CreateProcessingProject(ctx context.Context, usecases *interfaces.Container, op *usecase.Operator, teamId string) (*project.Project, error) {
	visibility := "private"
	coreSupport := true
-	unknown := "It's importing now..."
+	name := "Importing Project"
+	description := "Project being imported via chunked upload"
	workspaceID, err := accountdomain.WorkspaceIDFrom(teamId)
	if err != nil {
-		return nil, echo.NewHTTPError(http.StatusBadRequest, "Invalid team id")
+		return nil, fmt.Errorf("invalid team id: %w", err)
	}

	prj, err := usecases.Project.Create(ctx, interfaces.CreateProjectParam{
		WorkspaceID:  workspaceID,
		Visualizer:   visualizer.VisualizerCesium,
-		Name:         &unknown,
-		Description:  &unknown,
+		Name:         &name,
+		Description:  &description,
		CoreSupport:  &coreSupport,
		Visibility:   &visibility,
		ImportStatus: status.ProjectImportStatusProcessing,
	}, op)
	if err != nil {
-		return nil, echo.NewHTTPError(http.StatusBadRequest, "Failed to create project")
+		return nil, fmt.Errorf("failed to create project: %w", err)
	}

	return prj, nil
}
📝 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 CreateProcessingProject(ctx context.Context, usecases *interfaces.Container, op *usecase.Operator, teamId string) (*project.Project, error) {
visibility := "private"
coreSupport := true
unknown := "It's importing now..."
workspaceID, err := accountdomain.WorkspaceIDFrom(teamId)
if err != nil {
return nil, echo.NewHTTPError(http.StatusBadRequest, "Invalid team id")
}
prj, err := usecases.Project.Create(ctx, interfaces.CreateProjectParam{
WorkspaceID: workspaceID,
Visualizer: visualizer.VisualizerCesium,
Name: &unknown,
Description: &unknown,
CoreSupport: &coreSupport,
Visibility: &visibility,
ImportStatus: status.ProjectImportStatusProcessing, // PROCESSING
}, op)
if err != nil {
return nil, echo.NewHTTPError(http.StatusBadRequest, "Failed to create project")
}
return prj, nil
}
func CreateProcessingProject(ctx context.Context, usecases *interfaces.Container, op *usecase.Operator, teamId string) (*project.Project, error) {
visibility := "private"
coreSupport := true
name := "Importing Project"
description := "Project being imported via chunked upload"
workspaceID, err := accountdomain.WorkspaceIDFrom(teamId)
if err != nil {
return nil, fmt.Errorf("invalid team id: %w", err)
}
prj, err := usecases.Project.Create(ctx, interfaces.CreateProjectParam{
WorkspaceID: workspaceID,
Visualizer: visualizer.VisualizerCesium,
Name: &name,
Description: &description,
CoreSupport: &coreSupport,
Visibility: &visibility,
ImportStatus: status.ProjectImportStatusProcessing,
}, op)
if err != nil {
return nil, fmt.Errorf("failed to create project: %w", err)
}
return prj, nil
}
🤖 Prompt for AI Agents
In server/internal/app/file_uploader.go lines 128 to 153, replace the hardcoded
project name and description "It's importing now..." with more meaningful or
configurable default values to improve user experience. Also, change the error
returns from echo.NewHTTPError to standard Go errors or custom error types
appropriate for non-HTTP handler functions, ensuring consistent and proper error
handling outside HTTP contexts.

Comment on lines 185 to 287
return
}

bgctx := context.Background()

assembledPath := filepath.Join(m.tempDir, session.FileID)
defer safeRemove(assembledPath)

if err := m.AssembleChunks(session, assembledPath); err != nil {
log.Printf("failed to assemble chunks: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}

fs := afero.NewOsFs()
f, err := fs.Open(assembledPath)
if err != nil {
log.Printf("failed to open assembled file: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
defer closeWithError(f, &err)

importData, assetsZip, pluginsZip, err := file_.UncompressExportZip(adapter.CurrentHost(bgctx), f)
if err != nil {
log.Printf("fail UncompressExportZip: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}

newProject, err := usecases.Project.ImportProjectData(bgctx, teamId, importData, op)
newProject, err := usecases.Project.ImportProjectData(bgctx, teamId, &session.ProjectID, importData, op)
if err != nil {
log.Printf("fail Import ProjectData: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}

importData, err = usecases.Asset.ImportAssetFiles(bgctx, assetsZip, importData, newProject)
importData, err = usecases.Asset.ImportAssetFiles(bgctx, assetsZip, importData, newProject, op)
if err != nil {
log.Printf("fail Import AssetFiles: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}

newScene, err := usecases.Scene.Create(bgctx, newProject.ID(), false, op)
if err != nil {
log.Printf("fail Create Scene: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}

oldSceneID, err := replaceOldSceneID(importData, newScene)
if err != nil {
log.Printf("fail Get OldSceneID: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}

_, _, err = usecases.Plugin.ImportPlugins(bgctx, pluginsZip, oldSceneID, newScene, importData)
if err != nil {
log.Printf("fail ImportPlugins: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}

newScene, err = usecases.Scene.ImportScene(bgctx, newScene, importData)
if err != nil {
log.Printf("fail sceneJSON ImportScene: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}

_, err = usecases.Style.ImportStyles(bgctx, newScene.ID(), importData)
if err != nil {
log.Printf("fail sceneJSON ImportStyles: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}

_, err = usecases.NLSLayer.ImportNLSLayers(bgctx, newScene.ID(), importData)
if err != nil {
log.Printf("fail sceneJSON ImportNLSLayers: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}

_, err = usecases.StoryTelling.ImportStory(bgctx, newScene.ID(), importData)
if err != nil {
log.Printf("fail sceneJSON ImportStory: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}

m.CleanupSession(session.FileID)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusSuccess) // SUCCESS
log.Printf("Upload completed: %s (%d chunks)", session.FileID, session.TotalChunks)

}(session)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix context handling and improve error management in async import.

The async goroutine has several issues:

  1. Context loss: Using context.Background() loses request tracing and cancellation
  2. Session data race: Accessing session.ProjectID without synchronization
  3. Resource cleanup: No cleanup on early failures

Improve the async import handling:

	if completed {
		// Import process, this process will take some time
		go func(session *UploadSession) {
+			// Create a context with timeout for the import process
+			importCtx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
+			defer cancel()

-			pid, err := id.ProjectIDFrom(session.ProjectID)
+			// Copy ProjectID to avoid race condition
+			projectIDStr := session.ProjectID
+			pid, err := id.ProjectIDFrom(projectIDStr)
			if err != nil {
				log.Printf("Invalid project id: %v", err)
				return
			}

-			bgctx := context.Background()
+			// Ensure cleanup on any failure
+			defer func() {
+				if r := recover(); r != nil {
+					log.Printf("Import process panicked: %v", r)
+					UpdateImportStatus(importCtx, usecases, op, pid, status.ProjectImportStatusFailed)
+				}
+				m.CleanupSession(session.FileID)
+			}()

			assembledPath := filepath.Join(m.tempDir, session.FileID)
			defer safeRemove(assembledPath)

			if err := m.AssembleChunks(session, assembledPath); err != nil {
				log.Printf("failed to assemble chunks: %v", err)
-				UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
+				UpdateImportStatus(importCtx, usecases, op, pid, status.ProjectImportStatusFailed)
				return
			}

			// Continue with the rest of the import process...
			// (Apply similar context changes throughout)

-			m.CleanupSession(session.FileID)
-			UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusSuccess)
+			UpdateImportStatus(importCtx, usecases, op, pid, status.ProjectImportStatusSuccess)
			log.Printf("Upload completed: %s (%d chunks)", session.FileID, session.TotalChunks)

		}(session)
	}
📝 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
go func(session *UploadSession) {
pid, err := id.ProjectIDFrom(session.ProjectID)
if err != nil {
log.Printf("Invalid project id: %v", err)
return
}
bgctx := context.Background()
assembledPath := filepath.Join(m.tempDir, session.FileID)
defer safeRemove(assembledPath)
if err := m.AssembleChunks(session, assembledPath); err != nil {
log.Printf("failed to assemble chunks: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
fs := afero.NewOsFs()
f, err := fs.Open(assembledPath)
if err != nil {
log.Printf("failed to open assembled file: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
defer closeWithError(f, &err)
importData, assetsZip, pluginsZip, err := file_.UncompressExportZip(adapter.CurrentHost(bgctx), f)
if err != nil {
log.Printf("fail UncompressExportZip: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
newProject, err := usecases.Project.ImportProjectData(bgctx, teamId, importData, op)
newProject, err := usecases.Project.ImportProjectData(bgctx, teamId, &session.ProjectID, importData, op)
if err != nil {
log.Printf("fail Import ProjectData: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
importData, err = usecases.Asset.ImportAssetFiles(bgctx, assetsZip, importData, newProject)
importData, err = usecases.Asset.ImportAssetFiles(bgctx, assetsZip, importData, newProject, op)
if err != nil {
log.Printf("fail Import AssetFiles: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
newScene, err := usecases.Scene.Create(bgctx, newProject.ID(), false, op)
if err != nil {
log.Printf("fail Create Scene: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
oldSceneID, err := replaceOldSceneID(importData, newScene)
if err != nil {
log.Printf("fail Get OldSceneID: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
_, _, err = usecases.Plugin.ImportPlugins(bgctx, pluginsZip, oldSceneID, newScene, importData)
if err != nil {
log.Printf("fail ImportPlugins: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
newScene, err = usecases.Scene.ImportScene(bgctx, newScene, importData)
if err != nil {
log.Printf("fail sceneJSON ImportScene: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
_, err = usecases.Style.ImportStyles(bgctx, newScene.ID(), importData)
if err != nil {
log.Printf("fail sceneJSON ImportStyles: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
_, err = usecases.NLSLayer.ImportNLSLayers(bgctx, newScene.ID(), importData)
if err != nil {
log.Printf("fail sceneJSON ImportNLSLayers: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
_, err = usecases.StoryTelling.ImportStory(bgctx, newScene.ID(), importData)
if err != nil {
log.Printf("fail sceneJSON ImportStory: %v", err)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
m.CleanupSession(session.FileID)
UpdateImportStatus(bgctx, usecases, op, pid, status.ProjectImportStatusSuccess) // SUCCESS
log.Printf("Upload completed: %s (%d chunks)", session.FileID, session.TotalChunks)
}(session)
go func(session *UploadSession) {
// Create a context with timeout for the import process
importCtx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
defer cancel()
// Copy ProjectID to avoid race condition
projectIDStr := session.ProjectID
pid, err := id.ProjectIDFrom(projectIDStr)
if err != nil {
log.Printf("Invalid project id: %v", err)
return
}
// Ensure cleanup on any failure (including panics)
defer func() {
if r := recover(); r != nil {
log.Printf("Import process panicked: %v", r)
UpdateImportStatus(importCtx, usecases, op, pid, status.ProjectImportStatusFailed)
}
m.CleanupSession(session.FileID)
}()
assembledPath := filepath.Join(m.tempDir, session.FileID)
defer safeRemove(assembledPath)
if err := m.AssembleChunks(session, assembledPath); err != nil {
log.Printf("failed to assemble chunks: %v", err)
UpdateImportStatus(importCtx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
fs := afero.NewOsFs()
f, err := fs.Open(assembledPath)
if err != nil {
log.Printf("failed to open assembled file: %v", err)
UpdateImportStatus(importCtx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
defer closeWithError(f, &err)
importData, assetsZip, pluginsZip, err := file_.UncompressExportZip(adapter.CurrentHost(importCtx), f)
if err != nil {
log.Printf("fail UncompressExportZip: %v", err)
UpdateImportStatus(importCtx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
newProject, err := usecases.Project.ImportProjectData(importCtx, teamId, &projectIDStr, importData, op)
if err != nil {
log.Printf("fail Import ProjectData: %v", err)
UpdateImportStatus(importCtx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
importData, err = usecases.Asset.ImportAssetFiles(importCtx, assetsZip, importData, newProject, op)
if err != nil {
log.Printf("fail Import AssetFiles: %v", err)
UpdateImportStatus(importCtx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
newScene, err := usecases.Scene.Create(importCtx, newProject.ID(), false, op)
if err != nil {
log.Printf("fail Create Scene: %v", err)
UpdateImportStatus(importCtx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
oldSceneID, err := replaceOldSceneID(importData, newScene)
if err != nil {
log.Printf("fail Get OldSceneID: %v", err)
UpdateImportStatus(importCtx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
_, _, err = usecases.Plugin.ImportPlugins(importCtx, pluginsZip, oldSceneID, newScene, importData)
if err != nil {
log.Printf("fail ImportPlugins: %v", err)
UpdateImportStatus(importCtx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
newScene, err = usecases.Scene.ImportScene(importCtx, newScene, importData)
if err != nil {
log.Printf("fail sceneJSON ImportScene: %v", err)
UpdateImportStatus(importCtx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
_, err = usecases.Style.ImportStyles(importCtx, newScene.ID(), importData)
if err != nil {
log.Printf("fail sceneJSON ImportStyles: %v", err)
UpdateImportStatus(importCtx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
_, err = usecases.NLSLayer.ImportNLSLayers(importCtx, newScene.ID(), importData)
if err != nil {
log.Printf("fail sceneJSON ImportNLSLayers: %v", err)
UpdateImportStatus(importCtx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
_, err = usecases.StoryTelling.ImportStory(importCtx, newScene.ID(), importData)
if err != nil {
log.Printf("fail sceneJSON ImportStory: %v", err)
UpdateImportStatus(importCtx, usecases, op, pid, status.ProjectImportStatusFailed)
return
}
// Final success update
UpdateImportStatus(importCtx, usecases, op, pid, status.ProjectImportStatusSuccess)
log.Printf("Upload completed: %s (%d chunks)", session.FileID, session.TotalChunks)
}(session)
🤖 Prompt for AI Agents
In server/internal/app/file_uploader.go around lines 185 to 287, the async
goroutine handling the import uses context.Background(), causing loss of request
context and cancellation signals. To fix this, pass the parent context into the
goroutine and use it instead of context.Background(). Also, protect access to
session.ProjectID with proper synchronization or copy its value before the
goroutine to avoid data races. Finally, ensure resource cleanup like removing
temporary files and closing opened files happens on all code paths, including
early returns due to errors, by deferring cleanup functions immediately after
resource acquisition.

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

Successfully merging this pull request may close these issues.

1 participant