new repo from git.xdrm.io/go/aicra@v0.2.0

This commit is contained in:
Adrien Marquès 2019-05-01 16:42:17 +02:00
parent fc64e500f0
commit 1fb2a210a1
12 changed files with 419 additions and 398 deletions

View File

@ -1,18 +0,0 @@
{
"root": ".",
"host": "127.0.0.1",
"port": 8080,
"driver": "plugin",
"types": {
"default": true,
"folder": "type"
},
"controllers": {
"folder": "controller.plugin"
},
"middlewares": {
"folder": "middleware.plugin"
}
}

View File

@ -3,7 +3,7 @@
"info": "redirects to given tiny url",
"scope": [[]],
"in": {
"URL#0": { "info": "tiny url to redirect to", "name": "url", "type": "varchar(1,30)" }
"URL#0": { "info": "tiny url to redirect to", "name": "url", "type": "string(1,30)" }
},
"out": {}
},
@ -12,8 +12,8 @@
"info": "creates a new tiny url",
"scope": [["admin"]],
"in": {
"URL#0": { "info": "preferred tiny url", "type": "varchar(1,30)", "name": "url" },
"target": { "info": "url to shorten", "type": "varchar(5,300)" }
"URL#0": { "info": "preferred tiny url", "type": "string(1,30)", "name": "url" },
"target": { "info": "url to shorten", "type": "string(5,300)" }
},
"out": {}
},
@ -22,8 +22,8 @@
"info": "overrides an existing tiny url",
"scope": [["admin"]],
"in": {
"URL#0": { "info": "preferred tiny url", "type": "varchar(1,30)", "name": "url" },
"target": { "info": "url to shorten", "type": "varchar(5,300)" }
"URL#0": { "info": "preferred tiny url", "type": "string(1,30)", "name": "url" },
"target": { "info": "url to shorten", "type": "string(5,300)" }
},
"out": {}
},
@ -32,7 +32,7 @@
"info": "removes an existing tiny url",
"scope": [["admin"]],
"in": {
"URL#0": { "info": "preferred tiny url", "type": "varchar(1,30)", "name": "url" }
"URL#0": { "info": "preferred tiny url", "type": "string(1,30)", "name": "url" }
},
"out": {}
},
@ -40,13 +40,13 @@
"/": {
"token": {
"POST": {
"info": "creates a 1-minute access token",
"info": "creates a 5-minute access token",
"scope": [[]],
"in": {
"URL#0": { "info": "wanted role", "type": "varchar(3,10)", "name": "role" }
"URL#0": { "info": "wanted role", "type": "string(3,10)", "name": "role" }
},
"out": {
"token": { "info": "access token", "type": "varchar(128,128)" }
"token": { "info": "access token", "type": "string(128,128)" }
}
}
}

View File

@ -1,179 +0,0 @@
package main
import (
"git.xdrm.io/example/aicra/db"
"git.xdrm.io/go/aicra/api"
"git.xdrm.io/go/aicra/driver"
e "git.xdrm.io/go/aicra/err"
)
func main() {}
type RootController int
func Export() driver.Controller { return new(RootController) }
// Redirects to an url from a key
func (rctl RootController) Get(d api.Arguments) api.Response {
r := api.NewResponse()
/* (1) Init redis connection */
cli := db.Connect()
if cli == nil {
r.Err = e.Failure
return *r
}
/* (2) Extract api input */
key, err := d.GetString("url")
if err != nil {
r.Err = e.InvalidParam
r.Err.Put("url")
r.Err.Put(err.Error())
return *r
}
/* (3) Check if match for this key */
val := cli.Get(db.DATA, key)
if val == nil {
r.Err = e.NoMatchFound
return *r
}
/* (4) Redirect to value */
r.Set("_REDIRECT_", string(val))
r.Err = e.Success
return *r
}
// Stores a new tinyurl/fullurl combination
func (rctl RootController) Post(d api.Arguments) api.Response {
r := api.NewResponse()
/* (1) Init redis connection */
cli := db.Connect()
if cli == nil {
r.Err = e.Failure
return *r
}
/* (2) Extract api input */
target, err := d.GetString("target")
if err != nil {
r.Err = e.InvalidParam
r.Err.Put("target")
r.Err.Put(err.Error())
return *r
}
url, err := d.GetString("url")
if err != nil {
r.Err = e.InvalidParam
r.Err.Put("url")
r.Err.Put(err.Error())
return *r
}
/* (3) Check if key already used */
if cli.Get(db.DATA, url) != nil {
r.Err = e.AlreadyExists
r.Err.Put("url")
return *r
}
/* (4) Store */
if !cli.Set(db.DATA, url, target) {
r.Err = e.Failure
return *r
}
r.Err = e.Success
return *r
}
// Overrides a existing tinyurl with new target
func (rctl RootController) Put(d api.Arguments) api.Response {
r := api.NewResponse()
/* (1) Init redis connection */
cli := db.Connect()
if cli == nil {
r.Err = e.Failure
return *r
}
/* (2) Extract api input */
target, err := d.GetString("target")
if err != nil {
r.Err = e.InvalidParam
r.Err.Put("target")
r.Err.Put(err.Error())
return *r
}
url, err := d.GetString("url")
if err != nil {
r.Err = e.InvalidParam
r.Err.Put("url")
r.Err.Put(err.Error())
return *r
}
/* (3) Check if key already used */
if cli.Get(db.DATA, url) == nil {
r.Err = e.NoMatchFound
return *r
}
/* (4) Update */
if !cli.Set(db.DATA, url, target) {
r.Err = e.Failure
return *r
}
r.Err = e.Success
return *r
}
// Deletes an existing tinyurl
func (rctl RootController) Delete(d api.Arguments) api.Response {
r := api.NewResponse()
/* (1) Init redis connection */
cli := db.Connect()
if cli == nil {
r.Err = e.Failure
return *r
}
/* (2) Extract api input */
url, err := d.GetString("url")
if err != nil {
r.Err = e.InvalidParam
r.Err.Put("url")
r.Err.Put(err.Error())
return *r
}
/* (3) Check if key already used */
if cli.Get(db.DATA, url) == nil {
r.Err = e.NoMatchFound
return *r
}
/* (4) Delete */
if !cli.Del(db.DATA, url) {
r.Err = e.Failure
return *r
}
r.Err = e.Success
return *r
}

View File

@ -1,60 +0,0 @@
package main
import (
"crypto/sha512"
"encoding/hex"
"git.xdrm.io/example/aicra/db"
"git.xdrm.io/go/aicra/api"
"git.xdrm.io/go/aicra/driver"
e "git.xdrm.io/go/aicra/err"
"strconv"
"time"
)
func main() {}
type TokenController int
func Export() driver.Controller { return new(TokenController) }
// Builds an access token from credentials
func (tctl TokenController) Post(d api.Arguments) api.Response {
r := api.NewResponse()
/* (1) Init redis connection */
cli := db.Connect()
if cli == nil {
r.Err = e.Failure
return *r
}
/* (2) Extract api input */
role, err := d.GetString("role")
if err != nil {
r.Err = e.InvalidParam
r.Err.Put("url")
r.Err.Put(err.Error())
return *r
}
/* (3) Generate token */
hasher := sha512.New()
defer hasher.Reset()
hasher.Write([]byte(strconv.FormatInt(time.Now().Unix(), 5)))
token := hex.EncodeToString(hasher.Sum(nil))
/* (4) Store */
if !cli.Set(db.TOKEN, token, role, time.Minute) {
r.Err = e.Failure
return *r
}
r.Set("token", token)
r.Err = e.Success
return *r
}
func (tctl TokenController) Get(d api.Arguments) api.Response { return *api.NewResponse() }
func (tctl TokenController) Put(d api.Arguments) api.Response { return *api.NewResponse() }
func (tctl TokenController) Delete(d api.Arguments) api.Response { return *api.NewResponse() }

View File

@ -1,81 +0,0 @@
package db
import (
"fmt"
"github.com/go-redis/redis"
"time"
)
const NONCE = "go-tiny-url"
type Domain string
const (
DATA Domain = "data"
TOKEN Domain = "token"
)
type db redis.Client
// Returns a connected client to dataset
func Connect() *db {
cli := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "",
DB: 0,
})
if _, err := cli.Ping().Result(); err != nil {
return nil
}
return (*db)(cli)
}
// returns value from key (nil if nothing)
func (c *db) Get(dom Domain, key string) []byte {
// 1. Try to get
if val, err := (*redis.Client)(c).Get(fmt.Sprintf("%s:%s:%s", NONCE, dom, key)).Result(); err != nil {
// 2. nil if nothing found
return nil
} else {
// 3. return if found
return []byte(val)
}
}
// stores a value for a key (success state in return)
func (c *db) Set(dom Domain, key string, value string, exp ...time.Duration) bool {
var expiration time.Duration = 0
if len(exp) > 0 {
expiration = exp[0]
}
// 1. Try to set
if (*redis.Client)(c).Set(fmt.Sprintf("%s:%s:%s", NONCE, dom, key), value, expiration).Err() != nil {
// 2. failure
return false
}
// 3. success
return true
}
// deletes the value for a key (success state in return)
func (c *db) Del(dom Domain, key string) bool {
// 1. Try to set
if (*redis.Client)(c).Del(fmt.Sprintf("%s:%s:%s", NONCE, dom, key)).Err() != nil {
// 2. failure
return false
}
// 3. success
return true
}

