22 Commits

Author SHA1 Message Date
Daniel Goc
d73dad8975 merge and small bugfix 2026-04-16 15:02:38 +02:00
Daniel Goc
7995177fe1 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into expand_orders 2026-04-16 15:01:45 +02:00
70e0e23ace Merge pull request 'feat: implement logger' (#74) from logger into main
Reviewed-on: #74
Reviewed-by: goc_daniel <goc_daniel@ma-al.com>
2026-04-16 13:00:47 +00:00
9961d90fa7 feat: implement logger 2026-04-16 14:46:09 +02:00
Daniel Goc
f435a8839b expand orders 2026-04-16 11:47:55 +02:00
7bce04e05a Merge pull request 'is_oem' (#72) from is_oem into main
Reviewed-on: #72
Reviewed-by: Wiktor Dudzic <dudzic_wiktor@ma-al.com>
2026-04-15 11:32:43 +00:00
9a90de3f11 Merge pull request 'no-vat-customers' (#71) from no-vat-customers into main
Reviewed-on: #71
2026-04-15 11:32:13 +00:00
Daniel Goc
754bf2fe01 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into is_oem 2026-04-15 13:20:26 +02:00
Daniel Goc
2ca07f03ce expanded by is_oem 2026-04-15 13:19:28 +02:00
6efb39edf7 Merge branch 'no-vat-customers' of ssh://git.ma-al.com:8822/goc_daniel/b2b into no-vat-customers 2026-04-15 12:58:37 +02:00
e9af4bf311 feat: add no vat customers logic 2026-04-15 12:58:23 +02:00
cc570cc6a8 Merge branch 'main' into no-vat-customers 2026-04-15 10:57:16 +00:00
1bf706dcd0 feat: add no vat customers logic 2026-04-15 12:55:14 +02:00
84b4c70ffb Merge pull request 'bugfix' (#70) from countries_bugfix into main
Reviewed-on: #70
Reviewed-by: Wiktor Dudzic <dudzic_wiktor@ma-al.com>
2026-04-15 10:00:41 +00:00
66df535317 Merge pull request 'expand_carts' (#69) from expand_carts into main
Reviewed-on: #69
Reviewed-by: Wiktor Dudzic <dudzic_wiktor@ma-al.com>
2026-04-15 09:19:16 +00:00
e31ecda582 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into no-vat-customers 2026-04-15 11:14:40 +02:00
Daniel Goc
e0a86febc4 add missing permission 2026-04-14 15:52:45 +02:00
Daniel Goc
40154ec861 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into expand_carts 2026-04-14 15:43:44 +02:00
Daniel Goc
bb507036db change add-product endpoint + remove-product 2026-04-14 15:42:30 +02:00
8e063978a8 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into no-vat-customers 2026-04-14 13:40:37 +02:00
31a2744131 Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into no-vat-customers 2026-04-14 13:36:11 +02:00
f55d59a0fd feat: add no vat property to customers 2026-04-14 13:12:21 +02:00
50 changed files with 1116 additions and 194 deletions

3
.env
View File

@@ -64,3 +64,6 @@ IMAGE_PREFIX=https://www.naluconcept.com # remove prefix to serv them from same
CORS_ORGIN=https://www.naluconcept.com
DSN=root:Maal12345678@tcp(localhost:3306)/nalu
LOG_LEVEL=warn
LOG_COLORIZE=true

View File

@@ -4,8 +4,11 @@ import (
"log"
"os"
"git.ma-al.com/goc_daniel/b2b/app/config"
"git.ma-al.com/goc_daniel/b2b/app/delivery/web"
"git.ma-al.com/goc_daniel/b2b/app/service/langsService"
"git.ma-al.com/goc_daniel/b2b/app/utils/logger"
"git.ma-al.com/goc_daniel/b2b/app/utils/version"
"github.com/spf13/cobra"
)
@@ -21,6 +24,8 @@ var (
return
}
logger.Init("b2b", nil, config.Get().Log.LogLevel, config.Get().Log.LogColorize)
// Create and setup the server
server := web.New()

View File

@@ -28,6 +28,7 @@ type Config struct {
Cors CorsConfig
MeiliSearch MeiliSearchConfig
Storage StorageConfig
Log LogConfig
}
type I18n struct {
@@ -87,6 +88,11 @@ type AppConfig struct {
BaseURL string `env:"APP_BASE_URL,http://localhost:5173"`
}
type LogConfig struct {
LogLevel string `env:"LOG_LEVEL,warn"`
LogColorize bool `env:"LOG_COLORIZE,true"`
}
type EmailConfig struct {
SMTPHost string `env:"EMAIL_SMTP_HOST,localhost"`
SMTPPort int `env:"EMAIL_SMTP_PORT,587"`
@@ -209,6 +215,11 @@ func load() *Config {
if err != nil {
slog.Error("not possible to load env variables for storage : ", err.Error(), "")
}
err = loadEnv(&cfg.Log)
if err != nil {
slog.Error("not possible to load env variables for logger : ", err.Error(), "")
}
cfg.Storage.RootFolder = ResolveRelativePath(cfg.Storage.RootFolder)
return cfg

View File

@@ -1,7 +1,6 @@
package public
import (
"log"
"strconv"
"time"
@@ -11,6 +10,7 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/service/authService"
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/logger"
"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"
@@ -178,7 +178,13 @@ func (h *AuthHandler) ForgotPassword(c fiber.Ctx) error {
// Request password reset - always return success to prevent email enumeration
err := h.authService.RequestPasswordReset(req.Email)
if err != nil {
log.Printf("Password reset request error: %v", err)
logger.Warn("password reset request failed",
"handler", "AuthHandler.ForgotPassword",
"email", req.Email,
"error", err.Error(),
)
}
return c.JSON(fiber.Map{
@@ -307,7 +313,6 @@ func (h *AuthHandler) Register(c fiber.Ctx) error {
// Attempt registration
err := h.authService.Register(&req)
if err != nil {
log.Printf("Register error: %v", err)
return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
"error": responseErrors.GetErrorCode(c, err),
})
@@ -447,7 +452,6 @@ func (h *AuthHandler) GoogleCallback(c fiber.Ctx) error {
response, rawRefreshToken, err := h.authService.HandleGoogleCallback(code)
if err != nil {
log.Printf("Google OAuth callback error: %v", err)
return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
"error": responseErrors.GetErrorCode(c, err),
})

View File

@@ -6,6 +6,7 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/service/addressesService"
"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/logger"
"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"
@@ -45,6 +46,13 @@ func (h *AddressesHandler) GetTemplate(c fiber.Ctx) error {
template, err := h.addressesService.GetTemplate(uint(country_id))
if err != nil {
logger.Error("failed to get address template",
"handler", "AddressesHandler.GetTemplate",
"country_id", country_id,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -74,6 +82,13 @@ func (h *AddressesHandler) AddNewAddress(c fiber.Ctx) error {
err = h.addressesService.AddNewAddress(userID, address_info, uint(country_id))
if err != nil {
logger.Error("failed to add new address",
"handler", "AddressesHandler.AddNewAddress",
"user_id", userID,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -110,6 +125,14 @@ func (h *AddressesHandler) ModifyAddress(c fiber.Ctx) error {
err = h.addressesService.ModifyAddress(userID, uint(address_id), address_info, uint(country_id))
if err != nil {
logger.Error("failed to modify address",
"handler", "AddressesHandler.ModifyAddress",
"user_id", userID,
"address_id", address_id,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -126,6 +149,13 @@ func (h *AddressesHandler) RetrieveAddressesInfo(c fiber.Ctx) error {
addresses, err := h.addressesService.RetrieveAddresses(userID)
if err != nil {
logger.Error("failed to retrieve addresses",
"handler", "AddressesHandler.RetrieveAddressesInfo",
"user_id", userID,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -149,6 +179,14 @@ func (h *AddressesHandler) DeleteAddress(c fiber.Ctx) error {
err = h.addressesService.DeleteAddress(userID, uint(address_id))
if err != nil {
logger.Error("failed to delete address",
"handler", "AddressesHandler.DeleteAddress",
"user_id", userID,
"address_id", address_id,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}

View File

@@ -6,6 +6,7 @@ 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/logger"
"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"
@@ -29,10 +30,12 @@ func CartsHandlerRoutes(r fiber.Router) fiber.Router {
handler := NewCartsHandler()
r.Get("/add-new-cart", handler.AddNewCart)
r.Delete("/remove-cart", handler.RemoveCart)
r.Get("/change-cart-name", handler.ChangeCartName)
r.Get("/retrieve-carts-info", handler.RetrieveCartsInfo)
r.Get("/retrieve-cart", handler.RetrieveCart)
r.Get("/add-product-to-cart", handler.AddProduct)
r.Delete("/remove-product-from-cart", handler.RemoveProduct)
return r
}
@@ -44,8 +47,16 @@ func (h *CartsHandler) AddNewCart(c fiber.Ctx) error {
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
}
new_cart, err := h.cartsService.CreateNewCart(userID)
name := c.Query("name")
new_cart, err := h.cartsService.CreateNewCart(userID, name)
if err != nil {
logger.Error("failed to create cart",
"handler", "CartsHandler.AddNewCart",
"user_id", userID,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -53,6 +64,37 @@ func (h *CartsHandler) AddNewCart(c fiber.Ctx) error {
return c.JSON(response.Make(&new_cart, 0, i18n.T_(c, response.Message_OK)))
}
func (h *CartsHandler) RemoveCart(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
}
cart_id_attribute := c.Query("cart_id")
cart_id, err := strconv.Atoi(cart_id_attribute)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
}
err = h.cartsService.RemoveCart(userID, uint(cart_id))
if err != nil {
logger.Error("failed to remove cart",
"handler", "CartsHandler.RemoveCart",
"user_id", userID,
"cart_id", cart_id,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
}
func (h *CartsHandler) ChangeCartName(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
if !ok {
@@ -71,6 +113,14 @@ func (h *CartsHandler) ChangeCartName(c fiber.Ctx) error {
err = h.cartsService.UpdateCartName(userID, uint(cart_id), new_name)
if err != nil {
logger.Error("failed to update cart name",
"handler", "CartsHandler.ChangeCartName",
"user_id", userID,
"cart_id", cart_id,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -87,6 +137,13 @@ func (h *CartsHandler) RetrieveCartsInfo(c fiber.Ctx) error {
carts_info, err := h.cartsService.RetrieveCartsInfo(userID)
if err != nil {
logger.Error("failed to retrieve carts info",
"handler", "CartsHandler.RetrieveCartsInfo",
"user_id", userID,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -110,6 +167,14 @@ func (h *CartsHandler) RetrieveCart(c fiber.Ctx) error {
cart, err := h.cartsService.RetrieveCart(userID, uint(cart_id))
if err != nil {
logger.Error("failed to retrieve cart",
"handler", "CartsHandler.RetrieveCart",
"user_id", userID,
"cart_id", cart_id,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -117,6 +182,7 @@ func (h *CartsHandler) RetrieveCart(c fiber.Ctx) error {
return c.JSON(response.Make(cart, 0, i18n.T_(c, response.Message_OK)))
}
// adds or sets given amount of products to the cart
func (h *CartsHandler) AddProduct(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
if !ok {
@@ -159,8 +225,78 @@ func (h *CartsHandler) AddProduct(c fiber.Ctx) error {
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
}
err = h.cartsService.AddProduct(userID, uint(cart_id), uint(product_id), product_attribute_id, uint(amount))
set_amount_attribute := c.Query("set_amount")
set_amount, err := strconv.ParseBool(set_amount_attribute)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
}
err = h.cartsService.AddProduct(userID, uint(cart_id), uint(product_id), product_attribute_id, amount, set_amount)
if err != nil {
logger.Error("failed to add product to cart",
"handler", "CartsHandler.AddProduct",
"user_id", userID,
"cart_id", cart_id,
"product_id", product_id,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
}
// removes product from the cart.
func (h *CartsHandler) RemoveProduct(c fiber.Ctx) error {
userID, ok := localeExtractor.GetUserID(c)
if !ok {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
}
cart_id_attribute := c.Query("cart_id")
cart_id, err := strconv.Atoi(cart_id_attribute)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
}
product_id_attribute := c.Query("product_id")
product_id, err := strconv.Atoi(product_id_attribute)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
}
product_attribute_id_attribute := c.Query("product_attribute_id")
var product_attribute_id *uint
if product_attribute_id_attribute == "" {
product_attribute_id = nil
} else {
val, err := strconv.Atoi(product_attribute_id_attribute)
if err != nil {
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
}
uval := uint(val)
product_attribute_id = &uval
}
err = h.cartsService.RemoveProduct(userID, uint(cart_id), uint(product_id), product_attribute_id)
if err != nil {
logger.Error("failed to remove product from cart",
"handler", "CartsHandler.RemoveProduct",
"user_id", userID,
"cart_id", cart_id,
"product_id", product_id,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}

View File

@@ -9,6 +9,7 @@ import (
"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/logger"
"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"
@@ -46,6 +47,12 @@ func (h *CurrencyHandler) PostCurrencyRate(c fiber.Ctx) error {
err := h.CurrencyService.CreateCurrencyRate(&currencyRate)
if err != nil {
logger.Error("failed to create currency rate",
"handler", "CurrencyHandler.PostCurrencyRate",
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -63,6 +70,13 @@ func (h *CurrencyHandler) GetCurrencyRate(c fiber.Ctx) error {
currency, err := h.CurrencyService.GetCurrency(uint(id))
if err != nil {
logger.Error("failed to get currency",
"handler", "CurrencyHandler.GetCurrencyRate",
"currency_id", id,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}

View File

@@ -9,6 +9,7 @@ import (
"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/localeExtractor"
"git.ma-al.com/goc_daniel/b2b/app/utils/logger"
"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"
@@ -32,6 +33,7 @@ func CustomerHandlerRoutes(r fiber.Router) fiber.Router {
r.Get("", handler.customerData)
r.Get("/list", middleware.Require(perms.UserReadAny), handler.listCustomers)
r.Patch("/no-vat", middleware.Require(perms.UserWriteAny), handler.setCustomerNoVatStatus)
return r
}
@@ -63,6 +65,13 @@ func (h *customerHandler) customerData(fc fiber.Ctx) error {
customer, err := h.service.GetById(customerId)
if err != nil {
logger.Error("failed to get customer",
"handler", "customerHandler.customerData",
"customer_id", customerId,
"error", err.Error(),
)
return fc.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, err)))
}
@@ -87,6 +96,12 @@ func (h *customerHandler) listCustomers(fc fiber.Ctx) error {
customer, err := h.service.Find(user.LangID, p, filt, search)
if err != nil {
logger.Error("failed to list customers",
"handler", "customerHandler.listCustomers",
"error", err.Error(),
)
return fc.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, err)))
}
@@ -100,3 +115,35 @@ var columnMappingListUsers map[string]string = map[string]string{
"first_name": "users.first_name",
"last_name": "users.last_name",
}
func (h *customerHandler) setCustomerNoVatStatus(fc fiber.Ctx) error {
user, ok := localeExtractor.GetCustomer(fc)
if !ok || user == nil {
return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrInvalidBody)))
}
var req struct {
CustomerID uint `json:"customer_id"`
IsNoVat bool `json:"is_no_vat"`
}
if err := fc.Bind().Body(&req); err != nil {
return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrJSONBody)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrJSONBody)))
}
if err := h.service.SetCustomerNoVatStatus(req.CustomerID, req.IsNoVat); err != nil {
logger.Error("failed to set customer no vat status",
"handler", "customerHandler.setCustomerNoVatStatus",
"customer_id", req.CustomerID,
"error", err.Error(),
)
return fc.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, err)))
}
return fc.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(fc, response.Message_OK)))
}

View File

@@ -3,6 +3,7 @@ package restricted
import (
"git.ma-al.com/goc_daniel/b2b/app/service/localeSelectorService"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
"git.ma-al.com/goc_daniel/b2b/app/utils/logger"
"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,6 +35,12 @@ func LocaleSelectorHandlerRoutes(r fiber.Router) fiber.Router {
func (h *LocaleSelectorHandler) GetLanguages(c fiber.Ctx) error {
languages, err := h.localeSelectorService.GetLanguages()
if err != nil {
logger.Error("failed to get languages",
"handler", "LocaleSelectorHandler.GetLanguages",
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -44,6 +51,12 @@ func (h *LocaleSelectorHandler) GetLanguages(c fiber.Ctx) error {
func (h *LocaleSelectorHandler) GetCountries(c fiber.Ctx) error {
countries, err := h.localeSelectorService.GetCountriesAndCurrencies()
if err != nil {
logger.Error("failed to get countries",
"handler", "LocaleSelectorHandler.GetCountries",
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}

View File

@@ -6,6 +6,7 @@ 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/logger"
"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"
@@ -49,6 +50,12 @@ func (h *MenuHandler) GetCategoryTree(c fiber.Ctx) error {
category_tree, err := h.menuService.GetCategoryTree(uint(root_category_id), lang_id)
if err != nil {
logger.Error("failed to get category tree",
"handler", "MenuHandler.GetCategoryTree",
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -79,6 +86,12 @@ func (h *MenuHandler) GetBreadcrumb(c fiber.Ctx) error {
breadcrumb, err := h.menuService.GetBreadcrumb(uint(root_category_id), uint(category_id), lang_id)
if err != nil {
logger.Error("failed to get breadcrumb",
"handler", "MenuHandler.GetBreadcrumb",
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -94,6 +107,12 @@ func (h *MenuHandler) GetTopMenu(c fiber.Ctx) error {
}
menu, err := h.menuService.GetTopMenu(customer.LangID, customer.RoleID)
if err != nil {
logger.Error("failed to get top menu",
"handler", "MenuHandler.GetTopMenu",
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}

View File

@@ -7,6 +7,7 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/service/orderService"
"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/logger"
"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"
@@ -53,6 +54,12 @@ func (h *OrdersHandler) ListOrders(c fiber.Ctx) error {
list, err := h.ordersService.Find(user, paging, filters)
if err != nil {
logger.Error("failed to list orders",
"handler", "OrdersHandler.ListOrders",
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -66,6 +73,11 @@ var columnMappingListOrders map[string]string = map[string]string{
"name": "b2b_customer_orders.name",
"country_id": "b2b_customer_orders.country_id",
"status": "b2b_customer_orders.status",
"base_price": "b2b_customer_orders.base_price",
"tax_incl": "b2b_customer_orders.tax_incl",
"tax_excl": "b2b_customer_orders.tax_excl",
"created_at": "b2b_customer_orders.created_at",
"updated_at": "b2b_customer_orders.updated_at",
}
func (h *OrdersHandler) PlaceNewOrder(c fiber.Ctx) error {
@@ -99,6 +111,13 @@ func (h *OrdersHandler) PlaceNewOrder(c fiber.Ctx) error {
err = h.ordersService.PlaceNewOrder(userID, uint(cart_id), name, uint(country_id), address_info)
if err != nil {
logger.Error("failed to place order",
"handler", "OrdersHandler.PlaceNewOrder",
"user_id", userID,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -136,6 +155,13 @@ func (h *OrdersHandler) ChangeOrderAddress(c fiber.Ctx) error {
err = h.ordersService.ChangeOrderAddress(user, uint(order_id), uint(country_id), address_info)
if err != nil {
logger.Error("failed to change order address",
"handler", "OrdersHandler.ChangeOrderAddress",
"order_id", order_id,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -163,6 +189,13 @@ func (h *OrdersHandler) ChangeOrderStatus(c fiber.Ctx) error {
err = h.ordersService.ChangeOrderStatus(user, uint(order_id), status)
if err != nil {
logger.Error("failed to change order status",
"handler", "OrdersHandler.ChangeOrderStatus",
"order_id", order_id,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}

View File

@@ -9,6 +9,7 @@ import (
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/localeExtractor"
"git.ma-al.com/goc_daniel/b2b/app/utils/logger"
"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"
@@ -74,6 +75,15 @@ func (h *ProductsHandler) GetProductJson(c fiber.Ctx) error {
}
productJson, err := h.productService.Get(uint(p_id_product), customer.LangID, customer.ID, uint(b2b_id_country), uint(p_quantity))
if err != nil {
logger.Error("failed to get product",
"handler", "ProductsHandler.GetProductJson",
"product_id", p_id_product,
"lang_id", customer.LangID,
"customer_id", customer.ID,
"b2b_id_country", b2b_id_country,
"quantity", p_quantity,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -96,6 +106,13 @@ func (h *ProductsHandler) ListProducts(c fiber.Ctx) error {
list, err := h.productService.Find(customer.LangID, customer.ID, paging, filters, customer, constdata.DEFAULT_PRODUCT_QUANTITY, constdata.SHOP_ID)
if err != nil {
logger.Error("failed to list products",
"handler", "ProductsHandler.ListProducts",
"lang_id", customer.LangID,
"customer_id", customer.ID,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -112,6 +129,7 @@ var columnMappingListProducts map[string]string = map[string]string{
"quantity": "bp.quantity",
"is_favorite": "bp.is_favorite",
"is_new": "bp.is_new",
"is_oem": "bp.is_oem",
}
func (h *ProductsHandler) AddToFavorites(c fiber.Ctx) error {
@@ -131,6 +149,13 @@ func (h *ProductsHandler) AddToFavorites(c fiber.Ctx) error {
err = h.productService.AddToFavorites(userID, uint(productID))
if err != nil {
logger.Error("failed to add to favorites",
"handler", "ProductsHandler.AddToFavorites",
"user_id", userID,
"product_id", productID,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -155,6 +180,12 @@ func (h *ProductsHandler) RemoveFromFavorites(c fiber.Ctx) error {
err = h.productService.RemoveFromFavorites(userID, uint(productID))
if err != nil {
logger.Error("failed to remove from favorites",
"handler", "ProductsHandler.RemoveFromFavorites",
"user_id", userID,
"product_id", productID,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -179,6 +210,15 @@ func (h *ProductsHandler) ListProductVariants(c fiber.Ctx) error {
list, err := h.productService.GetProductAttributes(customer.LangID, uint(productID), constdata.SHOP_ID, customer.ID, customer.CountryID, constdata.DEFAULT_PRODUCT_QUANTITY)
if err != nil {
logger.Error("failed to list product variants",
"handler", "ProductsHandler.ListProductVariants",
"product_id", productID,
"customer_id", customer.ID,
"lang_id", customer.LangID,
"country_id", customer.CountryID,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}

View File

@@ -9,6 +9,7 @@ import (
"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/logger"
"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"
@@ -66,6 +67,13 @@ func (h *ProductTranslationHandler) GetProductDescription(c fiber.Ctx) error {
description, err := h.productTranslationService.GetProductDescription(userID, uint(productID), uint(productLangID))
if err != nil {
logger.Error("failed to get product description",
"handler", "ProductTranslationHandler.GetProductDescription",
"product_id", productID,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -103,6 +111,13 @@ func (h *ProductTranslationHandler) SaveProductDescription(c fiber.Ctx) error {
err = h.productTranslationService.SaveProductDescription(userID, uint(productID), uint(productLangID), updates)
if err != nil {
logger.Error("failed to save product description",
"handler", "ProductTranslationHandler.SaveProductDescription",
"product_id", productID,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -147,6 +162,13 @@ func (h *ProductTranslationHandler) TranslateProductDescription(c fiber.Ctx) err
description, err := h.productTranslationService.TranslateProductDescription(userID, uint(productID), uint(productFromLangID), uint(productToLangID), aiModel)
if err != nil {
logger.Error("failed to translate product description",
"handler", "ProductTranslationHandler.TranslateProductDescription",
"product_id", productID,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}

View File

@@ -2,7 +2,6 @@ package restricted
import (
"encoding/json"
"fmt"
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware"
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware/perms"
@@ -10,6 +9,7 @@ import (
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/logger"
"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"
@@ -47,7 +47,13 @@ func (h *MeiliSearchHandler) CreateIndex(c fiber.Ctx) error {
err := h.meiliService.CreateIndex(id_lang)
if err != nil {
fmt.Printf("CreateIndex error: %v\n", err)
logger.Error("failed to create search index",
"handler", "MeiliSearchHandler.CreateIndex",
"lang_id", id_lang,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -72,6 +78,13 @@ func (h *MeiliSearchHandler) Search(c fiber.Ctx) error {
result, err := h.searchService.Search(index, c.Body(), id_lang)
if err != nil {
logger.Error("failed to search",
"handler", "MeiliSearchHandler.Search",
"index", index,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -80,6 +93,13 @@ func (h *MeiliSearchHandler) Search(c fiber.Ctx) error {
if createErr := h.meiliService.CreateIndex(id_lang); createErr == nil {
result, err = h.searchService.Search(index, c.Body(), id_lang)
if err != nil {
logger.Error("failed to search after index creation",
"handler", "MeiliSearchHandler.Search",
"index", index,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -100,6 +120,13 @@ func (h *MeiliSearchHandler) GetSettings(c fiber.Ctx) error {
result, err := h.searchService.GetIndexSettings(index)
if err != nil {
logger.Error("failed to get index settings",
"handler", "MeiliSearchHandler.GetSettings",
"index", index,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}

View File

@@ -9,6 +9,7 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/service/specificPriceService"
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
"git.ma-al.com/goc_daniel/b2b/app/utils/logger"
"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"
@@ -51,6 +52,12 @@ func (h *SpecificPriceHandler) Create(c fiber.Ctx) error {
result, err := h.SpecificPriceService.Create(c.Context(), &pr)
if err != nil {
logger.Error("failed to create specific price",
"handler", "SpecificPriceHandler.Create",
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -74,6 +81,13 @@ func (h *SpecificPriceHandler) Update(c fiber.Ctx) error {
result, err := h.SpecificPriceService.Update(c.Context(), id, &pr)
if err != nil {
logger.Error("failed to update specific price",
"handler", "SpecificPriceHandler.Update",
"specific_price_id", id,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -84,6 +98,12 @@ func (h *SpecificPriceHandler) Update(c fiber.Ctx) error {
func (h *SpecificPriceHandler) List(c fiber.Ctx) error {
result, err := h.SpecificPriceService.List(c.Context())
if err != nil {
logger.Error("failed to list specific prices",
"handler", "SpecificPriceHandler.List",
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -101,6 +121,13 @@ func (h *SpecificPriceHandler) GetByID(c fiber.Ctx) error {
result, err := h.SpecificPriceService.GetByID(c.Context(), id)
if err != nil {
logger.Error("failed to get specific price",
"handler", "SpecificPriceHandler.GetByID",
"specific_price_id", id,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -118,6 +145,13 @@ func (h *SpecificPriceHandler) Activate(c fiber.Ctx) error {
err = h.SpecificPriceService.SetActive(c.Context(), id, true)
if err != nil {
logger.Error("failed to activate specific price",
"handler", "SpecificPriceHandler.Activate",
"specific_price_id", id,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -135,6 +169,13 @@ func (h *SpecificPriceHandler) Deactivate(c fiber.Ctx) error {
err = h.SpecificPriceService.SetActive(c.Context(), id, false)
if err != nil {
logger.Error("failed to deactivate specific price",
"handler", "SpecificPriceHandler.Deactivate",
"specific_price_id", id,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -152,6 +193,13 @@ func (h *SpecificPriceHandler) Delete(c fiber.Ctx) error {
err = h.SpecificPriceService.Delete(c.Context(), id)
if err != nil {
logger.Error("failed to delete specific price",
"handler", "SpecificPriceHandler.Delete",
"specific_price_id", id,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}

View File

@@ -9,6 +9,7 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/service/storageService"
"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/logger"
"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"
@@ -52,6 +53,12 @@ func (h *StorageHandler) ListContent(c fiber.Ctx) error {
entries_in_list, err := h.storageService.ListContent(abs_path)
if err != nil {
logger.Error("failed to list storage content",
"handler", "StorageHandler.ListContent",
"path", abs_path,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -68,6 +75,12 @@ func (h *StorageHandler) DownloadFile(c fiber.Ctx) error {
f, filename, filesize, err := h.storageService.DownloadFilePrep(abs_path)
if err != nil {
logger.Error("failed to prepare file download",
"handler", "StorageHandler.DownloadFile",
"path", abs_path,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}
@@ -87,6 +100,12 @@ func (h *StorageHandler) CreateNewWebdavToken(c fiber.Ctx) error {
new_token, err := h.storageService.NewWebdavToken(userID)
if err != nil {
logger.Error("failed to create webdav token",
"handler", "StorageHandler.CreateNewWebdavToken",
"user_id", userID,
"error", err.Error(),
)
return c.Status(responseErrors.GetErrorStatus(err)).
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
}

View File

@@ -35,6 +35,7 @@ type Customer struct {
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
IsNoVat bool `gorm:"default:false" json:"is_no_vat"`
}
func (u *Customer) HasPermission(permission perms.Permission) bool {

View File

@@ -1,5 +1,7 @@
package model
import "time"
type CustomerOrder struct {
OrderID uint `gorm:"column:order_id;primaryKey;autoIncrement" json:"order_id"`
UserID uint `gorm:"column:user_id;not null;index" json:"user_id"`
@@ -8,6 +10,11 @@ type CustomerOrder struct {
AddressString string `gorm:"column:address_string;not null" json:"address_string"`
AddressUnparsed *AddressUnparsed `gorm:"-" json:"address_unparsed"`
Status string `gorm:"column:status;size:50;not null" json:"status"`
BasePrice float64 `gorm:"column:base_price;type:decimal(10,2);not null" json:"base_price"`
TaxIncl float64 `gorm:"column:tax_incl;type:decimal(10,2);not null" json:"tax_incl"`
TaxExcl float64 `gorm:"column:tax_excl;type:decimal(10,2);not null" json:"tax_excl"`
CreatedAt time.Time `gorm:"column:created_at;not null" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;not null" json:"updated_at"`
Products []OrderProduct `gorm:"foreignKey:OrderID;references:OrderID" json:"products"`
}

View File

@@ -12,7 +12,8 @@ type ProductInList struct {
PriceTaxExcl float64 `gorm:"column:price_tax_excl" json:"price_tax_excl"`
PriceTaxIncl float64 `gorm:"column:price_tax_incl" json:"price_tax_incl"`
IsFavorite bool `gorm:"column:is_favorite" json:"is_favorite"`
IsNew uint `gorm:"column:is_new" json:"is_new"`
IsNew bool `gorm:"column:is_new" json:"is_new"`
IsOEM bool `gorm:"column:is_oem" json:"is_oem"`
}
type ProductFilters struct {

View File

@@ -1,21 +1,26 @@
package cartsRepo
import (
"errors"
"git.ma-al.com/goc_daniel/b2b/app/db"
"git.ma-al.com/goc_daniel/b2b/app/model"
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
"gorm.io/gorm"
)
type UICartsRepo interface {
CartsAmount(user_id uint) (uint, error)
CreateNewCart(user_id uint) (model.CustomerCart, error)
CreateNewCart(user_id uint, name string) (model.CustomerCart, error)
RemoveCart(user_id uint, cart_id uint) error
UserHasCart(user_id uint, cart_id uint) (bool, error)
UpdateCartName(user_id uint, cart_id uint, new_name string) error
RetrieveCartsInfo(user_id uint) ([]model.CustomerCart, error)
RetrieveCart(user_id uint, cart_id uint) (*model.CustomerCart, error)
CheckProductExists(product_id uint, product_attribute_id *uint) (bool, error)
AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount uint) error
AddProduct(cart_id uint, product_id uint, product_attribute_id *uint, amount uint, set_amount bool) error
RemoveProduct(cart_id uint, product_id uint, product_attribute_id *uint) error
}
type CartsRepo struct{}
@@ -37,10 +42,7 @@ func (repo *CartsRepo) CartsAmount(user_id uint) (uint, error) {
return amt, err
}
func (repo *CartsRepo) CreateNewCart(user_id uint) (model.CustomerCart, error) {
var name string
name = constdata.DEFAULT_NEW_CART_NAME
func (repo *CartsRepo) CreateNewCart(user_id uint, name string) (model.CustomerCart, error) {
cart := model.CustomerCart{
UserID: user_id,
Name: &name,
@@ -129,14 +131,61 @@ func (repo *CartsRepo) CheckProductExists(product_id uint, product_attribute_id
}
}
func (repo *CartsRepo) AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount uint) error {
product := model.CartProduct{
CartID: cart_id,
ProductID: product_id,
ProductAttributeID: product_attribute_id,
Amount: amount,
}
err := db.DB.Create(&product).Error
func (repo *CartsRepo) AddProduct(cart_id uint, product_id uint, product_attribute_id *uint, amount uint, set_amount bool) error {
var product model.CartProduct
return err
err := db.DB.
Where(&model.CartProduct{
CartID: cart_id,
ProductID: product_id,
ProductAttributeID: product_attribute_id,
}).
First(&product).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
if amount < 1 {
return responseErrors.ErrAmountMustBePositive
} else if amount > constdata.MAX_AMOUNT_OF_PRODUCT_IN_CART {
return responseErrors.ErrAmountMustBeReasonable
}
product = model.CartProduct{
CartID: cart_id,
ProductID: product_id,
ProductAttributeID: product_attribute_id,
Amount: amount,
}
return db.DB.Create(&product).Error
}
// Some other DB error
return err
}
// Product already exists in cart
if set_amount {
product.Amount = amount
} else {
product.Amount = product.Amount + amount
}
if product.Amount < 1 {
return responseErrors.ErrAmountMustBePositive
} else if product.Amount > constdata.MAX_AMOUNT_OF_PRODUCT_IN_CART {
return responseErrors.ErrAmountMustBeReasonable
}
return db.DB.Save(&product).Error
}
func (repo *CartsRepo) RemoveProduct(cart_id uint, product_id uint, product_attribute_id *uint) error {
return db.DB.
Where(&model.CartProduct{
CartID: cart_id,
ProductID: product_id,
ProductAttributeID: product_attribute_id,
}).
Delete(&model.CartProduct{}).Error
}

View File

@@ -17,6 +17,7 @@ type UICustomerRepo interface {
Find(langId uint, p find.Paging, filt *filters.FiltersList, search string) (*find.Found[model.UserInList], error)
Save(customer *model.Customer) error
Create(customer *model.Customer) error
SetCustomerNoVatStatus(customerID uint, isNoVat bool) error
}
type CustomerRepo struct{}
@@ -114,3 +115,7 @@ func (repo *CustomerRepo) Save(customer *model.Customer) error {
func (repo *CustomerRepo) Create(customer *model.Customer) error {
return db.DB.Create(customer).Error
}
func (repo *CustomerRepo) SetCustomerNoVatStatus(customerID uint, isNoVat bool) error {
return db.DB.Model(&model.Customer{}).Where("id = ?", customerID).Update("is_no_vat", isNoVat).Error
}

View File

@@ -1,6 +1,8 @@
package ordersRepo
import (
"time"
"git.ma-al.com/goc_daniel/b2b/app/db"
"git.ma-al.com/goc_daniel/b2b/app/model"
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
@@ -11,7 +13,7 @@ import (
type UIOrdersRepo interface {
UserHasOrder(user_id uint, order_id uint) (bool, error)
Find(user_id uint, p find.Paging, filt *filters.FiltersList) (*find.Found[model.CustomerOrder], error)
PlaceNewOrder(cart *model.CustomerCart, name string, country_id uint, address_info string) error
PlaceNewOrder(cart *model.CustomerCart, name string, country_id uint, address_info string, base_price float64, tax_incl float64, tax_excl float64) error
ChangeOrderAddress(order_id uint, country_id uint, address_info string) error
ChangeOrderStatus(order_id uint, status string) error
}
@@ -69,7 +71,7 @@ func (repo *OrdersRepo) Find(user_id uint, p find.Paging, filt *filters.FiltersL
}, nil
}
func (repo *OrdersRepo) PlaceNewOrder(cart *model.CustomerCart, name string, country_id uint, address_info string) error {
func (repo *OrdersRepo) PlaceNewOrder(cart *model.CustomerCart, name string, country_id uint, address_info string, base_price float64, tax_incl float64, tax_excl float64) error {
order := model.CustomerOrder{
UserID: cart.UserID,
Name: name,
@@ -87,6 +89,12 @@ func (repo *OrdersRepo) PlaceNewOrder(cart *model.CustomerCart, name string, cou
})
}
order.CreatedAt = time.Now()
order.UpdatedAt = time.Now()
order.BasePrice = base_price
order.TaxIncl = tax_incl
order.TaxExcl = tax_excl
return db.DB.Create(&order).Error
}
@@ -97,6 +105,7 @@ func (repo *OrdersRepo) ChangeOrderAddress(order_id uint, country_id uint, addre
Updates(map[string]interface{}{
"country_id": country_id,
"address_string": address_info,
"updated_at": time.Now(),
}).
Error
}
@@ -105,6 +114,9 @@ func (repo *OrdersRepo) ChangeOrderStatus(order_id uint, status string) error {
return db.DB.
Table("b2b_customer_orders").
Where("order_id = ?", order_id).
Update("status", status).
Updates(map[string]interface{}{
"status": status,
"updated_at": time.Now(),
}).
Error
}

View File

@@ -122,6 +122,19 @@ func (repo *ProductsRepo) Find(langID uint, userID uint, p find.Paging, filt *fi
Group("product_id"),
},
},
{
Name: "oems",
Subquery: exclause.Subquery{
DB: db.DB.
Table("b2b_oems").
Select(`
product_id AS product_id,
COUNT(*) > 0 AS is_customers_oem
`).
Where("user_id = ?", userID).
Group("product_id"),
},
},
{
Name: "new_product_days",
Subquery: exclause.Subquery{
@@ -150,6 +163,7 @@ func (repo *ProductsRepo) Find(langID uint, userID uint, p find.Paging, filt *fi
pl.name AS name,
ps.id_category_default AS category_id,
p.reference AS reference,
p.is_oem AS is_oem,
sa.quantity AS quantity,
COALESCE(f.is_favorite, 0) AS is_favorite,
CASE
@@ -166,7 +180,9 @@ func (repo *ProductsRepo) Find(langID uint, userID uint, p find.Paging, filt *fi
Joins("LEFT JOIN favorites f ON f.product_id = ps.id_product").
Joins("LEFT JOIN ps_stock_available sa ON sa.id_product = ps.id_product AND sa.id_product_attribute = 0").
Joins("LEFT JOIN new_product_days npd ON 1 = 1").
Joins("LEFT JOIN oems ON oems.product_id = ps.id_product").
Where("ps.active = ?", 1).
Where("(p.is_oem = 0 OR oems.is_customers_oem > 0)").
Group("ps.id_product"),
},
},
@@ -182,7 +198,8 @@ func (repo *ProductsRepo) Find(langID uint, userID uint, p find.Paging, filt *fi
COALESCE(v.variants_number, 0) AS variants_number,
bp.quantity AS quantity,
bp.is_favorite AS is_favorite,
bp.is_new AS is_new
bp.is_new AS is_new,
bp.is_oem AS is_oem
`, config.Get().Image.ImagePrefix).
Joins("JOIN ps_product_lang pl ON pl.id_product = bp.product_id AND pl.id_lang = ?", langID).
Joins("JOIN ps_image_shop ims ON ims.id_product = bp.product_id AND ims.cover = 1").

View File

@@ -15,6 +15,7 @@ import (
roleRepo "git.ma-al.com/goc_daniel/b2b/app/repos/rolesRepo"
"git.ma-al.com/goc_daniel/b2b/app/service/emailService"
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
"git.ma-al.com/goc_daniel/b2b/app/utils/logger"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
"github.com/dlclark/regexp2"
@@ -68,22 +69,47 @@ func (s *AuthService) Login(req *model.LoginRequest) (*model.AuthResponse, strin
// Find user by email
if err := s.db.Preload("Role.Permissions").Where("email = ?", req.Email).First(&user).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
logger.Info("login failed - invalid credentials",
"service", "AuthService.Login",
"email", req.Email,
"reason", "user not found",
)
return nil, "", responseErrors.ErrInvalidCredentials
}
logger.Error("login failed - database error",
"service", "AuthService.Login",
"email", req.Email,
"error", err.Error(),
)
return nil, "", fmt.Errorf("database error: %w", err)
}
// Check if user is active
if !user.IsActive {
logger.Info("login failed - user inactive",
"service", "AuthService.Login",
"email", req.Email,
"reason", "user account is inactive",
)
return nil, "", responseErrors.ErrUserInactive
}
// Check if email is verified
if !user.EmailVerified {
logger.Info("login failed - email not verified",
"service", "AuthService.Login",
"email", req.Email,
"reason", "email not verified",
)
return nil, "", responseErrors.ErrEmailNotVerified
}
// Verify password
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
logger.Info("login failed - invalid credentials",
"service", "AuthService.Login",
"email", req.Email,
"reason", "wrong password",
)
return nil, "", responseErrors.ErrInvalidCredentials
}
@@ -94,22 +120,38 @@ func (s *AuthService) Login(req *model.LoginRequest) (*model.AuthResponse, strin
if req.LangID != nil {
_, err := s.GetLangISOCode(*req.LangID)
if err != nil {
logger.Warn("login failed - invalid language ID",
"service", "AuthService.Login",
"email", req.Email,
"reason", "invalid language ID",
)
return nil, "", responseErrors.ErrBadLangID
}
user.LangID = *req.LangID
}
user.Country = nil
s.db.Save(&user)
// Generate access token (JWT)
accessToken, err := s.generateAccessToken(&user)
if err != nil {
logger.Error("login failed - token generation error",
"service", "AuthService.Login",
"email", req.Email,
"error", err.Error(),
)
return nil, "", fmt.Errorf("failed to generate access token: %w", err)
}
// Generate opaque refresh token and store in DB
rawRefreshToken, err := s.createRefreshToken(user.ID)
if err != nil {
logger.Error("login failed - refresh token creation error",
"service", "AuthService.Login",
"email", req.Email,
"error", err.Error(),
)
return nil, "", fmt.Errorf("failed to create refresh token: %w", err)
}
@@ -170,6 +212,11 @@ func (s *AuthService) Register(req *model.RegisterRequest) error {
}
if err := s.db.Create(&user).Error; err != nil {
logger.Error("registration failed - database error",
"service", "AuthService.Register",
"email", req.Email,
"error", err.Error(),
)
return fmt.Errorf("failed to create user: %w", err)
}
@@ -181,8 +228,11 @@ func (s *AuthService) Register(req *model.RegisterRequest) error {
}
if err := s.email.SendVerificationEmail(user.Email, user.EmailVerificationToken, baseURL, lang); err != nil {
// Log error but don't fail registration - user can request resend
_ = err
logger.Warn("failed to send verification email",
"service", "AuthService.Register",
"email", req.Email,
"error", err.Error(),
)
}
return nil
@@ -210,6 +260,7 @@ func (s *AuthService) CompleteRegistration(req *model.CompleteRegistrationReques
user.EmailVerificationToken = ""
user.EmailVerificationExpires = nil
user.Country = nil
if err := s.db.Save(&user).Error; err != nil {
return nil, "", fmt.Errorf("failed to update user: %w", err)
}
@@ -278,6 +329,7 @@ func (s *AuthService) RequestPasswordReset(emailAddr string) error {
user.PasswordResetToken = token
user.PasswordResetExpires = &expiresAt
user.LastPasswordResetRequest = &now
user.Country = nil
if err := s.db.Save(&user).Error; err != nil {
return fmt.Errorf("failed to save reset token: %w", err)
}
@@ -304,6 +356,10 @@ func (s *AuthService) ResetPassword(token, newPassword string) error {
if errors.Is(err, gorm.ErrRecordNotFound) {
return responseErrors.ErrInvalidResetToken
}
logger.Error("password reset failed - database error",
"service", "AuthService.ResetPassword",
"error", err.Error(),
)
return fmt.Errorf("database error: %w", err)
}
@@ -328,7 +384,12 @@ func (s *AuthService) ResetPassword(token, newPassword string) error {
user.PasswordResetToken = ""
user.PasswordResetExpires = nil
user.Country = nil
if err := s.db.Save(&user).Error; err != nil {
logger.Error("password reset failed - database error",
"service", "AuthService.ResetPassword",
"error", err.Error(),
)
return fmt.Errorf("failed to update password: %w", err)
}
@@ -539,6 +600,7 @@ func (s *AuthService) UpdateJWTToken(user *model.Customer) (string, error) {
}
// Save the updated user
user.Country = nil
if err := s.db.Save(user).Error; err != nil {
return "", fmt.Errorf("database error: %w", err)
}

View File

@@ -8,10 +8,12 @@ import (
"fmt"
"io"
"net/http"
"strings"
"time"
"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/utils/logger"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
"git.ma-al.com/goc_daniel/b2b/app/view"
"golang.org/x/oauth2"
@@ -77,12 +79,20 @@ func (s *AuthService) HandleGoogleCallback(code string) (*model.AuthResponse, st
// Find or create user
user, err := s.findOrCreateGoogleUser(userInfo)
if err != nil {
if strings.Contains(err.Error(), "database") {
logger.Error("google oauth callback failed - database error",
"service", "AuthService.HandleGoogleCallback",
"email", userInfo.Email,
"error", err.Error(),
)
}
return nil, "", err
}
// Update last login
now := time.Now()
user.LastLoginAt = &now
user.Country = nil
s.db.Save(user)
// Generate access token (JWT)

View File

@@ -4,6 +4,7 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/repos/cartsRepo"
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
"git.ma-al.com/goc_daniel/b2b/app/utils/logger"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
)
@@ -17,7 +18,7 @@ func New() *CartsService {
}
}
func (s *CartsService) CreateNewCart(user_id uint) (model.CustomerCart, error) {
func (s *CartsService) CreateNewCart(user_id uint, name string) (model.CustomerCart, error) {
var cart model.CustomerCart
customers_carts_amount, err := s.repo.CartsAmount(user_id)
@@ -28,8 +29,21 @@ func (s *CartsService) CreateNewCart(user_id uint) (model.CustomerCart, error) {
return cart, responseErrors.ErrMaxAmtOfCartsReached
}
if name == "" {
name = constdata.DEFAULT_NEW_CART_NAME
}
// create new cart for customer
cart, err = s.repo.CreateNewCart(user_id)
cart, err = s.repo.CreateNewCart(user_id, name)
if err != nil {
return cart, err
}
logger.Info("cart created",
"service", "cartsService",
"user_id", user_id,
"cart_id", cart.CartID,
)
return cart, nil
}
@@ -74,7 +88,7 @@ func (s *CartsService) RetrieveCart(user_id uint, cart_id uint) (*model.Customer
return s.repo.RetrieveCart(user_id, cart_id)
}
func (s *CartsService) AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount uint) error {
func (s *CartsService) AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount int, set_amount bool) error {
exists, err := s.repo.UserHasCart(user_id, cart_id)
if err != nil {
return err
@@ -91,5 +105,17 @@ func (s *CartsService) AddProduct(user_id uint, cart_id uint, product_id uint, p
return responseErrors.ErrProductOrItsVariationDoesNotExist
}
return s.repo.AddProduct(user_id, cart_id, product_id, product_attribute_id, amount)
return s.repo.AddProduct(cart_id, product_id, product_attribute_id, uint(amount), set_amount)
}
func (s *CartsService) RemoveProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint) error {
exists, err := s.repo.UserHasCart(user_id, cart_id)
if err != nil {
return err
}
if !exists {
return responseErrors.ErrUserHasNoSuchCart
}
return s.repo.RemoveProduct(cart_id, product_id, product_attribute_id)
}

View File

@@ -24,3 +24,7 @@ func (s *CustomerService) GetById(id uint) (*model.Customer, error) {
func (s *CustomerService) Find(langId uint, p find.Paging, filt *filters.FiltersList, search string) (*find.Found[model.UserInList], error) {
return s.repo.Find(langId, p, filt, search)
}
func (s *CustomerService) SetCustomerNoVatStatus(customerID uint, isNoVat bool) error {
return s.repo.SetCustomerNoVatStatus(customerID, isNoVat)
}

View File

@@ -231,21 +231,54 @@ func (s *MenuService) GetTopMenu(languageId uint, roleId uint) ([]*model.B2BTopM
func (s *MenuService) appendAdditional(all_categories *[]model.ScannedCategory, id_lang uint, iso_code string) {
for i := 0; i < len(*all_categories); i++ {
(*all_categories)[i].Filter = "category_id_in=" + strconv.Itoa(int((*all_categories)[i].CategoryID))
(*all_categories)[i].Filter = "category_id_eq=" + strconv.Itoa(int((*all_categories)[i].CategoryID))
}
var additional model.ScannedCategory
additional.CategoryID = 10001
additional.Name = "New Products"
additional.Active = 1
additional.Position = 10
additional.ParentID = 2
additional.IsRoot = 0
additional.LinkRewrite = i18n.T___(id_lang, "category.new_products")
additional.IsoCode = iso_code
// the new products category
var new_products_category model.ScannedCategory
new_products_category.CategoryID = constdata.ADDITIONAL_CATEGORIES_INDEX + 1
new_products_category.Name = "New Products"
new_products_category.Active = 1
new_products_category.Position = 10
new_products_category.ParentID = 2
new_products_category.IsRoot = 0
new_products_category.LinkRewrite = i18n.T___(id_lang, "category.new_products")
new_products_category.IsoCode = iso_code
additional.Visited = false
additional.Filter = "is_new_in=true"
new_products_category.Visited = false
new_products_category.Filter = "is_new_eq=true"
*all_categories = append(*all_categories, additional)
*all_categories = append(*all_categories, new_products_category)
// the oem products category
var oem_products_category model.ScannedCategory
oem_products_category.CategoryID = constdata.ADDITIONAL_CATEGORIES_INDEX + 2
oem_products_category.Name = "OEM Products"
oem_products_category.Active = 1
oem_products_category.Position = 11
oem_products_category.ParentID = 2
oem_products_category.IsRoot = 0
oem_products_category.LinkRewrite = i18n.T___(id_lang, "category.oem_products")
oem_products_category.IsoCode = iso_code
oem_products_category.Visited = false
oem_products_category.Filter = "is_oem_eq=true"
*all_categories = append(*all_categories, oem_products_category)
// the favorite products category
var favorite_products_category model.ScannedCategory
favorite_products_category.CategoryID = constdata.ADDITIONAL_CATEGORIES_INDEX + 3
favorite_products_category.Name = "Favourite Products" // British English version.
favorite_products_category.Active = 1
favorite_products_category.Position = 12
favorite_products_category.ParentID = 2
favorite_products_category.IsRoot = 0
favorite_products_category.LinkRewrite = i18n.T___(id_lang, "category.favorite_products")
favorite_products_category.IsoCode = iso_code
favorite_products_category.Visited = false
favorite_products_category.Filter = "is_favorite_eq=true"
*all_categories = append(*all_categories, favorite_products_category)
}

View File

@@ -1,15 +1,17 @@
package orderService
import (
"fmt"
"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/repos/cartsRepo"
"git.ma-al.com/goc_daniel/b2b/app/repos/ordersRepo"
"git.ma-al.com/goc_daniel/b2b/app/repos/productsRepo"
"git.ma-al.com/goc_daniel/b2b/app/service/addressesService"
"git.ma-al.com/goc_daniel/b2b/app/service/emailService"
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
"git.ma-al.com/goc_daniel/b2b/app/utils/logger"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
@@ -18,6 +20,7 @@ import (
type OrderService struct {
ordersRepo ordersRepo.UIOrdersRepo
cartsRepo cartsRepo.UICartsRepo
productsRepo productsRepo.UIProductsRepo
addressesService *addressesService.AddressesService
emailService *emailService.EmailService
}
@@ -26,6 +29,7 @@ func New() *OrderService {
return &OrderService{
ordersRepo: ordersRepo.New(),
cartsRepo: cartsRepo.New(),
productsRepo: productsRepo.New(),
addressesService: addressesService.New(),
emailService: emailService.NewEmailService(),
}
@@ -45,9 +49,12 @@ func (s *OrderService) Find(user *model.Customer, p find.Paging, filt *filters.F
for i := 0; i < len(list.Items); i++ {
address_unparsed, err := s.addressesService.ValidateAddressJson(list.Items[i].AddressString, list.Items[i].CountryID)
// log such errors
if err != nil {
fmt.Printf("err: %v\n", err)
logger.Warn("failed to validate address",
"service", "orderService",
"order_id", list.Items[i].OrderID,
"error", err.Error(),
)
}
list.Items[i].AddressUnparsed = &address_unparsed
@@ -82,8 +89,10 @@ func (s *OrderService) PlaceNewOrder(user_id uint, cart_id uint, name string, co
name = *cart.Name
}
base_price, tax_incl, tax_excl, err := s.getOrderTotalPrice(user_id, cart_id, country_id)
// all checks passed
err = s.ordersRepo.PlaceNewOrder(cart, name, country_id, address_info)
err = s.ordersRepo.PlaceNewOrder(cart, name, country_id, address_info, base_price, tax_incl, tax_excl)
if err != nil {
return err
}
@@ -92,16 +101,23 @@ func (s *OrderService) PlaceNewOrder(user_id uint, cart_id uint, name string, co
// if no error is returned, remove the cart. This should be smooth
err = s.cartsRepo.RemoveCart(user_id, cart_id)
if err != nil {
// Log error but don't fail placing order
_ = err
logger.Warn("failed to remove cart after order placement",
"service", "orderService",
"user_id", user_id,
"cart_id", cart_id,
"error", err.Error(),
)
}
// send email to admin
go func(user_id uint) {
err := s.emailService.SendNewOrderPlacedNotification(user_id)
if err != nil {
// Log error but don't fail placing order
_ = err
logger.Warn("failed to send new order notification",
"service", "orderService",
"user_id", user_id,
"error", err.Error(),
)
}
}(user_id)
@@ -143,3 +159,27 @@ func (s *OrderService) ChangeOrderStatus(user *model.Customer, order_id uint, st
return s.ordersRepo.ChangeOrderStatus(order_id, status)
}
func (s *OrderService) getOrderTotalPrice(user_id uint, cart_id uint, country_id uint) (float64, float64, float64, error) {
cart, err := s.cartsRepo.RetrieveCart(user_id, cart_id)
if err != nil {
return 0.0, 0.0, 0.0, err
}
base_price := 0.0
tax_incl := 0.0
tax_excl := 0.0
for _, product := range cart.Products {
prices, err := s.productsRepo.GetPrice(product.ProductID, product.ProductAttributeID, constdata.SHOP_ID, user_id, country_id, product.Amount)
if err != nil {
return 0.0, 0.0, 0.0, err
}
base_price += prices.Base
tax_incl += prices.FinalTaxIncl
tax_excl += prices.FinalTaxExcl
}
return base_price, tax_incl, tax_excl, nil
}

View File

@@ -9,12 +9,14 @@ const ADMIN_NOTIFICATION_LANGUAGE = 2
// CATEGORY_TREE_ROOT_ID corresponds to id_category in ps_category which has is_root_category=1
const CATEGORY_TREE_ROOT_ID = 2
const ADDITIONAL_CATEGORIES_INDEX = 10000
// since arrays can not be const
var CATEGORY_BLACKLIST = []uint{250}
const MAX_AMOUNT_OF_CARTS_PER_USER = 10
const DEFAULT_NEW_CART_NAME = "new cart"
const MAX_AMOUNT_OF_PRODUCT_IN_CART = 1024
const MAX_AMOUNT_OF_ADDRESSES_PER_USER = 10

151
app/utils/logger/logger.go Normal file
View File

@@ -0,0 +1,151 @@
package logger
import (
"context"
"fmt"
"io"
"log/slog"
"os"
"strings"
)
var L *slog.Logger
const (
reset = "\033[0m"
red = "\033[31m"
yellow = "\033[33m"
green = "\033[32m"
blue = "\033[36m"
gray = "\033[90m"
)
type consoleHandler struct {
w io.Writer
colorize bool
}
func (h *consoleHandler) Enabled(ctx context.Context, level slog.Level) bool {
return true
}
func (h *consoleHandler) Handle(ctx context.Context, r slog.Record) error {
level := r.Level.String()
color := reset
switch r.Level {
case slog.LevelError:
color = red
case slog.LevelWarn:
color = yellow
case slog.LevelInfo:
color = blue
case slog.LevelDebug:
color = gray
}
var msg string
if h.colorize {
msg = fmt.Sprintf("%s%s%s %s%s%s %s%s%s",
reset, r.Time.Format("15:04:05"), reset,
color, level, reset,
reset, r.Message, reset)
} else {
msg = fmt.Sprintf("%s %s %s", r.Time.Format("15:04:05"), level, r.Message)
}
var pairs []string
r.Attrs(func(attr slog.Attr) bool {
if h.colorize {
pairs = append(pairs, fmt.Sprintf("%s%s=%s%v%s", green, attr.Key, reset, attr.Value, reset))
} else {
pairs = append(pairs, fmt.Sprintf("%s=%v", attr.Key, attr.Value))
}
return true
})
if len(pairs) > 0 {
msg += " " + strings.Join(pairs, " ")
}
fmt.Fprintln(h.w, msg)
return nil
}
func (h *consoleHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &consoleHandler{w: h.w, colorize: h.colorize}
}
func (h *consoleHandler) WithGroup(name string) slog.Handler {
return &consoleHandler{w: h.w, colorize: h.colorize}
}
func Init(service string, output io.Writer, level string, colorize bool) {
if output == nil {
output = os.Stderr
}
var lvl slog.Level
switch level {
case "debug":
lvl = slog.LevelDebug
case "warn":
lvl = slog.LevelWarn
case "error":
lvl = slog.LevelError
default:
lvl = slog.LevelInfo
}
if colorize {
L = slog.New(&consoleHandler{w: output, colorize: true}).With("service", service, "level", lvl.String())
} else {
L = slog.New(slog.NewJSONHandler(output, &slog.HandlerOptions{
Level: lvl,
AddSource: false,
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey && groups == nil {
a.Key = "timestamp"
}
if a.Key == slog.LevelKey {
a.Key = "level"
}
if a.Key == slog.MessageKey {
a.Key = "message"
}
return a
},
})).With("service", service)
}
}
func Info(msg string, args ...any) {
if L != nil {
L.Info(msg, args...)
}
}
func Warn(msg string, args ...any) {
if L != nil {
L.Warn(msg, args...)
}
}
func Error(msg string, args ...any) {
if L != nil {
L.Error(msg, args...)
}
}
func Debug(msg string, args ...any) {
if L != nil {
L.Debug(msg, args...)
}
}
func With(args ...any) *slog.Logger {
if L == nil {
return nil
}
return L.With(args...)
}

View File

@@ -65,6 +65,8 @@ 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")
ErrAmountMustBePositive = errors.New("amount must be positive")
ErrAmountMustBeReasonable = errors.New("amount must be reasonable")
// Typed errors for orders handler
ErrEmptyCart = errors.New("the cart is empty")
@@ -205,6 +207,10 @@ func GetErrorCode(c fiber.Ctx, err error) string {
return i18n.T_(c, "error.err_user_has_no_such_cart")
case errors.Is(err, ErrProductOrItsVariationDoesNotExist):
return i18n.T_(c, "error.err_product_or_its_variation_does_not_exist")
case errors.Is(err, ErrAmountMustBePositive):
return i18n.T_(c, "error.err_amount_must_be_positive")
case errors.Is(err, ErrAmountMustBeReasonable):
return i18n.T_(c, "error.err_amount_must_be_reasonable")
case errors.Is(err, ErrEmptyCart):
return i18n.T_(c, "error.err_cart_is_empty")
@@ -292,6 +298,8 @@ func GetErrorStatus(err error) int {
errors.Is(err, ErrMaxAmtOfCartsReached),
errors.Is(err, ErrUserHasNoSuchCart),
errors.Is(err, ErrProductOrItsVariationDoesNotExist),
errors.Is(err, ErrAmountMustBePositive),
errors.Is(err, ErrAmountMustBeReasonable),
errors.Is(err, ErrEmptyCart),
errors.Is(err, ErrUserHasNoSuchOrder),
errors.Is(err, ErrInvalidReductionType),

View File

@@ -95,4 +95,6 @@ type Product struct {
Category string `gorm:"column:category" json:"category"`
IsFavorite bool `gorm:"column:is_favorite" json:"is_favorite"`
IsOEM bool `gorm:"column:is_oem" json:"is_oem"`
IsNew bool `gorm:"column:is_new" json:"is_new"`
}

View File

@@ -0,0 +1,22 @@
info:
name: Set is_no_vat
type: http
seq: 4
http:
method: PATCH
url: "{{bas_url}}/restricted/customer/no-vat"
body:
type: json
data: |-
{
"customer_id":1,
"is_no_vat": false
}
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -5,7 +5,7 @@ info:
http:
method: GET
url: "{{bas_url}}/restricted/product/list?p=1&elems=30&reference=~NC100"
url: "{{bas_url}}/restricted/product/list?p=1&elems=30&reference=~NC100&is_new_eq=0&is_favorite_eq=false&is_oem_eq=FALSE"
params:
- name: p
value: "1"
@@ -27,11 +27,12 @@ http:
- name: is_new_eq
value: "0"
type: query
disabled: true
- name: is_favorite_eq
value: "false"
type: query
disabled: true
- name: is_oem_eq
value: "FALSE"
type: query
body:
type: json
data: ""

View File

@@ -1,7 +1,7 @@
info:
name: addresses
type: folder
seq: 10
seq: 9
request:
auth: inherit

View File

@@ -5,15 +5,14 @@ info:
http:
method: POST
url: http://localhost:3000/api/v1/public/auth/update-choice?lang_id=0&country_id=1
url: http://localhost:3000/api/v1/public/auth/update-choice?lang_id=1&country_id=1
params:
- name: lang_id
value: "0"
value: "1"
type: query
- name: country_id
value: "1"
type: query
auth: inherit
settings:
encodeUrl: true

View File

@@ -5,7 +5,11 @@ info:
http:
method: GET
url: http://localhost:3000/api/v1/restricted/carts/add-new-cart
url: http://localhost:3000/api/v1/restricted/carts/add-new-cart?name=carttt
params:
- name: name
value: carttt
type: query
auth: inherit
settings:

View File

@@ -5,7 +5,7 @@ info:
http:
method: GET
url: http://localhost:3000/api/v1/restricted/carts/add-product-to-cart?cart_id=1&product_id=51&amount=1
url: http://localhost:3000/api/v1/restricted/carts/add-product-to-cart?cart_id=1&product_id=51&amount=1&set_amount=false
params:
- name: cart_id
value: "1"
@@ -16,6 +16,9 @@ http:
- name: amount
value: "1"
type: query
- name: set_amount
value: "false"
type: query
auth: inherit
settings:

View File

@@ -5,7 +5,7 @@ info:
http:
method: GET
url: http://localhost:3000/api/v1/restricted/carts/add-product-to-cart?cart_id=1&product_id=51&product_attribute_id=1115&amount=1
url: http://localhost:3000/api/v1/restricted/carts/add-product-to-cart?cart_id=1&product_id=51&product_attribute_id=1115&amount=2&set_amount=true
params:
- name: cart_id
value: "1"
@@ -17,7 +17,10 @@ http:
value: "1115"
type: query
- name: amount
value: "1"
value: "2"
type: query
- name: set_amount
value: "true"
type: query
auth: inherit

View File

@@ -1,7 +0,0 @@
info:
name: list
type: folder
seq: 3
request:
auth: inherit

View File

@@ -1,24 +0,0 @@
info:
name: list-products
type: http
seq: 1
http:
method: GET
url: http://localhost:3000/api/v1/restricted/list/list-products?p=1&elems=10&target_user_id=2
params:
- name: p
value: "1"
type: query
- name: elems
value: "10"
type: query
- name: target_user_id
value: "2"
type: query
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -1,21 +0,0 @@
info:
name: list-users
type: http
seq: 1
http:
method: GET
url: http://localhost:3000/api/v1/restricted/list/list-users?p=1&elems=10
params:
- name: p
value: "1"
type: query
- name: elems
value: "10"
type: query
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

View File

@@ -1,7 +1,7 @@
info:
name: orders
type: folder
seq: 11
seq: 10
request:
auth: inherit

View File

@@ -1,7 +1,7 @@
info:
name: product-translation
type: folder
seq: 2
seq: 3
request:
auth: inherit

View File

@@ -1,7 +1,7 @@
info:
name: storage-old
type: folder
seq: 1
seq: 2
request:
auth: inherit

View File

@@ -1,7 +1,7 @@
info:
name: storage-restricted
type: folder
seq: 9
seq: 8
request:
auth: inherit

View File

@@ -112,7 +112,8 @@ CREATE TABLE IF NOT EXISTS b2b_customers (
country_id INT NULL DEFAULT 2,
created_at DATETIME(6) NULL,
updated_at DATETIME(6) NULL,
deleted_at DATETIME(6) NULL
deleted_at DATETIME(6) NULL,
is_no_vat TINYINT(1) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE UNIQUE INDEX IF NOT EXISTS idx_customers_email
@@ -161,6 +162,16 @@ CREATE TABLE IF NOT EXISTS b2b_favorites (
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
-- oems
CREATE TABLE IF NOT EXISTS b2b_oems (
user_id BIGINT UNSIGNED NOT NULL,
product_id INT UNSIGNED NOT NULL,
PRIMARY KEY (user_id, product_id),
CONSTRAINT fk_oems_customer FOREIGN KEY (user_id) REFERENCES b2b_customers(id) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT fk_oems_product FOREIGN KEY (product_id) REFERENCES ps_product(id_product) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
-- refresh_tokens
CREATE TABLE IF NOT EXISTS b2b_refresh_tokens (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
@@ -240,6 +251,11 @@ CREATE TABLE IF NOT EXISTS b2b_customer_orders (
country_id BIGINT UNSIGNED NOT NULL,
address_string TEXT NOT NULL,
status VARCHAR(50) NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
base_price DECIMAL(10, 2) NOT NULL,
tax_incl DECIMAL(10, 2) NOT NULL,
tax_excl DECIMAL(10, 2) NOT NULL,
CONSTRAINT fk_customer_orders_customers FOREIGN KEY (user_id) REFERENCES b2b_customers(id) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT fk_customer_orders_countries FOREIGN KEY (country_id) REFERENCES b2b_countries(id) ON DELETE NO ACTION ON UPDATE CASCADE
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

View File

@@ -76,6 +76,7 @@ INSERT INTO `b2b_route_roles` (`route_id`, `role_id`) VALUES
(2, '1'),
(2, '2'),
(2, '3'),
(2, '4'),
(3, '1'),
(3, '2'),
(3, '3'),

View File

@@ -16,6 +16,7 @@ READS SQL DATA
BEGIN
DECLARE v_tax_rate DECIMAL(10,4) DEFAULT 0;
DECLARE v_tax_group INT;
DECLARE v_base_raw DECIMAL(20,6);
DECLARE v_base DECIMAL(20,6);
@@ -29,45 +30,54 @@ BEGIN
DECLARE v_has_specific INT DEFAULT 0;
-- currency
DECLARE v_target_currency BIGINT;
DECLARE v_target_rate DECIMAL(13,6) DEFAULT 1;
DECLARE v_specific_rate DECIMAL(13,6) DEFAULT 1;
DECLARE v_is_no_vat TINYINT DEFAULT 0;
SET p_id_product_attribute = NULLIF(p_id_product_attribute, 0);
-- ================= CUSTOMER VAT =================
SELECT COALESCE(c.is_no_vat, 0)
INTO v_is_no_vat
FROM b2b_customers c
WHERE c.id = p_id_customer
LIMIT 1;
-- ================= TAX GROUP =================
SELECT ps.id_tax_rules_group
INTO v_tax_group
FROM ps_product_shop ps
WHERE ps.id_product = p_id_product
AND ps.id_shop = p_id_shop
LIMIT 1;
-- ================= TAX =================
SELECT COALESCE(t.rate, 0)
INTO v_tax_rate
FROM ps_tax_rule tr
JOIN ps_tax t ON t.id_tax = tr.id_tax
LEFT JOIN b2b_countries c ON c.id = p_id_country
WHERE tr.id_tax_rules_group = (
SELECT ps.id_tax_rules_group
FROM ps_product_shop ps
WHERE ps.id_product = p_id_product
AND ps.id_shop = p_id_shop
LIMIT 1
)
AND tr.id_country = c.ps_id_country
WHERE tr.id_tax_rules_group = v_tax_group
AND tr.id_country = c.ps_id_country
LIMIT 1;
-- ================= TARGET CURRENCY =================
SELECT c.b2b_id_currency
INTO v_target_currency
IF v_is_no_vat = 1 THEN
SET v_tax_rate = 0;
END IF;
-- ================= CURRENCY =================
SELECT c.b2b_id_currency, r.conversion_rate
INTO v_target_currency, v_target_rate
FROM b2b_countries c
LEFT JOIN b2b_currency_rates r
ON r.b2b_id_currency = c.b2b_id_currency
WHERE c.id = p_id_country
LIMIT 1;
-- latest target rate
SELECT r.conversion_rate
INTO v_target_rate
FROM b2b_currency_rates r
WHERE r.b2b_id_currency = v_target_currency
ORDER BY r.created_at DESC
LIMIT 1;
-- ================= BASE PRICE (RAW) =================
-- ================= BASE PRICE =================
SELECT
COALESCE(ps.price, p.price) + COALESCE(pas.price, 0)
INTO v_base_raw
@@ -79,8 +89,8 @@ BEGIN
AND pas.id_shop = p_id_shop
WHERE p.id_product = p_id_product;
-- convert base to target currency
SET v_base = v_base_raw * v_target_rate;
SET v_excl = v_base;
-- ================= RULE SELECTION =================
SELECT
@@ -99,71 +109,67 @@ BEGIN
FROM b2b_specific_price bsp
LEFT JOIN b2b_specific_price_product spp
ON spp.b2b_specific_price_id = bsp.id
AND spp.id_product = p_id_product
LEFT JOIN b2b_specific_price_product_attribute spa
ON spa.b2b_specific_price_id = bsp.id
AND spa.id_product_attribute = p_id_product_attribute
LEFT JOIN b2b_specific_price_customer spc
ON spc.b2b_specific_price_id = bsp.id
AND spc.b2b_id_customer = p_id_customer
LEFT JOIN b2b_specific_price_country spco
ON spco.b2b_specific_price_id = bsp.id
AND spco.b2b_id_country = p_id_country
LEFT JOIN b2b_specific_price_category spcat
ON spcat.b2b_specific_price_id = bsp.id
LEFT JOIN ps_category_product cp
ON cp.id_category = spcat.id_category
AND cp.id_product = p_id_product
WHERE bsp.is_active = 1
AND bsp.from_quantity <= p_quantity
-- intersection rules (unchanged)
AND (
NOT EXISTS (SELECT 1 FROM b2b_specific_price_product x WHERE x.b2b_specific_price_id = bsp.id)
OR EXISTS (SELECT 1 FROM b2b_specific_price_product x WHERE x.b2b_specific_price_id = bsp.id AND x.id_product = p_id_product)
)
AND (spp.id_product IS NOT NULL OR NOT EXISTS (
SELECT 1 FROM b2b_specific_price_product x WHERE x.b2b_specific_price_id = bsp.id
))
AND (
NOT EXISTS (SELECT 1 FROM b2b_specific_price_product_attribute x WHERE x.b2b_specific_price_id = bsp.id)
OR EXISTS (SELECT 1 FROM b2b_specific_price_product_attribute x WHERE x.b2b_specific_price_id = bsp.id AND x.id_product_attribute = p_id_product_attribute)
)
AND (spa.id_product_attribute IS NOT NULL OR NOT EXISTS (
SELECT 1 FROM b2b_specific_price_product_attribute x WHERE x.b2b_specific_price_id = bsp.id
))
AND (
NOT EXISTS (SELECT 1 FROM b2b_specific_price_category x WHERE x.b2b_specific_price_id = bsp.id)
OR EXISTS (
SELECT 1 FROM b2b_specific_price_category x
JOIN ps_category_product cp ON cp.id_category = x.id_category
WHERE x.b2b_specific_price_id = bsp.id AND cp.id_product = p_id_product
)
)
AND (spc.b2b_id_customer IS NOT NULL OR NOT EXISTS (
SELECT 1 FROM b2b_specific_price_customer x WHERE x.b2b_specific_price_id = bsp.id
))
AND (
NOT EXISTS (SELECT 1 FROM b2b_specific_price_customer x WHERE x.b2b_specific_price_id = bsp.id)
OR EXISTS (SELECT 1 FROM b2b_specific_price_customer x WHERE x.b2b_specific_price_id = bsp.id AND x.b2b_id_customer = p_id_customer)
)
AND (spco.b2b_id_country IS NOT NULL OR NOT EXISTS (
SELECT 1 FROM b2b_specific_price_country x WHERE x.b2b_specific_price_id = bsp.id
))
AND (
NOT EXISTS (SELECT 1 FROM b2b_specific_price_country x WHERE x.b2b_specific_price_id = bsp.id)
OR EXISTS (SELECT 1 FROM b2b_specific_price_country x WHERE x.b2b_specific_price_id = bsp.id AND x.b2b_id_country = p_id_country)
)
AND (cp.id_product IS NOT NULL OR NOT EXISTS (
SELECT 1 FROM b2b_specific_price_category x WHERE x.b2b_specific_price_id = bsp.id
))
ORDER BY
-- customer wins
(EXISTS (SELECT 1 FROM b2b_specific_price_customer x WHERE x.b2b_specific_price_id = bsp.id AND x.b2b_id_customer = p_id_customer)) DESC,
-- attribute
(EXISTS (SELECT 1 FROM b2b_specific_price_product_attribute x WHERE x.b2b_specific_price_id = bsp.id AND x.id_product_attribute = p_id_product_attribute)) DESC,
-- product
(EXISTS (SELECT 1 FROM b2b_specific_price_product x WHERE x.b2b_specific_price_id = bsp.id AND x.id_product = p_id_product)) DESC,
-- category
(EXISTS (
SELECT 1 FROM b2b_specific_price_category x
JOIN ps_category_product cp ON cp.id_category = x.id_category
WHERE x.b2b_specific_price_id = bsp.id AND cp.id_product = p_id_product
)) DESC,
-- country
(EXISTS (SELECT 1 FROM b2b_specific_price_country x WHERE x.b2b_specific_price_id = bsp.id AND x.b2b_id_country = p_id_country)) DESC,
(spc.b2b_id_customer IS NOT NULL) DESC,
(spa.id_product_attribute IS NOT NULL) DESC,
(spp.id_product IS NOT NULL) DESC,
(cp.id_product IS NOT NULL) DESC,
(spco.b2b_id_country IS NOT NULL) DESC,
bsp.id DESC
LIMIT 1;
-- ================= APPLY =================
SET v_excl = v_base;
IF v_has_specific = 1 THEN
IF v_reduction_type = 'amount' THEN
-- convert specific price currency if needed
IF v_specific_currency_id IS NOT NULL AND v_specific_currency_id != v_target_currency THEN
SELECT r.conversion_rate
@@ -173,7 +179,6 @@ BEGIN
ORDER BY r.created_at DESC
LIMIT 1;
-- normalize → then convert to target
SET v_excl = (v_fixed_price / v_specific_rate) * v_target_rate;
ELSE
@@ -379,10 +384,19 @@ BEGIN
m.name AS manufacturer,
cl.name AS category,
p.is_oem,
EXISTS(
SELECT 1 FROM b2b_favorites f
WHERE f.user_id = p_id_customer AND f.product_id = p_id_product
) AS is_favorite
) AS is_favorite,
CASE
WHEN ps.date_add >= DATE_SUB(
NOW(),
INTERVAL COALESCE(CAST(ps_configuration.value AS SIGNED), 20) DAY
) AND ps.active = 1
THEN 1
ELSE 0
END AS is_new
@@ -400,6 +414,8 @@ BEGIN
AND cl.id_shop = p_id_shop
LEFT JOIN ps_manufacturer m
ON m.id_manufacturer = p.id_manufacturer
LEFT JOIN ps_configuration
ON ps_configuration.name = 'PS_NB_DAYS_NEW_PRODUCT'
WHERE p.id_product = p_id_product
LIMIT 1;