package menuService import ( "slices" "sort" "strconv" "git.ma-al.com/goc_daniel/b2b/app/model" categoryrepo "git.ma-al.com/goc_daniel/b2b/app/repos/categoryRepo" routesRepo "git.ma-al.com/goc_daniel/b2b/app/repos/routesRepo" constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data" "git.ma-al.com/goc_daniel/b2b/app/utils/i18n" "git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors" ) type MenuService struct { categoryRepo categoryrepo.UICategoryRepo routesRepo routesRepo.UIRoutesRepo } func New() *MenuService { return &MenuService{ categoryRepo: categoryrepo.New(), routesRepo: routesRepo.New(), } } func (s *MenuService) GetCategoryTree(root_category_id uint, id_lang uint) (*model.Category, error) { all_categories, err := s.categoryRepo.RetrieveMenuCategories(id_lang) if err != nil { return &model.Category{}, err } // remove blacklisted categories // to do so, we detach them from the main tree for i := 0; i < len(all_categories); i++ { if slices.Contains(constdata.CATEGORY_BLACKLIST, all_categories[i].CategoryID) { all_categories[i].ParentID = all_categories[i].CategoryID } } iso_code := all_categories[0].IsoCode s.appendAdditional(&all_categories, id_lang, iso_code) // find the root root_index := 0 root_found := false for i := 0; i < len(all_categories); i++ { if all_categories[i].CategoryID == root_category_id { root_index = i root_found = true break } } if !root_found { return &model.Category{}, responseErrors.ErrNoRootFound } // now create the children and reorder them according to position id_to_index := make(map[uint]int) for i := 0; i < len(all_categories); i++ { all_categories[i].Visited = false id_to_index[all_categories[i].CategoryID] = i } children_indices := make(map[int][]ChildWithPosition) for i := 0; i < len(all_categories); i++ { parent_index := id_to_index[all_categories[i].ParentID] children_indices[parent_index] = append(children_indices[parent_index], ChildWithPosition{Index: i, Position: all_categories[i].Position}) } for key := range children_indices { sort.Sort(ByPosition(children_indices[key])) } // finally, create the tree tree, success := s.createTree(root_index, &all_categories, &children_indices) if !success { return &tree, responseErrors.ErrCircularDependency } return &tree, nil } func (s *MenuService) createTree(index int, all_categories *([]model.ScannedCategory), children_indices *(map[int][]ChildWithPosition)) (model.Category, bool) { node := s.scannedToNormalCategory((*all_categories)[index]) if (*all_categories)[index].Visited { return node, false } (*all_categories)[index].Visited = true for i := 0; i < len((*children_indices)[index]); i++ { next_child, success := s.createTree((*children_indices)[index][i].Index, all_categories, children_indices) if !success { return node, false } node.Children = append(node.Children, next_child) } (*all_categories)[index].Visited = false // just in case we have a "diamond" diagram return node, true } func (s *MenuService) GetRoutes(id_lang, roleId uint) ([]model.Route, error) { return s.routesRepo.GetRoutes(id_lang, roleId) } func (s *MenuService) scannedToNormalCategory(scanned model.ScannedCategory) model.Category { var normal model.Category // normal.Active = scanned.Active normal.CategoryID = scanned.CategoryID normal.Label = scanned.Name // normal.Active = scanned.Active == 1 normal.Params = model.CategoryParams{CategoryID: normal.CategoryID, LinkRewrite: scanned.LinkRewrite, Locale: scanned.IsoCode, Filter: scanned.Filter} normal.Children = []model.Category{} return normal } type ChildWithPosition struct { Index int Position uint } type ByPosition []ChildWithPosition func (a ByPosition) Len() int { return len(a) } func (a ByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a ByPosition) Less(i, j int) bool { return a[i].Position < a[j].Position } func (s *MenuService) GetBreadcrumb(root_category_id uint, start_category_id uint, id_lang uint) ([]model.CategoryInBreadcrumb, error) { all_categories, err := s.categoryRepo.RetrieveMenuCategories(id_lang) if err != nil { return []model.CategoryInBreadcrumb{}, err } iso_code := all_categories[0].IsoCode s.appendAdditional(&all_categories, id_lang, iso_code) breadcrumb := []model.CategoryInBreadcrumb{} start_index := 0 start_found := false for i := 0; i < len(all_categories); i++ { if all_categories[i].CategoryID == start_category_id { start_index = i start_found = true break } } if !start_found { return []model.CategoryInBreadcrumb{}, responseErrors.ErrStartCategoryNotFound } // map category ids to indices id_to_index := make(map[uint]int) for i := 0; i < len(all_categories); i++ { all_categories[i].Visited = false id_to_index[all_categories[i].CategoryID] = i } // do a simple graph traversal, always jumping from node to its parent index := start_index success := true for { if all_categories[index].Visited { success = false break } all_categories[index].Visited = true var next_category model.CategoryInBreadcrumb next_category.CategoryID = all_categories[index].CategoryID next_category.Name = all_categories[index].Name breadcrumb = append(breadcrumb, next_category) if all_categories[index].CategoryID == root_category_id { break } next_index, ok := id_to_index[all_categories[index].ParentID] if !ok { success = false break } index = next_index } slices.Reverse(breadcrumb) if !success { return breadcrumb, responseErrors.ErrRootNeverReached } return breadcrumb, nil } func (s *MenuService) GetTopMenu(languageId uint, roleId uint) ([]*model.B2BTopMenu, error) { items, err := s.routesRepo.GetTopMenu(languageId, roleId) if err != nil { return nil, err } menuMap := make(map[int]*model.B2BTopMenu, len(items)) roots := make([]*model.B2BTopMenu, 0) for i := range items { menu := &items[i] menu.Children = make([]*model.B2BTopMenu, 0) menuMap[menu.MenuID] = menu } for i := range items { menu := &items[i] if menu.ParentID == nil { roots = append(roots, menu) continue } parent, ok := menuMap[*menu.ParentID] if !ok { // fallback for orphaned nodes roots = append(roots, menu) continue } parent.Children = append(parent.Children, menu) } return roots, nil } func (s *MenuService) appendAdditional(all_categories *[]model.ScannedCategory, id_lang uint, iso_code string) { for i := 0; i < len(*all_categories); i++ { (*all_categories)[i].Filter = "category_id_in=" + strconv.Itoa(int((*all_categories)[i].CategoryID)) } var additional model.ScannedCategory additional.CategoryID = 10001 additional.Name = "New Products" additional.Active = 1 additional.Position = 10 additional.ParentID = 2 additional.IsRoot = 0 additional.LinkRewrite = i18n.T___(id_lang, "category.new_products") additional.IsoCode = iso_code additional.Visited = false additional.Filter = "is_new_in=true" *all_categories = append(*all_categories, additional) }