Skip to content
Snippets Groups Projects
Commit a457f019 authored by Jano Hendriks's avatar Jano Hendriks
Browse files

Update audit events to handle custom formatting

parent d82e4aa5
No related branches found
No related tags found
No related merge requests found
......@@ -3,25 +3,29 @@ package audit
import (
"encoding/json"
"fmt"
"github.com/r3labs/diff/v2"
"github.com/samber/lo"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/logs"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/number_utils"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/reflection"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/string_utils"
"reflect"
"regexp"
"strconv"
"strings"
"github.com/r3labs/diff/v2"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/reflection"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/string_utils"
)
type FieldChange struct {
From interface{} `json:"change_from"`
To interface{} `json:"change_to"`
From any `json:"change_from"`
To any `json:"change_to"`
}
func VerifyAuditEvents(original interface{}, new interface{}) error {
type IAuditFormatter interface {
FormatForAuditEvent() string
}
func VerifyAuditEvents(original any, new any) error {
if original != nil {
structValue := reflect.ValueOf(original)
if structValue.Kind() != reflect.Struct {
......@@ -39,12 +43,12 @@ func VerifyAuditEvents(original interface{}, new interface{}) error {
return nil
}
func GetChanges(original interface{}, new interface{}) (map[string]interface{}, error) {
func GetChanges(original any, new any) (map[string]any, error) {
// Clean audit events
original = cleanStruct(original)
new = cleanStruct(new)
changes := map[string]interface{}{}
changes := map[string]any{}
changelog, err := diff.Diff(original, new)
if err != nil {
return changes, err
......@@ -64,6 +68,11 @@ func GetChanges(original interface{}, new interface{}) (map[string]interface{},
// ["Account", "ID"]
// 0 = Object
// 1 = field
objectKey := ToSnakeCase(change.Path[0])
didInsert := CheckToFormatForAuditEvent(changes, original, new, change.Path[0], objectKey, -1)
if didInsert {
continue
}
ChildObjectChanges(changes, change.Path[0], change.Path[1], change.From, change.To)
......@@ -79,6 +88,11 @@ func GetChanges(original interface{}, new interface{}) (map[string]interface{},
if !string_utils.IsNumericString(indexString) {
// Not an array, but a deeper nested object
didInsert := CheckToFormatForAuditEvent(changes, original, new, change.Path[0], objectKey, -1)
if didInsert {
continue
}
ChildObjectChanges(changes, change.Path[len(change.Path)-2], change.Path[len(change.Path)-1], change.From, change.To)
continue
}
......@@ -86,6 +100,11 @@ func GetChanges(original interface{}, new interface{}) (map[string]interface{},
index, _ := number_utils.StringToInt64(indexString)
field := ToSnakeCase(change.Path[2])
didInsert := CheckToFormatForAuditEvent(changes, original, new, change.Path[0], objectKey, int(index))
if didInsert {
continue
}
if len(change.Path) == 5 && string_utils.IsNumericString(change.Path[3]) {
// The field is actually an array of objects.
field += fmt.Sprintf("[%s] (%s)", change.Path[3], ToSnakeCase(change.Path[4]))
......@@ -93,7 +112,7 @@ func GetChanges(original interface{}, new interface{}) (map[string]interface{},
arrayObject, present := changes[objectKey]
if present {
if arrayOfObjects, ok := arrayObject.([]map[string]interface{}); ok {
if arrayOfObjects, ok := arrayObject.([]map[string]any); ok {
arrayIndex := ArrayIndexForObjectIndex(arrayOfObjects, index)
if arrayIndex != -1 {
// Add field to existing object in array
......@@ -104,7 +123,7 @@ func GetChanges(original interface{}, new interface{}) (map[string]interface{},
}
} else {
// new object, append to existing array
fieldChange := map[string]interface{}{
fieldChange := map[string]any{
"index": index,
field: FieldChange{
From: change.From,
......@@ -117,14 +136,14 @@ func GetChanges(original interface{}, new interface{}) (map[string]interface{},
}
} else {
// Create array of objects
fieldChange := map[string]interface{}{
fieldChange := map[string]any{
"index": index,
field: FieldChange{
From: change.From,
To: change.To,
},
}
changes[objectKey] = []map[string]interface{}{
changes[objectKey] = []map[string]any{
fieldChange,
}
}
......@@ -134,7 +153,7 @@ func GetChanges(original interface{}, new interface{}) (map[string]interface{},
return changes, nil
}
func cleanStruct(object interface{}) interface{} {
func cleanStruct(object any) any {
defer func() {
if err := recover(); err != nil {
logs.ErrorMsg(fmt.Sprintf("audit event panic: %+v", err))
......@@ -175,9 +194,10 @@ func cleanStruct(object interface{}) interface{} {
structField := val.Type().Field(i)
// Determine whether the field should be included or excluded
value, _ := structField.Tag.Lookup("audit")
shouldIncludeForAudit := value == "true"
shouldExcludeForAudit := value == "false"
auditTag := structField.Tag.Get("audit")
auditTagOptions := strings.Split(auditTag, ",")
shouldIncludeForAudit := lo.Contains(auditTagOptions, "true")
shouldExcludeForAudit := lo.Contains(auditTagOptions, "false")
// If the audit tag is present and specified to 'true', we should always include the relation
if shouldIncludeForAudit {
......@@ -197,14 +217,158 @@ func cleanStruct(object interface{}) interface{} {
return object
}
func ChildObjectChanges(changes map[string]interface{}, objectPath string, fieldPath string, changeFrom interface{}, changeTo interface{}) {
func CheckToFormatForAuditEvent(changes map[string]any, original any, new any, field string, objectKey string, index int) (didInsert bool) {
originalStructField, originalFieldValue, found := getFieldFromStruct(original, field)
if !found {
return false
}
_, newFieldValue, found := getFieldFromStruct(new, field)
if !found {
return false
}
doGroupSlice := doGroupSliceForAuditEvent(originalStructField)
originalFormattedObject := checkToFormatForAuditEvent(originalFieldValue, index)
newFormattedObject := checkToFormatForAuditEvent(newFieldValue, index)
if originalFormattedObject != nil ||
newFormattedObject != nil {
if _, present := changes[objectKey]; present {
if doGroupSlice {
// The object has already been added to the changes - group the new change with the previous
existingChanges, ok := changes[objectKey].(FieldChange)
if !ok {
return true
}
if originalFormattedObject != nil {
existingChanges.From = appendNewChangeToExistingChanges(existingChanges.From, *originalFormattedObject)
}
if newFormattedObject != nil {
existingChanges.To = appendNewChangeToExistingChanges(existingChanges.To, *newFormattedObject)
}
changes[objectKey] = existingChanges
}
return true
}
if doGroupSlice {
changes[objectKey] = FieldChange{
From: []string{*originalFormattedObject},
To: []string{*newFormattedObject},
}
} else {
if index > -1 {
objectKey = fmt.Sprintf("%s[%d]", field, index)
}
changes[objectKey] = FieldChange{
From: originalFormattedObject,
To: newFormattedObject,
}
}
return true
}
return false
}
func appendNewChangeToExistingChanges(existingChanges any, newChange string) any {
existingChangesStrings, ok := existingChanges.([]string)
if !ok {
return existingChanges
}
if !lo.Contains(existingChangesStrings, newChange) {
existingChangesStrings = append(existingChangesStrings, newChange)
}
return existingChangesStrings
}
func getFieldFromStruct(object any, field string) (reflect.StructField, reflect.Value, bool) {
objectValue := reflect.ValueOf(object)
if objectValue.Kind() == reflect.Ptr {
objectValue = objectValue.Elem()
}
if objectValue.Kind() != reflect.Struct {
// Fields can only be retrieved from structs
return reflect.StructField{}, reflect.Value{}, false
}
objectStructField, found := objectValue.Type().FieldByName(field)
if !found {
return reflect.StructField{}, reflect.Value{}, false
}
objectFieldValue := objectValue.FieldByName(field)
return objectStructField, objectFieldValue, true
}
func doGroupSliceForAuditEvent(objectField reflect.StructField) bool {
auditTag, found := objectField.Tag.Lookup("audit")
if found {
auditTagOptions := strings.Split(auditTag, ",")
if lo.Contains(auditTagOptions, "group") {
return true
}
}
return false
}
func checkToExecuteAuditFormatter(fieldValue reflect.Value) *string {
if auditFormatterObject, ok := fieldValue.Interface().(IAuditFormatter); ok {
fieldString := auditFormatterObject.FormatForAuditEvent()
if fieldString != "" {
return &fieldString
}
}
return nil
}
func checkToFormatForAuditEvent(fieldValue reflect.Value, index int) *string {
if fieldValue.Kind() == reflect.Ptr {
fieldValue = fieldValue.Elem()
}
var formattedField *string
if fieldValue.Kind() == reflect.Struct {
formattedField = checkToExecuteAuditFormatter(fieldValue)
} else if fieldValue.Kind() == reflect.Slice &&
fieldValue.Len() > 0 {
if index >= fieldValue.Len() {
return nil
}
sliceFieldValue := fieldValue.Index(index)
if sliceFieldValue.Kind() == reflect.Ptr {
sliceFieldValue = sliceFieldValue.Elem()
}
if sliceFieldValue.Kind() != reflect.Struct {
// Not a slice of structs - ignore the format flag
return nil
}
formattedField = checkToExecuteAuditFormatter(sliceFieldValue)
}
return formattedField
}
func ChildObjectChanges(changes map[string]any, objectPath string, fieldPath string, changeFrom any, changeTo any) {
objectKey := ToSnakeCase(objectPath)
field := ToSnakeCase(fieldPath)
existingObject, present := changes[objectKey]
if present {
if object, ok := existingObject.(map[string]interface{}); ok {
if object, ok := existingObject.(map[string]any); ok {
object[field] = FieldChange{
From: changeFrom,
To: changeTo,
......@@ -212,7 +376,7 @@ func ChildObjectChanges(changes map[string]interface{}, objectPath string, field
changes[objectKey] = object
}
} else {
fieldChange := map[string]interface{}{
fieldChange := map[string]any{
field: FieldChange{
From: changeFrom,
To: changeTo,
......@@ -223,7 +387,7 @@ func ChildObjectChanges(changes map[string]interface{}, objectPath string, field
}
// ArrayIndexForObjectIndex gets the index of arrayOfObjects where the object's index field is equal to objectIndex.
func ArrayIndexForObjectIndex(arrayOfObjects []map[string]interface{}, objectIndex int64) int {
func ArrayIndexForObjectIndex(arrayOfObjects []map[string]any, objectIndex int64) int {
for arrayIndex, object := range arrayOfObjects {
index, present := object["index"]
if present {
......@@ -238,8 +402,8 @@ func ArrayIndexForObjectIndex(arrayOfObjects []map[string]interface{}, objectInd
// GetAllChanges Returns the diff, structured in json, recursively
// Be warned, here be dragons. Debug this first to understand how it works
func GetAllChanges(original interface{}, new interface{}) (map[string]interface{}, error) {
changes := map[string]interface{}{}
func GetAllChanges(original any, new any) (map[string]any, error) {
changes := map[string]any{}
changelog, err := diff.Diff(original, new)
if err != nil {
return changes, err
......@@ -402,7 +566,7 @@ func ToSnakeCase(str string) string {
return strings.ToLower(snake)
}
func GetIntValue(object interface{}, key string) int64 {
func GetIntValue(object any, key string) int64 {
structValue := reflect.ValueOf(object)
if structValue.Kind() == reflect.Struct {
field := structValue.FieldByName(key)
......@@ -412,7 +576,7 @@ func GetIntValue(object interface{}, key string) int64 {
return 0
}
func GetStringValue(object interface{}, key string) string {
func GetStringValue(object any, key string) string {
structValue := reflect.ValueOf(object)
if structValue.Kind() == reflect.Struct {
field := structValue.FieldByName(key)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment