1 Commits

Author SHA1 Message Date
12f6249721 fix: requests 2026-03-31 12:22:21 +02:00
29 changed files with 348 additions and 267 deletions

View File

@@ -1,7 +1,6 @@
package middleware
import (
"strconv"
"strings"
"git.ma-al.com/goc_daniel/b2b/app/config"
@@ -61,52 +60,9 @@ func AuthMiddleware() fiber.Handler {
})
}
// Create locale. LangID is overwritten by auth Token
var userLocale model.UserLocale
userLocale.OriginalUser = user
// Check if target user is present
targetUserIDAttribute := c.Query("target_user_id")
if targetUserIDAttribute == "" {
userLocale.User = user
c.Locals(constdata.USER_LOCALE, &userLocale)
return c.Next()
}
// We now populate the target user
if user.Role != model.RoleAdmin {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "admin access required",
})
}
targetUserID, err := strconv.Atoi(targetUserIDAttribute)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "invalid target user id attribute",
})
}
// to verify target user, we use the same functionality as for verifying original user
// Get target user from database
user, err = authService.GetUserByID(uint(targetUserID))
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "target user not found",
})
}
// Check if target user is active
if !user.IsActive {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "target user account is inactive",
})
}
userLocale.User = user
c.Locals(constdata.USER_LOCALE, &userLocale)
// Set user in context
c.Locals(constdata.USER_LOCALES_NAME, user.ToSession())
c.Locals(constdata.USER_LOCALES_ID, user.ID)
return c.Next()
}
@@ -139,6 +95,24 @@ func RequireAdmin() fiber.Handler {
}
}
// GetUserID extracts user ID from context
func GetUserID(c fiber.Ctx) uint {
userID, ok := c.Locals("userID").(uint)
if !ok {
return 0
}
return userID
}
// GetUser extracts user from context
func GetUser(c fiber.Ctx) *model.UserSession {
user, ok := c.Locals("user").(*model.UserSession)
if !ok {
return nil
}
return user
}
// GetConfig returns the app config
func GetConfig() *config.Config {
return config.Get()

View File

@@ -4,9 +4,7 @@ import (
"strconv"
"strings"
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/service/langsService"
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
"github.com/gofiber/fiber/v3"
)
@@ -24,8 +22,12 @@ func LanguageMiddleware() fiber.Handler {
if id, err := strconv.ParseUint(langIDStr, 10, 32); err == nil {
langID = uint(id)
if langID > 0 {
c.Locals(constdata.USER_LOCALE, returnNewLocale(langID))
return c.Next()
lang, err := langService.GetLanguageById(langID)
if err == nil {
c.Locals("langID", langID)
c.Locals("lang", lang)
return c.Next()
}
}
}
}
@@ -36,8 +38,12 @@ func LanguageMiddleware() fiber.Handler {
if id, err := strconv.ParseUint(cookieLang, 10, 32); err == nil {
langID = uint(id)
if langID > 0 {
c.Locals(constdata.USER_LOCALE, returnNewLocale(langID))
return c.Next()
lang, err := langService.GetLanguageById(langID)
if err == nil {
c.Locals("langID", langID)
c.Locals("lang", lang)
return c.Next()
}
}
}
}
@@ -51,7 +57,8 @@ func LanguageMiddleware() fiber.Handler {
lang, err := langService.GetLanguageByISOCode(isoCode)
if err == nil && lang != nil {
langID = uint(lang.ID)
c.Locals(constdata.USER_LOCALE, returnNewLocale(langID))
c.Locals("langID", langID)
c.Locals("lang", lang)
return c.Next()
}
}
@@ -61,7 +68,8 @@ func LanguageMiddleware() fiber.Handler {
defaultLang, err := langService.GetDefaultLanguage()
if err == nil && defaultLang != nil {
langID = uint(defaultLang.ID)
c.Locals(constdata.USER_LOCALE, returnNewLocale(langID))
c.Locals("langID", langID)
c.Locals("lang", defaultLang)
}
return c.Next()
@@ -96,9 +104,11 @@ func parseAcceptLanguage(header string) string {
return strings.ToLower(first)
}
func returnNewLocale(lang_id uint) *model.UserLocale {
newLocale := model.UserLocale{}
newLocale.OriginalUser = &model.Customer{}
newLocale.OriginalUser.LangID = lang_id
return &newLocale
// GetLanguageID extracts language ID from context
func GetLanguageID(c fiber.Ctx) uint {
langID, ok := c.Locals("langID").(uint)
if !ok {
return 0
}
return langID
}

View File

@@ -268,15 +268,15 @@ func (h *AuthHandler) RefreshToken(c fiber.Ctx) error {
// Me returns the current user info
func (h *AuthHandler) Me(c fiber.Ctx) error {
userLocale := c.Locals(constdata.USER_LOCALE).(*model.UserLocale)
if userLocale.OriginalUser == nil {
user := c.Locals("user")
if user == nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": responseErrors.GetErrorCode(c, responseErrors.ErrNotAuthenticated),
})
}
return c.JSON(fiber.Map{
"user": *userLocale.OriginalUser,
"user": user,
})
}
@@ -351,12 +351,21 @@ func (h *AuthHandler) CompleteRegistration(c fiber.Ctx) error {
// Updates JWT Tokens. Requires authentication and updates access token only
func (h *AuthHandler) UpdateJWTToken(c fiber.Ctx) error {
userLocale, ok := c.Locals(constdata.USER_LOCALE).(*model.UserLocale)
userLocals, ok := c.Locals(constdata.USER_LOCALES_NAME).(*model.UserSession)
if !ok {
return c.Status(fiber.StatusUnauthorized).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrNotAuthenticated)))
}
user := model.Customer{
ID: userLocals.UserID,
Email: userLocals.Email,
Role: userLocals.Role,
LangID: userLocals.LangID,
CountryID: userLocals.CountryID,
IsActive: userLocals.IsActive,
}
// Parse language and country_id from query params
langIDStr := c.Query("lang_id")
@@ -366,7 +375,7 @@ func (h *AuthHandler) UpdateJWTToken(c fiber.Ctx) error {
return c.Status(fiber.StatusBadRequest).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadLangID)))
}
userLocale.OriginalUser.LangID = uint(parsedID)
user.LangID = uint(parsedID)
}
countryIDStr := c.Query("country_id")
@@ -377,10 +386,10 @@ func (h *AuthHandler) UpdateJWTToken(c fiber.Ctx) error {
return c.Status(fiber.StatusBadRequest).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadCountryID)))
}
userLocale.OriginalUser.CountryID = uint(parsedID)
user.CountryID = uint(parsedID)
}
newAccessToken, err := h.authService.UpdateJWTToken(userLocale.OriginalUser)
newAccessToken, err := h.authService.UpdateJWTToken(&user)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{

View File

@@ -3,7 +3,6 @@ package public
import (
"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"
"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"
@@ -31,7 +30,7 @@ func RoutingHandlerRoutes(r fiber.Router) fiber.Router {
}
func (h *RoutingHandler) GetRouting(c fiber.Ctx) error {
lang_id, ok := localeExtractor.GetLangID(c)
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)))

