Skip to content

Commit e59b64d

Browse files
feat(server): add migration to add new fields to project collection [VIZ-2269] (#1830)
1 parent 3c7c5a7 commit e59b64d

File tree

3 files changed

+205
-0
lines changed

3 files changed

+205
-0
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package migration
2+
3+
import (
4+
"context"
5+
"log"
6+
"time"
7+
8+
"github.com/reearth/reearthx/mongox"
9+
"go.mongodb.org/mongo-driver/bson"
10+
"go.mongodb.org/mongo-driver/bson/primitive"
11+
)
12+
13+
func AddProjectMetadataFields(ctx context.Context, c DBClient) error {
14+
projectCol := c.WithCollection("project")
15+
16+
return projectCol.Find(ctx, bson.M{}, &mongox.BatchConsumer{
17+
Size: 1000,
18+
Callback: func(rows []bson.Raw) error {
19+
log.Printf("Processing batch of %d projects\n", len(rows))
20+
21+
ids := make([]string, 0, len(rows))
22+
newRows := make([]interface{}, 0, len(rows))
23+
24+
for _, row := range rows {
25+
var project map[string]interface{}
26+
if err := bson.Unmarshal(row, &project); err != nil {
27+
log.Printf("Error unmarshaling project: %v\n", err)
28+
continue
29+
}
30+
31+
id, ok := project["id"].(string)
32+
if !ok {
33+
log.Printf("Skipping project with missing or invalid id\n")
34+
continue
35+
}
36+
37+
// Check if the new fields already exist
38+
if _, exists := project["created_at"]; exists {
39+
log.Printf("Project %s already has metadata fields, skipping\n", id)
40+
continue
41+
}
42+
43+
// Use existing updatedat as created_at if available, otherwise use current time
44+
now := time.Now()
45+
createdAt := now
46+
if updatedat, exists := project["updatedat"]; exists {
47+
// Handle both time.Time and primitive.DateTime types
48+
switch v := updatedat.(type) {
49+
case time.Time:
50+
createdAt = v
51+
case primitive.DateTime:
52+
createdAt = v.Time()
53+
default:
54+
log.Printf("Warning: updatedat field has unexpected type %T for project %s, using current time\n", v, id)
55+
}
56+
}
57+
58+
// Add the new fields
59+
project["created_at"] = createdAt
60+
project["topics"] = []string{}
61+
project["star_count"] = 0
62+
project["starred_by"] = []string{}
63+
64+
ids = append(ids, id)
65+
newRows = append(newRows, project)
66+
log.Printf("Updated project %s with new metadata fields\n", id)
67+
}
68+
69+
if len(newRows) > 0 {
70+
return projectCol.SaveAll(ctx, ids, newRows)
71+
}
72+
return nil
73+
},
74+
})
75+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package migration
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/reearth/reearthx/mongox"
9+
"github.com/reearth/reearthx/mongox/mongotest"
10+
"github.com/stretchr/testify/assert"
11+
"go.mongodb.org/mongo-driver/bson"
12+
"go.mongodb.org/mongo-driver/bson/primitive"
13+
)
14+
15+
func TestAddProjectMetadataFields(t *testing.T) {
16+
ctx := context.Background()
17+
db := mongotest.Connect(t)(t)
18+
client := mongox.NewClientWithDatabase(db)
19+
20+
// Setup: Insert test projects
21+
projectColl := client.WithCollection("project").Client()
22+
now := time.Now()
23+
var err error
24+
25+
testProjects := []bson.M{
26+
{
27+
"id": "project1",
28+
"workspace": "workspace1",
29+
"name": "Test Project 1",
30+
"updatedat": primitive.NewDateTimeFromTime(now.Add(-24 * time.Hour)),
31+
},
32+
{
33+
"id": "project2",
34+
"workspace": "workspace2",
35+
"name": "Test Project 2",
36+
"updatedat": primitive.NewDateTimeFromTime(now.Add(-48 * time.Hour)),
37+
},
38+
}
39+
40+
_, err = projectColl.InsertMany(ctx, []interface{}{testProjects[0], testProjects[1]})
41+
assert.NoError(t, err)
42+
43+
// Run migration
44+
err = AddProjectMetadataFields(ctx, client)
45+
assert.NoError(t, err)
46+
47+
// Verify results
48+
cursor, err := projectColl.Find(ctx, bson.M{})
49+
assert.NoError(t, err)
50+
defer cursor.Close(ctx)
51+
52+
projects := []bson.M{}
53+
err = cursor.All(ctx, &projects)
54+
assert.NoError(t, err)
55+
assert.Len(t, projects, 2)
56+
57+
for _, project := range projects {
58+
// Check that new fields were added
59+
assert.Contains(t, project, "created_at")
60+
assert.Contains(t, project, "topics")
61+
assert.Contains(t, project, "star_count")
62+
assert.Contains(t, project, "starred_by")
63+
64+
// Check that updatedat field is still present (unchanged)
65+
assert.Contains(t, project, "updatedat")
66+
67+
// Verify field types and values
68+
assert.IsType(t, primitive.DateTime(0), project["created_at"])
69+
assert.IsType(t, primitive.DateTime(0), project["updatedat"])
70+
assert.IsType(t, primitive.A{}, project["topics"])
71+
assert.IsType(t, int32(0), project["star_count"])
72+
assert.IsType(t, primitive.A{}, project["starred_by"])
73+
74+
// Verify topics is empty array
75+
topics := project["topics"].(primitive.A)
76+
assert.Len(t, topics, 0)
77+
78+
// Verify star_count is 0
79+
assert.Equal(t, int32(0), project["star_count"])
80+
81+
// Verify starred_by is empty array
82+
starredBy := project["starred_by"].(primitive.A)
83+
assert.Len(t, starredBy, 0)
84+
}
85+
}
86+
87+
func TestAddProjectMetadataFields_AlreadyMigrated(t *testing.T) {
88+
ctx := context.Background()
89+
db := mongotest.Connect(t)(t)
90+
client := mongox.NewClientWithDatabase(db)
91+
92+
// Setup: Insert project that's already been migrated
93+
projectColl := client.WithCollection("project").Client()
94+
95+
testProject := bson.M{
96+
"id": "project1",
97+
"workspace": "workspace1",
98+
"name": "Test Project 1",
99+
"created_at": primitive.NewDateTimeFromTime(time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)),
100+
"topics": []string{"existing", "topics"},
101+
"star_count": 5,
102+
"starred_by": []string{"user1", "user2"},
103+
}
104+
105+
_, err := projectColl.InsertOne(ctx, testProject)
106+
assert.NoError(t, err)
107+
108+
// Run migration
109+
err = AddProjectMetadataFields(ctx, client)
110+
assert.NoError(t, err)
111+
112+
// Verify the existing values weren't changed
113+
var result bson.M
114+
err = projectColl.FindOne(ctx, bson.M{"id": "project1"}).Decode(&result)
115+
assert.NoError(t, err)
116+
117+
assert.Equal(t, primitive.NewDateTimeFromTime(time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)), result["created_at"])
118+
assert.Equal(t, int32(5), result["star_count"])
119+
120+
topics := result["topics"].(primitive.A)
121+
assert.Len(t, topics, 2)
122+
assert.Equal(t, "existing", topics[0])
123+
assert.Equal(t, "topics", topics[1])
124+
125+
starredBy := result["starred_by"].(primitive.A)
126+
assert.Len(t, starredBy, 2)
127+
assert.Equal(t, "user1", starredBy[0])
128+
assert.Equal(t, "user2", starredBy[1])
129+
}

server/internal/infrastructure/mongo/migration/migrations.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)