2025-11-05 15:25:41 +00:00
|
|
|
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 {
|
2025-11-05 16:35:36 +00:00
|
|
|
userRepo repositories.UserRepository
|
|
|
|
|
jwtService JWTService
|
|
|
|
|
notificationService NotificationService
|
|
|
|
|
validator *validator.Validate
|
2025-11-05 15:25:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewUserService creates a new instance of UserService
|
2025-11-05 16:35:36 +00:00
|
|
|
func NewUserService(userRepo repositories.UserRepository, jwtService JWTService, notificationService NotificationService) UserService {
|
2025-11-05 15:25:41 +00:00
|
|
|
return &userService{
|
2025-11-05 16:35:36 +00:00
|
|
|
userRepo: userRepo,
|
|
|
|
|
jwtService: jwtService,
|
|
|
|
|
notificationService: notificationService,
|
|
|
|
|
validator: validator.New(),
|
2025-11-05 15:25:41 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-05 16:35:36 +00:00
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-05 15:25:41 +00:00
|
|
|
// 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
|
|
|
|
|
}
|