View File

@@ -5,7 +5,6 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/service/cartsService"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
"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"
@@ -38,7 +37,7 @@ func CartsHandlerRoutes(r fiber.Router) fiber.Router {
}
func (h *CartsHandler) AddNewCart(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
@@ -54,7 +53,7 @@ func (h *CartsHandler) AddNewCart(c fiber.Ctx) error {
}
func (h *CartsHandler) ChangeCartName(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
@@ -79,7 +78,7 @@ func (h *CartsHandler) ChangeCartName(c fiber.Ctx) error {
}
func (h *CartsHandler) RetrieveCartsInfo(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
@@ -95,7 +94,7 @@ func (h *CartsHandler) RetrieveCartsInfo(c fiber.Ctx) error {
}
func (h *CartsHandler) RetrieveCart(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
@@ -118,7 +117,7 @@ func (h *CartsHandler) RetrieveCart(c fiber.Ctx) error {
}
func (h *CartsHandler) AddProduct(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))

View File

@@ -5,7 +5,6 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/service/listService"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/query_params"
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
@@ -44,19 +43,19 @@ func (h *ListHandler) ListProducts(c fiber.Ctx) error {
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
id_lang, ok := localeExtractor.GetLangID(c)
id_lang, 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)))
}
list, err := h.listService.ListProducts(id_lang, paging, filters)
listing, err := h.listService.ListProducts(id_lang, paging, filters)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(&list.Items, int(list.Count), i18n.T_(c, response.Message_OK)))
return c.JSON(response.Make(&listing.Items, int(listing.Count), i18n.T_(c, response.Message_OK)))
}
var columnMappingListProducts map[string]string = map[string]string{
@@ -75,19 +74,19 @@ func (h *ListHandler) ListUsers(c fiber.Ctx) error {
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
id_lang, ok := localeExtractor.GetLangID(c)
id_lang, 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)))
}
list, err := h.listService.ListUsers(id_lang, paging, filters)
listing, err := h.listService.ListUsers(id_lang, paging, filters)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(&list.Items, int(list.Count), i18n.T_(c, response.Message_OK)))
return c.JSON(response.Make(&listing.Items, int(listing.Count), i18n.T_(c, response.Message_OK)))
}
var columnMappingListUsers map[string]string = map[string]string{

View File

@@ -5,7 +5,6 @@ import (
"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"
"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"
@@ -34,7 +33,7 @@ func MenuHandlerRoutes(r fiber.Router) fiber.Router {
}
func (h *MenuHandler) GetCategoryTree(c fiber.Ctx) error {
lang_id, ok := localeExtractor.GetLangID(c)
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)))
@@ -57,7 +56,7 @@ func (h *MenuHandler) GetCategoryTree(c fiber.Ctx) error {
}
func (h *MenuHandler) GetBreadcrumb(c fiber.Ctx) error {
lang_id, ok := localeExtractor.GetLangID(c)
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)))
@@ -87,7 +86,7 @@ func (h *MenuHandler) GetBreadcrumb(c fiber.Ctx) error {
}
func (h *MenuHandler) GetTopMenu(c fiber.Ctx) error {
lang_id, ok := localeExtractor.GetLangID(c)
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)))

