feat: sorting for primitive fields

# Conflicts:
#	frontend/src/App.vue
This commit is contained in:
2025-03-12 15:47:49 +07:00
parent e655bb5d5f
commit 10c7a9bac8
14 changed files with 123 additions and 71 deletions

View File

@@ -0,0 +1,45 @@
package utils
import (
"errors"
"reflect"
"gorm.io/gorm"
)
// AppendAssociations uses reflection to find all exported slice fields
// in 'item' and then appends any elements in each slice to the association.
func AppendAssociations(db *gorm.DB, item interface{}) error {
// We expect a pointer to a struct so we can do db.Model(item).
val := reflect.ValueOf(item)
if val.Kind() != reflect.Ptr {
return errors.New("item must be a pointer to a struct")
}
elem := val.Elem()
if elem.Kind() != reflect.Struct {
return errors.New("item must be a pointer to a struct")
}
t := elem.Type()
for i := 0; i < elem.NumField(); i++ {
fieldVal := elem.Field(i)
fieldType := t.Field(i)
// Process only exported fields (PkgPath == "") and slices.
if fieldType.PkgPath == "" && fieldVal.Kind() == reflect.Slice {
// The association name is the struct field name by default.
assocName := fieldType.Name
// Append slice elements to the existing association.
// You can add checks here if you want to skip empty slices, etc.
if fieldVal.Len() > 0 {
if err := db.Model(item).Association(assocName).Append(fieldVal.Interface()); err != nil {
return err
}
}
}
}
return nil
}

View File

@@ -0,0 +1,49 @@
package utils
import "reflect"
// ReplaceEmptySlicesWithNil takes a pointer to any struct (or struct-like type)
// and replaces all empty slices with nil, traversing nested fields recursively.
func ReplaceEmptySlicesWithNil(v interface{}) {
// Must be a pointer so we can set fields
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr {
panic("ReplaceEmptySlicesWithNil expects a pointer to a struct")
}
replaceEmptySlices(rv.Elem())
}
// replaceEmptySlices is the recursive helper that does the actual reflection work.
func replaceEmptySlices(v reflect.Value) {
switch v.Kind() {
case reflect.Ptr:
// If it is a pointer, recurse on the element it points to.
if !v.IsNil() {
replaceEmptySlices(v.Elem())
}
case reflect.Struct:
// For a struct, iterate each field and process it.
for i := 0; i < v.NumField(); i++ {
fieldVal := v.Field(i)
if !fieldVal.CanSet() {
// If we can't set the field (e.g. unexported), skip it
continue
}
replaceEmptySlices(fieldVal)
}
case reflect.Slice:
// If it is a slice, check if it's empty; if it is, set it to nil.
if v.Len() == 0 && v.CanSet() {
// v.Set(reflect.Zero(v.Type())) sets slice to nil
v.Set(reflect.Zero(v.Type()))
} else {
// If not empty, we should recurse on each element in case it's a struct.
for i := 0; i < v.Len(); i++ {
replaceEmptySlices(v.Index(i))
}
}
}
}

40
internal/utils/sorting.go Normal file
View File

@@ -0,0 +1,40 @@
package utils
import (
"app/internal/database"
"errors"
"fmt"
"reflect"
)
// SortByOrder Order items by specified field and a sort type
// Example: SortByOrder(map[string]string{"name": "ASC"}, &models.Post{})
// ASC - по возрастанию (от А до Я)
// DESC - по убыванию (от Я до А)
func SortByOrder[T any](fieldsSortOrder map[string]string, entity T) ([]*T, error) {
var (
orderQuery string
items []*T
)
for name, order := range fieldsSortOrder {
structInfo := reflect.ValueOf(entity).Type()
_, fieldExist := structInfo.FieldByName(name)
if !fieldExist {
return nil, errors.New(fmt.Sprintf("Field `%s` not found", name))
}
if order != "ASC" && order != "DESC" {
return nil, errors.New(fmt.Sprintf("Field `%s` can only be sorted by ASC or DESC", name))
}
orderQuery += fmt.Sprintf("%s %s", name, order)
}
result := database.GetInstance().Order(orderQuery).Find(&items)
if result.Error != nil {
return items, result.Error
}
return items, nil
}

View File

@@ -0,0 +1,41 @@
package utils
import (
"errors"
"reflect"
"gorm.io/gorm"
)
func UpdateAssociations(db *gorm.DB, item interface{}) error {
// We expect a pointer to a struct so we can do db.Model(item).
val := reflect.ValueOf(item)
if val.Kind() != reflect.Ptr {
return errors.New("item must be a pointer to a struct")
}
elem := val.Elem()
if elem.Kind() != reflect.Struct {
return errors.New("item must be a pointer to a struct")
}
t := elem.Type()
for i := 0; i < elem.NumField(); i++ {
fieldVal := elem.Field(i)
fieldType := t.Field(i)
// Only process exported fields (capitalized) and slices.
if fieldType.PkgPath == "" && fieldVal.Kind() == reflect.Slice {
// For clarity, the association name is the struct field name by default.
assocName := fieldType.Name
// Replace the association with the current slice value.
// If you only want to replace on non-nil or non-empty slices, you can add extra checks here.
if err := db.Model(item).Association(assocName).Replace(fieldVal.Interface()); err != nil {
return err
}
}
}
return nil
}