diff --git a/models/booking/booking.go b/models/booking/booking.go index 711e5ba..80c3068 100644 --- a/models/booking/booking.go +++ b/models/booking/booking.go @@ -10,7 +10,6 @@ import ( "cloud.o-forge.io/core/oc-lib/tools" ) - /* * Booking is a struct that represents a booking */ diff --git a/models/utils/common.go b/models/utils/common.go index 0c749d7..8feefca 100755 --- a/models/utils/common.go +++ b/models/utils/common.go @@ -128,10 +128,8 @@ func ModelGenericUpdateOne(change map[string]interface{}, id string, a Accessor) r.Sign() } - loaded := r.Serialize(r) // get the loaded object - for k, v := range change { // apply the changes, with a flatten method - loaded[k] = v - } + loaded := r.Serialize(r) // get the loaded object + deepMerge(loaded, change) newObj := a.NewObj() b, err = json.Marshal(loaded) if err != nil { @@ -255,6 +253,87 @@ func IsMySelf(peerID string, wfa Accessor) (bool, string) { return peerID == pp.GetID(), pp.GetID() } +// deepMerge overlays patch values onto base, preserving base values for keys +// absent from patch, nil patch values, and empty strings when base is non-empty. +// This prevents partial frontend payloads from silently erasing server-managed +// fields (source, env, country, owners, creator_id, creation_date, …). +func deepMerge(base, patch map[string]interface{}) { + for k, pv := range patch { + bv := base[k] + switch pvTyped := pv.(type) { + case map[string]interface{}: + if bvMap, ok := bv.(map[string]interface{}); ok { + deepMerge(bvMap, pvTyped) + } else { + base[k] = pv + } + case []interface{}: + if bvSlice, ok := bv.([]interface{}); ok { + base[k] = mergeSlices(bvSlice, pvTyped) + } else { + base[k] = pv + } + case string: + // Don't overwrite a non-empty base value with an empty string. + if pvTyped != "" { + base[k] = pv + } + default: + if pv != nil { + base[k] = pv + } + } + } +} + +// mergeSlices merges two slices element-wise. +// For slices of maps it matches elements by their "id" field when available; +// falls back to positional matching. An empty patch slice leaves base intact. +func mergeSlices(base, patch []interface{}) []interface{} { + if len(patch) == 0 { + return base + } + for _, e := range patch { + if _, ok := e.(map[string]interface{}); !ok { + return patch // non-map elements: replace wholesale + } + } + baseByID := map[string]map[string]interface{}{} + for _, e := range base { + if em, ok := e.(map[string]interface{}); ok { + if id, ok := em["id"].(string); ok && id != "" { + baseByID[id] = em + } + } + } + result := make([]interface{}, 0, len(patch)) + for i, pe := range patch { + pm, _ := pe.(map[string]interface{}) + if pm == nil { + result = append(result, pe) + continue + } + var baseElem map[string]interface{} + if id, ok := pm["id"].(string); ok && id != "" { + baseElem = baseByID[id] + } + if baseElem == nil && i < len(base) { + baseElem, _ = base[i].(map[string]interface{}) + } + if baseElem != nil { + merged := make(map[string]interface{}, len(baseElem)) + for k, v := range baseElem { + merged[k] = v + } + deepMerge(merged, pm) + result = append(result, merged) + } else { + result = append(result, pe) + } + } + return result +} + func GenerateNodeID() (string, error) { folderStatic := "/var/lib/opencloud-node" if _, err := os.Stat(folderStatic); err == nil {