niki/domain/account/service/service.go

116 lines
3.5 KiB
Go

package service
import (
"context"
"math/rand"
"time"
smscontract "git.gocasts.ir/ebhomengo/niki/contract/sms"
"git.gocasts.ir/ebhomengo/niki/domain/account/entity"
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
)
type Config struct {
LengthOfOtpCode int `koanf:"length_of_otp_code"`
OtpChars string `koanf:"otp_chars"`
OtpExpireTime time.Duration `koanf:"otp_expire_time"`
}
type RepositoryOtp interface {
IsExistPhoneNumber(ctx context.Context, phoneNumber string) (bool, error)
SaveCodeWithPhoneNumber(ctx context.Context, phoneNumber string, code string, expireTime time.Duration) error
GetCodeByPhoneNumber(ctx context.Context, phoneNumber string) (string, error)
DeleteCodeByPhoneNumber(ctx context.Context, PhoneNumber string) (bool, error)
}
type Repository interface {
IsExistDriverByPhoneNumber(ctx context.Context, phoneNumber string) (bool, entity.Driver, error)
CreateDriver(ctx context.Context, driver entity.Driver) (entity.Driver, error)
}
type Service struct {
config Config
repositoryOtp RepositoryOtp
repository Repository
smsContract smscontract.SmsAdapter
}
func NewService(cfg Config, repositoryOtp RepositoryOtp, repository Repository, smsContract smscontract.SmsAdapter) Service {
return Service{
config: cfg,
repositoryOtp: repositoryOtp,
repository: repository,
smsContract: smsContract,
}
}
func (s Service) SendOTP(ctx context.Context, phoneNumber string) error {
const op = "accountService.SendOTP"
isExist, iErr := s.repositoryOtp.IsExistPhoneNumber(ctx, phoneNumber)
if iErr != nil {
return richerror.New(op).WithErr(iErr).WithKind(richerror.KindUnexpected)
}
if isExist {
return richerror.New(op).WithMessage(errmsg.ErrorMsgOtpCodeExist).WithKind(richerror.KindForbidden)
}
newCode := s.generateVerificationCode()
sErr := s.repositoryOtp.SaveCodeWithPhoneNumber(ctx, phoneNumber, newCode, s.config.OtpExpireTime)
if sErr != nil {
return richerror.New(op).WithErr(sErr).WithKind(richerror.KindUnexpected)
}
go s.smsContract.Send(phoneNumber, newCode)
return nil
}
func (s Service) LoginOrRegisterDriver(ctx context.Context, phoneNumber string, verifyCode string) (entity.Driver, error) {
const op = "accountService.LoginOrRegisterDriver"
code, gErr := s.repositoryOtp.GetCodeByPhoneNumber(ctx, phoneNumber)
if gErr != nil {
return entity.Driver{}, richerror.New(op).WithErr(gErr).WithKind(richerror.KindUnexpected)
}
if code == "" || code != verifyCode {
return entity.Driver{}, richerror.New(op).WithMessage(errmsg.ErrorMsgOtpCodeIsNotValid).WithKind(richerror.KindForbidden)
}
_, dErr := s.repositoryOtp.DeleteCodeByPhoneNumber(ctx, phoneNumber)
if dErr != nil {
return entity.Driver{}, richerror.New(op).WithErr(dErr).WithKind(richerror.KindUnexpected)
}
isExist, driver, eErr := s.repository.IsExistDriverByPhoneNumber(ctx, phoneNumber)
if eErr != nil {
return entity.Driver{}, richerror.New(op).WithErr(eErr).WithKind(richerror.KindUnexpected)
}
if !isExist {
newDriver, cErr := s.repository.CreateDriver(ctx, entity.Driver{
PhoneNumber: phoneNumber,
})
if cErr != nil {
return entity.Driver{}, richerror.New(op).WithErr(cErr).WithKind(richerror.KindUnexpected)
}
driver = newDriver
}
return driver, nil
}
func (s Service) generateVerificationCode() string {
result := make([]byte, s.config.LengthOfOtpCode)
for i := 0; i < s.config.LengthOfOtpCode; i++ {
result[i] = s.config.OtpChars[rand.Intn(len(s.config.OtpChars))]
}
return string(result)
}