From e67329117161081f94b9eeeccd2a279083961001 Mon Sep 17 00:00:00 2001
From: nart4hire <13520129@std.stei.itb.ac.id>
Date: Tue, 28 Feb 2023 23:46:33 +0700
Subject: [PATCH] feat(redis): Added rudimentary implementation

Can connect and add values, no thorough testing yet, added test route just to test it out
---
 .env                        |  3 +-
 .env.docker                 | 11 +++++++
 docker-compose.yml          |  2 +-
 go.mod                      |  7 ++++
 go.sum                      |  8 +++++
 handler/reset/test.go       | 21 ++++++++++++
 handler/reset/types.go      |  1 +
 model/domain/cache/cache.go | 50 ++++++++++++++++++++++++++++
 provider/di.go              |  5 +++
 provider/redis/cache.go     | 66 +++++++++++++++++++++++++++++++++++++
 provider/redis/type.go      |  7 ++++
 repository/cache/cache.go   | 57 ++++++++++++++++++++++++++++++++
 repository/cache/type.go    | 11 +++++++
 repository/di.go            |  5 +++
 routes/reset/route.go       |  1 +
 service/reset/impl.go       |  4 +++
 service/reset/request.go    | 16 +++++++--
 utils/env/env.go            |  3 ++
 18 files changed, 274 insertions(+), 4 deletions(-)
 create mode 100644 handler/reset/test.go
 create mode 100644 model/domain/cache/cache.go
 create mode 100644 provider/redis/cache.go
 create mode 100644 provider/redis/type.go
 create mode 100644 repository/cache/cache.go
 create mode 100644 repository/cache/type.go

diff --git a/.env b/.env
index 1f5f0f1..d4be505 100644
--- a/.env
+++ b/.env
@@ -7,4 +7,5 @@ LOG_FLUSH_INTERVAL_MS=1000
 DB_STRING="host=localhost user=ocw password=ocw dbname=ocw-db port=5433 sslmode=disable TimeZone=Asia/Shanghai"
 SMTP_USERNAME="noreply@ocw.id"
 SMTP_SERVER=localhost
-SMTP_PORT=1025
\ No newline at end of file
+SMTP_PORT=1025
+REDIS_STRING="localhost"
\ No newline at end of file
diff --git a/.env.docker b/.env.docker
index 11f1edf..2f87fea 100644
--- a/.env.docker
+++ b/.env.docker
@@ -1,3 +1,14 @@
 POSTGRES_USER=ocw
 POSTGRES_PASSWORD=ocw
 POSTGRES_DB=ocw-db
+ENV=DEVELOPMENT
+LISTEN_ADDR=0.0.0.0
+PORT=8080
+LOGTAIL_TOKEN=
+HTTP_TIMEOUT_SEC=2
+LOG_FLUSH_INTERVAL_MS=1000
+DB_STRING="host=database user=ocw password=ocw dbname=ocw-db port=5432 sslmode=disable TimeZone=Asia/Shanghai"
+SMTP_USERNAME="noreply@ocw.id"
+SMTP_SERVER=mailhog
+SMTP_PORT=1025
+REDIS_STRING="redis"
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index 7ccabe6..3eccf48 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -21,7 +21,7 @@ services:
       - .:/app
     ports:
       - 8888:8080
-    env_file: .env
+    env_file: .env.docker
     depends_on:
       - database
       - minio
diff --git a/go.mod b/go.mod
index b69d320..b6cf3e2 100644
--- a/go.mod
+++ b/go.mod
@@ -19,6 +19,11 @@ require (
 	gorm.io/gorm v1.24.5
 )
 
