package service import ( "context" "math/rand" "time" smscontract "git.gocasts.ir/ebhomengo/niki/contract/sms" "git.gocasts.ir/ebhomengo/niki/driverapp/service/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 validator Validator } func NewService(cfg Config, repositoryOtp RepositoryOtp, repository Repository, smsContract smscontract.SmsAdapter, validator Validator) Service { return Service{ config: cfg, repositoryOtp: repositoryOtp, repository: repository, smsContract: smsContract, validator: validator, } } func (s Service) SendOtp(ctx context.Context, req SendOtpRequest) (SendOtpResponse, error) { const op = "driverService.SendOtp" err := s.validator.ValidateSendOtpRequest(req) if err != nil { return SendOtpResponse{}, richerror.New(op).WithErr(err).WithMessage(err.Error()) } isExist, iErr := s.repositoryOtp.IsExistPhoneNumber(ctx, req.PhoneNumber) if iErr != nil { return SendOtpResponse{}, richerror.New(op).WithErr(iErr).WithKind(richerror.KindUnexpected) } if isExist { return SendOtpResponse{}, richerror.New(op).WithMessage(errmsg.ErrorMsgOtpCodeExist).WithKind(richerror.KindForbidden) } newCode := s.generateVerificationCode() sErr := s.repositoryOtp.SaveCodeWithPhoneNumber(ctx, req.PhoneNumber, newCode, s.config.OtpExpireTime) if sErr != nil { return SendOtpResponse{}, richerror.New(op).WithErr(sErr).WithKind(richerror.KindUnexpected) } go s.smsContract.Send(req.PhoneNumber, newCode) return SendOtpResponse{}, nil } func (s Service) LoginOrRegister(ctx context.Context, req LoginOrRegisterRequest) (LoginOrRegisterResponse, error) { const op = "driverService.LoginOrRegister" err := s.validator.ValidateLoginOrRegisterRequest(req) if err != nil { return LoginOrRegisterResponse{}, richerror.New(op).WithErr(err).WithMessage(err.Error()) } code, gErr := s.repositoryOtp.GetCodeByPhoneNumber(ctx, req.PhoneNumber) if gErr != nil { return LoginOrRegisterResponse{}, richerror.New(op).WithErr(gErr).WithKind(richerror.KindUnexpected) } if code == "" || code != req.VerifyCode { return LoginOrRegisterResponse{}, richerror.New(op).WithMessage(errmsg.ErrorMsgOtpCodeIsNotValid).WithKind(richerror.KindForbidden) } _, dErr := s.repositoryOtp.DeleteCodeByPhoneNumber(ctx, req.PhoneNumber) if dErr != nil { return LoginOrRegisterResponse{}, richerror.New(op).WithErr(dErr).WithKind(richerror.KindUnexpected) } isExist, driver, eErr := s.repository.IsExistDriverByPhoneNumber(ctx, req.PhoneNumber) if eErr != nil { return LoginOrRegisterResponse{}, richerror.New(op).WithErr(eErr).WithKind(richerror.KindUnexpected) } if !isExist { newDriver, cErr := s.repository.CreateDriver(ctx, entity.Driver{ PhoneNumber: req.PhoneNumber, }) if cErr != nil { return LoginOrRegisterResponse{}, richerror.New(op).WithErr(cErr).WithKind(richerror.KindUnexpected) } driver = newDriver } // TODO : CreateAccessToken and create CreateRefreshToken return LoginOrRegisterResponse{ Data: Data{ ID: driver.ID, PhoneNumber: driver.PhoneNumber, }, }, 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) }