10
go.mod Normal file
View File

@ -0,0 +1,10 @@
module git.xdrm.io/example/aicra
go 1.12
require (
git.xdrm.io/go/aicra v0.2.0
github.com/go-redis/redis v6.15.2+incompatible
github.com/onsi/ginkgo v1.8.0 // indirect
github.com/onsi/gomega v1.5.0 // indirect
)

31
go.sum Normal file
View File

@ -0,0 +1,31 @@
git.xdrm.io/go/aicra v0.2.0 h1:JrX773PfTaI4jYkeihpdv0U1caLGSsAKpBh21W6W0Ik=
git.xdrm.io/go/aicra v0.2.0/go.mod h1:ulAzCdKqUN5X4eWQSER70QXSYteSXtybAqupcUuYPdw=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

38
main.go
View File

@ -1,24 +1,54 @@
package main
import (
"git.xdrm.io/go/aicra"
"log"
"net/http"
"git.xdrm.io/example/aicra/service/auth"
"git.xdrm.io/example/aicra/service/shortener"
"git.xdrm.io/example/aicra/storage"
"git.xdrm.io/go/aicra"
"git.xdrm.io/go/aicra/typecheck/builtin"
)
func main() {
listenTo := "127.0.0.1:4242"
// 1. build server
log.Printf("[server] building")
server, err := aicra.New("api.json")
if err != nil {
panic(err)
log.Fatalf("/!\\ cannot init server: %v\n", err)
}
log.Printf("[Server up] %s\n", listenTo)
// 2. add type checkers
server.Checkers.Add(builtin.NewAny())
server.Checkers.Add(builtin.NewString())
server.Checkers.Add(builtin.NewFloat64())
// 3. storage connect
log.Printf("[storage] connecting")
storageClient, err := storage.New()
if err != nil {
log.Fatalf("/!\\ cannot connect %v", err)
}
defer storageClient.Close()
// 4. init services
authService := auth.New(storageClient)
shortenerService := shortener.New(storageClient, authService)
// 5. wire services
shortenerService.Wire(server)
authService.Wire(server)
// 6. listen and serve
log.Printf("[server] listening")
err = http.ListenAndServe(listenTo, server)
if err != nil {
panic(err)
log.Fatalf("/!\\ cannot listen: %v\n", err)
}
}