+require (
+	github.com/cespare/xxhash/v2 v2.1.2 // indirect
+	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+)
+
 require (
 	github.com/DATA-DOG/go-sqlmock v1.5.0 // indirect
 	github.com/KyleBanks/depth v1.2.1 // indirect
@@ -35,6 +40,8 @@ require (
 	github.com/go-openapi/swag v0.22.3 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/go-redis/redis/v8 v8.11.5
+	github.com/gomodule/redigo v1.8.9
 	github.com/jackc/pgpassfile v1.0.0 // indirect
 	github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
 	github.com/jackc/pgx/v5 v5.2.0 // indirect
diff --git a/go.sum b/go.sum
index d771a87..104db32 100644
--- a/go.sum
+++ b/go.sum
@@ -8,6 +8,8 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II=
 github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
+github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cosmtrek/air v1.41.0 h1:6ck2LbcVvby6cyuwE8ruia41U2nppMZGWOpq+E/EhoU=
 github.com/cosmtrek/air v1.41.0/go.mod h1:+RBGjJt7T2f3I7td8Tvk0XsH/hZ3E1QBLfiWObICO4c=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -16,6 +18,8 @@ github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr
 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/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
 github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
 github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
@@ -49,8 +53,12 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
 github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
+github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
+github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
 github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
 github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
+github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
 github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
diff --git a/handler/reset/test.go b/handler/reset/test.go
new file mode 100644
index 0000000..6ca8512
--- /dev/null
+++ b/handler/reset/test.go
@@ -0,0 +1,21 @@
+package reset
+
+import (
+	"fmt"
+	"net/http"
+
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/reset/request"
+)
+
+func (rs ResetHandlerImpl) Test(w http.ResponseWriter, r *http.Request) {
+	payload := request.RequestRequestPayload{Email: "test@test.com",}
+
+	err := rs.ResetService.Request(payload)
+
+	if err != nil {
+		fmt.Print("Oh no :)")
+	}
+
+	responsePayload := rs.WrapperUtil.SuccessResponseWrap(nil)
+	rs.HttpUtil.WriteSuccessJson(w, responsePayload)
+}
\ No newline at end of file
diff --git a/handler/reset/types.go b/handler/reset/types.go
index 7f0a8db..91fb286 100644
--- a/handler/reset/types.go
+++ b/handler/reset/types.go
@@ -6,4 +6,5 @@ type ResetHandler interface {
 	Request(w http.ResponseWriter, r *http.Request)
 	Confirm(w http.ResponseWriter, r *http.Request)
 	Validate(w http.ResponseWriter, r *http.Request)
+	Test(w http.ResponseWriter, r *http.Request)
 }
diff --git a/model/domain/cache/cache.go b/model/domain/cache/cache.go
new file mode 100644
index 0000000..1cab93c
--- /dev/null
+++ b/model/domain/cache/cache.go
@@ -0,0 +1,50 @@
+package cache
+
+import "fmt"
+
+type Cache struct {
+	Key             Key
+	Values          []Value
+	ExpiryInMinutes int
+}
+
+type Key struct {
+	Hash string
+	Id   string
+}
+
+type Value struct {
+	Field string
+	Store string
+}
+
+func (c *Cache) AppendValue(value Value) *Cache {
+	c.Values = append(c.Values, value)
+	return c
+}
+
+func NewKey(hash string, id string) *Key {
+	return &Key{hash, id}
+}
+
+func (k Key) String() string {
+	return fmt.Sprintf("%s:%s", k.Hash, k.Id)
+}
+
+func NewValue(field string, store string) *Value {
+	return &Value{field, store}
+}
+
+func NewCache(key Key, initValue Value, expiryInMinutes int) *Cache {
+	return &Cache{key, []Value{initValue}, expiryInMinutes}
+}
+
+func (c *Cache) Slice() ([]interface{}) {
+	slice := make([]interface{}, len(c.Values) * 2 + 1)
+	slice[0] = c.Key.String()
+	for i := range make([]int, len(c.Values)) {
+		slice[i * 2 + 1] = c.Values[i].Field
+		slice[i * 2 + 2] = c.Values[i].Store
+	}
+	return slice
+}
diff --git a/provider/di.go b/provider/di.go
index 51e100b..7b0746b 100644
--- a/provider/di.go
+++ b/provider/di.go
@@ -3,6 +3,7 @@ package provider
 import (
 	"github.com/google/wire"
 	"gitlab.informatika.org/ocw/ocw-backend/provider/db"
+	"gitlab.informatika.org/ocw/ocw-backend/provider/redis"
 	"gitlab.informatika.org/ocw/ocw-backend/provider/mail"
 	"gitlab.informatika.org/ocw/ocw-backend/provider/mail/smtp"
 )
@@ -22,4 +23,8 @@ var ProviderSet = wire.NewSet(
 	// Database utility
 	wire.Bind(new(db.Database), new(*db.DatabaseImpl)),
 	db.NewPostgresEnv,
+	
+	// Redis utility
+	wire.Bind(new(redis.Redis), new(*redis.RedisImpl)),
+	redis.NewRedisEnv,
 )
diff --git a/provider/redis/cache.go b/provider/redis/cache.go
new file mode 100644
index 0000000..2ffd506
--- /dev/null
+++ b/provider/redis/cache.go
@@ -0,0 +1,66 @@
+package redis
+
+import (
+	"fmt"
+	"os"
+	"runtime/debug"
+	"strings"
+	"time"
+
+	"github.com/gomodule/redigo/redis"
+	"gitlab.informatika.org/ocw/ocw-backend/service/logger"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/env"
+)
+
+type RedisImpl struct {
+	pool *redis.Pool
+}
+
+func resolver(log logger.Logger) {
+	if rec := recover(); rec != nil {
+		log.Error("Some panic occured when processing request:")
+		log.Error(fmt.Sprint(rec))
+		log.Error("")
+
+		log.Error("Stack Trace:")
+		stacks := strings.Split(string(debug.Stack()), "\n")
+
+		for _, val := range stacks {
+			log.Error(val)
+		}
+
+		os.Exit(-1)
+	}
+}
+
+func NewRedisEnv(
+	env *env.Environment,
+	log logger.Logger,
+) (*RedisImpl, error) {
+	return &RedisImpl{
+				&redis.Pool{
+					MaxIdle: 3,
+					IdleTimeout: 240 * time.Second,
+					Dial: func() (redis.Conn, error) {
+						defer resolver(log)
+						conn, err := redis.Dial("tcp", env.RedisConnection + ":" + env.RedisPort)
+						
+						if err != nil {
+							return nil, err
+						}
+
+						return conn, err
+					},
+					TestOnBorrow: func(c redis.Conn, t time.Time) error {
+						if time.Since(t) < time.Minute {
+						return nil
+						}
+						_, err := c.Do("PING")
+						return err
+					},
+				}}, nil
+}
+
+func (r RedisImpl) Pool() (*redis.Pool) {
+	return r.pool
+}
\ No newline at end of file
diff --git a/provider/redis/type.go b/provider/redis/type.go
new file mode 100644
index 0000000..c2b4e2c
--- /dev/null
+++ b/provider/redis/type.go
@@ -0,0 +1,7 @@
+package redis
+
+import "github.com/gomodule/redigo/redis"
+
+type Redis interface {
+	Pool() *redis.Pool
+}
\ No newline at end of file
diff --git a/repository/cache/cache.go b/repository/cache/cache.go
new file mode 100644
index 0000000..80b61d2
--- /dev/null
+++ b/repository/cache/cache.go
@@ -0,0 +1,57 @@
+package cache
+
+import (
+	"github.com/gomodule/redigo/redis"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/cache"
+	rd "gitlab.informatika.org/ocw/ocw-backend/provider/redis"
+)
+
+type CacheRepositoryImpl struct {
+	pool *redis.Pool
+}
+
+func New(
+	cache rd.Redis,
+) *CacheRepositoryImpl {
+	return &CacheRepositoryImpl{cache.Pool()}
+}
+
+func (c CacheRepositoryImpl) Get(cache cache.Cache, field string) (string, error) {
+	conn := c.pool.Get()
+	defer conn.Close()
+
+	value, err := redis.String(conn.Do("HGET", cache.Key.String(), field))
+
+	if err != nil {
+		return "", err
+	}
+
+	return value, nil
+}
+
+func (c CacheRepositoryImpl) GetAll(cache cache.Cache) (map[string]string, error) {
+	conn := c.pool.Get()
+	defer conn.Close()
+
+	value, err := redis.StringMap(conn.Do("HGETALL", cache.Key.String()))
+	
+	if err != nil {
+		return nil, err
+	}
+
+	return value, nil
+}
+
+func (c CacheRepositoryImpl) Set(cache cache.Cache) error {
+	conn := c.pool.Get()
+	defer conn.Close()
+
+	slice := cache.Slice()
+	_, err := conn.Do("HSET", slice...)
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
\ No newline at end of file
diff --git a/repository/cache/type.go b/repository/cache/type.go
new file mode 100644
index 0000000..97b3691
--- /dev/null
+++ b/repository/cache/type.go
@@ -0,0 +1,11 @@
+package cache
+
+import (
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/cache"
+)
+
+type CacheRepository interface {
+	Get(cache cache.Cache, field string) (string, error)
+	GetAll(cache cache.Cache) (map[string]string, error)
+	Set(cache cache.Cache) error
+}
diff --git a/repository/di.go b/repository/di.go
index ccec959..3a1d5b9 100644
--- a/repository/di.go
+++ b/repository/di.go
@@ -3,12 +3,17 @@ package repository
 import (
 	"github.com/google/wire"
 	"gitlab.informatika.org/ocw/ocw-backend/repository/user"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/cache"
 )
 
 var RepositoryBasicSet = wire.NewSet(
 	// User Repository
 	user.New,
 	wire.Bind(new(user.UserRepository), new(*user.UserRepositoryImpl)),
+
+	// Cache Repository
+	cache.New,
+	wire.Bind(new(cache.CacheRepository), new(*cache.CacheRepositoryImpl)),
 )
 
 var RepositorySet = wire.NewSet(
diff --git a/routes/reset/route.go b/routes/reset/route.go
index 46b32e0..e225524 100644
--- a/routes/reset/route.go
+++ b/routes/reset/route.go
@@ -14,5 +14,6 @@ func (rr ResetRoutes) Register(r chi.Router) {
 		r.Post("/request", rr.ResetHandler.Request)
 		r.Post("/confirm", rr.ResetHandler.Confirm)
 		r.Post("/validate", rr.ResetHandler.Validate)
+		r.Get("/test", rr.ResetHandler.Test)
 	})
 }
diff --git a/service/reset/impl.go b/service/reset/impl.go
index b9986e2..ac2d214 100644
--- a/service/reset/impl.go
+++ b/service/reset/impl.go
@@ -2,7 +2,9 @@ package reset
 
 import (
 	"gitlab.informatika.org/ocw/ocw-backend/repository/user"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/cache"
 	"gitlab.informatika.org/ocw/ocw-backend/service/verification"
+	"gitlab.informatika.org/ocw/ocw-backend/service/logger"
 	"gitlab.informatika.org/ocw/ocw-backend/utils/env"
 	"gitlab.informatika.org/ocw/ocw-backend/utils/password"
 	"gitlab.informatika.org/ocw/ocw-backend/utils/token"
@@ -10,8 +12,10 @@ import (
 
 type ResetServiceImpl struct {
 	user.UserRepository
+	cache.CacheRepository
 	password.PasswordUtil
 	*env.Environment
 	token.TokenUtil
 	verification.VerificationService
+	logger.Logger
 }
diff --git a/service/reset/request.go b/service/reset/request.go
index 05584e6..1b90703 100644
--- a/service/reset/request.go
+++ b/service/reset/request.go
@@ -1,11 +1,23 @@
 package reset
 
 import (
-	// "gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/cache"
 	"gitlab.informatika.org/ocw/ocw-backend/model/web/reset/request"
 )
 
 func (rs ResetServiceImpl) Request(payload request.RequestRequestPayload) error {
-	// TODO replace dummy
+	c := cache.NewCache(*cache.NewKey("Test", "123"), *cache.NewValue("Test", "123"), 30)
+	c.AppendValue(*cache.NewValue("Hello", "World"))
+
+	err := rs.CacheRepository.Set(*c)
+	if err != nil {
+		panic(err)
+	}
+
+	_, err = rs.CacheRepository.Get(*c, "Test")
+	if err != nil {
+		panic(err)
+	}
+	
 	return nil
 }
\ No newline at end of file
diff --git a/utils/env/env.go b/utils/env/env.go
index e1b914f..e688d94 100644
--- a/utils/env/env.go
+++ b/utils/env/env.go
@@ -40,6 +40,9 @@ type Environment struct {
 	FrontendBaseURL       string `env:"FE_BASE_URL"`
 	ResetPasswordPath     string `env:"RESET_PASSWORD_PATH" envDefault:""`
 	EmailVerificationPath string `env:"EMAIL_VERIFICATION_PATH" envDefault:""`
+
+	RedisConnection string `env:"REDIS_STRING"`
+	RedisPort string `env:"REDIS_PORT" envDefault:"6379"`
 }
 
 func New() (*Environment, error) {
-- 
GitLab