Skip to content

Commit

Permalink
refactor(api) update
Browse files Browse the repository at this point in the history
  • Loading branch information
helderbetiol committed Jul 22, 2024
1 parent 06c743b commit 7f23c9a
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 131 deletions.
15 changes: 1 addition & 14 deletions API/controllers/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
12 changes: 12 additions & 0 deletions API/models/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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
}
41 changes: 25 additions & 16 deletions API/models/entity_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
29 changes: 14 additions & 15 deletions API/models/entity_get_attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
}
}
}
Expand Down
180 changes: 94 additions & 86 deletions API/models/entity_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package models
import (
"encoding/json"
"errors"
"fmt"
"p3/repository"
u "p3/utils"
"strings"
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 7f23c9a

Please sign in to comment.