From 10c7a9bac885490eb74d90b5a433757ff9cbef91 Mon Sep 17 00:00:00 2001 From: gogacoder Date: Wed, 12 Mar 2025 15:47:49 +0700 Subject: [PATCH] feat: sorting for primitive fields # Conflicts: # frontend/src/App.vue --- .xlsx | Bin 0 -> 6685 bytes .../app/internal/services/authorservice.ts | 9 ++++ frontend/src/author/AuthorScheme.vue | 4 +- internal/dialogs/dialogs.go | 2 +- internal/extras/excel/export.go | 2 +- internal/services/author.go | 24 +++++----- internal/services/comment.go | 9 ++-- internal/services/post.go | 9 ++-- internal/services/posttype.go | 9 ++-- internal/services/update_associations.go | 41 ------------------ .../append_associations.go | 2 +- .../replace_empty_to_nil.go} | 2 +- internal/utils/sorting.go | 40 +++++++++++++++++ internal/utils/update_associations.go | 41 ++++++++++++++++++ 14 files changed, 123 insertions(+), 71 deletions(-) create mode 100755 .xlsx delete mode 100644 internal/services/update_associations.go rename internal/{services => utils}/append_associations.go (98%) rename internal/{services/handle_nil.go => utils/replace_empty_to_nil.go} (98%) create mode 100644 internal/utils/sorting.go create mode 100644 internal/utils/update_associations.go diff --git a/.xlsx b/.xlsx new file mode 100755 index 0000000000000000000000000000000000000000..01b4c50f6e87f8cc0dc66d53ae8d36652b653b36 GIT binary patch literal 6685 zcmaJ_1yoe)7AB<|WN4&ILXc3pV@5(iy1Rz%7HJ&1QG`LHL4+YB2k8b8L_k34p+jnT zaPPZUpX=Sb&st~JoOSj->)+r0zy0q|Lk%5+90mLO`rQE64}6{Ye8BFG9(LB&o*sOE z{M_U9b#_Wk1%YQ70iiqR6w&A51WDt_N$!lt z2f-4-m;p_tghtSsifysMCsY`3tGp(TcW9AkeivuhFS{s(?GDV^UfJ81D3nEA6_gr!OaOrPH`WXh?~Ox26SVz)9Hnx>Z2N&3CjL-$y62 zch5?1?;LB*`*zkwKW-a?L5QbmoV|Z_O2Ph;15A$3byfih>{}S*8%G!IUO63@{OJ5E%dTJ7W{MR5iMg3=;$5( zgt{%Dzuo9C2MYXYFjw_s3n}2H~x& z5%8=qdC$%{H}?FKJ}IjnnKY)FWpBsv+n~>{dTe9Tr!}C`<0^qPpC@?HZ+XIb(Ynm;)4j2dQbdz&gnT@^;(0Mh0ND7l@%~g+%~i0 zNEXAeIU#`;UtKTOZ?)DO^?6NC3aDT|00Exld|lSt^qg?hCQiYayq`oGoLya#h-2pk z>g~m0&DEf19kg$D8Bqy!H`?FldY}2oURseUlQm+qGlm6Uh{EWDkPg62IU2IF61m1t z77ZIN+P;_U#zljjDUR)a$(A%5sZ_BA$u7H-vIfY6sB;i~=({8K5g?%9-wTDzU0{KY zeTEYb21sq2I^q`1bFa#MImUM1%6z_IiAWfHSLNEx`cO~xp&7%kKY$jtpbDw>L60;VV3aSj{_OD>L$4orEZqF@DLQp z+q=Er7A_8lq@)64kVr(2$Vh9QNB=jItNo9gQpoqJqQ*(*a|fUJUbLMdH`{`PZ93{% zfxxXqHu+Z%zhTT#;uRDMN-OSA#VuFYrblO=Vf(+r|ZJq=bS?4>E0wqv4 z2d(K!JR-kiy>@M8`6M$OG8pD4=U~3Kzkf( z-fa7upofTBKP^`1Yb9X|q6q$&BvI<_MCep52L!Yt=*5D8?x}89CaM`^Q5(>~v8x6f zlT$sMrIRDFBmV?$cd}t~hOm)*Jg^vkEzQ-DThyAa5p17g=m4G%nV#Ruvq?x8A}lW~ za#nK)8qJ`Yu|B?nAKwqBYE$xcl9TkVg3N7No|IC;>bJdSW)6@6Qw5+jc(-i>bdUK< z>OJrL)%dLv@wcS$k(T{>!6|Z-4D=Jey)54QC=|`gvcU<^QBw^%hOR_X2G~A!5|^$j0_fn>K zGaZsVYLd(0kMi%nc$EMq-%Q)hXp452^7oVM_V$>-G$OQ7;v;4rP+XjDR0b90#Oi~Z zjZ^0H1L~Me&5O91;t{$O=1y>c>|62oD|Phv8yQW;+=Xp>l2f1cC1?Ou^RLJlP9D67 z(?ltO6ypmi$|Ofh>drLc@+?Ubdg`yi#PqdF4zab3GQU4oK;nFqXHq0%0A~)g|By8- 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 & { cancel() return $typingPromise; } +export function SortedByOrder(fieldsSortOrder: { [_: string]: string }): Promise<($models.Author | null)[]> & { cancel(): void } { + let $resultPromise = $Call.ByID(3046628691, fieldsSortOrder) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType2($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + export function Update(item: $models.Author): Promise<$models.Author> & { cancel(): void } { let $resultPromise = $Call.ByID(2240704960, item) as any; let $typingPromise = $resultPromise.then(($result: any) => { diff --git a/frontend/src/author/AuthorScheme.vue b/frontend/src/author/AuthorScheme.vue index dab27ea..97f3a1c 100644 --- a/frontend/src/author/AuthorScheme.vue +++ b/frontend/src/author/AuthorScheme.vue @@ -5,6 +5,7 @@ import { getDefaultValues } from "../utils/structs/defaults.util"; import Service from "./author.service.ts"; import type { Scheme } from "../types/scheme.type"; import { Author } from "../../bindings/app/internal/services"; +import { SortedByOrder } from "../../bindings/app/internal/services/authorservice.ts"; import { ref } from "vue"; import type { Validate } from "../types/validate.type"; @@ -29,7 +30,8 @@ const load = async () => { }; onMounted(async () => { - load(); + await load(); + console.log(await SortedByOrder({"Name": "ASC"})) }); const scheme: Scheme = reactive({ diff --git a/internal/dialogs/dialogs.go b/internal/dialogs/dialogs.go index aafbc30..b0a8de3 100644 --- a/internal/dialogs/dialogs.go +++ b/internal/dialogs/dialogs.go @@ -18,7 +18,7 @@ func ErrorDialog(title string, message string) { application.ErrorDialog().SetTitle(title).SetMessage(message).Show() } -func SaveFileDialog(title string, filename string) (string, error) { +func SaveFileDialog(filename string) (string, error) { selection, err := application.SaveFileDialog().SetFilename(filename).PromptForSingleSelection() if err != nil { return "", err diff --git a/internal/extras/excel/export.go b/internal/extras/excel/export.go index bc17675..d8f843d 100644 --- a/internal/extras/excel/export.go +++ b/internal/extras/excel/export.go @@ -329,7 +329,7 @@ func ApplyStyleHeaders(file *excelize.File, sheetName string, headers TableHeade } func WriteData(file *excelize.File, filename string) error { - filepath, err := dialogs.SaveFileDialog("Экспорт данных", filename) + filepath, err := dialogs.SaveFileDialog(filename) if !strings.HasSuffix(filepath, ".xlsx") { filepath += ".xlsx" diff --git a/internal/services/author.go b/internal/services/author.go index b3049a9..a749c66 100644 --- a/internal/services/author.go +++ b/internal/services/author.go @@ -4,31 +4,30 @@ import ( "app/internal/dal" "app/internal/database" "app/internal/models" + "app/internal/utils" "errors" - "gorm.io/gen/field" "gorm.io/gorm" ) -type AuthorService struct{} +type AuthorService struct { +} type Author = models.Author func (service *AuthorService) Create(item Author) (Author, error) { - ReplaceEmptySlicesWithNil(&item) + utils.ReplaceEmptySlicesWithNil(&item) err := dal.Author.Create(&item) if err != nil { return item, err } - err = AppendAssociations(database.GetInstance(), &item) + err = utils.AppendAssociations(database.GetInstance(), &item) return item, err } - func (service *AuthorService) GetAll() ([]*Author, error) { var authors []*Author authors, err := dal.Author.Preload(field.Associations).Find() return authors, err } - func (service *AuthorService) GetById(id uint) (*Author, error) { item, err := dal.Author.Preload(field.Associations).Where(dal.Author.Id.Eq(id)).First() if err != nil { @@ -40,16 +39,13 @@ func (service *AuthorService) GetById(id uint) (*Author, error) { } return item, nil } - func (service *AuthorService) Update(item Author) (Author, error) { - ReplaceEmptySlicesWithNil(&item) - + utils.ReplaceEmptySlicesWithNil(&item) _, err := dal.Author.Updates(&item) if err != nil { return item, err } - - err = UpdateAssociations(database.GetInstance(), &item) + err = utils.UpdateAssociations(database.GetInstance(), &item) if err != nil { return item, err @@ -57,13 +53,15 @@ func (service *AuthorService) Update(item Author) (Author, error) { return item, err } - func (service *AuthorService) Delete(id uint) error { _, err := dal.Author.Unscoped().Where(dal.Author.Id.Eq(id)).Delete() return err } - func (service *AuthorService) Count() (int64, error) { amount, err := dal.Author.Count() return amount, err } + +func (service *AuthorService) SortedByOrder(fieldsSortOrder map[string]string) ([]*Author, error) { + return utils.SortByOrder(fieldsSortOrder, Author{}) +} diff --git a/internal/services/comment.go b/internal/services/comment.go index 5039867..34b9bf8 100644 --- a/internal/services/comment.go +++ b/internal/services/comment.go @@ -4,6 +4,7 @@ import ( "app/internal/dal" "app/internal/database" "app/internal/models" + "app/internal/utils" "errors" "gorm.io/gen/field" @@ -15,12 +16,12 @@ type CommentService struct { type Comment = models.Comment func (service *CommentService) Create(item Comment) (Comment, error) { - ReplaceEmptySlicesWithNil(&item) + utils.ReplaceEmptySlicesWithNil(&item) err := dal.Comment.Preload(field.Associations).Create(&item) if err != nil { return item, err } - err = AppendAssociations(database.GetInstance(), &item) + err = utils.AppendAssociations(database.GetInstance(), &item) return item, err } @@ -43,13 +44,13 @@ func (service *CommentService) GetById(id uint) (*Comment, error) { } func (service *CommentService) Update(item Comment) (Comment, error) { - ReplaceEmptySlicesWithNil(&item) + utils.ReplaceEmptySlicesWithNil(&item) err := dal.Comment.Preload(field.Associations).Save(&item) if err != nil { return item, err } - err = UpdateAssociations(database.GetInstance(), &item) + err = utils.UpdateAssociations(database.GetInstance(), &item) if err != nil { return item, err diff --git a/internal/services/post.go b/internal/services/post.go index a90936d..38da3f8 100644 --- a/internal/services/post.go +++ b/internal/services/post.go @@ -6,6 +6,7 @@ import ( "app/internal/dialogs" "app/internal/extras/excel" "app/internal/models" + "app/internal/utils" "errors" "fmt" @@ -17,12 +18,12 @@ type PostService struct{} type Post = models.Post func (service *PostService) Create(item Post) (Post, error) { - ReplaceEmptySlicesWithNil(&item) + utils.ReplaceEmptySlicesWithNil(&item) err := dal.Post.Preload(field.Associations).Create(&item) if err != nil { return item, err } - err = AppendAssociations(database.GetInstance(), &item) + err = utils.AppendAssociations(database.GetInstance(), &item) return item, err } func (service *PostService) GetAll() ([]*Post, error) { @@ -43,12 +44,12 @@ func (service *PostService) GetById(id uint) (*Post, error) { } func (service *PostService) Update(item Post) (Post, error) { - ReplaceEmptySlicesWithNil(&item) + utils.ReplaceEmptySlicesWithNil(&item) err := dal.Post.Preload(field.Associations).Save(&item) if err != nil { return item, err } - err = UpdateAssociations(database.GetInstance(), &item) + err = utils.UpdateAssociations(database.GetInstance(), &item) if err != nil { return item, err } diff --git a/internal/services/posttype.go b/internal/services/posttype.go index 6054de6..bf28af8 100644 --- a/internal/services/posttype.go +++ b/internal/services/posttype.go @@ -6,6 +6,7 @@ import ( "app/internal/dialogs" "app/internal/extras/excel" "app/internal/models" + "app/internal/utils" "errors" "gorm.io/gen/field" "gorm.io/gorm" @@ -17,12 +18,12 @@ type PostTypeService struct { type PostType = models.PostType func (service *PostTypeService) Create(item PostType) (PostType, error) { - ReplaceEmptySlicesWithNil(&item) + utils.ReplaceEmptySlicesWithNil(&item) err := dal.PostType.Preload(field.Associations).Create(&item) if err != nil { return item, err } - err = AppendAssociations(database.GetInstance(), &item) + err = utils.AppendAssociations(database.GetInstance(), &item) return item, err } func (service *PostTypeService) GetAll() ([]*PostType, error) { @@ -42,12 +43,12 @@ func (service *PostTypeService) GetById(id uint) (*PostType, error) { return item, nil } func (service *PostTypeService) Update(item PostType) (PostType, error) { - ReplaceEmptySlicesWithNil(&item) + utils.ReplaceEmptySlicesWithNil(&item) err := dal.PostType.Preload(field.Associations).Save(&item) if err != nil { return item, err } - err = UpdateAssociations(database.GetInstance(), &item) + err = utils.UpdateAssociations(database.GetInstance(), &item) if err != nil { return item, err } diff --git a/internal/services/update_associations.go b/internal/services/update_associations.go deleted file mode 100644 index 6b186e3..0000000 --- a/internal/services/update_associations.go +++ /dev/null @@ -1,41 +0,0 @@ -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 -} diff --git a/internal/services/append_associations.go b/internal/utils/append_associations.go similarity index 98% rename from internal/services/append_associations.go rename to internal/utils/append_associations.go index 0ba068f..ad667fc 100644 --- a/internal/services/append_associations.go +++ b/internal/utils/append_associations.go @@ -1,4 +1,4 @@ -package services +package utils import ( "errors" diff --git a/internal/services/handle_nil.go b/internal/utils/replace_empty_to_nil.go similarity index 98% rename from internal/services/handle_nil.go rename to internal/utils/replace_empty_to_nil.go index 985c376..d941009 100644 --- a/internal/services/handle_nil.go +++ b/internal/utils/replace_empty_to_nil.go @@ -1,4 +1,4 @@ -package services +package utils import "reflect" diff --git a/internal/utils/sorting.go b/internal/utils/sorting.go new file mode 100644 index 0000000..ebaa331 --- /dev/null +++ b/internal/utils/sorting.go @@ -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 +} diff --git a/internal/utils/update_associations.go b/internal/utils/update_associations.go new file mode 100644 index 0000000..1c9a8eb --- /dev/null +++ b/internal/utils/update_associations.go @@ -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 +}