func (r *redisStore) get(namespace, id string, c redis.Conn) (models.OTP, error) {
	var (
		key = r.makeKey(namespace, id)
		out = models.OTP{
			Namespace: namespace,
			ID:        id,
		}
	)
	resp, err := redis.Values(c.Do("HGETALL", key))
	if err != nil {
		return out, err
	}
	if err := redis.ScanStruct(resp, &out); err != nil {
		return out, err
	}
	if out.OTP == "" {
		return out, ErrNotExist
	}
	ttl, err := redis.Int64(c.Do("TTL", key))
	if err != nil {
		return out, err
	}
	out.TTL = time.Duration(ttl) * time.Second
	out.TTLSeconds = out.TTL.Seconds()
	return out, nil
}
get begins a transaction.
package otpgateway
import (
	"encoding/json"
	"errors"
	"fmt"
	"time"
	"github.com/gomodule/redigo/redis"
	"github.com/knadh/otpgateway/models"
)
var ErrNotExist = errors.New("the OTP does not exist")
type Store interface {
	Set(namespace, id string, otp models.OTP) (models.OTP, error)
	SetAddress(namespace, id, address string) error
	Check(namespace, id string, counter bool) (models.OTP, error)
	Close(namespace, id string) error
	Delete(namespace, id string) error
	Ping() error
}
type redisStore struct {
	pool *redis.Pool
	conf RedisConf
}
type RedisConf struct {
	Host      string        `json:"host"`
	Port      int           `json:"port"`
	Username  string        `json:"username"`
	Password  string        `json:"password"`
	MaxActive int           `json:"max_active"`
	MaxIdle   int           `json:"max_idle"`
	Timeout   time.Duration `json:"timeout"`
	KeyPrefix string        `json:"key_prefix"`
	PublishKey string `json:"publish_key"`
}
type event struct {
	Type string `json:"type"`
	Namespace string          `json:"namespace"`
	ID        string          `json:"id"`
	Data      json.RawMessage `json:"data"`
}
func NewRedisStore(c RedisConf) Store {
	if c.KeyPrefix == "" {
		c.KeyPrefix = "OTP"
	}
	pool := &redis.Pool{
		Wait:      true,
		MaxActive: c.MaxActive,
		MaxIdle:   c.MaxIdle,
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial(
				"tcp",
				fmt.Sprintf("%s:%d", c.Host, c.Port),
				redis.DialPassword(c.Password),
				redis.DialConnectTimeout(c.Timeout),
				redis.DialReadTimeout(c.Timeout),
				redis.DialWriteTimeout(c.Timeout),
			)
			return c, err
		},
	}
	return &redisStore{
		conf: c,
		pool: pool,
	}
}
func (r *redisStore) Ping() error {
	c := r.pool.Get()
	defer c.Close()
	_, err := c.Do("PING")  
	return err
}
func (r *redisStore) Check(namespace, id string, counter bool) (models.OTP, error) {
	c := r.pool.Get()
	defer c.Close()
	out, err := r.get(namespace, id, c)
	if err != nil {
		return out, err
	}
	if !counter {
		return out, err
	}
	key := r.makeKey(namespace, id)
	r.begin(c)
	c.Send("HINCRBY", key, "attempts", 1)
	c.Send("TTL", key)
	resp, err := r.end(c)
	if err != nil {
		return out, err
	}
	attempts, _ := redis.Int(resp[0], nil)
	out.Attempts = attempts
	ttl, _ := redis.Int64(resp[1], nil)
	out.TTL = time.Duration(ttl) * time.Second
	if r.conf.PublishKey != "" {
		b, _ := json.Marshal(out)
		e, _ := json.Marshal(event{
			Type:      "check",
			Namespace: namespace,
			ID:        id,
			Data:      json.RawMessage(b),
		})
		_, _ = c.Do("PUBLISH", r.conf.PublishKey, e)
	}
	return out, err
}
func (r *redisStore) Set(namespace, id string, otp models.OTP) (models.OTP, error) {
	c := r.pool.Get()
	defer c.Close()
	var (
		key = r.makeKey(namespace, id)
		exp = otp.TTL.Nanoseconds() / int64(time.Millisecond)
	)
	r.begin(c)
	c.Send("HMSET", key,
		"otp", otp.OTP,
		"to", otp.To,
		"channel_description", otp.ChannelDesc,
		"address_description", otp.AddressDesc,
		"extra", string(otp.Extra),
		"provider", otp.Provider,
		"closed", false,
		"max_attempts", otp.MaxAttempts)
	c.Send("HINCRBY", key, "attempts", 1)
	c.Send("PEXPIRE", key, exp)
	resp, err := r.end(c)
	if err != nil {
		return otp, err
	}
	attempts, err := redis.Int(resp[1], nil)
	if err != nil {
		return otp, err
	}
	otp.Attempts = attempts
	otp.TTLSeconds = otp.TTL.Seconds()
	otp.Namespace = namespace
	otp.ID = id
	return otp, nil
}
func (r *redisStore) SetAddress(namespace, id, address string) error {
	c := r.pool.Get()
	defer c.Close()
	var key = r.makeKey(namespace, id)
	if _, err := c.Do("HSET", key, "to", address); err != nil {
		return err
	}
	return nil
}
func (r *redisStore) Close(namespace, id string) error {
	c := r.pool.Get()
	defer c.Close()
	_, err := c.Do("HSET", r.makeKey(namespace, id), "closed", true)
	if r.conf.PublishKey != "" {
		e, _ := json.Marshal(event{
			Type:      "close",
			Namespace: namespace,
			ID:        id,
			Data:      json.RawMessage([]byte(`null`)),
		})
		_, _ = c.Do("PUBLISH", r.conf.PublishKey, e)
	}
	return err
}
func (r *redisStore) Delete(namespace, id string) error {
	c := r.pool.Get()
	defer c.Close()
	_, err := c.Do("DEL", r.makeKey(namespace, id))
	return err
}