From 612e97e76edcbd6d4a7aecba5cfe224acf48460f Mon Sep 17 00:00:00 2001 From: Wiktor Date: Fri, 17 Apr 2026 09:17:26 +0200 Subject: [PATCH] feat: create customer management menu --- app/delivery/web/api/restricted/menu.go | 18 ++++++++++ app/model/{topMenu.go => menu.go} | 18 ++++++++-- app/repos/routesRepo/routesRepo.go | 16 ++++++++- app/service/menuService/menuService.go | 33 ++++++++++++++++--- bruno/api_v1/menu/Breadcrumb.yml | 22 +++++++++++++ bruno/api_v1/menu/Category tree.yml | 19 +++++++++++ .../menu/Top Customer Management Menu.yml | 15 +++++++++ bruno/api_v1/menu/Top Menu.yml | 15 +++++++++ bruno/api_v1/menu/folder.yml | 7 ++++ i18n/migrations/20260302163100_routes.sql | 23 +++++++++++++ .../20260302163123_create_tables_data.sql | 13 ++++++++ 11 files changed, 190 insertions(+), 9 deletions(-) rename app/model/{topMenu.go => menu.go} (64%) create mode 100644 bruno/api_v1/menu/Breadcrumb.yml create mode 100644 bruno/api_v1/menu/Category tree.yml create mode 100644 bruno/api_v1/menu/Top Customer Management Menu.yml create mode 100644 bruno/api_v1/menu/Top Menu.yml create mode 100644 bruno/api_v1/menu/folder.yml diff --git a/app/delivery/web/api/restricted/menu.go b/app/delivery/web/api/restricted/menu.go index 8114e9c..f4b7c40 100644 --- a/app/delivery/web/api/restricted/menu.go +++ b/app/delivery/web/api/restricted/menu.go @@ -3,6 +3,8 @@ package restricted import ( "strconv" + "git.ma-al.com/goc_daniel/b2b/app/delivery/middleware" + "git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms" "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/localeExtractor" @@ -29,6 +31,7 @@ func MenuHandlerRoutes(r fiber.Router) fiber.Router { r.Get("/get-category-tree", handler.GetCategoryTree) r.Get("/get-breadcrumb", handler.GetBreadcrumb) r.Get("/get-top-menu", handler.GetTopMenu) + r.Get("/get-customer-management-menu", middleware.Require(perms.UserReadAny), handler.GetCustomerManagementMenu) return r } @@ -100,3 +103,18 @@ func (h *MenuHandler) GetTopMenu(c fiber.Ctx) error { return c.JSON(response.Make(&menu, len(menu), i18n.T_(c, response.Message_OK))) } + +func (h *MenuHandler) GetCustomerManagementMenu(c fiber.Ctx) error { + langId, ok := localeExtractor.GetLangID(c) + if !ok { + return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)). + JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute))) + } + menu, err := h.menuService.GetCustomerManagementMenu(langId) + 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, len(menu), i18n.T_(c, response.Message_OK))) +} diff --git a/app/model/topMenu.go b/app/model/menu.go similarity index 64% rename from app/model/topMenu.go rename to app/model/menu.go index 29e2aa4..84bf5a4 100644 --- a/app/model/topMenu.go +++ b/app/model/menu.go @@ -2,7 +2,7 @@ package model import "encoding/json" -type B2BTopMenu struct { +type B2BMenu struct { MenuID int `gorm:"column:menu_id;primaryKey;autoIncrement" json:"menu_id"` Label json.RawMessage `gorm:"column:label;type:longtext;not null;default:'{}'" json:"label"` ParentID *int `gorm:"column:parent_id;index:FK_b2b_top_menu_parent_id" json:"parent_id,omitempty"` @@ -10,10 +10,22 @@ type B2BTopMenu struct { Active int8 `gorm:"column:active;type:tinyint;not null;default:1" json:"active"` Position int `gorm:"column:position;not null;default:1" json:"position"` - Parent *B2BTopMenu `gorm:"foreignKey:ParentID;references:MenuID;constraint:OnDelete:RESTRICT,OnUpdate:RESTRICT" json:"parent,omitempty"` - Children []*B2BTopMenu `gorm:"foreignKey:ParentID" json:"children,omitempty"` + Parent *B2BMenu `gorm:"foreignKey:ParentID;references:MenuID;constraint:OnDelete:RESTRICT,OnUpdate:RESTRICT" json:"parent,omitempty"` + Children []*B2BMenu `gorm:"foreignKey:ParentID" json:"children,omitempty"` +} + +type B2BTopMenu struct { + B2BMenu } func (B2BTopMenu) TableName() string { return "b2b_top_menu" } + +type B2BCustomerManagementMenu struct { + B2BMenu +} + +func (B2BCustomerManagementMenu) TableName() string { + return "b2b_customer_management_menu" +} diff --git a/app/repos/routesRepo/routesRepo.go b/app/repos/routesRepo/routesRepo.go index 0b70dd8..442fa23 100644 --- a/app/repos/routesRepo/routesRepo.go +++ b/app/repos/routesRepo/routesRepo.go @@ -9,6 +9,7 @@ import ( type UIRoutesRepo interface { GetRoutes(langId uint, roleId uint) ([]model.Route, error) GetTopMenu(id uint, roleId uint) ([]model.B2BTopMenu, error) + GetCustomerManagementMenu(langId uint) ([]model.B2BCustomerManagementMenu, error) } type RoutesRepo struct{} @@ -38,10 +39,23 @@ func (p *RoutesRepo) GetTopMenu(langId uint, roleId uint) ([]model.B2BTopMenu, e Get(). Model(model.B2BTopMenu{}). Joins("JOIN b2b_top_menu_roles tmr ON tmr.top_menu_id = b2b_top_menu.menu_id"). - Where(model.B2BTopMenu{Active: 1}). + Where(model.B2BTopMenu{B2BMenu: model.B2BMenu{Active: 1}}). Where("tmr.role_id = ?", roleId). Order("b2b_top_menu.parent_id ASC, b2b_top_menu.position ASC"). Find(&menus).Error return menus, err } + +func (p *RoutesRepo) GetCustomerManagementMenu(langId uint) ([]model.B2BCustomerManagementMenu, error) { + var menus []model.B2BCustomerManagementMenu + + err := db. + Get(). + Model(model.B2BCustomerManagementMenu{}). + Where(model.B2BCustomerManagementMenu{B2BMenu: model.B2BMenu{Active: 1}}). + Order("b2b_customer_management_menu.parent_id ASC, b2b_customer_management_menu.position ASC"). + Find(&menus).Error + + return menus, err +} diff --git a/app/service/menuService/menuService.go b/app/service/menuService/menuService.go index 595303a..4cd44f4 100644 --- a/app/service/menuService/menuService.go +++ b/app/service/menuService/menuService.go @@ -193,18 +193,41 @@ func (s *MenuService) GetBreadcrumb(root_category_id uint, start_category_id uin return breadcrumb, nil } -func (s *MenuService) GetTopMenu(languageId uint, roleId uint) ([]*model.B2BTopMenu, error) { +func (s *MenuService) GetTopMenu(languageId uint, roleId uint) ([]*model.B2BMenu, 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) + menus := make([]model.B2BMenu, len(items)) + for i := range items { + menus[i] = items[i].B2BMenu + } + + return buildMenu(menus), nil +} + +func (s *MenuService) GetCustomerManagementMenu(languageId uint) ([]*model.B2BMenu, error) { + items, err := s.routesRepo.GetCustomerManagementMenu(languageId) + if err != nil { + return nil, err + } + + menus := make([]model.B2BMenu, len(items)) + for i := range items { + menus[i] = items[i].B2BMenu + } + + return buildMenu(menus), nil +} + +func buildMenu(items []model.B2BMenu) []*model.B2BMenu { + menuMap := make(map[int]*model.B2BMenu, len(items)) + roots := make([]*model.B2BMenu, 0) for i := range items { menu := &items[i] - menu.Children = make([]*model.B2BTopMenu, 0) + menu.Children = make([]*model.B2BMenu, 0) menuMap[menu.MenuID] = menu } @@ -226,7 +249,7 @@ func (s *MenuService) GetTopMenu(languageId uint, roleId uint) ([]*model.B2BTopM parent.Children = append(parent.Children, menu) } - return roots, nil + return roots } func (s *MenuService) appendAdditional(all_categories *[]model.ScannedCategory, id_lang uint, iso_code string) { diff --git a/bruno/api_v1/menu/Breadcrumb.yml b/bruno/api_v1/menu/Breadcrumb.yml new file mode 100644 index 0000000..164e519 --- /dev/null +++ b/bruno/api_v1/menu/Breadcrumb.yml @@ -0,0 +1,22 @@ +info: + name: Breadcrumb + type: http + seq: 1 + +http: + method: GET + url: http://localhost:3000/api/v1/restricted/menu/get-breadcrumb?root_category_id=2&category_id=13 + params: + - name: root_category_id + value: "2" + type: query + - name: category_id + value: "13" + type: query + auth: inherit + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 diff --git a/bruno/api_v1/menu/Category tree.yml b/bruno/api_v1/menu/Category tree.yml new file mode 100644 index 0000000..d546004 --- /dev/null +++ b/bruno/api_v1/menu/Category tree.yml @@ -0,0 +1,19 @@ +info: + name: Category tree + type: http + seq: 2 + +http: + method: GET + url: http://localhost:3000/api/v1/restricted/menu/get-category-tree?root_category_id=2 + params: + - name: root_category_id + value: "2" + type: query + auth: inherit + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 diff --git a/bruno/api_v1/menu/Top Customer Management Menu.yml b/bruno/api_v1/menu/Top Customer Management Menu.yml new file mode 100644 index 0000000..484155d --- /dev/null +++ b/bruno/api_v1/menu/Top Customer Management Menu.yml @@ -0,0 +1,15 @@ +info: + name: Top Customer Management Menu + type: http + seq: 4 + +http: + method: GET + url: "{{bas_url}}/restricted/menu/get-customer-management-menu" + auth: inherit + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 diff --git a/bruno/api_v1/menu/Top Menu.yml b/bruno/api_v1/menu/Top Menu.yml new file mode 100644 index 0000000..14e9d7a --- /dev/null +++ b/bruno/api_v1/menu/Top Menu.yml @@ -0,0 +1,15 @@ +info: + name: Top Menu + type: http + seq: 3 + +http: + method: GET + url: "{{bas_url}}/restricted/menu/get-top-menu" + auth: inherit + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 diff --git a/bruno/api_v1/menu/folder.yml b/bruno/api_v1/menu/folder.yml new file mode 100644 index 0000000..c14186d --- /dev/null +++ b/bruno/api_v1/menu/folder.yml @@ -0,0 +1,7 @@ +info: + name: menu + type: folder + seq: 12 + +request: + auth: inherit diff --git a/i18n/migrations/20260302163100_routes.sql b/i18n/migrations/20260302163100_routes.sql index 22051cf..6beee5d 100644 --- a/i18n/migrations/20260302163100_routes.sql +++ b/i18n/migrations/20260302163100_routes.sql @@ -42,9 +42,32 @@ INSERT IGNORE INTO `b2b_top_menu` (`menu_id`, `label`, `parent_id`, `params`, `a (3, JSON_COMPACT('{"name":"admin-products","trans":{"pl":{"label":"admin-products"},"en":{"label":"admin-products"},"de":{"label":"admin-products"}}}'),1,JSON_COMPACT('{}'),1,1), (9, JSON_COMPACT('{"name":"carts","trans":{"pl":{"label":"Koszyki"},"en":{"label":"Carts"},"de":{"label":"Warenkörbe"}}}'),3,JSON_COMPACT('{"route": {"name": "home", "params":{"locale": ""}}}'),1,1); +CREATE TABLE IF NOT EXISTS b2b_customer_management_menu ( + menu_id INT AUTO_INCREMENT NOT NULL, + label LONGTEXT NOT NULL DEFAULT '{}', + parent_id INT NULL DEFAULT NULL, + params LONGTEXT NOT NULL DEFAULT '{}', + active TINYINT NOT NULL DEFAULT 1, + position INT NOT NULL DEFAULT 1, + PRIMARY KEY (menu_id), + CONSTRAINT FK_b2b_customer_management_menu_parent_id FOREIGN KEY (parent_id) + REFERENCES b2b_customer_management_menu (menu_id) + ON DELETE RESTRICT ON UPDATE RESTRICT, + INDEX FK_b2b_customer_management_menu_parent_id_idx (parent_id ASC) +) ENGINE = InnoDB; + +INSERT IGNORE INTO `b2b_customer_management_menu` (`menu_id`, `label`, `parent_id`, `params`, `active`, `position`) VALUES +(1, JSON_COMPACT('{"name":"root","trans":{"pl":{"label":"Menu główne"},"en":{"label":"Main Menu"},"de":{"label":"Hauptmenü"}}}'),NULL,JSON_COMPACT('{}'),1,1), +(3, JSON_COMPACT('{"name":"admin-products","trans":{"pl":{"label":"admin-products"},"en":{"label":"admin-products"},"de":{"label":"admin-products"}}}'),1,JSON_COMPACT('{}'),1,1), +(9, JSON_COMPACT('{"name":"carts","trans":{"pl":{"label":"Koszyki"},"en":{"label":"Carts"},"de":{"label":"Warenkörbe"}}}'),3,JSON_COMPACT('{"route": {"name": "home", "params":{"locale": ""}}}'),1,1); + +INSERT INTO `b2b_customer_management_menu` (`menu_id`, `label`, `parent_id`, `params`, `active`, `position`) VALUES (1, '{"name":"root","trans":{"pl":{"label":"Menu główne"},"en":{"label":"Main Menu"},"de":{"label":"Hauptmenü"}}}', NULL, '{}', 1, 1); +INSERT INTO `b2b_customer_management_menu` (`menu_id`, `label`, `parent_id`, `params`, `active`, `position`) VALUES (3, '{"name":"admin-products","trans":{"pl":{"label":"admin-products"},"en":{"label":"admin-products"},"de":{"label":"admin-products"}}}', 1, '{}', 1, 1); +INSERT INTO `b2b_customer_management_menu` (`menu_id`, `label`, `parent_id`, `params`, `active`, `position`) VALUES (9, '{"name":"carts","trans":{"pl":{"label":"Koszyki"},"en":{"label":"Carts"},"de":{"label":"Warenkörbe"}}}', 3, '{"route":{"name":"home","params":{"locale":""}}}', 1, 1); -- +goose Down DROP TABLE IF EXISTS b2b_routes; DROP TABLE IF EXISTS b2b_top_menu; +DROP TABLE IF EXISTS b2b_customer_management_menu; DROP FUNCTION IF EXISTS `slugify_eu`; diff --git a/i18n/migrations/20260302163123_create_tables_data.sql b/i18n/migrations/20260302163123_create_tables_data.sql index 2e9770b..137421d 100644 --- a/i18n/migrations/20260302163123_create_tables_data.sql +++ b/i18n/migrations/20260302163123_create_tables_data.sql @@ -12,6 +12,19 @@ INSERT INTO `b2b_roles` (`name`, `id`) VALUES ('admin','2'); INSERT INTO `b2b_roles` (`name`, `id`) VALUES ('super_admin','3'); INSERT INTO `b2b_roles` (`name`, `id`) VALUES ('unlogged','4'); +INSERT INTO `b2b_top_menu_roles` (`top_menu_id`, `role_id`) VALUES (1, '1'); +INSERT INTO `b2b_top_menu_roles` (`top_menu_id`, `role_id`) VALUES (3, '1'); +INSERT INTO `b2b_top_menu_roles` (`top_menu_id`, `role_id`) VALUES (9, '1'); +INSERT INTO `b2b_top_menu_roles` (`top_menu_id`, `role_id`) VALUES (1, '2'); +INSERT INTO `b2b_top_menu_roles` (`top_menu_id`, `role_id`) VALUES (3, '2'); +INSERT INTO `b2b_top_menu_roles` (`top_menu_id`, `role_id`) VALUES (9, '2'); +INSERT INTO `b2b_top_menu_roles` (`top_menu_id`, `role_id`) VALUES (1, '3'); +INSERT INTO `b2b_top_menu_roles` (`top_menu_id`, `role_id`) VALUES (3, '3'); +INSERT INTO `b2b_top_menu_roles` (`top_menu_id`, `role_id`) VALUES (9, '3'); +INSERT INTO `b2b_top_menu_roles` (`top_menu_id`, `role_id`) VALUES (1, '4'); +INSERT INTO `b2b_top_menu_roles` (`top_menu_id`, `role_id`) VALUES (3, '4'); +INSERT INTO `b2b_top_menu_roles` (`top_menu_id`, `role_id`) VALUES (9, '4'); + -- insert sample admin user admin@ma-al.com/Maal12345678 INSERT IGNORE INTO b2b_customers (id, email, password, first_name, last_name, role_id, provider, provider_id, avatar_url, is_active, email_verified, email_verification_token, email_verification_expires, password_reset_token, password_reset_expires, last_password_reset_request, last_login_at, lang_id, country_id, created_at, updated_at, deleted_at)