Yimaru-BackEnd/internal/services/user/service.go
2025-03-30 22:18:20 +03:00

206 lines
4.8 KiB
Go

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
}