package service import ( "context" "time" "github.com/google/uuid" "git.threesix.ai/jordan/slack-auth-1770277926/pkg/auth" "git.threesix.ai/jordan/slack-auth-1770277926/pkg/logging" "git.threesix.ai/jordan/slack-auth-1770277926/services/auth-api/internal/domain" "git.threesix.ai/jordan/slack-auth-1770277926/services/auth-api/internal/port" ) // UserService handles user-related business logic. type UserService struct { repo port.UserRepository jwtSecret []byte jwtIssuer string logger *logging.Logger } // NewUserService creates a new user service. func NewUserService(repo port.UserRepository, jwtSecret []byte, jwtIssuer string, logger *logging.Logger) *UserService { return &UserService{ repo: repo, jwtSecret: jwtSecret, jwtIssuer: jwtIssuer, logger: logger.WithService("UserService"), } } // RegisterInput contains the data needed to register a user. type RegisterInput struct { Email string Password string } // RegisterOutput contains the result of user registration. type RegisterOutput struct { User *domain.User Token string } // Register creates a new user with duplicate detection. // Returns domain.ErrDuplicateUser if email already exists. // Returns domain.ErrInvalidEmail if email is invalid. // Returns domain.ErrInvalidPassword if password is invalid. func (s *UserService) Register(ctx context.Context, input RegisterInput) (*RegisterOutput, error) { // Check for duplicates exists, err := s.repo.ExistsByEmail(ctx, input.Email) if err != nil { return nil, err } if exists { return nil, domain.ErrDuplicateUser } // Generate new ID id := domain.UserID(uuid.New().String()) // Create domain entity (validates email/password and hashes password) user, err := domain.NewUser(id, input.Email, input.Password) if err != nil { return nil, err } // Persist if err := s.repo.Create(ctx, user); err != nil { return nil, err } // Generate JWT token token, err := s.generateToken(user) if err != nil { s.logger.Error("failed to generate token after registration", "error", err, "user_id", id) return nil, err } s.logger.Info("user registered", "id", id, "email", input.Email) return &RegisterOutput{ User: user, Token: token, }, nil } // LoginInput contains the data needed to authenticate a user. type LoginInput struct { Email string Password string } // LoginOutput contains the result of user authentication. type LoginOutput struct { User *domain.User Token string } // Login authenticates a user and returns a JWT token. // Returns domain.ErrInvalidCredentials if credentials are incorrect. func (s *UserService) Login(ctx context.Context, input LoginInput) (*LoginOutput, error) { // Find user by email user, err := s.repo.GetByEmail(ctx, input.Email) if err != nil { if err == domain.ErrUserNotFound { return nil, domain.ErrInvalidCredentials } return nil, err } // Verify password if !user.CheckPassword(input.Password) { return nil, domain.ErrInvalidCredentials } // Generate JWT token token, err := s.generateToken(user) if err != nil { s.logger.Error("failed to generate token", "error", err, "user_id", user.ID) return nil, err } s.logger.Info("user logged in", "id", user.ID, "email", input.Email) return &LoginOutput{ User: user, Token: token, }, nil } // GetByID returns a user by ID. // Returns domain.ErrUserNotFound if not found. func (s *UserService) GetByID(ctx context.Context, id domain.UserID) (*domain.User, error) { return s.repo.Get(ctx, id) } // generateToken creates a JWT token for the user. func (s *UserService) generateToken(user *domain.User) (string, error) { authUser := &auth.User{ ID: user.ID.String(), Email: user.Email, } return auth.GenerateTokenWithIssuer(s.jwtSecret, authUser, 24*time.Hour, s.jwtIssuer, s.jwtIssuer) }