package user import ( "Yimaru-Backend/internal/domain" "context" "time" ) func (s *Service) VerifyOtp(ctx context.Context, userName string, otpCode string) error { // 1. Retrieve the OTP from the store storedOtp, err := s.otpStore.GetOtp(ctx, userName) if err != nil { return err // could be ErrOtpNotFound or other DB errors } // 2. Check if OTP was already used if storedOtp.Used { return domain.ErrOtpAlreadyUsed } // 3. Check if OTP has expired if time.Now().After(storedOtp.ExpiresAt) { return domain.ErrOtpExpired } // 4. Check if the provided OTP matches if storedOtp.Otp != otpCode { return domain.ErrInvalidOtp } // 5. Mark OTP as used storedOtp.Used = true storedOtp.UsedAt = timePtr(time.Now()) if err := s.otpStore.MarkOtpAsUsed(ctx, storedOtp); err != nil { return err } return nil } 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, provider domain.SMSProvider) error { var err error // check if user exists switch medium { case domain.OtpMediumEmail: _, err = s.userStore.GetUserByEmailPhone(ctx, sentTo, "") case domain.OtpMediumSms: _, err = s.userStore.GetUserByEmailPhone(ctx, "", sentTo) } if err != nil && err != domain.ErrUserNotFound { return err } // send otp based on the medium return s.SendOtp(ctx, sentTo, domain.OtpRegister, medium, provider) } func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) { // Check if the email or phone is already registered based on OTP medium phoneExists, emailExists, err := s.userStore.CheckPhoneEmailExist(ctx, registerReq.PhoneNumber, registerReq.Email) if err != nil { return domain.User{}, err } if registerReq.OtpMedium == domain.OtpMediumEmail { if emailExists { return domain.User{}, domain.ErrEmailAlreadyRegistered } } else { if phoneExists { return domain.User{}, domain.ErrPhoneAlreadyRegistered } } // Hash the password hashedPassword, err := hashPassword(registerReq.Password) if err != nil { return domain.User{}, err } // Prepare the user userR := domain.User{ FirstName: registerReq.FirstName, LastName: registerReq.LastName, UserName: registerReq.UserName, Email: registerReq.Email, PhoneNumber: registerReq.PhoneNumber, Password: hashedPassword, Role: domain.RoleStudent, EmailVerified: false, // verification pending via OTP PhoneVerified: false, EducationLevel: registerReq.EducationLevel, Age: registerReq.Age, Country: registerReq.Country, Region: registerReq.Region, Status: domain.UserStatusPending, ProfileCompleted: false, PreferredLanguage: registerReq.PreferredLanguage, CreatedAt: time.Now(), } var sentTo string // var provider domain.Provid if registerReq.OtpMedium == domain.OtpMediumEmail { sentTo = registerReq.Email } else { sentTo = registerReq.PhoneNumber } // Send OTP to the user (email/SMS) if err := s.SendOtp(ctx, sentTo, domain.OtpRegister, registerReq.OtpMedium, domain.TwilioSms); err != nil { return domain.User{}, err } // Create the user (no OTP validation yet) user, err := s.userStore.CreateUserWithoutOtp(ctx, userR) if err != nil { return domain.User{}, err } return user, nil }