package audit import ( "reflect" "regexp" "strings" "github.com/r3labs/diff/v2" "gitlab.com/uafrica/go-utils/reflection" "gitlab.com/uafrica/go-utils/string_utils" ) type FieldChange struct { From interface{} `json:"change_from"` To interface{} `json:"change_to"` } func GetChanges(original interface{}, new interface{}) (map[string]interface{}, error) { changes := map[string]interface{}{} changelog, err := diff.Diff(original, new) if err != nil { return changes, err } for _, change := range changelog { if len(change.Path) == 1 { // Root object change field := ToSnakeCase(change.Path[0]) changes[field] = FieldChange{ From: change.From, To: change.To, } } else if len(change.Path) == 2 { // Child object changed // ["Account", "ID"] // 0 = Object // 1 = field objectKey := ToSnakeCase(change.Path[0]) field := ToSnakeCase(change.Path[1]) existingObject, present := changes[objectKey] if present { if object, ok := existingObject.(map[string]interface{}); ok { object[field] = FieldChange{ From: change.From, To: change.To, } changes[objectKey] = object } } else { fieldChange := map[string]interface{}{ field: FieldChange{ From: change.From, To: change.To, }, } changes[objectKey] = fieldChange } } else if len(change.Path) == 3 { // Array of objects // ["Parcel", "0", "ActualWeight"] // 0 = Object // 1 = Index of object // 2 = field objectKey := ToSnakeCase(change.Path[0]) indexString := change.Path[1] index, _ := string_utils.StringToInt64(indexString) field := ToSnakeCase(change.Path[2]) arrayObject, present := changes[objectKey] if present { if arrayOfObjects, ok := arrayObject.([]map[string]interface{}); ok { if len(arrayOfObjects) > int(index) { // Add field to existing object in array object := arrayOfObjects[index] object[field] = FieldChange{ From: change.From, To: change.To, } } else { // new object, append to existing array fieldChange := map[string]interface{}{ field: FieldChange{ From: change.From, To: change.To, }, } changes[objectKey] = append(arrayOfObjects, fieldChange) } } } else { // Create array of objects fieldChange := map[string]interface{}{ field: FieldChange{ From: change.From, To: change.To, }, } changes[objectKey] = []map[string]interface{}{ fieldChange, } } } } return changes, nil } var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") func ToSnakeCase(str string) string { snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}") snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}") return strings.ToLower(snake) } func GetIntValue(object interface{}, key string) int64 { structValue := reflect.ValueOf(object) if structValue.Kind() == reflect.Struct { field := structValue.FieldByName(key) id := reflection.GetInt64Value(field) return id } return 0 } func GetStringValue(object interface{}, key string) string { structValue := reflect.ValueOf(object) if structValue.Kind() == reflect.Struct { field := structValue.FieldByName(key) id := reflection.GetStringValue(field) return id } return "" }