View File

@@ -6,7 +6,6 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/config"
"git.ma-al.com/goc_daniel/b2b/app/service/productTranslationService"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
"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"
@@ -42,7 +41,7 @@ func ProductTranslationHandlerRoutes(r fiber.Router) fiber.Router {
// GetProductDescription returns the product description for a given product ID
func (h *ProductTranslationHandler) GetProductDescription(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
@@ -73,7 +72,7 @@ func (h *ProductTranslationHandler) GetProductDescription(c fiber.Ctx) error {
// SaveProductDescription saves the description for a given product ID, in given language
func (h *ProductTranslationHandler) SaveProductDescription(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
@@ -110,7 +109,7 @@ func (h *ProductTranslationHandler) SaveProductDescription(c fiber.Ctx) error {
// TranslateProductDescription returns translated product description
func (h *ProductTranslationHandler) TranslateProductDescription(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))

View File

@@ -7,7 +7,6 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/service/meiliService"
searchservice "git.ma-al.com/goc_daniel/b2b/app/service/searchService"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
"git.ma-al.com/goc_daniel/b2b/app/utils/localeExtractor"
"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"
@@ -37,7 +36,7 @@ func MeiliSearchHandlerRoutes(r fiber.Router) fiber.Router {
}
func (h *MeiliSearchHandler) CreateIndex(c fiber.Ctx) error {
id_lang, ok := localeExtractor.GetLangID(c)
id_lang, 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)))
@@ -50,11 +49,12 @@ func (h *MeiliSearchHandler) CreateIndex(c fiber.Ctx) error {
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
nothing := ""
return c.JSON(response.Make(&nothing, 0, i18n.T_(c, response.Message_OK)))
}
func (h *MeiliSearchHandler) Search(c fiber.Ctx) error {
id_lang, ok := localeExtractor.GetLangID(c)
id_lang, 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)))
@@ -88,7 +88,7 @@ func (h *MeiliSearchHandler) Search(c fiber.Ctx) error {
}
func (h *MeiliSearchHandler) GetSettings(c fiber.Ctx) error {
id_lang, ok := localeExtractor.GetLangID(c)
id_lang, 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)))

View File

