From 7f23c9a240cd4d3bc75fb7b4fcb22882b6ae2bcf Mon Sep 17 00:00:00 2001 From: helderbetiol <37706737+helderbetiol@users.noreply.github.com> Date: Mon, 22 Jul 2024 15:14:43 +0200 Subject: [PATCH] refactor(api) update --- API/controllers/entity.go | 15 +-- API/models/db.go | 12 ++ API/models/entity_get.go | 41 ++++--- API/models/entity_get_attribute.go | 29 +++-- API/models/entity_update.go | 180 +++++++++++++++-------------- 5 files changed, 146 insertions(+), 131 deletions(-) diff --git a/API/controllers/entity.go b/API/controllers/entity.go index e2437333..7806f74f 100644 --- a/API/controllers/entity.go +++ b/API/controllers/entity.go @@ -788,20 +788,7 @@ func GetEntity(w http.ResponseWriter, r *http.Request) { // Get entity if id, canParse = mux.Vars(r)["id"]; canParse { - var req primitive.M - if entityStr == u.HIERARCHYOBJS_ENT { - data, modelErr = models.GetHierarchicalObjectById(id, filters, user.Roles) - } else { - if u.IsEntityNonHierarchical(u.EntityStrToInt(entityStr)) { - // Get by slug - req = bson.M{"slug": id} - - } else { - req = bson.M{"id": id} - } - - data, modelErr = models.GetObject(req, entityStr, filters, user.Roles) - } + data, modelErr = models.GetObjectById(id, entityStr, filters, user.Roles) } else { w.WriteHeader(http.StatusBadRequest) u.Respond(w, u.Message("Error while parsing path parameters")) diff --git a/API/models/db.go b/API/models/db.go index 0006947d..eafa1d28 100644 --- a/API/models/db.go +++ b/API/models/db.go @@ -7,6 +7,8 @@ import ( u "p3/utils" "strings" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" ) @@ -105,3 +107,13 @@ func castResult[T any](result any) T { return result.(T) } + +func GetIdReqByEntity(entityStr, id string) primitive.M { + var idFilter primitive.M + if u.IsEntityNonHierarchical(u.EntityStrToInt(entityStr)) { + idFilter = bson.M{"slug": id} + } else { + idFilter = bson.M{"id": id} + } + return idFilter +} diff --git a/API/models/entity_get.go b/API/models/entity_get.go index ed68919e..f0bfee02 100644 --- a/API/models/entity_get.go +++ b/API/models/entity_get.go @@ -11,6 +11,15 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" ) +func GetObjectById(id, entityStr string, filters u.RequestFilters, userRoles map[string]Role) (map[string]any, *u.Error) { + if entityStr == u.HIERARCHYOBJS_ENT { + return GetHierarchicalObjectById(id, filters, userRoles) + } else { + req := GetIdReqByEntity(entityStr, id) + return GetObject(req, entityStr, filters, userRoles) + } +} + func GetObject(req bson.M, entityStr string, filters u.RequestFilters, userRoles map[string]Role) (map[string]interface{}, *u.Error) { object, err := repository.GetObject(req, entityStr, filters) @@ -40,6 +49,22 @@ func GetObject(req bson.M, entityStr string, filters u.RequestFilters, userRoles return object, nil } +func GetHierarchicalObjectById(hierarchyName string, filters u.RequestFilters, userRoles map[string]Role) (map[string]interface{}, *u.Error) { + // Get possible collections for this name + rangeEntities := u.GetEntitiesById(u.PHierarchy, hierarchyName) + req := bson.M{"id": hierarchyName} + + // Search each collection + for _, entityStr := range rangeEntities { + data, _ := GetObject(req, entityStr, filters, userRoles) + if data != nil { + return data, nil + } + } + + return nil, &u.Error{Type: u.ErrNotFound, Message: "Unable to find object"} +} + func GetManyObjects(entityStr string, req bson.M, filters u.RequestFilters, complexFilterExp string, userRoles map[string]Role) ([]map[string]interface{}, *u.Error) { ctx, cancel := u.Connect() var err error @@ -83,22 +108,6 @@ func GetManyObjects(entityStr string, req bson.M, filters u.RequestFilters, comp return data, nil } -func GetHierarchicalObjectById(hierarchyName string, filters u.RequestFilters, userRoles map[string]Role) (map[string]interface{}, *u.Error) { - // Get possible collections for this name - rangeEntities := u.GetEntitiesById(u.PHierarchy, hierarchyName) - req := bson.M{"id": hierarchyName} - - // Search each collection - for _, entityStr := range rangeEntities { - data, _ := GetObject(req, entityStr, filters, userRoles) - if data != nil { - return data, nil - } - } - - return nil, &u.Error{Type: u.ErrNotFound, Message: "Unable to find object"} -} - func GetEntityCount(entity int) int64 { ent := u.EntityToString(entity) ctx, cancel := u.Connect() diff --git a/API/models/entity_get_attribute.go b/API/models/entity_get_attribute.go index 64ca7e7a..9f883864 100644 --- a/API/models/entity_get_attribute.go +++ b/API/models/entity_get_attribute.go @@ -24,21 +24,20 @@ func GetSiteParentAttribute(id string, attribute string) (map[string]any, *u.Err } // Find object for _, collName := range collNames { - err := db.Collection(collName).FindOne(ctx, bson.M{"id": id}).Decode(&data) - if err == nil { - // Found object with given id - if data["category"].(string) == "site" { - // it's a site - break - } else { - // Find its parent site - nameSlice := strings.Split(data["id"].(string), u.HN_DELIMETER) - siteName := nameSlice[0] // CONSIDER SITE AS 0 - err := db.Collection("site").FindOne(ctx, bson.M{"id": siteName}).Decode(&data) - if err != nil { - return nil, &u.Error{Type: u.ErrNotFound, - Message: "Could not find parent site for given object"} - } + if err := db.Collection(collName).FindOne(ctx, bson.M{"id": id}).Decode(&data); err != nil { + continue + } + // Found object with given id + if data["category"].(string) == "site" { + // it's a site + break + } else { + // Find its parent site + nameSlice := strings.Split(data["id"].(string), u.HN_DELIMETER) + siteName := nameSlice[0] // CONSIDER SITE AS 0 + if err := db.Collection("site").FindOne(ctx, bson.M{"id": siteName}).Decode(&data); err != nil { + return nil, &u.Error{Type: u.ErrNotFound, + Message: "Could not find parent site for given object"} } } } diff --git a/API/models/entity_update.go b/API/models/entity_update.go index 7bb22cdd..472b70dd 100644 --- a/API/models/entity_update.go +++ b/API/models/entity_update.go @@ -3,6 +3,7 @@ package models import ( "encoding/json" "errors" + "fmt" "p3/repository" u "p3/utils" "strings" @@ -18,30 +19,14 @@ import ( var AttrsWithInnerObj = []string{"pillars", "separators", "breakers"} func UpdateObject(entityStr string, id string, updateData map[string]interface{}, isPatch bool, userRoles map[string]Role, isRecursive bool) (map[string]interface{}, *u.Error) { - var idFilter bson.M - if u.IsEntityNonHierarchical(u.EntityStrToInt(entityStr)) { - idFilter = bson.M{"slug": id} - } else { - idFilter = bson.M{"id": id} - } - - //Update timestamp requires first obj retrieval - //there isn't any way for mongoDB to make a field - //immutable in a document - var oldObj map[string]any - var err *u.Error - if entityStr == u.HIERARCHYOBJS_ENT { - oldObj, err = GetHierarchicalObjectById(id, u.RequestFilters{}, userRoles) - if err == nil { - entityStr = oldObj["category"].(string) - } - } else { - oldObj, err = GetObject(idFilter, entityStr, u.RequestFilters{}, userRoles) - } + // Update timestamp requires, first, obj retrieval + oldObj, err := GetObjectById(id, entityStr, u.RequestFilters{}, userRoles) if err != nil { return nil, err + } else if entityStr == u.HIERARCHYOBJS_ENT { + // overwrite category + entityStr = oldObj["category"].(string) } - entity := u.EntityStrToInt(entityStr) // Check if permission is only readonly @@ -55,41 +40,39 @@ func UpdateObject(entityStr string, id string, updateData map[string]interface{} // Update old object data with patch data if isPatch { - if tagsPresent { - return nil, &u.Error{ - Type: u.ErrBadFormat, - Message: "Tags cannot be modified in this way, use tags+ and tags-", - } - } - - var formattedOldObj map[string]interface{} - // Convert primitive.A and similar types - bytes, _ := json.Marshal(oldObj) - json.Unmarshal(bytes, &formattedOldObj) - // Update old with new - err := updateOldObjWithPatch(formattedOldObj, updateData) - if err != nil { - return nil, &u.Error{Type: u.ErrBadFormat, Message: err.Error()} + println("is PATCH") + if patchData, err := preparePatch(tagsPresent, updateData, oldObj); err != nil { + return nil, err + } else { + updateData = patchData } - - updateData = formattedOldObj - // Remove API set fields - delete(updateData, "id") - delete(updateData, "lastUpdated") - delete(updateData, "createdDate") } else if tagsPresent { - err := verifyTagList(tags) - if err != nil { + if err := verifyTagList(tags); err != nil { return nil, err } } - result, err := WithTransaction(func(ctx mongo.SessionContext) (interface{}, error) { - err = prepareUpdateObject(ctx, entity, id, updateData, oldObj, userRoles) + fmt.Println(updateData) + result, err := UpdateTransaction(entity, id, isRecursive, updateData, oldObj, userRoles) + if err != nil { + return nil, err + } + + var updatedDoc map[string]interface{} + result.(*mongo.SingleResult).Decode(&updatedDoc) + + return fixID(updatedDoc), nil +} + +func UpdateTransaction(entity int, id string, isRecursive bool, updateData, oldObj map[string]any, userRoles map[string]Role) (any, *u.Error) { + entityStr := u.EntityToString(entity) + return WithTransaction(func(ctx mongo.SessionContext) (interface{}, error) { + err := prepareUpdateObject(ctx, entity, id, updateData, oldObj, userRoles) if err != nil { return nil, err } + idFilter := GetIdReqByEntity(entityStr, id) mongoRes := repository.GetDB().Collection(entityStr).FindOneAndReplace( ctx, idFilter, updateData, @@ -99,54 +82,39 @@ func UpdateObject(entityStr string, id string, updateData map[string]interface{} return nil, mongoRes.Err() } - if oldObj["id"] != updateData["id"] { - // Changes to id should be propagated - if err := repository.PropagateParentIdChange( - ctx, - oldObj["id"].(string), - updateData["id"].(string), - entity, - ); err != nil { - return nil, err - } else if entity == u.DOMAIN { - if err := repository.PropagateDomainChange(ctx, - oldObj["id"].(string), - updateData["id"].(string), - ); err != nil { - return nil, err - } - } - } - if u.IsEntityHierarchical(entity) && (oldObj["domain"] != updateData["domain"]) { - if isRecursive { - // Change domain of all children too - if err := repository.PropagateDomainChangeToChildren( - ctx, - updateData["id"].(string), - updateData["domain"].(string), - ); err != nil { - return nil, err - } - } else { - // Check if children domains are compatible - if err := repository.CheckParentDomainChange(entity, updateData["id"].(string), - updateData["domain"].(string)); err != nil { - return nil, err - } - } + if err := propagateUpdateChanges(ctx, entity, oldObj, updateData, isRecursive); err != nil { + return nil, err } return mongoRes, nil }) +} - if err != nil { - return nil, err +func preparePatch(tagsPresent bool, updateData, oldObj map[string]any) (map[string]any, *u.Error) { + if tagsPresent { + return nil, &u.Error{ + Type: u.ErrBadFormat, + Message: "Tags cannot be modified in this way, use tags+ and tags-", + } } - var updatedDoc map[string]interface{} - result.(*mongo.SingleResult).Decode(&updatedDoc) + var formattedOldObj map[string]interface{} + // Convert primitive.A and similar types + bytes, _ := json.Marshal(oldObj) + json.Unmarshal(bytes, &formattedOldObj) + // Update old with new + err := updateOldObjWithPatch(formattedOldObj, updateData) + if err != nil { + return nil, &u.Error{Type: u.ErrBadFormat, Message: err.Error()} + } - return fixID(updatedDoc), nil + updateData = formattedOldObj + // Remove API set fields + delete(updateData, "id") + delete(updateData, "lastUpdated") + delete(updateData, "createdDate") + fmt.Println(updateData) + return updateData, nil } func prepareUpdateObject(ctx mongo.SessionContext, entity int, id string, updateData, oldObject map[string]any, userRoles map[string]Role) *u.Error { @@ -219,6 +187,46 @@ func updateOldObjWithPatch(old map[string]interface{}, patch map[string]interfac return nil } +func propagateUpdateChanges(ctx mongo.SessionContext, entity int, oldObj, updateData map[string]any, isRecursive bool) error { + if oldObj["id"] != updateData["id"] { + // Changes to id should be propagated + if err := repository.PropagateParentIdChange( + ctx, + oldObj["id"].(string), + updateData["id"].(string), + entity, + ); err != nil { + return err + } else if entity == u.DOMAIN { + if err := repository.PropagateDomainChange(ctx, + oldObj["id"].(string), + updateData["id"].(string), + ); err != nil { + return err + } + } + } + if u.IsEntityHierarchical(entity) && (oldObj["domain"] != updateData["domain"]) { + if isRecursive { + // Change domain of all children too + if err := repository.PropagateDomainChangeToChildren( + ctx, + updateData["id"].(string), + updateData["domain"].(string), + ); err != nil { + return err + } + } else { + // Check if children domains are compatible + if err := repository.CheckParentDomainChange(entity, updateData["id"].(string), + updateData["domain"].(string)); err != nil { + return err + } + } + } + return nil +} + // SwapEntity: use id to remove object from deleteEnt and then use data to create it in createEnt. // Propagates id changes to children objects. For atomicity, all is done in a Mongo transaction. func SwapEntity(createEnt, deleteEnt, id string, data map[string]interface{}, userRoles map[string]Role) *u.Error {