diff --git a/app/delivery/web/api/restricted/menu.go b/app/delivery/web/api/restricted/menu.go index ee7e615..cee5673 100644 --- a/app/delivery/web/api/restricted/menu.go +++ b/app/delivery/web/api/restricted/menu.go @@ -1,6 +1,8 @@ package restricted import ( + "strconv" + "git.ma-al.com/goc_daniel/b2b/app/service/menuService" "git.ma-al.com/goc_daniel/b2b/app/utils/i18n" "git.ma-al.com/goc_daniel/b2b/app/utils/nullable" @@ -23,25 +25,33 @@ func NewMenuHandler() *MenuHandler { func MenuHandlerRoutes(r fiber.Router) fiber.Router { handler := NewMenuHandler() - r.Get("/get-menu", handler.GetMenu) + r.Get("/get-category-tree", handler.GetCategoryTree) r.Get("/get-top-menu", handler.GetTopMenu) return r } -func (h *MenuHandler) GetMenu(c fiber.Ctx) error { +func (h *MenuHandler) GetCategoryTree(c fiber.Ctx) error { lang_id, ok := c.Locals("langID").(uint) if !ok { return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)). JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute))) } - menu, err := h.menuService.GetMenu(lang_id) + + root_category_id_attribute := c.Query("root_category_id") + root_category_id, err := strconv.Atoi(root_category_id_attribute) + if err != nil { + return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)). + JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute))) + } + + category_tree, err := h.menuService.GetCategoryTree(uint(root_category_id), lang_id) if err != nil { return c.Status(responseErrors.GetErrorStatus(err)). JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err))) } - return c.JSON(response.Make(&menu, 0, i18n.T_(c, response.Message_OK))) + return c.JSON(response.Make(&category_tree, 0, i18n.T_(c, response.Message_OK))) } func (h *MenuHandler) GetTopMenu(c fiber.Ctx) error { diff --git a/app/model/product.go b/app/model/product.go index 6a9212b..51a646b 100644 --- a/app/model/product.go +++ b/app/model/product.go @@ -93,16 +93,18 @@ type ScannedCategory struct { IsRoot uint `gorm:"column:is_root_category"` LinkRewrite string `gorm:"column:link_rewrite"` IsoCode string `gorm:"column:iso_code"` + + Visited bool //this is for internal backend use only } type Category struct { CategoryID uint `json:"category_id" form:"category_id"` Label string `json:"label" form:"label"` // Active bool `json:"active" form:"active"` - Params CategpryParams `json:"params" form:"params"` + Params CategoryParams `json:"params" form:"params"` Children []Category `json:"children" form:"children"` } -type CategpryParams struct { +type CategoryParams struct { CategoryID uint `json:"category_id" form:"category_id"` LinkRewrite string `json:"link_rewrite" form:"link_rewrite"` Locale string `json:"locale" form:"locale"` diff --git a/app/repos/categoriesRepo/categoriesRepo.go b/app/repos/categoriesRepo/categoriesRepo.go index 45fdc2d..955292a 100644 --- a/app/repos/categoriesRepo/categoriesRepo.go +++ b/app/repos/categoriesRepo/categoriesRepo.go @@ -37,12 +37,11 @@ func (r *CategoriesRepo) GetAllCategories(idLang uint) ([]model.ScannedCategory, ps_category_lang.link_rewrite AS link_rewrite, ps_lang.iso_code AS iso_code `). - Joins(`LEFT JOIN ? ON ??.id_category = ??.id_category AND ??.id_shop = ? AND ??.id_lang = ?`, - categoryLangTbl, categoryLangTbl, categoryTbl, categoryLangTbl, constdata.SHOP_ID, categoryLangTbl, idLang). - Joins(`LEFT JOIN ? ON ??.id_category = ??.id_category AND ??.id_shop = ?`, - categoryShopTbl, categoryShopTbl, categoryTbl, categoryShopTbl, constdata.SHOP_ID). - Joins(`JOIN ? ON ??.id_lang = ??.id_lang`, - langTbl, langTbl, categoryLangTbl). + Joins(`LEFT JOIN `+categoryLangTbl+` ON `+categoryLangTbl+`.id_category = `+categoryTbl+`.id_category AND `+categoryLangTbl+`.id_shop = ? AND `+categoryLangTbl+`.id_lang = ?`, + constdata.SHOP_ID, idLang). + Joins(`LEFT JOIN `+categoryShopTbl+` ON `+categoryShopTbl+`.id_category = `+categoryTbl+`.id_category AND `+categoryShopTbl+`.id_shop = ?`, + constdata.SHOP_ID). + Joins(`JOIN ` + langTbl + ` ON ` + langTbl + `.id_lang = ` + categoryLangTbl + `.id_lang`). Scan(&allCategories).Error return allCategories, err diff --git a/app/repos/productDescriptionRepo/productDescriptionRepo.go b/app/repos/productDescriptionRepo/productDescriptionRepo.go index a5e7cbb..76463cd 100644 --- a/app/repos/productDescriptionRepo/productDescriptionRepo.go +++ b/app/repos/productDescriptionRepo/productDescriptionRepo.go @@ -28,6 +28,7 @@ func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productid var ProductDescription model.ProductDescription err := db.Get(). + Model(dbmodel.PsProductLang{}). Where(&dbmodel.PsProductLang{ IDProduct: int32(productID), IDShop: int32(constdata.SHOP_ID), @@ -50,6 +51,7 @@ func (r *ProductDescriptionRepo) CreateIfDoesNotExist(productID uint, productid_ } err := db.Get(). + Model(dbmodel.PsProductLang{}). Where(&dbmodel.PsProductLang{ IDProduct: int32(productID), IDShop: int32(constdata.SHOP_ID), diff --git a/app/service/menuService/menuService.go b/app/service/menuService/menuService.go index e689ede..4e4b100 100644 --- a/app/service/menuService/menuService.go +++ b/app/service/menuService/menuService.go @@ -21,7 +21,7 @@ func New() *MenuService { } } -func (s *MenuService) GetMenu(id_lang uint) (*model.Category, error) { +func (s *MenuService) GetCategoryTree(root_category_id uint, id_lang uint) (*model.Category, error) { all_categories, err := s.categoriesRepo.GetAllCategories(id_lang) if err != nil { return &model.Category{}, err @@ -31,7 +31,7 @@ func (s *MenuService) GetMenu(id_lang uint) (*model.Category, error) { root_index := 0 root_found := false for i := 0; i < len(all_categories); i++ { - if all_categories[i].IsRoot == 1 { + if all_categories[i].CategoryID == root_category_id { root_index = i root_found = true break @@ -44,6 +44,7 @@ func (s *MenuService) GetMenu(id_lang uint) (*model.Category, error) { // 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 } @@ -58,19 +59,32 @@ func (s *MenuService) GetMenu(id_lang uint) (*model.Category, error) { } // finally, create the tree - tree := s.createTree(root_index, &all_categories, &children_indices) + 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 { +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++ { - node.Children = append(node.Children, s.createTree((*children_indices)[index][i].Index, all_categories, children_indices)) + 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) } - return node + (*all_categories)[index].Visited = false // just in case we have a "diamond" diagram + return node, true } func (s *MenuService) GetRoutes(id_lang uint) ([]model.Route, error) { @@ -83,7 +97,7 @@ func (s *MenuService) scannedToNormalCategory(scanned model.ScannedCategory) mod normal.CategoryID = scanned.CategoryID normal.Label = scanned.Name // normal.Active = scanned.Active == 1 - normal.Params = model.CategpryParams{CategoryID: normal.CategoryID, LinkRewrite: scanned.LinkRewrite, Locale: scanned.IsoCode} + normal.Params = model.CategoryParams{CategoryID: normal.CategoryID, LinkRewrite: scanned.LinkRewrite, Locale: scanned.IsoCode} normal.Children = []model.Category{} return normal } diff --git a/app/utils/responseErrors/responseErrors.go b/app/utils/responseErrors/responseErrors.go index f658430..2656a3b 100644 --- a/app/utils/responseErrors/responseErrors.go +++ b/app/utils/responseErrors/responseErrors.go @@ -50,7 +50,8 @@ var ( ErrBadPaging = errors.New("bad or missing paging attribute value in header") // Typed errors for menu handler - ErrNoRootFound = errors.New("no root found in categories table") + ErrNoRootFound = errors.New("no root found in categories table") + ErrCircularDependency = errors.New("circular dependency structure in tree (could be caused by improper root id)") // Typed errors for carts handler ErrMaxAmtOfCartsReached = errors.New("maximal amount of carts reached") @@ -145,6 +146,8 @@ func GetErrorCode(c fiber.Ctx, err error) string { case errors.Is(err, ErrNoRootFound): return i18n.T_(c, "error.no_root_found") + case errors.Is(err, ErrCircularDependency): + return i18n.T_(c, "error.circular_dependency") case errors.Is(err, ErrMaxAmtOfCartsReached): return i18n.T_(c, "error.max_amt_of_carts_reached") @@ -189,6 +192,7 @@ func GetErrorStatus(err error) int { errors.Is(err, ErrInvalidXHTML), errors.Is(err, ErrBadPaging), errors.Is(err, ErrNoRootFound), + errors.Is(err, ErrCircularDependency), errors.Is(err, ErrMaxAmtOfCartsReached), errors.Is(err, ErrUserHasNoSuchCart), errors.Is(err, ErrProductOrItsVariationDoesNotExist): diff --git a/bo/components.d.ts b/bo/components.d.ts new file mode 100644 index 0000000..51b00ed --- /dev/null +++ b/bo/components.d.ts @@ -0,0 +1,57 @@ +/* eslint-disable */ +// @ts-nocheck +// biome-ignore lint: disable +// oxlint-disable +// ------ +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 + +export {} + +/* prettier-ignore */ +declare module 'vue' { + export interface GlobalComponents { + CartDetails: typeof import('./src/components/customer/CartDetails.vue')['default'] + CategoryMenu: typeof import('./src/components/inner/categoryMenu.vue')['default'] + CategoryMenuListing: typeof import('./src/components/inner/categoryMenuListing.vue')['default'] + Cs_PrivacyPolicyView: typeof import('./src/components/terms/cs_PrivacyPolicyView.vue')['default'] + Cs_TermsAndConditionsView: typeof import('./src/components/terms/cs_TermsAndConditionsView.vue')['default'] + En_PrivacyPolicyView: typeof import('./src/components/terms/en_PrivacyPolicyView.vue')['default'] + En_TermsAndConditionsView: typeof import('./src/components/terms/en_TermsAndConditionsView.vue')['default'] + LangSwitch: typeof import('./src/components/inner/langSwitch.vue')['default'] + PageAddresses: typeof import('./src/components/customer/PageAddresses.vue')['default'] + PageCarts: typeof import('./src/components/customer/PageCarts.vue')['default'] + PageOrders: typeof import('./src/components/customer/PageOrders.vue')['default'] + PageProduct: typeof import('./src/components/customer/PageProduct.vue')['default'] + PageProducts: typeof import('./src/components/admin/PageProducts.vue')['default'] + PageProfileDetails: typeof import('./src/components/customer/PageProfileDetails.vue')['default'] + PageProfileDetailsAddInfo: typeof import('./src/components/customer/PageProfileDetailsAddInfo.vue')['default'] + PageStatistic: typeof import('./src/components/customer/PageStatistic.vue')['default'] + Pl_PrivacyPolicyView: typeof import('./src/components/terms/pl_PrivacyPolicyView.vue')['default'] + Pl_TermsAndConditionsView: typeof import('./src/components/terms/pl_TermsAndConditionsView.vue')['default'] + ProductCustomization: typeof import('./src/components/customer/components/ProductCustomization.vue')['default'] + ProductDetailView: typeof import('./src/components/admin/ProductDetailView.vue')['default'] + ProductVariants: typeof import('./src/components/customer/components/ProductVariants.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + ThemeSwitch: typeof import('./src/components/inner/themeSwitch.vue')['default'] + TopBar: typeof import('./src/components/TopBar.vue')['default'] + TopBarLogin: typeof import('./src/components/TopBarLogin.vue')['default'] + UAlert: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Alert.vue')['default'] + UButton: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Button.vue')['default'] + UCard: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Card.vue')['default'] + UCheckbox: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Checkbox.vue')['default'] + UDrawer: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Drawer.vue')['default'] + UForm: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Form.vue')['default'] + UFormField: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/FormField.vue')['default'] + UIcon: typeof import('./node_modules/@nuxt/ui/dist/runtime/vue/components/Icon.vue')['default'] + UInput: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Input.vue')['default'] + UInputNumber: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/InputNumber.vue')['default'] + UModal: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Modal.vue')['default'] + UNavigationMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/NavigationMenu.vue')['default'] + UPagination: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Pagination.vue')['default'] + USelect: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Select.vue')['default'] + USelectMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/SelectMenu.vue')['default'] + UTable: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Table.vue')['default'] + } +} diff --git a/bruno/b2b-daniel/get-menu.yml b/bruno/b2b-daniel/get-category-tree.yml similarity index 52% rename from bruno/b2b-daniel/get-menu.yml rename to bruno/b2b-daniel/get-category-tree.yml index 959ec0e..6073e4f 100644 --- a/bruno/b2b-daniel/get-menu.yml +++ b/bruno/b2b-daniel/get-category-tree.yml @@ -1,14 +1,14 @@ info: - name: get-menu + name: get-category-tree type: http seq: 5 http: method: GET - url: http://localhost:3000/api/v1/restricted/menu/get-menu?lang_id=1 + url: http://localhost:3000/api/v1/restricted/menu/get-category-tree?root_category_id=3 params: - - name: lang_id - value: "1" + - name: root_category_id + value: "3" type: query auth: inherit diff --git a/bruno/b2b-daniel/get-product-description.yml b/bruno/b2b-daniel/get-product-description.yml new file mode 100644 index 0000000..23e3c8e --- /dev/null +++ b/bruno/b2b-daniel/get-product-description.yml @@ -0,0 +1,22 @@ +info: + name: get-product-description + type: http + seq: 17 + +http: + method: GET + url: http://localhost:3000/api/v1/restricted/product-translation/get-product-description?productID=51&productLangID=2 + params: + - name: productID + value: "51" + type: query + - name: productLangID + value: "2" + type: query + auth: inherit + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5