From 8b087b50216f40ae1ee80b37a12a662b263604d9 Mon Sep 17 00:00:00 2001
From: gjahn <gregor.jahn@bht-berlin.de>
Date: Sat, 25 Nov 2023 00:21:16 +0100
Subject: [PATCH] Add Redis as backing service to persist state

---
 configuration/config.go |   6 +++
 go.mod                  |   3 ++
 go.sum                  |   6 +++
 main.go                 |   7 ++-
 state/persistent.go     | 109 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 130 insertions(+), 1 deletion(-)
 create mode 100644 state/persistent.go

diff --git a/configuration/config.go b/configuration/config.go
index 83dc351..22d9fcf 100644
--- a/configuration/config.go
+++ b/configuration/config.go
@@ -12,6 +12,12 @@ type Config struct {
     Environment string `env:"ENV_NAME" envDefault:"development"`
     Host        string `env:"HOST" envDefault:"127.0.0.1"`
     Port        int16  `env:"PORT" envDefault:"3000"`
+
+    DatabaseHost        string `env:"DB_HOST"       envDefault:""`
+    DatabasePort        int16  `env:"DB_PORT"       envDefault:"6379"`
+    DatabaseName        int    `env:"DB_NAME"       envDefault:"0"`
+    DatabaseUsername    string `env:"DB_USERNAME"   envDefault:""`
+    DatabasePassword    string `env:"DB_PASSWORD"   envDefault:""`
 }
 
 
diff --git a/go.mod b/go.mod
index 83356a2..3e4f764 100644
--- a/go.mod
+++ b/go.mod
@@ -6,12 +6,15 @@ require (
 	github.com/caarlos0/env/v9 v9.0.0
 	github.com/go-playground/validator/v10 v10.15.5
 	github.com/gofiber/fiber/v2 v2.49.2
+	github.com/redis/go-redis/v9 v9.3.0
 	github.com/stretchr/testify v1.8.4
 )
 
 require (
 	github.com/andybalholm/brotli v1.0.5 // indirect
+	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
diff --git a/go.sum b/go.sum
index 91ab0de..e1c0817 100644
--- a/go.sum
+++ b/go.sum
@@ -2,9 +2,13 @@ github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/
 github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
 github.com/caarlos0/env/v9 v9.0.0 h1:SI6JNsOA+y5gj9njpgybykATIylrRMklbs5ch6wO6pc=
 github.com/caarlos0/env/v9 v9.0.0/go.mod h1:ye5mlCVMYh6tZ+vCgrs/B95sj88cg5Tlnc0XIzgZ020=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
@@ -25,6 +29,8 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
 github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0=
+github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
diff --git a/main.go b/main.go
index c7866eb..cacfd73 100644
--- a/main.go
+++ b/main.go
@@ -28,7 +28,12 @@ func main() {
         DisableStartupMessage: config.Environment != "development",
     })
 
-    store := state.NewEphemeralStore()
+    var store state.Store
+    if len( config.DatabaseHost ) <= 0 {
+        store = state.NewEphemeralStore()
+    } else {
+        store = state.NewPersistentStore( config )
+    }
 
     var isHealthy = false
 
diff --git a/state/persistent.go b/state/persistent.go
new file mode 100644
index 0000000..ae23264
--- /dev/null
+++ b/state/persistent.go
@@ -0,0 +1,109 @@
+package state
+
+import (
+    "fmt"
+    "runtime"
+    "context"
+    "time"
+
+    "webservice/configuration"
+
+    db "github.com/redis/go-redis/v9"
+)
+
+
+
+type Persistent struct {
+    client      *db.Client
+    ctx         context.Context
+    timeout     time.Duration
+}
+
+
+func NewPersistentStore( c *configuration.Config ) *Persistent {
+    return &Persistent{
+        client: db.NewClient( &db.Options{
+            Addr: fmt.Sprintf( "%s:%d", c.DatabaseHost, c.DatabasePort ),
+            Username: c.DatabaseUsername,
+            Password: c.DatabasePassword,
+            DB: c.DatabaseName,
+
+            DialTimeout: time.Second * 3,
+            ContextTimeoutEnabled: true,
+
+            MaxRetries: 3,
+            MinRetryBackoff: time.Second * 1,
+            MaxRetryBackoff: time.Second * 2,
+
+            PoolSize: 10 * runtime.NumCPU(),
+            MaxActiveConns: 10 * runtime.NumCPU(),
+        }),
+
+        timeout: time.Second * 20,
+    }
+}
+
+
+func ( e *Persistent ) Add( i *Item ) error {
+    ctx, cancel := context.WithTimeout( context.TODO(), e.timeout )
+    defer cancel()
+
+    name := i.Name()
+    if err := e.client.HSet(
+        ctx, name,
+        "data", i.Data(),
+        "mime", i.MimeType(),
+    ).Err(); err != nil {
+        return err
+    }
+    return nil
+}
+
+
+func ( e *Persistent ) Remove( name string ) error {
+    ctx, cancel := context.WithTimeout( context.TODO(), e.timeout )
+    defer cancel()
+
+    if err := e.client.Del( ctx, name ).Err(); err != nil {
+        return err
+    }
+    return nil
+}
+
+
+func ( e *Persistent ) Fetch( name string ) ( *Item, error ) {
+    ctx, cancel := context.WithTimeout( context.TODO(), e.timeout )
+    defer cancel()
+
+    value, err := e.client.HGetAll( ctx, name ).Result()
+    if err != nil {
+        return nil, err
+    }
+
+    var item *Item = nil
+    if len( value ) >= 1 {
+        item = NewItem( name, value[ "mime" ], []byte( value[ "data" ] ) )
+    }
+    return item, nil
+}
+
+
+func ( e *Persistent ) Show() ( []string, error ) {
+    ctx, cancel := context.WithTimeout( context.TODO(), e.timeout )
+    defer cancel()
+
+    var names []string
+    i := e.client.Scan( ctx, 0, "", 0 ).Iterator()
+    for i.Next( ctx ){
+        names = append( names, i.Val() )
+    }
+    if err := i.Err(); err != nil {
+        return nil, err
+    }
+    return names, nil
+}
+
+
+func ( e *Persistent ) Disconnect() error {
+    return e.client.Close()
+}
\ No newline at end of file
-- 
GitLab