diff --git a/server/internal/infrastructure/mongo/migration/251022100000_convert_topics_to_string.go b/server/internal/infrastructure/mongo/migration/251022100000_convert_topics_to_string.go new file mode 100644 index 0000000000..53f03f7ea1 --- /dev/null +++ b/server/internal/infrastructure/mongo/migration/251022100000_convert_topics_to_string.go @@ -0,0 +1,107 @@ +package migration + +import ( + "context" + "log" + "strings" + + "github.com/reearth/reearthx/mongox" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +func ConvertTopicsToString(ctx context.Context, c DBClient) error { + projectCol := c.WithCollection("project") + metadataCol := c.WithCollection("projectmetadata") + + // First, add empty topics field to all projects + log.Printf("Adding empty topics field to project collection\n") + if _, err := projectCol.Client().UpdateMany(ctx, bson.M{}, bson.M{ + "$set": bson.M{ + "topics": "", + }, + }); err != nil { + log.Printf("Failed to add topics field to projects: %v\n", err) + return err + } + log.Printf("Successfully added empty topics field to all projects\n") + + // Then convert topics from array to string in projectmetadata + return metadataCol.Find(ctx, bson.M{}, &mongox.BatchConsumer{ + Size: 1000, + Callback: func(rows []bson.Raw) error { + log.Printf("Processing batch of %d project metadata records\n", len(rows)) + + for _, row := range rows { + var metadata map[string]interface{} + if err := bson.Unmarshal(row, &metadata); err != nil { + log.Printf("Error unmarshaling project metadata: %v\n", err) + continue + } + + id, ok := metadata["project"].(string) + if !ok { + log.Printf("Skipping metadata with missing or invalid project id\n") + continue + } + + // Check if topics field exists and is an array + if topicsField, exists := metadata["topics"]; exists { + var topicStrings []string + var shouldUpdate bool + + switch topics := topicsField.(type) { + case []interface{}: + // Convert array of interfaces to string array + for _, topic := range topics { + if topicStr, ok := topic.(string); ok { + topicStrings = append(topicStrings, topicStr) + } + } + shouldUpdate = true + log.Printf("Found []interface{} topics for project %s\n", id) + + case primitive.A: + // Handle MongoDB primitive array type + for _, topic := range topics { + if topicStr, ok := topic.(string); ok { + topicStrings = append(topicStrings, topicStr) + } + } + shouldUpdate = true + log.Printf("Found primitive.A topics for project %s\n", id) + + case string: + // Already a string, skip + log.Printf("Topics already string format for project %s\n", id) + + default: + log.Printf("Unexpected topics format for project %s: %T\n", id, topics) + } + + if shouldUpdate { + // Join topics with comma and space + topicsString := strings.Join(topicStrings, ", ") + + // Update the metadata record + updateFilter := bson.M{"project": id} + updateFields := bson.M{ + "$set": bson.M{ + "topics": topicsString, + }, + } + + if _, err := metadataCol.Client().UpdateOne(ctx, updateFilter, updateFields); err != nil { + log.Printf("Failed to update topics for project metadata %s: %v\n", id, err) + continue + } + + log.Printf("Converted topics from array to string for project %s: %s\n", id, topicsString) + } + } + } + + return nil + }, + }) +} diff --git a/server/internal/infrastructure/mongo/migration/251022100100_update_project_metadata_fields.go b/server/internal/infrastructure/mongo/migration/251022100100_update_project_metadata_fields.go new file mode 100644 index 0000000000..0c6c980b2f --- /dev/null +++ b/server/internal/infrastructure/mongo/migration/251022100100_update_project_metadata_fields.go @@ -0,0 +1,78 @@ +package migration + +import ( + "context" + "log" + + "github.com/reearth/reearthx/mongox" + "go.mongodb.org/mongo-driver/bson" +) + +func UpdateProjectMetadataFields(ctx context.Context, c DBClient) error { + projectCol := c.WithCollection("project") + metadataCol := c.WithCollection("projectmetadata") + + return projectCol.Find(ctx, bson.M{}, &mongox.BatchConsumer{ + Size: 1000, + Callback: func(rows []bson.Raw) error { + log.Printf("Processing batch of %d projects\n", len(rows)) + + ids := make([]string, 0, len(rows)) + newRows := make([]interface{}, 0, len(rows)) + + for _, row := range rows { + var project map[string]interface{} + if err := bson.Unmarshal(row, &project); err != nil { + log.Printf("Error unmarshaling project: %v\n", err) + continue + } + + id, ok := project["id"].(string) + if !ok { + log.Printf("Skipping project with missing or invalid id\n") + continue + } + + // Remove unwanted fields if they exist in the project collection + for _, field := range []string{"topics", "star_count", "starred_by", "created_at"} { + if _, exists := project[field]; exists { + delete(project, field) + log.Printf("Removed field '%s' from project %s\n", field, id) + } + } + + // Check if projectmetadata exists for this project and update if found + var existingMetadata bson.Raw + err := metadataCol.Client().FindOne(ctx, bson.M{"project": id}).Decode(&existingMetadata) + + if err == nil { + // Existing metadata found, update the fields + updateFields := bson.M{ + "$set": bson.M{ + "topics": []string{}, + "star_count": 0, + "starred_by": []string{}, + }, + } + + if _, updateErr := metadataCol.Client().UpdateOne(ctx, bson.M{"project": id}, updateFields); updateErr != nil { + log.Printf("Failed to update metadata for project %s: %v\n", id, updateErr) + continue + } + log.Printf("Updated existing metadata for project %s in projectmetadata collection\n", id) + } else { + log.Printf("No existing metadata found for project %s, skipping metadata update\n", id) + } + + ids = append(ids, id) + newRows = append(newRows, project) + log.Printf("Processed project %s\n", id) + } + + if len(newRows) > 0 { + return projectCol.SaveAll(ctx, ids, newRows) + } + return nil + }, + }) +} diff --git a/server/internal/infrastructure/mongo/migration/251022133500_update_project_metadata_fields.go b/server/internal/infrastructure/mongo/migration/251022133500_update_project_metadata_fields.go index 0c6c980b2f..1a92573a70 100644 --- a/server/internal/infrastructure/mongo/migration/251022133500_update_project_metadata_fields.go +++ b/server/internal/infrastructure/mongo/migration/251022133500_update_project_metadata_fields.go @@ -8,7 +8,7 @@ import ( "go.mongodb.org/mongo-driver/bson" ) -func UpdateProjectMetadataFields(ctx context.Context, c DBClient) error { +func UpdateProjectMetadataFieldsFix(ctx context.Context, c DBClient) error { projectCol := c.WithCollection("project") metadataCol := c.WithCollection("projectmetadata") diff --git a/server/internal/infrastructure/mongo/migration/migrations.go b/server/internal/infrastructure/mongo/migration/migrations.go index 3696659213..aee6bbe1e7 100644 --- a/server/internal/infrastructure/mongo/migration/migrations.go +++ b/server/internal/infrastructure/mongo/migration/migrations.go @@ -10,33 +10,35 @@ import "github.com/reearth/reearthx/usecasex/migration" // If the migration takes too long, the deployment may fail in a serverless environment. // Set the batch size to as large a value as possible without using up the RAM of the deployment destination. var migrations = migration.Migrations[DBClient]{ - 201217132559: AddSceneWidgetId, - 201217193948: AddSceneDefaultTile, - 210310145844: RemovePreviewToken, - 220214180713: SplitSchemaOfProperties, - 220309174648: AddSceneFieldToPropertySchema, - 221028204300: MoveTerrainProperties, - 250305230545: AssetProjectAssociation, - 250317115957: AddIndex, - 250417160823: SetProjectVisibility, - 250507125156: UpdateAlias, - 250514185337: DeleteJunkData, - 250530154721: AddProjectMetadata, - 250709132207: SetProjectIdStorytelling, - 250709145819: SetProjectAlias, - 250711143148: AddProjectAlias, - 250716145417: TeamToWorkspace, - 250724121006: SetVisibilityPublic, - 250724145417: CopyWorkspaceToTeam, - 250724184601: ConvertProjectMetadataId, - 250724191400: CopyTeamToWorkspace, - 250725111952: AddProjectAlias2, - 250725112722: MetadataUpdate, - 250725133631: SetPhotoOverlayDefault, - 250725145932: ConvertNonValidProjectAliases, - 250820113009: ChangeEsriToDefault, - 251010100253: MultipleWidgetAlignSystems, - 251010100300: AddDefaultDataAttributionForMobile, - 251022133500: UpdateProjectMetadataFields, - 251029181312: GcschangeEsriToDefault, + 201217132559: AddSceneWidgetId, + 201217193948: AddSceneDefaultTile, + 210310145844: RemovePreviewToken, + 220214180713: SplitSchemaOfProperties, + 220309174648: AddSceneFieldToPropertySchema, + 221028204300: MoveTerrainProperties, + 250305230545: AssetProjectAssociation, + 250317115957: AddIndex, + 250417160823: SetProjectVisibility, + 250507125156: UpdateAlias, + 250514185337: DeleteJunkData, + 250530154721: AddProjectMetadata, + 250709132207: SetProjectIdStorytelling, + 250709145819: SetProjectAlias, + 250711143148: AddProjectAlias, + 250716145417: TeamToWorkspace, + 250724121006: SetVisibilityPublic, + 250724145417: CopyWorkspaceToTeam, + 250724184601: ConvertProjectMetadataId, + 250724191400: CopyTeamToWorkspace, + 250725111952: AddProjectAlias2, + 250725112722: MetadataUpdate, + 250725133631: SetPhotoOverlayDefault, + 250725145932: ConvertNonValidProjectAliases, + 250820113009: ChangeEsriToDefault, + 251010100253: MultipleWidgetAlignSystems, + 251010100300: AddDefaultDataAttributionForMobile, + 251022100000: ConvertTopicsToString, + 251022100100: UpdateProjectMetadataFields, + 251022133500: UpdateProjectMetadataFieldsFix, + 251029181312: GcschangeEsriToDefault, }