Merge branch 'main' of ssh://git.ma-al.com:8822/goc_daniel/b2b into order-actions

This commit is contained in:
2026-04-17 09:49:11 +02:00
27 changed files with 689 additions and 25 deletions

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"
)
@@ -34,6 +35,15 @@ func (s *CartsService) CreateNewCart(user_id uint, name string) (model.CustomerC
// create new cart for customer
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
}

View File

@@ -1,7 +1,6 @@
package orderService
import (
"fmt"
"strconv"
"git.ma-al.com/goc_daniel/b2b/app/actions/orderStatusActions"
@@ -10,8 +9,11 @@ import (
"git.ma-al.com/goc_daniel/b2b/app/model/enums"
"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"
@@ -20,6 +22,7 @@ import (
type OrderService struct {
ordersRepo ordersRepo.UIOrdersRepo
cartsRepo cartsRepo.UICartsRepo
productsRepo productsRepo.UIProductsRepo
addressesService *addressesService.AddressesService
emailService *emailService.EmailService
actionRegistry *orderStatusActions.ActionRegistry
@@ -29,6 +32,7 @@ func New() *OrderService {
return &OrderService{
ordersRepo: ordersRepo.New(),
cartsRepo: cartsRepo.New(),
productsRepo: productsRepo.New(),
addressesService: addressesService.New(),
emailService: emailService.NewEmailService(),
actionRegistry: &orderStatusActions.GlobalRegistry,
@@ -62,9 +66,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
@@ -99,8 +106,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
order, err := s.ordersRepo.PlaceNewOrder(cart, name, country_id, address_info, originalUserId)
order, err := s.ordersRepo.PlaceNewOrder(cart, name, country_id, address_info, originalUserId, base_price, tax_incl, tax_excl)
if err != nil {
return err
}
@@ -109,8 +118,12 @@ 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(),
)
}
return s.ChangeOrderStatus(user_id, order.OrderID, enums.OrderStatusPending)
@@ -167,3 +180,27 @@ func (s *OrderService) ChangeOrderStatus(userId, orderId uint, newStatus enums.O
return nil
}
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
}