@@ -82,15 +82,6 @@ type UserSession struct {
IsActive bool `json:"is_active"`
}
type UserLocale struct {
// User is the Target user if present, otherwise same as Original.
// User ought to be used in applications
User *Customer
// Original user is the one associated with auth token
OriginalUser *Customer
// Importantly, lang_id used in application is stored as OriginalUser.LangID
}
// ToSession converts User to UserSession
func (u *Customer) ToSession() *UserSession {
return &UserSession{
@@ -107,7 +98,6 @@ func (u *Customer) ToSession() *UserSession {
type LoginRequest struct {
Email string `json:"email" form:"email"`
Password string `json:"password" form:"password"`
LangID *uint `json:"lang_id" form:"lang_id"`
}
// RegisterRequest represents the initial registration form data

View File

@@ -18,10 +18,9 @@ type ProductDescription struct {
AvailableLater string `gorm:"column:available_later;type:varchar(255)" json:"available_later" form:"available_later"`
DeliveryInStock string `gorm:"column:delivery_in_stock;type:varchar(255)" json:"delivery_in_stock" form:"delivery_in_stock"`
DeliveryOutStock string `gorm:"column:delivery_out_stock;type:varchar(255)" json:"delivery_out_stock" form:"delivery_out_stock"`
Usage string `gorm:"column:_usage_;type:text" json:"usage" form:"usage"`
Usage string `gorm:"column:usage;type:text" json:"usage" form:"usage"`
ImageLink string `gorm:"column:image_link" json:"image_link"`
ExistsInDatabase bool `gorm:"-" json:"exists_in_database"`
ExistsInDatabse bool `gorm:"-" json:"exists_in_database"`
}
type ProductRow struct {

View File

@@ -32,7 +32,7 @@ func (repo *ListRepo) ListProducts(id_lang uint, p find.Paging, filt *filters.Fi
ps.id_product AS product_id,
pl.name AS name,
pl.link_rewrite AS link_rewrite,
CONCAT(?, '/', ims.id_image, '-small_default/', pl.link_rewrite, '.webp') AS image_link,
CONCAT(?, ims.id_image, '-small_default/', pl.link_rewrite, '.webp') AS image_link,
cl.name AS category_name,
p.reference AS reference,
COALESCE(v.variants_number, 0) AS variants_number,

View File

@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"git.ma-al.com/goc_daniel/b2b/app/config"
"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/model/dbmodel"
@@ -37,36 +36,15 @@ func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productid
IDShop: int32(constdata.SHOP_ID),
IDLang: int32(productid_lang),
}).
Select(`
`+dbmodel.PsProductLangCols.IDProduct.TabCol()+` AS id_product,
`+dbmodel.PsProductLangCols.IDShop.TabCol()+` AS id_shop,
`+dbmodel.PsProductLangCols.IDLang.TabCol()+` AS id_lang,
`+dbmodel.PsProductLangCols.Description.TabCol()+` AS description,
`+dbmodel.PsProductLangCols.DescriptionShort.TabCol()+` AS description_short,
`+dbmodel.PsProductLangCols.LinkRewrite.TabCol()+` AS link_rewrite,
`+dbmodel.PsProductLangCols.MetaDescription.TabCol()+` AS meta_description,
`+dbmodel.PsProductLangCols.MetaKeywords.TabCol()+` AS meta_keywords,
`+dbmodel.PsProductLangCols.MetaTitle.TabCol()+` AS meta_title,
`+dbmodel.PsProductLangCols.Name.TabCol()+` AS name,
`+dbmodel.PsProductLangCols.AvailableNow.TabCol()+` AS available_now,
`+dbmodel.PsProductLangCols.AvailableLater.TabCol()+` AS available_later,
`+dbmodel.PsProductLangCols.DeliveryInStock.TabCol()+` AS delivery_in_stock,
`+dbmodel.PsProductLangCols.DeliveryOutStock.TabCol()+` AS delivery_out_stock,
`+dbmodel.PsProductLangCols.Usage.TabCol()+` AS _usage_,
CONCAT(?, '/', `+dbmodel.PsImageShopCols.IDImage.TabCol()+`, '-large_default/', `+dbmodel.PsProductLangCols.LinkRewrite.TabCol()+`, '.webp') AS image_link
`, config.Get().Image.ImagePrefix).
Joins("JOIN " + dbmodel.TableNamePsImageShop +
" ON " + dbmodel.PsImageShopCols.IDProduct.TabCol() + "=" + dbmodel.PsProductLangCols.IDProduct.TabCol() +
" AND " + dbmodel.PsImageShopCols.Cover.TabCol() + " = 1").
First(&ProductDescription).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
// handle "not found" case only
ProductDescription.ExistsInDatabase = false
ProductDescription.ExistsInDatabse = false
} else if err != nil {
return nil, fmt.Errorf("database error: %w", err)
} else {
ProductDescription.ExistsInDatabase = true
ProductDescription.ExistsInDatabse = true
}
return &ProductDescription, nil

View File

@@ -83,15 +83,6 @@ func (s *AuthService) Login(req *model.LoginRequest) (*model.AuthResponse, strin
// Update last login time
now := time.Now()
user.LastLoginAt = &now
if req.LangID != nil {
_, err := s.GetLangISOCode(*req.LangID)
if err != nil {
return nil, "", responseErrors.ErrBadLangID
}
user.LangID = *req.LangID
}
s.db.Save(&user)
// Generate access token (JWT)

View File

@@ -11,4 +11,5 @@ const CATEGORY_TREE_ROOT_ID = 2
const MAX_AMOUNT_OF_CARTS_PER_USER = 10
const DEFAULT_NEW_CART_NAME = "new cart"
const USER_LOCALE = "user"
const USER_LOCALES_NAME = "user"
const USER_LOCALES_ID = "userID"

View File

@@ -1,23 +0,0 @@
package localeExtractor
import (
"git.ma-al.com/goc_daniel/b2b/app/model"
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
"github.com/gofiber/fiber/v3"
)
func GetLangID(c fiber.Ctx) (uint, bool) {
user_locale, ok := c.Locals(constdata.USER_LOCALE).(*model.UserLocale)
if !ok || user_locale.OriginalUser == nil {
return 0, false
}
return user_locale.OriginalUser.LangID, true
}
func GetUserID(c fiber.Ctx) (uint, bool) {
user_locale, ok := c.Locals(constdata.USER_LOCALE).(*model.UserLocale)
if !ok || user_locale.User == nil {
return 0, false
}
return user_locale.User.ID, true
}

View File

@@ -1,11 +1,70 @@
<template>
<suspense>
<component :is="Default || 'div'">
<div class="flex gap-10">
<!-- <div class="w-64 h-128">
<CategoryMenu />
<div class="w-full flex flex-col items-center gap-4">
<UTable :data="productsList" :columns="columns" class="flex-1 w-full" />
<UPagination v-model:page="page" :total="total" :items-per-page="perPage" />
</div> -->
<CategoryMenuListing />
<UTable :data="productsList" :columns="columns" class="flex-1">
<template #expanded="{ row }">
<UTable :data="productsList.slice(0, 3)" :columns="columnsChild" :ui="{
thead: 'hidden'
}" />
</template>
</UTable>
<div class="container mx-auto mt-20">
<h1 class="text-2xl font-bold mb-6 text-gray-900 dark:text-white">Products</h1>
<div v-if="loading" class="text-center py-8">
<span class="text-gray-600 dark:text-gray-400">Loading products...</span>
</div>
<div v-else-if="error" class="mb-4 p-3 bg-red-100 text-red-700 rounded">
{{ error }}
</div>
<div v-else class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-50 dark:bg-gray-800">
<tr>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Image</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Product Code</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Name</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Link</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700">
<tr v-for="product in productsList" :key="product.product_id"
class="hover:bg-gray-50 dark:hover:bg-gray-800"
@click="goToProduct(product.product_id)">
<td class="px-6 py-4 whitespace-nowrap">
<img :src="product.image_link" alt="product image"
class="w-16 h-16 object-cover rounded" />
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">{{
product.reference }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">{{
product.name }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-blue-600 dark:text-blue-400">
{{ product.link_rewrite }}
</td>
</tr>
</tbody>
</table>
<div class="flex justify-center items-center py-8">
<UPagination v-model:page="page" :total="total" :page-size="perPage" />
</div>
<div v-if="productsList.length === 0" class="text-center py-8 text-gray-500 dark:text-gray-400">
No products found
</div>
</div>
</div>
</component>
@@ -18,20 +77,26 @@ import { useFetchJson } from '@/composable/useFetchJson'
import Default from '@/layouts/default.vue'
import { useRoute, useRouter } from 'vue-router'
import type { TableColumn } from '@nuxt/ui'
import CategoryMenu from '../inner/categoryMenu.vue'
import type { Product } from '@/types/product'
import CategoryMenuListing from '../inner/categoryMenuListing.vue'
interface Product {
reference: number
product_id: number
name: string
image_link: string
link_rewrite: string
}
const router = useRouter()
const route = useRoute()
const perPage = ref(15)
const page = computed({
get: () => Number(route.query.p) || 1,
get: () => Number(route.query.page) || 1,
set: (val: number) => {
router.push({
query: {
...route.query,
p: val
page: val
}
})
}
@@ -67,6 +132,7 @@ const sortField = computed({
}
})
const perPage = ref(15)
const total = ref(0)
interface ApiResponse {
@@ -127,25 +193,17 @@ async function fetchProductList() {
error.value = null
const params = new URLSearchParams()
Object.entries(route.query).forEach(([key, value]) => {
if (value === undefined || value === null) return
if (Array.isArray(value)) {
value.forEach(v => params.append(key, String(v)))
} else {
params.append(key, String(value))
}
if (value) params.append(key, String(value))
})
if (route.params.category_id)
params.append('category_id', String(route.params.category_id))
const url = `/api/v1/restricted/list/list-products?${params}`
const url = `/api/v1/restricted/list/list-products?elems=${perPage.value}&${params.toString()}`
try {
const response = await useFetchJson<ApiResponse>(url)
productsList.value = response.items || []
total.value = Number(response.count) || 0
total.value = response.count || 0
} catch (e: unknown) {
error.value = e instanceof Error ? e.message : 'Failed to load products'
} finally {
@@ -155,11 +213,16 @@ async function fetchProductList() {
function goToProduct(productId: number) {
router.push({
name: 'customer-product-details',
params: { product_id: productId }
name: 'product-detail',
params: { id: productId }
})
}
const selectedCount = ref({
product_id: null,
count: 0
})
function getIcon(name: string) {
if (sortField.value[0] === name) {
if (sortField.value[1] === 'asc') return 'i-lucide-arrow-up-narrow-wide'
@@ -173,7 +236,25 @@ const UInput = resolveComponent('UInput')
const UButton = resolveComponent('UButton')
const UIcon = resolveComponent('UIcon')
const columns: TableColumn<Product>[] = [
const columns: TableColumn<Payment>[] = [
{
id: 'expand',
cell: ({ row }) =>
h(UButton, {
color: 'neutral',
variant: 'ghost',
icon: 'i-lucide-chevron-down',
square: true,
'aria-label': 'Expand',
ui: {
leadingIcon: [
'transition-transform',
row.getIsExpanded() ? 'duration-200 rotate-180' : ''
]
},
onClick: () => row.toggleExpanded()
})
},
{
accessorKey: 'product_id',
header: ({ column }) => {
@@ -264,17 +345,108 @@ const columns: TableColumn<Product>[] = [
},
cell: ({ row }) => row.getValue('quantity') as number
},
{
accessorKey: 'count',
header: 'Count',
cell: ({ row }) => {
return h(UInputNumber, {
modelValue: selectedCount.value.product_id === row.original.product_id ? selectedCount.value.count : 0,
'onUpdate:modelValue': (val: number) => {
if (val)
selectedCount.value = {
product_id: row.original.product_id,
count: val
}
else {
selectedCount.value = {
product_id: null,
count: 0
}
}
},
min: 0,
max: row.original.quantity
})
}
},
{
accessorKey: 'count',
header: '',
cell: ({ row }) => {
return h(UButton, {
onClick: () => {
goToProduct(row.original.product_id)
console.log('Clicked', row.original)
},
color: 'primary',
color: selectedCount.value.product_id !== row.original.product_id ? 'info' : 'primary',
disabled: selectedCount.value.product_id !== row.original.product_id,
variant: 'solid'
}, () => 'Show product')
}, 'Add to cart')
},
}
]
const columnsChild: TableColumn<Payment>[] = [
{
accessorKey: 'product_id',
header: '',
cell: ({ row }) => `#${row.getValue('product_id') as number}`
},
{
accessorKey: 'image_link',
header: '',
cell: ({ row }) => {
return h('img', {
src: row.getValue('image_link') as string,
style: 'width:40px;height:40px;object-fit:cover;'
})
}
},
{
accessorKey: 'name',
header: '',
cell: ({ row }) => row.getValue('name') as string
},
{
accessorKey: 'quantity',
header: '',
cell: ({ row }) => row.getValue('quantity') as number
},
{
accessorKey: 'count',
header: '',
cell: ({ row }) => {
return h(UInputNumber, {
modelValue: selectedCount.value.product_id === row.original.product_id ? selectedCount.value.count : 0,
'onUpdate:modelValue': (val: number) => {
if (val)
selectedCount.value = {
product_id: row.original.product_id,
count: val
}
else {
selectedCount.value = {
product_id: null,
count: 0
}
}
},
min: 0,
max: row.original.quantity
})
}
},
{
accessorKey: 'count',
header: '',
cell: ({ row }) => {
return h(UButton, {
onClick: () => {
console.log('Clicked', row.original)
},
color: selectedCount.value.product_id !== row.original.product_id ? 'info' : 'primary',
disabled: selectedCount.value.product_id !== row.original.product_id,
variant: 'solid'
}, 'Add to cart')
},
}
]

View File

@@ -19,7 +19,7 @@
</div>
<div v-else class="overflow-x-auto">
<div class="flex gap-2">
<CategoryMenu />
<CategoryMenuListing />
<UTable :data="productsList" :columns="columns" class="flex-1">
<template #expanded="{ row }">
<UTable :data="productsList.slice(0, 3)" :columns="columnsChild" :ui="{
@@ -46,7 +46,7 @@ import { useFetchJson } from '@/composable/useFetchJson'
import Default from '@/layouts/default.vue'
import { useRoute, useRouter } from 'vue-router'
import type { TableColumn } from '@nuxt/ui'
import CategoryMenu from '../inner/categoryMenu.vue'
import CategoryMenuListing from '../inner/categoryMenuListing.vue'
interface Product {
reference: number

View File

@@ -1,57 +1,20 @@
<template>
<UNavigationMenu orientation="vertical" type="single" :items="items" class="data-[orientation=vertical]:w-72" />
</template>
<script setup lang="ts">
import { getMenu } from '@/router/menu'
import type { NavigationMenuItem } from '@nuxt/ui';
import { ref } from 'vue';
import { useRoute } from 'vue-router';
let menu = await getMenu() as NavigationMenuItem[]
const openAll = ref(false)
const route = useRoute()
let categoryId = ref(route.params.category_id)
function findPath(tree: NavigationMenuItem[], id: number, path: Array<number> = []): Array<number> | null {
for (let item of tree) {
let newPath: Array<number> = [...path, item.category_id]
if (item.category_id === id) {
return newPath
}
if (item.children) {
const result: Array<number> | null = findPath(item.children, id, newPath)
if (result) {
return result
}
}
}
return null
}
let path = findPath(menu, Number(categoryId.value))
function adaptMenu(menu: NavigationMenuItem[]) {
for (const item of menu) {
if (item.children && item.children.length > 0) {
item.open = path && path.includes(item.category_id) ? true : openAll.value
console.log(item);
adaptMenu(item.children);
item.children.unshift({
label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: {
name: 'customer-products-category', params: {
category_id: item.params.category_id,
link_rewrite: item.params.link_rewrite
}
}
})
item.open = openAll.value
item.children.unshift({ label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: { name: 'category', params: item.params } })
} else {
item.to = {
name: 'customer-products-category', params: {
category_id: item.params.category_id,
link_rewrite: item.params.link_rewrite
}
};
item.to = { name: 'category', params: item.params };
item.icon = 'i-lucide-file-text'
}
}
@@ -59,6 +22,7 @@ function adaptMenu(menu: NavigationMenuItem[]) {
}
menu = adaptMenu(menu)
const items = ref<NavigationMenuItem[][]>([
[
...menu as NavigationMenuItem[]
@@ -66,3 +30,7 @@ const items = ref<NavigationMenuItem[][]>([
])
</script>
<template>
<UNavigationMenu orientation="vertical" type="single" :items="items" class="p-4" />
</template>

View File

@@ -0,0 +1,35 @@
<script setup lang="ts">
import { getMenu } from '@/router/menu'
import type { NavigationMenuItem } from '@nuxt/ui';
import { ref } from 'vue';
let menu = await getMenu() as NavigationMenuItem[]
const openAll = ref(false)
function adaptMenu(menu: NavigationMenuItem[]) {
for (const item of menu) {
if (item.children && item.children.length > 0) {
adaptMenu(item.children);
item.open = openAll.value
item.children.unshift({ label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: { name: 'category', params: item.params } })
} else {
item.to = { name: 'category', params: item.params };
item.icon = 'i-lucide-file-text'
}
}
return menu;
}
menu = adaptMenu(menu)
const items = ref<NavigationMenuItem[][]>([
[
...menu as NavigationMenuItem[]
],
])
</script>
<template>
<UNavigationMenu orientation="vertical" type="single" :items="items" class="p-4" />
</template>

View File

@@ -59,6 +59,7 @@ async function setRoutes() {
const componentName = item.component
const [, folder] = componentName.split('/')
const componentPath = `/src${componentName}`
console.log(componentPath);
let modules =

View File

@@ -1,15 +1,8 @@
import { useFetchJson } from "@/composable/useFetchJson";
import type { MenuItem, Route } from "@/types/menu";
import { ref } from "vue";
import { settings } from "./settings";
const categoryId = ref()
export const getMenu = async () => {
if(!categoryId.value){
categoryId.value = settings['app'].category_tree_root_id
}
const resp = await useFetchJson<MenuItem>(`/api/v1/restricted/menu/get-category-tree?root_category_id=${categoryId.value}`);
const resp = await useFetchJson<MenuItem>('/api/v1/restricted/menu/get-category-tree');
return resp.items.children
}

View File

@@ -13,24 +13,24 @@ const products = [
// type CategoryProducts = {}
export const useCategoryStore = defineStore('category', () => {
const idCategory = ref(0)
const id_category = ref(0)
const categoryProducts = ref(products)
function setCategoryID(id: number) {
idCategory.value = id
id_category.value = id
}
async function getCategoryProducts() {
return new Promise<typeof products>((resolve) => {
setTimeout(() => {
// console.log('Fetching products from category id: ', idCategory.value);
console.log('Fetching products from category id: ', id_category.value);
resolve(categoryProducts.value)
}, 2000 * Math.random())
})
}
return {
idCategory,
id_category,
getCategoryProducts,
setCategoryID
}

View File

@@ -36,6 +36,7 @@ export const useProductStore = defineStore('product', () => {
const response = await useFetchJson<ProductDescription>(
`/api/v1/restricted/product-translation/get-product-description?productID=${productID}&productLangID=${langId}`
)
console.log(response, 'dfsfsdf');
productDescription.value = response.items
} catch (e: unknown) {

View File

@@ -1,4 +1,4 @@
export interface ProductDescription {
export interface ProductDescription {
id?: number
name?: string
description: string
@@ -7,11 +7,3 @@ export interface ProductDescription {
available_now: string
usage: string
}
export interface Product {
reference: number
product_id: number
name: string
image_link: string
link_rewrite: string
}

View File

@@ -7,7 +7,6 @@ export interface Settings {
}
export interface App {
category_tree_root_id: number
name: string
environment: string
base_url: string

View File

@@ -5,13 +5,13 @@ info:
http:
method: GET
url: http://localhost:3000/api/v1/restricted/product-translation/get-product-description?productID=51&productLangID=1
url: http://localhost:3000/api/v1/restricted/product-translation/get-product-description?productID=51&productLangID=4
params:
- name: productID
value: "51"
type: query
- name: productLangID
value: "1"
value: "4"
type: query
auth: inherit

View File

@@ -5,7 +5,7 @@ info:
http:
method: GET
url: http://localhost:3000/api/v1/restricted/list/list-products?p=1&elems=10&target_user_id=2
url: http://localhost:3000/api/v1/restricted/list/list-products?p=1&elems=10
params:
- name: p
value: "1"
@@ -13,9 +13,6 @@ http:
- name: elems
value: "10"
type: query
- name: target_user_id
value: "2"
type: query
settings:
encodeUrl: true

View File

@@ -1 +0,0 @@
This is a test.