package customer import ( "context" "errors" "time" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" "golang.org/x/crypto/bcrypt" "tm/internal/customer/domain/aggregate" "tm/internal/customer/domain/entity" ) // Register creates a new customer account func (s *CustomerService) Register(ctx context.Context, req RegisterForm) (*aggregate.AuthorizationResponse, error) { s.logger.Info("Registering new customer", map[string]interface{}{ "email": req.Email, }) // Check if customer already exists existingCustomer, err := s.customerRepo.GetByEmail(ctx, req.Email) if err == nil && existingCustomer != nil { return nil, errors.New("customer already exists") } // Hash password hashedPassword, err := s.hashPassword(req.Password) if err != nil { s.logger.Error("Failed to hash password", map[string]interface{}{ "error": err.Error(), }) return nil, errors.New("failed to process password") } // Create customer customer := &entity.Customer{ ID: uuid.New(), Email: req.Email, Password: hashedPassword, FirstName: req.FirstName, LastName: req.LastName, Mobile: req.Mobile, IsActive: true, IsVerified: false, // Require email verification DeviceTokens: make([]string, 0), UpdatedAt: time.Now(), CreatedAt: time.Now(), } if err := s.customerRepo.Create(ctx, customer); err != nil { s.logger.Error("Failed to create customer", map[string]interface{}{ "error": err.Error(), "email": req.Email, }) return nil, errors.New("failed to create customer") } // Generate tokens accessToken, err := s.generateAccessToken(customer) if err != nil { s.logger.Error("Failed to generate access token", map[string]interface{}{ "error": err.Error(), "customer_id": customer.ID.String(), }) return nil, errors.New("failed to generate access token") } refreshToken, err := s.generateRefreshToken(customer) if err != nil { s.logger.Error("Failed to generate refresh token", map[string]interface{}{ "error": err.Error(), "customer_id": customer.ID.String(), }) return nil, errors.New("failed to generate refresh token") } s.logger.Info("Customer registered successfully", map[string]interface{}{ "customer_id": customer.ID.String(), "email": customer.Email, "company_id": customer.CompanyID.String(), }) // TODO: Send verification email s.sendVerificationEmail(ctx, customer) return &aggregate.AuthorizationResponse{ Customer: aggregate.NewCustomer(*customer), AccessToken: accessToken, RefreshToken: refreshToken, ExpiresIn: int64(s.config.JWT.AccessTokenDuration.Seconds()), }, nil } // Login authenticates a customer and returns tokens func (s *CustomerService) Login(ctx context.Context, req LoginForm) (*aggregate.AuthorizationResponse, error) { s.logger.Info("Customer login attempt", map[string]interface{}{ "email": req.Email, }) // Get customer by email customer, err := s.customerRepo.GetByEmail(ctx, req.Email) if err != nil { s.logger.Warn("Customer login failed - customer not found", map[string]interface{}{ "email": req.Email, }) return nil, errors.New("invalid credentials") } // Check if customer is active if !customer.IsActive { s.logger.Warn("Customer login failed - account inactive", map[string]interface{}{ "email": req.Email, "customer_id": customer.ID.String(), }) return nil, errors.New("account is inactive") } // Verify password if !s.verifyPassword(req.Password, customer.Password) { s.logger.Warn("Customer login failed - invalid password", map[string]interface{}{ "email": req.Email, "customer_id": customer.ID.String(), }) return nil, errors.New("invalid credentials") } // Update last login time customer.LastLoginAt = &time.Time{} *customer.LastLoginAt = time.Now() customer.UpdatedAt = time.Now() if err := s.customerRepo.Update(ctx, customer); err != nil { s.logger.Warn("Failed to update customer last login", map[string]interface{}{ "error": err.Error(), "customer_id": customer.ID.String(), }) // Don't fail login for this } // Generate tokens accessToken, err := s.generateAccessToken(customer) if err != nil { s.logger.Error("Failed to generate access token", map[string]interface{}{ "error": err.Error(), "customer_id": customer.ID.String(), }) return nil, errors.New("failed to generate access token") } refreshToken, err := s.generateRefreshToken(customer) if err != nil { s.logger.Error("Failed to generate refresh token", map[string]interface{}{ "error": err.Error(), "customer_id": customer.ID.String(), }) return nil, errors.New("failed to generate refresh token") } s.logger.Info("Customer logged in successfully", map[string]interface{}{ "customer_id": customer.ID.String(), "email": customer.Email, "is_verified": customer.IsVerified, }) return &aggregate.AuthorizationResponse{ Customer: aggregate.NewCustomer(*customer), AccessToken: accessToken, RefreshToken: refreshToken, ExpiresIn: int64(s.config.JWT.AccessTokenDuration.Seconds()), }, nil } // RefreshToken generates new tokens using refresh token func (s *CustomerService) RefreshToken(ctx context.Context, refreshToken string) (*aggregate.AuthorizationResponse, error) { // Parse and validate refresh token token, err := jwt.Parse(refreshToken, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, errors.New("invalid signing method") } return []byte(s.config.JWT.Secret), nil }) if err != nil || !token.Valid { return nil, errors.New("invalid refresh token") } claims, ok := token.Claims.(jwt.MapClaims) if !ok { return nil, errors.New("invalid token claims") } // Verify token type tokenType, ok := claims["type"].(string) if !ok || tokenType != "refresh" { return nil, errors.New("invalid token type") } // Verify user type userType, ok := claims["user_type"].(string) if !ok || userType != "customer" { return nil, errors.New("invalid user type") } customerIDStr, ok := claims["customer_id"].(string) if !ok { return nil, errors.New("invalid customer ID in token") } customerID, err := uuid.Parse(customerIDStr) if err != nil { return nil, errors.New("invalid customer ID format") } // Get customer from database customer, err := s.customerRepo.GetByID(ctx, customerID) if err != nil { return nil, errors.New("customer not found") } if !customer.IsActive { return nil, errors.New("account is inactive") } // Generate new tokens accessToken, err := s.generateAccessToken(customer) if err != nil { return nil, errors.New("failed to generate access token") } newRefreshToken, err := s.generateRefreshToken(customer) if err != nil { return nil, errors.New("failed to generate refresh token") } return &aggregate.AuthorizationResponse{ Customer: aggregate.NewCustomer(*customer), AccessToken: accessToken, RefreshToken: newRefreshToken, ExpiresIn: int64(s.config.JWT.AccessTokenDuration.Seconds()), }, nil } // ValidateToken validates a JWT token and returns the customer func (s *CustomerService) ValidateToken(ctx context.Context, tokenString string) (*entity.Customer, error) { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, errors.New("invalid signing method") } return []byte(s.config.JWT.Secret), nil }) if err != nil || !token.Valid { return nil, errors.New("invalid token") } claims, ok := token.Claims.(jwt.MapClaims) if !ok { return nil, errors.New("invalid token claims") } // Verify token type tokenType, ok := claims["type"].(string) if !ok || tokenType != "access" { return nil, errors.New("invalid token type") } // Verify user type userType, ok := claims["user_type"].(string) if !ok || userType != "customer" { return nil, errors.New("invalid user type") } customerIDStr, ok := claims["customer_id"].(string) if !ok { return nil, errors.New("invalid customer ID in token") } customerID, err := uuid.Parse(customerIDStr) if err != nil { return nil, errors.New("invalid customer ID format") } customer, err := s.customerRepo.GetByID(ctx, customerID) if err != nil { return nil, errors.New("customer not found") } if !customer.IsActive { return nil, errors.New("account is inactive") } return customer, nil } // ChangePassword changes customer password func (s *CustomerService) ChangePassword(ctx context.Context, customerID uuid.UUID, oldPassword, newPassword string) error { customer, err := s.customerRepo.GetByID(ctx, customerID) if err != nil { return errors.New("customer not found") } // Verify old password if !s.verifyPassword(oldPassword, customer.Password) { return errors.New("invalid old password") } // Hash new password hashedPassword, err := s.hashPassword(newPassword) if err != nil { return errors.New("failed to process new password") } // Update password customer.Password = hashedPassword customer.UpdatedAt = time.Now() if err := s.customerRepo.Update(ctx, customer); err != nil { s.logger.Error("Failed to update customer password", map[string]interface{}{ "error": err.Error(), "customer_id": customerID.String(), }) return errors.New("failed to update password") } s.logger.Info("Customer password changed successfully", map[string]interface{}{ "customer_id": customerID.String(), }) return nil } // ResetPassword initiates password reset process func (s *CustomerService) ResetPassword(ctx context.Context, email string) error { // TODO: Implement password reset logic // This would typically involve: // 1. Generate reset token // 2. Store token with expiration // 3. Send reset email return errors.New("password reset not implemented") } // VerifyEmail verifies customer's email address func (s *CustomerService) VerifyEmail(ctx context.Context, customerID uuid.UUID, token string) error { // TODO: Implement email verification logic // This would typically involve: // 1. Validate verification token // 2. Update customer verification status // 3. Send welcome email return errors.New("email verification not implemented") } // ResendVerification resend verification email func (s *CustomerService) ResendVerification(ctx context.Context, email string) error { customer, err := s.customerRepo.GetByEmail(ctx, email) if err != nil { return errors.New("customer not found") } if customer.IsVerified { return errors.New("email already verified") } // TODO: Send verification email s.sendVerificationEmail(ctx, customer) return nil } // Private helper methods func (s *CustomerService) hashPassword(password string) (string, error) { hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return "", err } return string(hashedBytes), nil } func (s *CustomerService) verifyPassword(password, hashedPassword string) bool { err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) return err == nil } func (s *CustomerService) generateAccessToken(customer *entity.Customer) (string, error) { claims := jwt.MapClaims{ "customer_id": customer.ID.String(), "email": customer.Email, "company_id": customer.CompanyID.String(), "user_type": "customer", "exp": time.Now().Add(s.config.JWT.AccessTokenDuration).Unix(), "iat": time.Now().Unix(), "type": "access", } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(s.config.JWT.Secret)) } func (s *CustomerService) generateRefreshToken(customer *entity.Customer) (string, error) { claims := jwt.MapClaims{ "customer_id": customer.ID.String(), "user_type": "customer", "exp": time.Now().Add(s.config.JWT.RefreshTokenDuration).Unix(), "iat": time.Now().Unix(), "type": "refresh", } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(s.config.JWT.Secret)) } func (s *CustomerService) sendVerificationEmail(ctx context.Context, customer *entity.Customer) { // TODO: Implement email sending logic _ = ctx s.logger.Info("Verification email would be sent", map[string]interface{}{ "customer_id": customer.ID.String(), "email": customer.Email, }) }