View File

@ -1,47 +0,0 @@
package main
import (
"git.xdrm.io/example/aicra/db"
"git.xdrm.io/go/aicra/driver"
"net/http"
"strings"
)
func main() {}
type AuthMiddleware int
func Export() driver.Middleware { return new(AuthMiddleware) }
// Authentication middleware
func (amw AuthMiddleware) Inspect(req http.Request, scope *[]string) {
// 1. get authorization header
token := req.Header.Get("Authorization")
// fail if no header
if len(token) < 1 {
return
}
// 2. fail on invalid token format
if len(token) != 128 || strings.ContainsAny(token, "$-_") {
return
}
// 3. get role for this token
cli := db.Connect()
if cli == nil {
return
}
defer cli.Close()
role := cli.Get(db.TOKEN, token)
if role == nil {
return
}
// add role to scope
*scope = append(*scope, string(role))
}

102
service/auth/auth.go Normal file
View File

@ -0,0 +1,102 @@
package auth
import (
"crypto/sha512"
"encoding/hex"
"log"
"strconv"
"strings"
"time"
"git.xdrm.io/example/aicra/storage"
"git.xdrm.io/go/aicra"
"git.xdrm.io/go/aicra/api"
)
// Service manages the url shortener
type Service struct {
storage *storage.Client
}
// New returns a bare service
func New(storage *storage.Client) *Service {
log.Printf("[service.auth] created")
return &Service{
storage: storage,
}
}
// Wire to the aicra server
func (svc *Service) Wire(server *aicra.Server) {
log.Printf("[service.auth] wired")
server.HandleFunc("POST", "/token", svc.generateToken)
}
// generateToken generates a token valid for 5 mintes
func (svc *Service) generateToken(req api.Request, res *api.Response) {
// 1. extract input
role, err := req.Param.GetString("role")
if err != nil {
res.Err = api.ErrorInvalidParam()
res.Err.Put("role")
res.Err.Put(err.Error())
return
}
// 2. generate token
hasher := sha512.New()
defer hasher.Reset()
hasher.Write([]byte(strconv.FormatInt(time.Now().Unix(), 5)))
token := hex.EncodeToString(hasher.Sum(nil))
// 3. store token
if !svc.storage.Set(storage.TOKEN, token, role, 5*time.Minute) {
res.Err = api.ErrorFailure()
return
}
// 4. return data
res.Data["token"] = token
res.Err = api.ErrorSuccess()
}
// CheckToken returns whether a token is valid
func (svc *Service) CheckToken(handler api.HandlerFunc) api.HandlerFunc {
return func(req api.Request, res *api.Response) {
// success if no scope [['admin']]
if req.Scope == nil || len(req.Scope) < 1 {
handler(req, res)
return
}
headerToken := req.Request.Header.Get("Authorization")
// fail if invalid header
if len(headerToken) != 128 || strings.ContainsAny(headerToken, "$-_") {
res.Err = api.ErrorPermission()
return
}
tokenRole := svc.storage.Get(storage.TOKEN, headerToken)
// fail if the role of the token does not match any scope
for _, scope := range req.Scope {
if scope == nil || len(scope) != 1 {
continue
}
// success
if scope[0] == string(tokenRole) {
handler(req, res)
return
}
}
// failure
res.Err = api.ErrorPermission()
return
}
}

