diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..082ab29
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,14 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Launch Package",
+ "type": "go",
+ "request": "launch",
+ "mode": "auto",
+ "program": "./app/cmd/main.go",
+ "cwd": "${workspaceFolder}",
+ "envFile": "${workspaceFolder}/.env"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/app/delivery/middleware/auth.go b/app/delivery/middleware/auth.go
index 2aefce0..30651f8 100644
--- a/app/delivery/middleware/auth.go
+++ b/app/delivery/middleware/auth.go
@@ -63,7 +63,7 @@ func AuthMiddleware() fiber.Handler {
// Set user in context
c.Locals(constdata.USER_LOCALES_NAME, user.ToSession())
c.Locals(constdata.USER_LOCALES_ID, user.ID)
-
+ c.Locals(constdata.LANG_LOCALES_ID, user.LangID)
return c.Next()
}
}
@@ -85,7 +85,7 @@ func RequireAdmin() fiber.Handler {
})
}
- if userSession.Role != model.RoleAdmin {
+ if model.CustomerRole(userSession.RoleName) != model.RoleAdmin {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "admin access required",
})
diff --git a/app/delivery/middleware/permissions.go b/app/delivery/middleware/permissions.go
new file mode 100644
index 0000000..96ab057
--- /dev/null
+++ b/app/delivery/middleware/permissions.go
@@ -0,0 +1,28 @@
+package middleware
+
+import (
+ "git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
+ "git.ma-al.com/goc_daniel/b2b/app/model"
+ "github.com/gofiber/fiber/v3"
+)
+
+func Require(p perms.Permission) fiber.Handler {
+ return func(c fiber.Ctx) error {
+ u := c.Locals("user")
+ if u == nil {
+ return c.SendStatus(fiber.StatusUnauthorized)
+ }
+
+ user, ok := u.(*model.UserSession)
+ if !ok {
+ return c.SendStatus(fiber.StatusInternalServerError)
+ }
+
+ for _, perm := range user.Permissions {
+ if perm == p {
+ return c.Next()
+ }
+ }
+ return c.SendStatus(fiber.StatusForbidden)
+ }
+}
diff --git a/app/delivery/middleware/perms/permissions.go b/app/delivery/middleware/perms/permissions.go
new file mode 100644
index 0000000..d4b9f07
--- /dev/null
+++ b/app/delivery/middleware/perms/permissions.go
@@ -0,0 +1,11 @@
+package perms
+
+type Permission string
+
+const (
+ UserRead Permission = "user.read"
+ UserWrite Permission = "user.write"
+ UserReadAny Permission = "user.read.any"
+ UserWriteAny Permission = "user.write.any"
+ UserDeleteAny Permission = "user.delete.any"
+)
diff --git a/app/delivery/web/api/public/auth.go b/app/delivery/web/api/public/auth.go
index 852a4ac..edf67f3 100644
--- a/app/delivery/web/api/public/auth.go
+++ b/app/delivery/web/api/public/auth.go
@@ -360,7 +360,7 @@ func (h *AuthHandler) UpdateJWTToken(c fiber.Ctx) error {
user := model.Customer{
ID: userLocals.UserID,
Email: userLocals.Email,
- Role: userLocals.Role,
+ Role: model.Role{ID: userLocals.RoleID, Name: userLocals.RoleName},
LangID: userLocals.LangID,
CountryID: userLocals.CountryID,
IsActive: userLocals.IsActive,
diff --git a/app/delivery/web/api/restricted/currency.go b/app/delivery/web/api/restricted/currency.go
new file mode 100644
index 0000000..52dee21
--- /dev/null
+++ b/app/delivery/web/api/restricted/currency.go
@@ -0,0 +1,68 @@
+package restricted
+
+import (
+ "strconv"
+
+ "git.ma-al.com/goc_daniel/b2b/app/config"
+ "git.ma-al.com/goc_daniel/b2b/app/model"
+ "git.ma-al.com/goc_daniel/b2b/app/service/currencyService"
+ "git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
+ "git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
+ "git.ma-al.com/goc_daniel/b2b/app/utils/response"
+ "git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
+
+ "github.com/gofiber/fiber/v3"
+)
+
+type CurrencyHandler struct {
+ CurrencyService *currencyService.CurrencyService
+ config *config.Config
+}
+
+func NewCurrencyHandler() *CurrencyHandler {
+ currencyService := currencyService.New()
+ return &CurrencyHandler{
+ CurrencyService: currencyService,
+ config: config.Get(),
+ }
+}
+
+func CurrencyHandlerRoutes(r fiber.Router) fiber.Router {
+ handler := NewCurrencyHandler()
+
+ r.Post("/currency-rate", handler.PostCurrencyRate)
+ r.Get("/currency-rate/:id", handler.GetCurrencyRate)
+ return r
+}
+
+func (h *CurrencyHandler) PostCurrencyRate(c fiber.Ctx) error {
+ var currencyRate model.CurrencyRate
+ if err := c.Bind().Body(¤cyRate); err != nil {
+ return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrJSONBody)).
+ JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrJSONBody)))
+ }
+
+ err := h.CurrencyService.CreateCurrencyRate(¤cyRate)
+ if err != nil {
+ return c.Status(responseErrors.GetErrorStatus(err)).
+ JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
+ }
+
+ return c.JSON(response.Make(nullable.GetNil(""), 1, i18n.T_(c, response.Message_OK)))
+}
+
+func (h *CurrencyHandler) GetCurrencyRate(c fiber.Ctx) error {
+ idStr := c.Params("id")
+ id, err := strconv.Atoi(idStr)
+ if err != nil {
+ return c.Status(responseErrors.GetErrorStatus(err)).JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
+
+ }
+
+ currency, err := h.CurrencyService.GetCurrency(uint(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(currency, 0, i18n.T_(c, response.Message_OK)))
+}
diff --git a/app/delivery/web/api/restricted/customer.go b/app/delivery/web/api/restricted/customer.go
new file mode 100644
index 0000000..a15695d
--- /dev/null
+++ b/app/delivery/web/api/restricted/customer.go
@@ -0,0 +1,70 @@
+package restricted
+
+import (
+ "strconv"
+
+ "git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
+ "git.ma-al.com/goc_daniel/b2b/app/model"
+ "git.ma-al.com/goc_daniel/b2b/app/service/customerService"
+ "git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
+ "git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
+ "git.ma-al.com/goc_daniel/b2b/app/utils/response"
+ "git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
+ "github.com/gofiber/fiber/v3"
+)
+
+type customerHandler struct {
+ service *customerService.CustomerService
+}
+
+func NewCustomerHandler() *customerHandler {
+ customerService := customerService.New()
+ return &customerHandler{
+ service: customerService,
+ }
+}
+
+func CustomerHandlerRoutes(r fiber.Router) fiber.Router {
+ handler := NewCustomerHandler()
+
+ r.Get("", handler.customerData)
+ return r
+}
+
+func (h *customerHandler) customerData(fc fiber.Ctx) error {
+ var customerId uint
+ customerIdStr := fc.Query("id")
+ if customerIdStr != "" {
+ user, ok := fc.Locals("user").(*model.UserSession)
+ if !ok {
+ return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
+ JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrBadAttribute)))
+ }
+ id, err := strconv.ParseUint(customerIdStr, 10, 64)
+ if err != nil {
+ return fiber.ErrBadRequest
+ }
+
+ if user.UserID != uint(id) && !user.HasPermission(perms.UserReadAny) {
+ return fc.Status(fiber.StatusForbidden).
+ JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrForbidden)))
+ }
+
+ customerId = uint(id)
+ } else {
+ id, ok := fc.Locals("userID").(uint)
+ if !ok {
+ return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
+ JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrBadAttribute)))
+ }
+ customerId = id
+ }
+
+ customer, err := h.service.GetById(customerId)
+ if err != nil {
+ return fc.Status(responseErrors.GetErrorStatus(err)).
+ JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, err)))
+ }
+
+ return fc.JSON(response.Make(&customer, 0, i18n.T_(fc, response.Message_OK)))
+}
diff --git a/app/delivery/web/api/restricted/menu.go b/app/delivery/web/api/restricted/menu.go
index b269fb7..5173d3f 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/model"
+
"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"
@@ -86,12 +88,12 @@ func (h *MenuHandler) GetBreadcrumb(c fiber.Ctx) error {
}
func (h *MenuHandler) GetTopMenu(c fiber.Ctx) error {
- lang_id, ok := c.Locals("langID").(uint)
+ session, ok := c.Locals("user").(*model.UserSession)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
}
- menu, err := h.menuService.GetTopMenu(lang_id)
+ menu, err := h.menuService.GetTopMenu(session.LangID, session.RoleID)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
diff --git a/app/delivery/web/api/restricted/product.go b/app/delivery/web/api/restricted/product.go
new file mode 100644
index 0000000..eaade19
--- /dev/null
+++ b/app/delivery/web/api/restricted/product.go
@@ -0,0 +1,82 @@
+package restricted
+
+import (
+ "fmt"
+ "strconv"
+
+ "git.ma-al.com/goc_daniel/b2b/app/config"
+ "git.ma-al.com/goc_daniel/b2b/app/service/productService"
+ 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/nullable"
+ "git.ma-al.com/goc_daniel/b2b/app/utils/response"
+ "git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
+ "github.com/gofiber/fiber/v3"
+)
+
+type ProductsHandler struct {
+ productService *productService.ProductService
+ config *config.Config
+}
+
+// NewListProductsHandler creates a new ListProductsHandler instance
+func NewProductsHandler() *ProductsHandler {
+ productService := productService.New()
+ return &ProductsHandler{
+ productService: productService,
+ config: config.Get(),
+ }
+}
+
+func ProductsHandlerRoutes(r fiber.Router) fiber.Router {
+ handler := NewProductsHandler()
+
+ r.Get("/:id/:country_id/:quantity", handler.GetProductJson)
+
+ return r
+}
+
+func (h *ProductsHandler) GetProductJson(c fiber.Ctx) error {
+ idStr := c.Params("id")
+
+ p_id_product, err := strconv.Atoi(idStr)
+ if err != nil {
+ return c.Status(responseErrors.GetErrorStatus(err)).
+ JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
+ }
+
+ country_idStr := c.Params("country_id")
+
+ b2b_id_country, err := strconv.Atoi(country_idStr)
+ if err != nil {
+ return c.Status(responseErrors.GetErrorStatus(err)).
+ JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
+ }
+
+ quantityStr := c.Params("quantity")
+
+ p_quantity, err := strconv.Atoi(quantityStr)
+ if err != nil {
+ return c.Status(responseErrors.GetErrorStatus(err)).
+ JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
+ }
+
+ p_id_customer, ok := c.Locals(constdata.USER_LOCALES_ID).(uint)
+ if !ok {
+ return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
+ JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
+ }
+ fmt.Printf("p_id_customer: %v\n", p_id_customer)
+ id_lang, ok := c.Locals(constdata.LANG_LOCALES_ID).(uint)
+ if !ok {
+ return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
+ JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
+ }
+ productJson, err := h.productService.GetJSON(p_id_product, int(id_lang), int(p_id_customer), b2b_id_country, p_quantity)
+ if err != nil {
+ return c.Status(responseErrors.GetErrorStatus(err)).
+ JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
+ }
+
+ return c.JSON(response.Make(&productJson, 1, i18n.T_(c, response.Message_OK)))
+}
diff --git a/app/delivery/web/init.go b/app/delivery/web/init.go
index eaf41d9..be7730b 100644
--- a/app/delivery/web/init.go
+++ b/app/delivery/web/init.go
@@ -90,6 +90,9 @@ func (s *Server) Setup() error {
menuRouting := s.public.Group("/menu")
public.RoutingHandlerRoutes(menuRouting)
+ pCustomer := s.restricted.Group("/customer")
+ restricted.CustomerHandlerRoutes(pCustomer)
+
// product translation routes (restricted)
productTranslation := s.restricted.Group("/product-translation")
restricted.ProductTranslationHandlerRoutes(productTranslation)
@@ -98,6 +101,9 @@ func (s *Server) Setup() error {
list := s.restricted.Group("/list")
restricted.ListHandlerRoutes(list)
+ product := s.restricted.Group("/product")
+ restricted.ProductsHandlerRoutes(product)
+
// locale selector (restricted)
// this is basically for changing user's selected language and country
localeSelector := s.restricted.Group("/langs-and-countries")
@@ -119,6 +125,8 @@ func (s *Server) Setup() error {
return c.SendStatus(fiber.StatusNotFound)
})
+ restricted.CurrencyHandlerRoutes(s.restricted)
+
// // Restricted routes example
// restricted := s.api.Group("/restricted")
// restricted.Use(middleware.AuthMiddleware())
diff --git a/app/model/currency.go b/app/model/currency.go
new file mode 100644
index 0000000..18ca8ce
--- /dev/null
+++ b/app/model/currency.go
@@ -0,0 +1,25 @@
+package model
+
+import "time"
+
+type Currency struct {
+ ID int `json:"id"`
+ PsIDCurrency uint `json:"ps_id_currency"`
+ IsDefault bool `json:"is_default"`
+ IsActive bool `json:"is_active"`
+ ConversionRate *float64 `json:"conversion_rate,omitempty"`
+}
+
+func (Currency) TableName() string {
+ return "b2b_currencies"
+}
+
+type CurrencyRate struct {
+ B2bIdCurrency uint `json:"b2b_id_currency"`
+ CreatedAt time.Time `json:"created_at"`
+ ConversionRate *float64 `json:"conversion_rate,omitempty"`
+}
+
+func (CurrencyRate) TableName() string {
+ return "b2b_currency_rates"
+}
diff --git a/app/model/customer.go b/app/model/customer.go
index ec7b63d..f7db443 100644
--- a/app/model/customer.go
+++ b/app/model/customer.go
@@ -3,6 +3,7 @@ package model
import (
"time"
+ "git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
"gorm.io/gorm"
)
@@ -13,7 +14,8 @@ type Customer struct {
Password string `gorm:"size:255" json:"-"` // Hashed password, not exposed in JSON
FirstName string `gorm:"size:100" json:"first_name"`
LastName string `gorm:"size:100" json:"last_name"`
- Role CustomerRole `gorm:"type:varchar(20);default:'user'" json:"role"`
+ RoleID uint `gorm:"column:role_id;not null;default:1" json:"-"`
+ Role Role `gorm:"foreignKey:RoleID" json:"role"`
Provider AuthProvider `gorm:"type:varchar(20);default:'local'" json:"provider"`
ProviderID string `gorm:"size:255" json:"provider_id,omitempty"` // ID from OAuth provider
AvatarURL string `gorm:"size:500" json:"avatar_url,omitempty"`
@@ -32,14 +34,6 @@ type Customer struct {
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
-// CustomerRole represents the role of a user
-type CustomerRole string
-
-const (
- RoleUser CustomerRole = "user"
- RoleAdmin CustomerRole = "admin"
-)
-
// AuthProvider represents the authentication provider
type AuthProvider string
@@ -53,16 +47,6 @@ func (Customer) TableName() string {
return "b2b_customers"
}
-// IsAdmin checks if the user has admin role
-func (u *Customer) IsAdmin() bool {
- return u.Role == RoleAdmin
-}
-
-// CanManageUsers checks if the user can manage other users
-func (u *Customer) CanManageUsers() bool {
- return u.Role == RoleAdmin
-}
-
// FullName returns the user's full name
func (u *Customer) FullName() string {
if u.FirstName == "" && u.LastName == "" {
@@ -73,27 +57,51 @@ func (u *Customer) FullName() string {
// UserSession represents a user session for JWT claims
type UserSession struct {
- UserID uint `json:"user_id"`
- Email string `json:"email"`
- Username string `json:"username"`
- Role CustomerRole `json:"role"`
- LangID uint `json:"lang_id"`
- CountryID uint `json:"country_id"`
- IsActive bool `json:"is_active"`
+ UserID uint `json:"user_id"`
+ Email string `json:"email"`
+ Username string `json:"username"`
+ RoleID uint `json:"role_id"`
+ RoleName string `json:"role_name"`
+ LangID uint `json:"lang_id"`
+ CountryID uint `json:"country_id"`
+ IsActive bool `json:"is_active"`
+ Permissions []perms.Permission `json:"permissions"`
+}
+
+func (us *UserSession) HasPermission(permission perms.Permission) bool {
+ for _, p := range us.Permissions {
+ if p == permission {
+ return true
+ }
+ }
+ return false
}
// ToSession converts User to UserSession
func (u *Customer) ToSession() *UserSession {
+
return &UserSession{
- UserID: u.ID,
- Email: u.Email,
- Role: u.Role,
- LangID: u.LangID,
- CountryID: u.CountryID,
- IsActive: u.IsActive,
+ UserID: u.ID,
+ Email: u.Email,
+ RoleID: u.Role.ID,
+ RoleName: u.Role.Name,
+ Permissions: BuildPermissionSlice(u),
+ LangID: u.LangID,
+ CountryID: u.CountryID,
+ IsActive: u.IsActive,
}
}
+func BuildPermissionSlice(user *Customer) []perms.Permission {
+ var perms []perms.Permission
+
+ for _, p := range user.Role.Permissions {
+ perms = append(perms, p.Name)
+ }
+
+ return perms
+}
+
// LoginRequest represents the login form data
type LoginRequest struct {
Email string `json:"email" form:"email"`
diff --git a/app/model/model.go b/app/model/model.go
new file mode 100644
index 0000000..620b57a
--- /dev/null
+++ b/app/model/model.go
@@ -0,0 +1,18 @@
+package model
+
+import (
+ "time"
+
+ "gorm.io/gorm"
+)
+
+type Model struct {
+ ID uint `gorm:"primarykey;autoIncrement" swaggerignore:"true" json:"id,omitempty" hidden:"true"`
+ CreatedAt time.Time `gorm:"not null;autoCreateTime" swaggerignore:"true" json:"-"`
+ UpdatedAt time.Time `gorm:"autoUpdateTime" swaggerignore:"true" json:"-"`
+ DeletedAt gorm.DeletedAt `gorm:"index" swaggerignore:"true" json:"-"`
+}
+
+// Makes all objects embedding db.Model implementators of ModelWithID interface
+func (m Model) ModelWithID() {
+}
diff --git a/app/model/permission.go b/app/model/permission.go
new file mode 100644
index 0000000..4b21efe
--- /dev/null
+++ b/app/model/permission.go
@@ -0,0 +1,12 @@
+package model
+
+import "git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
+
+type Permission struct {
+ ID uint
+ Name perms.Permission
+}
+
+func (Permission) TableName() string {
+ return "b2b_permissions"
+}
diff --git a/app/model/role.go b/app/model/role.go
new file mode 100644
index 0000000..3c663b5
--- /dev/null
+++ b/app/model/role.go
@@ -0,0 +1,19 @@
+package model
+
+type Role struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ Name string `gorm:"size:64" json:"name"`
+ Permissions []Permission `gorm:"many2many:b2b_role_permissions;" json:"-"`
+}
+
+func (Role) TableName() string {
+ return "b2b_roles"
+}
+
+type CustomerRole string
+
+const (
+ RoleUser CustomerRole = "user"
+ RoleAdmin CustomerRole = "admin"
+ RoleSuperAdmin CustomerRole = "super_admin"
+)
diff --git a/app/repos/currencyRepo/currencyRepo.go b/app/repos/currencyRepo/currencyRepo.go
new file mode 100644
index 0000000..9cc153c
--- /dev/null
+++ b/app/repos/currencyRepo/currencyRepo.go
@@ -0,0 +1,53 @@
+package currencyRepo
+
+import (
+ "git.ma-al.com/goc_daniel/b2b/app/db"
+ "git.ma-al.com/goc_daniel/b2b/app/model"
+ "git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
+ "git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
+)
+
+type UICurrencyRepo interface {
+ CreateConversionRate(currencyRate *model.CurrencyRate) error
+ Get(id uint) (*model.Currency, error)
+}
+
+type CurrencyRepo struct{}
+
+func New() UICurrencyRepo {
+ return &CurrencyRepo{}
+}
+
+func (repo *CurrencyRepo) CreateConversionRate(currencyRate *model.CurrencyRate) error {
+ return db.DB.Create(currencyRate).Error
+}
+
+func (repo *CurrencyRepo) Get(id uint) (*model.Currency, error) {
+ var currency model.Currency
+
+ err := db.DB.Table("b2b_currencies c").
+ Select("c.*, r.conversion_rate").
+ Joins(`
+ LEFT JOIN b2b_currency_rates r
+ ON r.b2b_id_currency = c.id
+ AND r.created_at = (
+ SELECT MAX(created_at)
+ FROM b2b_currency_rates
+ WHERE b2b_id_currency = c.id
+ )
+ `).
+ Where("c.id = ?", id).
+ Scan(¤cy).Error
+
+ return ¤cy, err
+}
+
+func (repo *CurrencyRepo) Find(langId uint, p find.Paging, filt *filters.FiltersList) (*find.Found[model.Currency], error) {
+
+ found, err := find.Paginate[model.Currency](langId, p, db.DB.
+ Model(&model.Currency{}).
+ Scopes(filt.All()...),
+ )
+
+ return &found, err
+}
diff --git a/app/repos/customerRepo/customerRepo.go b/app/repos/customerRepo/customerRepo.go
new file mode 100644
index 0000000..058d5fd
--- /dev/null
+++ b/app/repos/customerRepo/customerRepo.go
@@ -0,0 +1,27 @@
+package customerRepo
+
+import (
+ "git.ma-al.com/goc_daniel/b2b/app/db"
+ "git.ma-al.com/goc_daniel/b2b/app/model"
+)
+
+type UICustomerRepo interface {
+ Get(id uint) (*model.Customer, error)
+}
+
+type CustomerRepo struct{}
+
+func New() UICustomerRepo {
+ return &CustomerRepo{}
+}
+
+func (repo *CustomerRepo) Get(id uint) (*model.Customer, error) {
+ var customer model.Customer
+
+ err := db.DB.
+ Preload("Role").
+ First(&customer, id).
+ Error
+
+ return &customer, err
+}
diff --git a/app/repos/productsRepo/productsRepo.go b/app/repos/productsRepo/productsRepo.go
new file mode 100644
index 0000000..7c6c08f
--- /dev/null
+++ b/app/repos/productsRepo/productsRepo.go
@@ -0,0 +1,39 @@
+package productsRepo
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "git.ma-al.com/goc_daniel/b2b/app/db"
+)
+
+type UIProductsRepo interface {
+ GetJSON(p_id_product, p_id_shop, p_id_lang, p_id_customer, b2b_id_country, p_quantity int) (*json.RawMessage, error)
+}
+
+type ProductsRepo struct{}
+
+func New() UIProductsRepo {
+ return &ProductsRepo{}
+}
+
+func (repo *ProductsRepo) GetJSON(p_id_product, p_id_shop, p_id_lang, p_id_customer, b2b_id_country, p_quantity int) (*json.RawMessage, error) {
+ var productStr string // β Scan as string first
+
+ err := db.DB.Raw(`CALL get_full_product(?,?,?,?,?,?)`,
+ p_id_product, p_id_shop, p_id_lang, p_id_customer, b2b_id_country, p_quantity).
+ Scan(&productStr).
+ Error
+
+ if err != nil {
+ return nil, err
+ }
+
+ // Optional: validate it's valid JSON
+ if !json.Valid([]byte(productStr)) {
+ return nil, fmt.Errorf("invalid json returned from stored procedure")
+ }
+
+ raw := json.RawMessage(productStr)
+ return &raw, nil
+}
diff --git a/app/repos/routesRepo/routesRepo.go b/app/repos/routesRepo/routesRepo.go
index d12d488..09e5754 100644
--- a/app/repos/routesRepo/routesRepo.go
+++ b/app/repos/routesRepo/routesRepo.go
@@ -8,7 +8,7 @@ import (
type UIRoutesRepo interface {
GetRoutes(langId uint) ([]model.Route, error)
- GetTopMenu(id uint) ([]model.B2BTopMenu, error)
+ GetTopMenu(id uint, roleId uint) ([]model.B2BTopMenu, error)
}
type RoutesRepo struct{}
@@ -26,12 +26,16 @@ func (p *RoutesRepo) GetRoutes(langId uint) ([]model.Route, error) {
return routes, nil
}
-func (p *RoutesRepo) GetTopMenu(id uint) ([]model.B2BTopMenu, error) {
+func (p *RoutesRepo) GetTopMenu(langId uint, roleId uint) ([]model.B2BTopMenu, error) {
var menus []model.B2BTopMenu
- err := db.Get().
- Where("active = ?", 1).
- Order("parent_id ASC, position ASC").
+ err := db.
+ 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("tmr.role_id = ?", roleId).
+ Order("b2b_top_menu.parent_id ASC, b2b_top_menu.position ASC").
Find(&menus).Error
return menus, err
diff --git a/app/service/authService/auth.go b/app/service/authService/auth.go
index 2fc4a7d..6effc43 100644
--- a/app/service/authService/auth.go
+++ b/app/service/authService/auth.go
@@ -23,13 +23,13 @@ import (
// JWTClaims represents the JWT claims
type JWTClaims struct {
- UserID uint `json:"user_id"`
- Email string `json:"email"`
- Username string `json:"username"`
- Role model.CustomerRole `json:"customer_role"`
- CartsIDs []uint `json:"carts_ids"`
- LangID uint `json:"lang_id"`
- CountryID uint `json:"country_id"`
+ UserID uint `json:"user_id"`
+ Email string `json:"email"`
+ Username string `json:"username"`
+ Role string `json:"customer_role"`
+ CartsIDs []uint `json:"carts_ids"`
+ LangID uint `json:"lang_id"`
+ CountryID uint `json:"country_id"`
jwt.RegisteredClaims
}
@@ -59,7 +59,7 @@ func (s *AuthService) Login(req *model.LoginRequest) (*model.AuthResponse, strin
var user model.Customer
// Find user by email
- if err := s.db.Where("email = ?", req.Email).First(&user).Error; err != nil {
+ if err := s.db.Preload("Role.Permissions").Where("email = ?", req.Email).First(&user).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, "", responseErrors.ErrInvalidCredentials
}
@@ -144,7 +144,7 @@ func (s *AuthService) Register(req *model.RegisterRequest) error {
Password: string(hashedPassword),
FirstName: req.FirstName,
LastName: req.LastName,
- Role: model.RoleUser,
+ Role: model.Role{},
Provider: model.ProviderLocal,
IsActive: false,
EmailVerified: false,
@@ -422,7 +422,7 @@ func (s *AuthService) RevokeAllRefreshTokens(userID uint) {
// GetUserByID retrieves a user by ID
func (s *AuthService) GetUserByID(userID uint) (*model.Customer, error) {
var user model.Customer
- if err := s.db.First(&user, userID).Error; err != nil {
+ if err := s.db.Preload("Role.Permissions").First(&user, userID).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, responseErrors.ErrUserNotFound
}
@@ -489,7 +489,7 @@ func (s *AuthService) generateAccessToken(user *model.Customer) (string, error)
UserID: user.ID,
Email: user.Email,
Username: user.Email,
- Role: user.Role,
+ Role: user.Role.Name,
CartsIDs: []uint{},
LangID: user.LangID,
CountryID: user.CountryID,
diff --git a/app/service/authService/google_oauth.go b/app/service/authService/google_oauth.go
index a4c2cd1..c26da16 100644
--- a/app/service/authService/google_oauth.go
+++ b/app/service/authService/google_oauth.go
@@ -150,7 +150,7 @@ func (s *AuthService) findOrCreateGoogleUser(info *view.GoogleUserInfo) (*model.
Provider: model.ProviderGoogle,
ProviderID: info.ID,
AvatarURL: info.Picture,
- Role: model.RoleUser,
+ Role: model.Role{},
IsActive: true,
EmailVerified: true,
LangID: 2, // default is english
diff --git a/app/service/currencyService/currencyService.go b/app/service/currencyService/currencyService.go
new file mode 100644
index 0000000..d4924d8
--- /dev/null
+++ b/app/service/currencyService/currencyService.go
@@ -0,0 +1,25 @@
+package currencyService
+
+import (
+ "git.ma-al.com/goc_daniel/b2b/app/model"
+ "git.ma-al.com/goc_daniel/b2b/app/repos/currencyRepo"
+)
+
+type CurrencyService struct {
+ repo currencyRepo.UICurrencyRepo
+}
+
+func (s *CurrencyService) GetCurrency(id uint) (*model.Currency, error) {
+ return s.repo.Get(id)
+}
+
+func (s *CurrencyService) CreateCurrencyRate(currency *model.CurrencyRate) error {
+ return s.repo.CreateConversionRate(currency)
+}
+
+func New() *CurrencyService {
+ repo := currencyRepo.New()
+ return &CurrencyService{
+ repo: repo,
+ }
+}
diff --git a/app/service/customerService/customerService.go b/app/service/customerService/customerService.go
new file mode 100644
index 0000000..7af553c
--- /dev/null
+++ b/app/service/customerService/customerService.go
@@ -0,0 +1,20 @@
+package customerService
+
+import (
+ "git.ma-al.com/goc_daniel/b2b/app/model"
+ "git.ma-al.com/goc_daniel/b2b/app/repos/customerRepo"
+)
+
+type CustomerService struct {
+ repo customerRepo.UICustomerRepo
+}
+
+func New() *CustomerService {
+ return &CustomerService{
+ repo: customerRepo.New(),
+ }
+}
+
+func (s *CustomerService) GetById(id uint) (*model.Customer, error) {
+ return s.repo.Get(id)
+}
diff --git a/app/service/menuService/menuService.go b/app/service/menuService/menuService.go
index 2d72cea..de2498d 100644
--- a/app/service/menuService/menuService.go
+++ b/app/service/menuService/menuService.go
@@ -176,8 +176,8 @@ func (s *MenuService) GetBreadcrumb(root_category_id uint, start_category_id uin
return breadcrumb, nil
}
-func (s *MenuService) GetTopMenu(id uint) ([]*model.B2BTopMenu, error) {
- items, err := s.routesRepo.GetTopMenu(id)
+func (s *MenuService) GetTopMenu(languageId uint, roleId uint) ([]*model.B2BTopMenu, error) {
+ items, err := s.routesRepo.GetTopMenu(languageId, roleId)
if err != nil {
return nil, err
}
diff --git a/app/service/productService/productService.go b/app/service/productService/productService.go
new file mode 100644
index 0000000..66245f1
--- /dev/null
+++ b/app/service/productService/productService.go
@@ -0,0 +1,27 @@
+package productService
+
+import (
+ "encoding/json"
+
+ "git.ma-al.com/goc_daniel/b2b/app/repos/productsRepo"
+ constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
+)
+
+type ProductService struct {
+ productsRepo productsRepo.UIProductsRepo
+}
+
+func New() *ProductService {
+ return &ProductService{
+ productsRepo: productsRepo.New(),
+ }
+}
+
+func (s *ProductService) GetJSON(p_id_product, p_id_lang, p_id_customer, b2b_id_country, p_quantity int) (*json.RawMessage, error) {
+ products, err := s.productsRepo.GetJSON(p_id_product, constdata.SHOP_ID, p_id_lang, p_id_customer, b2b_id_country, p_quantity)
+ if err != nil {
+ return products, err
+ }
+
+ return products, nil
+}
diff --git a/app/utils/const_data/consts.go b/app/utils/const_data/consts.go
index 9c64ee5..9fb53b5 100644
--- a/app/utils/const_data/consts.go
+++ b/app/utils/const_data/consts.go
@@ -13,3 +13,4 @@ const DEFAULT_NEW_CART_NAME = "new cart"
const USER_LOCALES_NAME = "user"
const USER_LOCALES_ID = "userID"
+const LANG_LOCALES_ID = "langID"
diff --git a/app/utils/responseErrors/responseErrors.go b/app/utils/responseErrors/responseErrors.go
index c4247ea..d2fce1a 100644
--- a/app/utils/responseErrors/responseErrors.go
+++ b/app/utils/responseErrors/responseErrors.go
@@ -9,6 +9,7 @@ import (
var (
// Typed errors for request validation and authentication
+ ErrForbidden = errors.New("forbidden")
ErrInvalidBody = errors.New("invalid request body")
ErrNotAuthenticated = errors.New("not authenticated")
ErrUserNotFound = errors.New("user not found")
@@ -59,6 +60,9 @@ var (
ErrMaxAmtOfCartsReached = errors.New("maximal amount of carts reached")
ErrUserHasNoSuchCart = errors.New("user does not have cart with given id")
ErrProductOrItsVariationDoesNotExist = errors.New("product or its variation with given ids does not exist")
+
+ // Typed errors for data parsing
+ ErrJSONBody = errors.New("invalid JSON body")
)
// Error represents an error with HTTP status code
@@ -83,6 +87,8 @@ func NewError(err error, status int) *Error {
// GetErrorCode returns the error code string for HTTP response mapping
func GetErrorCode(c fiber.Ctx, err error) string {
switch {
+ case errors.Is(err, ErrForbidden):
+ return i18n.T_(c, "error.err_forbidden")
case errors.Is(err, ErrInvalidBody):
return i18n.T_(c, "error.err_invalid_body")
case errors.Is(err, ErrInvalidCredentials):
@@ -162,6 +168,9 @@ func GetErrorCode(c fiber.Ctx, err error) string {
case errors.Is(err, ErrProductOrItsVariationDoesNotExist):
return i18n.T_(c, "error.product_or_its_variation_does_not_exist")
+ case errors.Is(err, ErrJSONBody):
+ return i18n.T_(c, "error.err_json_body")
+
default:
return i18n.T_(c, "error.err_internal_server_error")
}
@@ -170,6 +179,8 @@ func GetErrorCode(c fiber.Ctx, err error) string {
// GetErrorStatus returns the HTTP status code for the given error
func GetErrorStatus(err error) int {
switch {
+ case errors.Is(err, ErrForbidden):
+ return fiber.StatusForbidden
case errors.Is(err, ErrInvalidCredentials),
errors.Is(err, ErrNotAuthenticated),
errors.Is(err, ErrInvalidToken),
@@ -203,7 +214,8 @@ func GetErrorStatus(err error) int {
errors.Is(err, ErrRootNeverReached),
errors.Is(err, ErrMaxAmtOfCartsReached),
errors.Is(err, ErrUserHasNoSuchCart),
- errors.Is(err, ErrProductOrItsVariationDoesNotExist):
+ errors.Is(err, ErrProductOrItsVariationDoesNotExist),
+ errors.Is(err, ErrJSONBody):
return fiber.StatusBadRequest
case errors.Is(err, ErrEmailExists):
return fiber.StatusConflict
diff --git a/bo/components.d.ts b/bo/components.d.ts
index 2581158..f0f9cc4 100644
--- a/bo/components.d.ts
+++ b/bo/components.d.ts
@@ -33,6 +33,7 @@ declare module 'vue' {
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']
+ 'ProductDetailView copy': typeof import('./src/components/admin/ProductDetailView copy.vue')['default']
ProductVariants: typeof import('./src/components/customer/components/ProductVariants.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
diff --git a/bo/src/components/admin/ProductDetailView copy.vue b/bo/src/components/admin/ProductDetailView copy.vue
new file mode 100644
index 0000000..aacca62
--- /dev/null
+++ b/bo/src/components/admin/ProductDetailView copy.vue
@@ -0,0 +1,526 @@
+
+
+ Back to products {{ productStore.error }}
+ {{ productStore.productDescription.name || 'Product Name' }}
+
+ {{ productStore.productDescription.available_now }}
+
+ {{ productStore.productDescription.delivery_in_stock || 'Delivery information' }}
+ Change Text Save the edited text Change Text Save the edited text
Back to products
-Change Text
+{{ productStore.error }}
- {{ productStore.productDescription.name || 'Product Name' }} -
- ++ {{ productStore.productDescription.name || 'Product Name' }} +
+Title:
+Short description:
+- {{ productStore.productDescription.available_now }} +
+ {{ productStore.productDescription.available_now || 'Available now' }}
+
{{ productStore.productDescription.delivery_in_stock || 'Delivery information' }}