package services import ( "errors" "fmt" "strings" "attune-heart-therapy/internal/models" "attune-heart-therapy/internal/repositories" "github.com/go-playground/validator/v10" ) // userService implements the UserService interface type userService struct { userRepo repositories.UserRepository jwtService JWTService notificationService NotificationService validator *validator.Validate } // NewUserService creates a new instance of UserService func NewUserService(userRepo repositories.UserRepository, jwtService JWTService, notificationService NotificationService) UserService { return &userService{ userRepo: userRepo, jwtService: jwtService, notificationService: notificationService, validator: validator.New(), } } // Register creates a new user account with password hashing and validation func (s *userService) Register(req RegisterRequest) (*models.User, error) { // Validate the request if err := s.validator.Struct(req); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } // Additional password validation if len(req.Password) < 8 { return nil, errors.New("password must be at least 8 characters long") } // Normalize email req.Email = strings.ToLower(strings.TrimSpace(req.Email)) // Check if user already exists existingUser, err := s.userRepo.GetByEmail(req.Email) if err == nil && existingUser != nil { return nil, fmt.Errorf("user with email %s already exists", req.Email) } // Create new user user := &models.User{ FirstName: strings.TrimSpace(req.FirstName), LastName: strings.TrimSpace(req.LastName), Email: req.Email, Phone: strings.TrimSpace(req.Phone), Location: strings.TrimSpace(req.Location), DateOfBirth: req.DateOfBirth, IsAdmin: false, // New users are not admin by default } // Hash the password if err := user.HashPassword(req.Password); err != nil { return nil, fmt.Errorf("failed to hash password: %w", err) } // Save user to database if err := s.userRepo.Create(user); err != nil { return nil, fmt.Errorf("failed to create user: %w", err) } // Send welcome email notification if err := s.notificationService.SendWelcomeEmail(user); err != nil { // Log the error but don't fail the registration fmt.Printf("Failed to send welcome email to %s: %v\n", user.Email, err) } // Clear password hash from response for security user.PasswordHash = "" return user, nil } // Login authenticates a user and returns user info with JWT token func (s *userService) Login(email, password string) (*models.User, string, error) { // Validate input if email == "" { return nil, "", errors.New("email is required") } if password == "" { return nil, "", errors.New("password is required") } // Normalize email email = strings.ToLower(strings.TrimSpace(email)) // Get user by email user, err := s.userRepo.GetByEmail(email) if err != nil { return nil, "", errors.New("invalid credentials") } // Check password if !user.CheckPassword(password) { return nil, "", errors.New("invalid credentials") } // Generate JWT token token, err := s.jwtService.GenerateToken(user.ID, user.Email, user.IsAdmin) if err != nil { return nil, "", fmt.Errorf("failed to generate token: %w", err) } // Clear password hash from response for security user.PasswordHash = "" return user, token, nil } // GetProfile retrieves user profile information by user ID func (s *userService) GetProfile(userID uint) (*models.User, error) { if userID == 0 { return nil, errors.New("invalid user ID") } user, err := s.userRepo.GetByID(userID) if err != nil { return nil, fmt.Errorf("failed to get user profile: %w", err) } // Clear password hash from response for security user.PasswordHash = "" return user, nil } // UpdateProfile updates user profile information func (s *userService) UpdateProfile(userID uint, req UpdateProfileRequest) (*models.User, error) { if userID == 0 { return nil, errors.New("invalid user ID") } // Validate the request if err := s.validator.Struct(req); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } // Get existing user user, err := s.userRepo.GetByID(userID) if err != nil { return nil, fmt.Errorf("failed to get user: %w", err) } // Update fields if provided if req.FirstName != "" { user.FirstName = strings.TrimSpace(req.FirstName) } if req.LastName != "" { user.LastName = strings.TrimSpace(req.LastName) } if req.Phone != "" { user.Phone = strings.TrimSpace(req.Phone) } if req.Location != "" { user.Location = strings.TrimSpace(req.Location) } if !req.DateOfBirth.IsZero() { user.DateOfBirth = req.DateOfBirth } // Update user in database if err := s.userRepo.Update(user); err != nil { return nil, fmt.Errorf("failed to update user: %w", err) } // Clear password hash from response for security user.PasswordHash = "" return user, nil } // CreateAdmin creates a new admin account with validation and password hashing func (s *userService) CreateAdmin(req CreateAdminRequest) (*models.User, error) { // Validate the request if err := s.validator.Struct(req); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } // Additional password validation if len(req.Password) < 8 { return nil, errors.New("password must be at least 8 characters long") } // Normalize email req.Email = strings.ToLower(strings.TrimSpace(req.Email)) // Check if user already exists existingUser, err := s.userRepo.GetByEmail(req.Email) if err == nil && existingUser != nil { return nil, fmt.Errorf("user with email %s already exists", req.Email) } // Create new admin user user := &models.User{ FirstName: strings.TrimSpace(req.FirstName), LastName: strings.TrimSpace(req.LastName), Email: req.Email, IsAdmin: true, // Set admin flag } // Hash the password if err := user.HashPassword(req.Password); err != nil { return nil, fmt.Errorf("failed to hash password: %w", err) } // Save user to database if err := s.userRepo.Create(user); err != nil { return nil, fmt.Errorf("failed to create admin user: %w", err) } // Clear password hash from response for security user.PasswordHash = "" return user, nil }