View File

@ -0,0 +1,157 @@
package shortener
import (
"log"
"net/http"
"git.xdrm.io/example/aicra/service/auth"
"git.xdrm.io/example/aicra/storage"
"git.xdrm.io/go/aicra"
"git.xdrm.io/go/aicra/api"
)
// Service manages the url shortener
type Service struct {
storage *storage.Client
authService *auth.Service
}
// New returns a bare service
func New(storage *storage.Client, auth *auth.Service) *Service {
log.Printf("[service.shortener] created")
return &Service{
storage: storage,
authService: auth,
}
}
// Wire to the aicra server
func (svc *Service) Wire(server *aicra.Server) {
log.Printf("[service.shortener] wired")
server.HandleFunc("GET", "/", svc.redirect)
server.HandleFunc("POST", "/", svc.authService.CheckToken(svc.register))
server.HandleFunc("PUT", "/", svc.authService.CheckToken(svc.update))
server.HandleFunc("DELETE", "/", svc.authService.CheckToken(svc.delete))
}
// redirect from a tiny url to the long url
func (svc *Service) redirect(req api.Request, res *api.Response) {
// 1. extract input
tinyURL, err := req.Param.GetString("url")
if err != nil {
res.Err = api.ErrorInvalidParam()
res.Err.Put("url")
res.Err.Put(err.Error())
return
}
// 2. check in db if exists
longURL := svc.storage.Get(storage.DATA, tinyURL)
if longURL == nil {
res.Err = api.ErrorNoMatchFound()
return
}
// 3. redirect
res.Status = http.StatusPermanentRedirect
res.Headers.Set("Location", string(longURL))
res.Err = api.ErrorSuccess()
}
// register registers a new tiny url to a long one
func (svc *Service) register(req api.Request, res *api.Response) {
// 1. extract arguments
longURL, err := req.Param.GetString("target")
if err != nil {
res.Err = api.ErrorInvalidParam()
res.Err.Put("target")
res.Err.Put(err.Error())
return
}
tinyURL, err := req.Param.GetString("url")
if err != nil {
res.Err = api.ErrorInvalidParam()
res.Err.Put("url")
res.Err.Put(err.Error())
return
}
// 2. fail if already used
if svc.storage.Get(storage.DATA, tinyURL) != nil {
res.Err = api.ErrorAlreadyExists()
res.Err.Put("url")
return
}
// 3. store association
if !svc.storage.Set(storage.DATA, tinyURL, longURL) {
res.Err = api.ErrorFailure()
return
}
res.Err = api.ErrorSuccess()
}
// update updates an existing tiny url to a new long one
func (svc *Service) update(req api.Request, res *api.Response) {
// 1. extract arguments
longURL, err := req.Param.GetString("target")
if err != nil {
res.Err = api.ErrorInvalidParam()
res.Err.Put("target")
res.Err.Put(err.Error())
return
}
tinyURL, err := req.Param.GetString("url")
if err != nil {
res.Err = api.ErrorInvalidParam()
res.Err.Put("url")
res.Err.Put(err.Error())
return
}
// 2. fail if not already existing
if svc.storage.Get(storage.DATA, tinyURL) == nil {
res.Err = api.ErrorNoMatchFound()
return
}
// 3. update association
if !svc.storage.Set(storage.DATA, tinyURL, longURL) {
res.Err = api.ErrorFailure()
return
}
res.Err = api.ErrorSuccess()
}
// delete removes a new tiny url
func (svc *Service) delete(req api.Request, res *api.Response) {
// 1. extract arguments
tinyURL, err := req.Param.GetString("url")
if err != nil {
res.Err = api.ErrorInvalidParam()
res.Err.Put("url")
res.Err.Put(err.Error())
return
}
// 2. fail if not already existing
if svc.storage.Get(storage.DATA, tinyURL) == nil {
res.Err = api.ErrorNoMatchFound()
return
}
// 3. update association
if !svc.storage.Del(storage.DATA, tinyURL) {
res.Err = api.ErrorFailure()
return
}
res.Err = api.ErrorSuccess()
}

