diff --git a/insert.go b/insert.go index b4f2086..ab2d596 100644 --- a/insert.go +++ b/insert.go @@ -9,7 +9,6 @@ import ( "git.gsuntres.com/general/commons" ) - // InsertOneWithStruct can be used to insert defined structs. func (c *MongoClient) InsertOneFromStruct(ctx context.Context, database, name string, data any) (bson.M, error) { o, err := ToMap(data) diff --git a/replace.go b/replace.go index a11ef35..617f1b0 100644 --- a/replace.go +++ b/replace.go @@ -2,14 +2,19 @@ package mongo import ( "context" + "slices" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" + + "git.gsuntres.com/general/commons" ) -func (c *MongoClient) Replace(ctx context.Context, database, name string, id string, data bson.M) (bson.M, error) { +func (c *MongoClient) Replace(ctx context.Context, database, name string, id string, original bson.M) (bson.M, error) { collection := c.GetCollection(database, name) + data := commons.BsonClone(original) + filter := map[string]any { "_id": id } var found bson.M @@ -31,6 +36,22 @@ func (c *MongoClient) Replace(ctx context.Context, database, name string, id str PostReplace(updateResult, data, id) + ignoreAudit := slices.Contains(c.IgnoreAudit, name) + + if c.WithAudit && !ignoreAudit { + contx := commons.ContextSerialize(ctx, c.ContextFields) + audit := &AuditResult { + Entity: name, + Op: OpUpdate, + Data: original, + Before: found, + After: data, + Context: contx, + } + + (*c.OnAudit)(audit) + } + return data, nil } diff --git a/replace_test.go b/replace_test.go index ac65794..7d8ea2a 100644 --- a/replace_test.go +++ b/replace_test.go @@ -2,6 +2,7 @@ package mongo import ( "os" + "fmt" "context" "testing" "encoding/json" @@ -18,21 +19,23 @@ func TestReplace(t *testing.T) { ctx := context.Background() + // Insert to mycollection o, err := client.InsertOne(ctx, "mydb", "mycollection", data) if err != nil { t.Fatalf("Failed to insertOne %#v", err) } id := o["_id"].(string) - // first we retrieve the entity + // Retrieve from mycollection fetched, err := client.GetOne(ctx, "mydb", "mycollection", id) if err != nil { t.Fatalf("Failed to fetch %#v", err) } fetched["name"] = "Noah Patel" + + // Replace in mycollection replaced, err := client.Replace(ctx, "mydb", "mycollection", id, fetched) if err != nil { t.Fatalf("Failed to replace %#v", err) } - // t.Fatalf("-> %v", replaced) if replaced["_id"] != fetched["_id"] { t.Fatalf("Not the same entity") } @@ -177,5 +180,86 @@ func TestReplace_Discrimination_EnsureStore(t *testing.T) { if changed["store"] != "str_1234" { t.Fatal("Should have ensured store") } +} +func TestReplace_WithAudit(t *testing.T) { + client := GetMongoClient() + + // Insert sample + data := map[string]any { + "_id": "su_123458", + "name": "MyNameTODelete", + "age": int32(25), + } + + before, err := client.InsertOne(context.Background(), "mydb", "mycollection", data) + if err != nil { t.Fatalf("Failed to insertOne %#v", err) } + + var ( + onAudit_calls int + onAudit_data any + onAudit_before any + onAudit_after any + onAudit_context any + ) + + cancel := client.Subscribe(func(audit *AuditResult) error { + onAudit_calls++ + onAudit_data = audit.Data + onAudit_before = audit.Before + onAudit_after = audit.After + onAudit_context = audit.Context + + return nil + }) + + toupdate := map[string]any { "name": "** CHANGED NAME **" } + + ctx := context.Background() + ctx = context.WithValue(ctx, "account", "xxxxxx") + ctx = context.WithValue(ctx, "store", "str_4321") + o, err := client.Replace(ctx, "mydb", "mycollection", "su_123458", toupdate) + if err != nil { t.Fatalf("Failed to replace %#v", err) } + + cancel() + + if onAudit_calls != 1 { + t.Fatalf("ondelete should have been called once, not %d", onAudit_calls) + } + + if onAudit_data != nil { + dta := onAudit_data.(bson.M) + AssertSubset(t, dta, toupdate, "Should have been equal") + } else { + t.Fatal("should have data") + } + + if onAudit_before != nil { + bf := onAudit_before.(bson.M) + AssertSubset(t, bf, before, "Should have been equal") + } else { + t.Fatal("should have before") + } + + if onAudit_after != nil { + tp := fmt.Sprintf("%T", onAudit_after) + + expectedType := fmt.Sprintf("%T", map[string]any{}) + if tp != expectedType { + t.Fatalf("after has the wrong type %s", tp) + } + + after := onAudit_after.(map[string]any) + + AssertSubset(t, after, o, "Should have been equal") + } else { + t.Fatal("should have after") + } + + if onAudit_context != nil { + ctx := onAudit_context.(map[string]any) + AssertSubset(t, ctx, map[string]any{"account": "xxxxxx", "store": "str_4321"}, "Should have been equal") + } else { + t.Fatal("should have context") + } } \ No newline at end of file diff --git a/update_set.go b/update_set.go index a77693a..6cd9ce0 100644 --- a/update_set.go +++ b/update_set.go @@ -2,32 +2,66 @@ package mongo import ( "context" + "slices" "go.mongodb.org/mongo-driver/v2/bson" + "git.gsuntres.com/general/commons" ) // UpdateSet search documents using filter and updates the first it finds using the $set operator. -func (c *MongoClient) UpdateSet(ctx context.Context, database, name string, filter, data bson.M) (bool, error) { +func (c *MongoClient) UpdateSet(ctx context.Context, database, name string, filter, original bson.M) (bool, error) { collection := c.GetCollection(database, name) - + + data := commons.BsonClone(original) + prepareForUpdateSet(data) if err := c.DiscriminatorCheckAndApplyToFilter(ctx, name, filter); err != nil { return false, err } + f := Mongofy(&Query{ Filter: filter }) + if err := c.DiscriminatorOmitInData(name, data); err != nil { return false, err } + + var found bson.M + if c.WithAudit { + if err := collection.FindOne(ctx, f).Decode(&found); err != nil { + return false, err + } + } update := bson.M{ "$set": data } - updateResult, err := collection.UpdateOne(ctx, filter, update) + updateResult, err := collection.UpdateOne(ctx, f, update) if err != nil { return false, err } changed := updateResult.ModifiedCount != 0 + ignoreAudit := slices.Contains(c.IgnoreAudit, name) + + if changed && c.WithAudit && !ignoreAudit { + var after bson.M + if err := collection.FindOne(ctx, f).Decode(&after); err != nil { + return false, err + } + + contx := commons.ContextSerialize(ctx, c.ContextFields) + audit := &AuditResult { + Entity: name, + Op: OpUpdate, + Data: original, + Before: found, + After: after, + Context: contx, + } + + (*c.OnAudit)(audit) + } + return changed, nil } diff --git a/update_set_test.go b/update_set_test.go new file mode 100644 index 0000000..091b8c2 --- /dev/null +++ b/update_set_test.go @@ -0,0 +1,95 @@ +package mongo + +import ( + "fmt" + "context" + "testing" + + "go.mongodb.org/mongo-driver/v2/bson" +) + +func TestUpdateSet_WithAudit(t *testing.T) { + client := GetMongoClient() + + // Insert sample + data := map[string]any { + "_id": "su_122258", + "name": "MyNameToUpdateSet", + "age": int32(25), + } + + before, err := client.InsertOne(context.Background(), "mydb", "mycollection", data) + if err != nil { t.Fatalf("Failed to insertOne %#v", err) } + + var ( + onAudit_calls int + onAudit_data any + onAudit_before any + onAudit_after any + onAudit_context any + ) + + cancel := client.Subscribe(func(audit *AuditResult) error { + onAudit_calls++ + onAudit_data = audit.Data + onAudit_before = audit.Before + onAudit_after = audit.After + onAudit_context = audit.Context + + return nil + }) + + toupdate := map[string]any { "name": "** CHANGED USING UPDATE SET **" } + + ctx := context.Background() + ctx = context.WithValue(ctx, "account", "xxxxxx") + ctx = context.WithValue(ctx, "store", "str_4321") + ok, err := client.UpdateSet(ctx, "mydb", "mycollection", bson.M{"_id": "su_122258"}, toupdate) + if err != nil { t.Fatalf("Failed to update %#v", err) } + + if !ok { t.Fatal("Should have updated") } + + cancel() + + if onAudit_calls != 1 { + t.Fatalf("ondelete should have been called once, not %d", onAudit_calls) + } + + if onAudit_data != nil { + dta := onAudit_data.(bson.M) + AssertSubset(t, dta, toupdate, "Should have been equal") + } else { + t.Fatal("should have data") + } + + if onAudit_before != nil { + bf := onAudit_before.(bson.M) + AssertSubset(t, bf, before, "Should have been equal") + } else { + t.Fatal("should have before") + } + + if onAudit_after != nil { + tp := fmt.Sprintf("%T", onAudit_after) + + expectedType := fmt.Sprintf("%T", bson.M{}) + if tp != expectedType { + t.Fatalf("after has the wrong type %s", tp) + } + + after := onAudit_after.(bson.M) + + before["name"] = "** CHANGED USING UPDATE SET **" + + AssertSubset(t, after, before, "Should have been equal") + } else { + t.Fatal("should have after") + } + + if onAudit_context != nil { + ctx := onAudit_context.(map[string]any) + AssertSubset(t, ctx, map[string]any{"account": "xxxxxx", "store": "str_4321"}, "Should have been equal") + } else { + t.Fatal("should have context") + } +} \ No newline at end of file