package services import ( "fmt" "time" "github.com/google/uuid" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" "github.com/golang-jwt/jwt/v5" "github.com/ydn/yourdreamnamehere/internal/config" "github.com/ydn/yourdreamnamehere/internal/models" ) type UserService struct { db *gorm.DB config *config.Config } func NewUserService(db *gorm.DB, config *config.Config) *UserService { return &UserService{ db: db, config: config, } } func (s *UserService) CreateUser(email, firstName, lastName, password string) (*models.User, error) { // Check if user already exists var existingUser models.User if err := s.db.Where("email = ?", email).First(&existingUser).Error; err == nil { return nil, fmt.Errorf("user with email %s already exists", email) } // Hash password hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return nil, fmt.Errorf("failed to hash password: %w", err) } // Create user and customer in a transaction err = s.db.Transaction(func(tx *gorm.DB) error { user := &models.User{ ID: uuid.New(), Email: email, FirstName: firstName, LastName: lastName, PasswordHash: string(hashedPassword), CreatedAt: time.Now(), UpdatedAt: time.Now(), } if err := tx.Create(user).Error; err != nil { return fmt.Errorf("failed to create user: %w", err) } // Create associated customer record for future Stripe integration customer := &models.Customer{ UserID: user.ID, Email: email, Status: "pending", // Will be updated when Stripe customer is created } if err := tx.Create(customer).Error; err != nil { return fmt.Errorf("failed to create customer: %w", err) } return nil }) if err != nil { return nil, err } // Return the created user var user models.User if err := s.db.Where("email = ?", email).First(&user).Error; err != nil { return nil, fmt.Errorf("failed to retrieve created user: %w", err) } return &user, nil } func (s *UserService) AuthenticateUser(email, password string) (string, error) { var user models.User if err := s.db.Where("email = ?", email).First(&user).Error; err != nil { if err == gorm.ErrRecordNotFound { return "", fmt.Errorf("invalid credentials") } return "", fmt.Errorf("failed to authenticate user: %w", err) } if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil { return "", fmt.Errorf("invalid credentials") } // Generate JWT token token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "user_id": user.ID.String(), "email": user.Email, "role": user.Role, "exp": time.Now().Add(s.config.JWT.Expiry).Unix(), }) tokenString, err := token.SignedString([]byte(s.config.JWT.Secret)) if err != nil { return "", fmt.Errorf("failed to generate token: %w", err) } return tokenString, nil } func (s *UserService) GetUserByID(userID string) (*models.User, error) { var user models.User if err := s.db.Where("id = ?", userID).First(&user).Error; err != nil { return nil, err } return &user, nil } func (s *UserService) UpdateUser(userID, firstName, lastName string) (*models.User, error) { var user models.User if err := s.db.Where("id = ?", userID).First(&user).Error; err != nil { return nil, err } if firstName != "" { user.FirstName = firstName } if lastName != "" { user.LastName = lastName } user.UpdatedAt = time.Now() if err := s.db.Save(&user).Error; err != nil { return nil, fmt.Errorf("failed to update user: %w", err) } return &user, nil } func (s *UserService) GetUserDomains(userID string) ([]models.Domain, error) { var domains []models.Domain if err := s.db.Joins("JOIN customers ON domains.customer_id = customers.id"). Where("customers.user_id = ?", userID). Find(&domains).Error; err != nil { return nil, err } return domains, nil } func (s *UserService) GetDomainByID(userID string, domainID uuid.UUID) (*models.Domain, error) { var domain models.Domain if err := s.db.Joins("JOIN customers ON domains.customer_id = customers.id"). Where("customers.user_id = ? AND domains.id = ?", userID, domainID). First(&domain).Error; err != nil { return nil, err } return &domain, nil } func (s *UserService) GetUserVPS(userID string) ([]models.VPS, error) { var vpsList []models.VPS if err := s.db.Joins("JOIN domains ON vps.domain_id = domains.id"). Joins("JOIN customers ON domains.customer_id = customers.id"). Where("customers.user_id = ?", userID). Find(&vpsList).Error; err != nil { return nil, err } return vpsList, nil } func (s *UserService) GetVPSByID(userID string, vpsID uuid.UUID) (*models.VPS, error) { var vps models.VPS if err := s.db.Joins("JOIN domains ON vps.domain_id = domains.id"). Joins("JOIN customers ON domains.customer_id = customers.id"). Where("customers.user_id = ? AND vps.id = ?", userID, vpsID). First(&vps).Error; err != nil { return nil, err } return &vps, nil } func (s *UserService) GetUserSubscriptions(userID string) ([]models.Subscription, error) { var subscriptions []models.Subscription if err := s.db.Joins("JOIN customers ON subscriptions.customer_id = customers.id"). Where("customers.user_id = ?", userID). Find(&subscriptions).Error; err != nil { return nil, err } return subscriptions, nil } func (s *UserService) GetDeploymentLogs(userID string, vpsID *uuid.UUID) ([]models.DeploymentLog, error) { var logs []models.DeploymentLog query := s.db.Joins("JOIN vps ON deployment_logs.vps_id = vps.id"). Joins("JOIN domains ON vps.domain_id = domains.id"). Joins("JOIN customers ON domains.customer_id = customers.id"). Where("customers.user_id = ?", userID) if vpsID != nil { query = query.Where("vps.id = ?", *vpsID) } if err := query.Order("deployment_logs.created_at DESC").Find(&logs).Error; err != nil { return nil, err } return logs, nil } func (s *UserService) GetInvitationByToken(token string) (*models.Invitation, error) { var invitation models.Invitation if err := s.db.Where("token = ? AND status = 'pending' AND expires_at > ?", token, time.Now()). First(&invitation).Error; err != nil { return nil, err } return &invitation, nil } func (s *UserService) AcceptInvitation(token, password, firstName, lastName string) error { return s.db.Transaction(func(tx *gorm.DB) error { // Get invitation var invitation models.Invitation if err := tx.Where("token = ? AND status = 'pending' AND expires_at > ?", token, time.Now()). First(&invitation).Error; err != nil { return fmt.Errorf("invitation not found or expired") } // Get VPS to extract email var vps models.VPS if err := tx.Preload("Domain.Customer").Where("id = ?", invitation.VPSID).First(&vps).Error; err != nil { return fmt.Errorf("failed to get VPS: %w", err) } // Create user hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return fmt.Errorf("failed to hash password: %w", err) } user := &models.User{ ID: uuid.New(), Email: invitation.Email, FirstName: firstName, LastName: lastName, PasswordHash: string(hashedPassword), CreatedAt: time.Now(), UpdatedAt: time.Now(), } if err := tx.Create(user).Error; err != nil { return fmt.Errorf("failed to create user: %w", err) } // Update customer user_id if err := tx.Model(&vps.Domain.Customer).Update("user_id", user.ID).Error; err != nil { return fmt.Errorf("failed to update customer: %w", err) } // Update invitation now := time.Now() invitation.Status = "accepted" invitation.AcceptedAt = &now if err := tx.Save(&invitation).Error; err != nil { return fmt.Errorf("failed to update invitation: %w", err) } return nil }) }