76
storage/redis.go Normal file
View File

@ -0,0 +1,76 @@
package storage
import (
"fmt"
"time"
"github.com/go-redis/redis"
)
const nonce = "go-tiny-url"
const (
// DATA domain used to store actual data
DATA string = "data"
// TOKEN domain used to store tokens
TOKEN string = "token"
)
// Client is a wrapper around the redis client
type Client struct {
client *redis.Client
}
// New returns new client
func New() (*Client, error) {
client := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "",
DB: 0,
})
if _, err := client.Ping().Result(); err != nil {
return nil, err
}
return &Client{
client: client,
}, nil
}
// Close closes the connection
func (c *Client) Close() error {
return c.client.Close()
}
// Get returns a value from key or NIL
func (c *Client) Get(dom, key string) []byte {
redisKey := fmt.Sprintf("%s:%s:%s", nonce, dom, key)
val, err := c.client.Get(redisKey).Result()
if err != nil {
return nil
}
return []byte(val)
}
// Set stores a value for a key (success state in return)
func (c *Client) Set(dom, key string, value string, exp ...time.Duration) bool {
redisKey := fmt.Sprintf("%s:%s:%s", nonce, dom, key)
var expiration time.Duration
if len(exp) > 0 {
expiration = exp[0]
}
return c.client.Set(redisKey, value, expiration).Err() == nil
}
// Del deletes the value for a key (success state in return)
func (c *Client) Del(dom, key string) bool {
redisKey := fmt.Sprintf("%s:%s:%s", nonce, dom, key)
return c.client.Del(redisKey).Err() == nil
}