diff --git a/frontend/src/post/PostScheme.vue b/frontend/src/post/PostScheme.vue index 7ee3ca7..65fb6da 100644 --- a/frontend/src/post/PostScheme.vue +++ b/frontend/src/post/PostScheme.vue @@ -17,6 +17,7 @@ onMounted(async () => { const scheme: Scheme = reactive({ entityId: "PostId", + Id: { hidden: true, type: { diff --git a/internal/database/database.go b/internal/database/database.go index 62f717b..d93824b 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -2,13 +2,14 @@ package database import ( "app/internal/dal" - "gorm.io/driver/sqlite" - "gorm.io/gorm" - "gorm.io/gorm/logger" "log" "os" "sync" "time" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/logger" ) var ( @@ -31,7 +32,8 @@ func initialize() error { }, ) db, err = gorm.Open(sqlite.Open("file:"+Path+"?_fk=1"), &gorm.Config{ - Logger: newLogger, + Logger: newLogger, + FullSaveAssociations: false, }) if err != nil { return err diff --git a/internal/gen/gen.go b/internal/gen/gen.go index 1f59ae7..dd40dde 100644 --- a/internal/gen/gen.go +++ b/internal/gen/gen.go @@ -1,17 +1,18 @@ package main import ( - "gorm.io/gen" - "app/internal/models" + "app/internal/models" + + "gorm.io/gen" ) func main() { - g := gen.NewGenerator(gen.Config{ - OutPath: "../dal", // output directory, default value is ./query - Mode: gen.WithDefaultQuery | gen.WithQueryInterface | gen.WithoutContext, - FieldNullable: true, - WithUnitTest: true, - }) - g.ApplyBasic(models.Entities...) - g.Execute() + g := gen.NewGenerator(gen.Config{ + OutPath: "../dal", // output directory, default value is ./query + Mode: gen.WithDefaultQuery | gen.WithQueryInterface | gen.WithoutContext, + FieldNullable: true, + WithUnitTest: true, + }) + g.ApplyBasic(models.Entities...) + g.Execute() } diff --git a/internal/services/author.go b/internal/services/author.go index e098567..9f59e9e 100644 --- a/internal/services/author.go +++ b/internal/services/author.go @@ -2,6 +2,7 @@ package services import ( "app/internal/dal" + "app/internal/database" "app/internal/models" "errors" @@ -13,6 +14,7 @@ type AuthorService struct{} type Author = models.Author func (service *AuthorService) Create(item Author) (Author, error) { + ReplaceEmptySlicesWithNil(&item) err := dal.Author.Preload(field.Associations).Create(&item) return item, err } @@ -32,15 +34,24 @@ func (service *AuthorService) GetById(id uint) (*Author, error) { } return item, nil } + func (service *AuthorService) Update(item Author) (Author, error) { - var posts []*models.Post - for _, post := range item.Posts { - posts = append(posts, &post) + ReplaceEmptySlicesWithNil(&item) + + _, err := dal.Author.Updates(&item) + if err != nil { + return item, err } - err := dal.Author.Posts.Model(&item).Replace(posts...) - _ = dal.Author.Save(&item) + + err = UpdateAssociations(database.GetInstance(), &item) + + if err != nil { + return item, err + } + return item, err } + func (service *AuthorService) Delete(id uint) error { _, err := dal.Author.Unscoped().Where(dal.Author.Id.Eq(id)).Delete() return err diff --git a/internal/services/handle_nil.go b/internal/services/handle_nil.go new file mode 100644 index 0000000..985c376 --- /dev/null +++ b/internal/services/handle_nil.go @@ -0,0 +1,49 @@ +package services + +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)) + } + } + } +} diff --git a/internal/services/update_associations.go b/internal/services/update_associations.go new file mode 100644 index 0000000..6b186e3 --- /dev/null +++ b/internal/services/update_associations.go @@ -0,0 +1,41 @@ +package services + +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 +}