Yimaru-BackEnd/internal/services/user/common.go

169 lines
3.7 KiB
Go

package user
import (
"Yimaru-Backend/internal/domain"
"Yimaru-Backend/internal/pkgs/helpers"
"context"
"fmt"
"time"
"golang.org/x/crypto/bcrypt"
)
func (s *Service) VerifyOtp(ctx context.Context, email, phone, otpCode string) error {
user, err := s.userStore.GetUserByEmailPhone(ctx, email, phone)
if err != nil {
return err
}
// 1. Retrieve the OTP from the store
storedOtp, err := s.otpStore.GetOtp(ctx, user.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
}
// user, err := s.userStore.GetUserByUserName(ctx, userName)
// if err != nil {
// return err
// }
newUser := domain.UpdateUserReq{
UserID: user.ID,
Status: domain.ValidString{
Value: string(domain.UserStatusActive),
Valid: true,
},
}
s.userStore.UpdateUserStatus(ctx, newUser)
return nil
}
func (s *Service) ResendOtp(
ctx context.Context,
email, phone string,
) error {
user, err := s.userStore.GetUserByEmailPhone(ctx, email, phone)
if err != nil {
return err
}
otpCode := helpers.GenerateOTP()
message := fmt.Sprintf(
"Welcome to Yimaru Online Learning Platform, your OTP is %s please don't share with anyone.",
otpCode,
)
otp, err := s.otpStore.GetOtp(ctx, user.UserName)
if err != nil {
return err
}
// Broadcast OTP (same logic as SendOtp)
switch otp.Medium {
case domain.OtpMediumSms:
if err := s.messengerSvc.SendAfroMessageSMS(ctx, otp.SentTo, message); err != nil {
return err
}
case domain.OtpMediumEmail:
if err := s.messengerSvc.SendEmail(
ctx,
otp.SentTo,
message,
message,
"Yimaru - One Time Password",
); err != nil {
return err
}
default:
return fmt.Errorf("invalid otp medium: %s", otp.Medium)
}
if err := s.otpStore.UpdateOtp(ctx, otpCode, user.UserName); err != nil {
return err
}
return nil
}
func (s *Service) SendOtp(ctx context.Context, userName string, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium, provider domain.SMSProvider) error {
otpCode := helpers.GenerateOTP()
message := fmt.Sprintf("Welcome to Yimaru Online Learning Platform, your OTP is %s please don't share with anyone.", otpCode)
switch medium {
case domain.OtpMediumSms:
switch provider {
case domain.TwilioSms:
if err := s.messengerSvc.SendTwilioSMS(ctx, sentTo, message); err != nil {
return err
}
case domain.AfroMessage:
if err := s.messengerSvc.SendAfroMessageSMS(ctx, sentTo, message); err != nil {
return err
}
default:
return fmt.Errorf("invalid sms provider: %s", provider)
}
case domain.OtpMediumEmail:
if err := s.messengerSvc.SendEmail(ctx, sentTo, message, message, "Yimaru - One Time Password"); err != nil {
return err
}
}
otp := domain.Otp{
UserName: userName,
SentTo: sentTo,
Medium: medium,
For: otpFor,
Otp: otpCode,
Used: false,
CreatedAt: time.Now(),
ExpiresAt: time.Now().Add(OtpExpiry),
}
return s.otpStore.CreateOtp(ctx, otp)
}
// helper function to get a pointer to time.Time
func timePtr(t time.Time) time.Time {
return t
}
func hashPassword(plaintextPassword string) ([]byte, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(plaintextPassword), 12)
if err != nil {
return []byte{}, err
}
return hash, nil
}