package user import ( "context" "errors" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "golang.org/x/crypto/bcrypt" ) const ( OtpExpiry = 5 * time.Minute ) var ( ErrOtpAlreadyUsed = errors.New("otp already used") ErrInvalidOtp = errors.New("invalid otp") ErrOtpExpired = errors.New("otp expired") ) type Service struct { userStore UserStore otpStore OtpStore smsGateway SmsGateway emailGateway EmailGateway } func NewService( userStore UserStore, RefreshExpiry int, otpStore OtpStore, smsGateway SmsGateway, emailGateway EmailGateway, ) *Service { return &Service{ userStore: userStore, otpStore: otpStore, } } func (s *Service) CheckPhoneEmailExist(ctx context.Context, phoneNum, email string) (bool, bool, error) { // email,phone,error return s.userStore.CheckPhoneEmailExist(ctx, phoneNum, email) } func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, sentTo string) error { var err error // check if user exists switch medium { case domain.OtpMediumEmail: _, err = s.userStore.GetUserByEmail(ctx, sentTo) case domain.OtpMediumSms: _, err = s.userStore.GetUserByPhone(ctx, sentTo) } if err != nil { return err } // send otp based on the medium return s.SendOtp(ctx, sentTo, domain.OtpReset, medium) } func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) { // normal // get otp var sentTo string if registerReq.OtpMedium == domain.OtpMediumEmail { sentTo = registerReq.Email } else { sentTo = registerReq.PhoneNumber } // otp, err := s.otpStore.GetOtp( ctx, sentTo, domain.OtpRegister, registerReq.OtpMedium) if err != nil { return domain.User{}, err } // verify otp if otp.Used { return domain.User{}, ErrOtpAlreadyUsed } if time.Now().After(otp.ExpiresAt) { return domain.User{}, ErrOtpExpired } if otp.Otp != registerReq.Otp { return domain.User{}, ErrInvalidOtp } hashedPassword, err := hashPassword(registerReq.Password) if err != nil { return domain.User{}, err } userR := domain.User{ FirstName: registerReq.FirstName, LastName: registerReq.LastName, Email: registerReq.Email, PhoneNumber: registerReq.PhoneNumber, Password: hashedPassword, Role: "user", EmailVerified: registerReq.OtpMedium == domain.OtpMediumEmail, PhoneVerified: registerReq.OtpMedium == domain.OtpMediumSms, } // create the user and mark otp as used user, err := s.userStore.CreateUser(ctx, userR, otp.ID) if err != nil { return domain.User{}, err } return user, nil } func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, sentTo string) error { var err error // check if user exists switch medium { case domain.OtpMediumEmail: _, err = s.userStore.GetUserByEmail(ctx, sentTo) case domain.OtpMediumSms: _, err = s.userStore.GetUserByPhone(ctx, sentTo) } if err != nil { return err } return s.SendOtp(ctx, sentTo, domain.OtpReset, medium) } func (s *Service) ResetPassword(ctx context.Context, resetReq domain.ResetPasswordReq) error { var sentTo string if resetReq.OtpMedium == domain.OtpMediumEmail { sentTo = resetReq.Email } else { sentTo = resetReq.PhoneNumber } otp, err := s.otpStore.GetOtp( ctx, sentTo, domain.OtpRegister, resetReq.OtpMedium) if err != nil { return err } // if otp.Used { return ErrOtpAlreadyUsed } if time.Now().After(otp.ExpiresAt) { return ErrOtpExpired } if otp.Otp != resetReq.Otp { return ErrInvalidOtp } // hash password hashedPassword, err := hashPassword(resetReq.Password) if err != nil { return err } // reset pass and mark otp as used err = s.userStore.UpdatePassword(ctx, sentTo, hashedPassword, otp.ID) if err != nil { return err } return nil } func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium) error { otpCode := "123456" // Generate OTP code otp := domain.Otp{ SentTo: sentTo, Medium: medium, For: otpFor, Otp: otpCode, Used: false, CreatedAt: time.Now(), ExpiresAt: time.Now().Add(OtpExpiry), } err := s.otpStore.CreateOtp(ctx, otp) if err != nil { return err } switch medium { case domain.OtpMediumSms: return s.smsGateway.SendSMSOTP(ctx, sentTo, otpCode) case domain.OtpMediumEmail: return s.emailGateway.SendEmailOTP(ctx, sentTo, otpCode) } return nil } func (s *Service) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error { // update user return s.userStore.UpdateUser(ctx, user) } func (s *Service) GetUserByID(ctx context.Context, id int64) (domain.User, error) { return s.userStore.GetUserByID(ctx, id) } func hashPassword(plaintextPassword string) ([]byte, error) { hash, err := bcrypt.GenerateFromPassword([]byte(plaintextPassword), 12) if err != nil { return []byte{}, err } return hash, nil }