10 files changed,
250 insertions(+),
47 deletions(-)
Author:
Smirnov Oleksandr
ss2316544@gmail.com
Committed by:
GitHub
noreply@github.com
Committed at:
2024-10-27 12:51:04 +0200
Parent:
9d9e251
M
.env.example
··· 20 20 POSTGRESQL_DSN="postgres://$POSTGRES_USERNAME:$POSTGRES_PASSWORD@$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DATABASE?sslmode=disable" 21 21 MIGRATION_DSN="postgres://$POSTGRES_USERNAME:$POSTGRES_PASSWORD@localhost:$POSTGRES_PORT/$POSTGRES_DATABASE?sslmode=disable" 22 22 23 +REDIS_ADDR="redis:6379" 24 +CACHE_USERS_TTL=1h 25 + 23 26 MAILGUN_FROM=onasty@mail.com 24 27 MAILGUN_DOMAI='<domain>' 25 28 MAILGUN_API_KEY='<token>'
M
cmd/server/main.go
··· 23 23 "github.com/olexsmir/onasty/internal/store/psql/userepo" 24 24 "github.com/olexsmir/onasty/internal/store/psql/vertokrepo" 25 25 "github.com/olexsmir/onasty/internal/store/psqlutil" 26 + "github.com/olexsmir/onasty/internal/store/rdb" 27 + "github.com/olexsmir/onasty/internal/store/rdb/usercache" 26 28 httptransport "github.com/olexsmir/onasty/internal/transport/http" 27 29 "github.com/olexsmir/onasty/internal/transport/http/httpserver" 28 30 "github.com/olexsmir/onasty/internal/transport/http/ratelimit" ··· 57 59 58 60 // app deps 59 61 psqlDB, err := psqlutil.Connect(ctx, cfg.PostgresDSN) 62 + if err != nil { 63 + return err 64 + } 65 + 66 + redisDB, err := rdb.Connect(ctx, cfg.RedisAddr, cfg.RedisPassword, cfg.RedisDB) 60 67 if err != nil { 61 68 return err 62 69 } ··· 69 76 vertokrepo := vertokrepo.New(psqlDB) 70 77 71 78 userepo := userepo.New(psqlDB) 79 + usercache := usercache.New(redisDB, cfg.CacheUsersTTL) 72 80 usersrv := usersrv.New( 73 81 userepo, 74 82 sessionrepo, ··· 76 84 sha256Hasher, 77 85 jwtTokenizer, 78 86 mailGunMailer, 87 + usercache, 79 88 cfg.JwtRefreshTokenTTL, 80 89 cfg.VerificationTokenTTL, 81 90 cfg.AppURL, ··· 127 136 128 137 if err := psqlDB.Close(); err != nil { 129 138 return errors.Join(errors.New("failed to close postgres connection"), err) 139 + } 140 + 141 + if err := redisDB.Close(); err != nil { 142 + return errors.Join(errors.New("failed to close redis connection"), err) 130 143 } 131 144 132 145 return nil
M
e2e/e2e_test.go
··· 25 25 "github.com/olexsmir/onasty/internal/store/psql/userepo" 26 26 "github.com/olexsmir/onasty/internal/store/psql/vertokrepo" 27 27 "github.com/olexsmir/onasty/internal/store/psqlutil" 28 + "github.com/olexsmir/onasty/internal/store/rdb" 29 + "github.com/olexsmir/onasty/internal/store/rdb/usercache" 28 30 httptransport "github.com/olexsmir/onasty/internal/transport/http" 29 31 "github.com/olexsmir/onasty/internal/transport/http/ratelimit" 32 + "github.com/redis/go-redis/v9" 30 33 "github.com/stretchr/testify/require" 31 34 "github.com/stretchr/testify/suite" 32 35 "github.com/testcontainers/testcontainers-go" 33 - "github.com/testcontainers/testcontainers-go/modules/postgres" 36 + tcpostgres "github.com/testcontainers/testcontainers-go/modules/postgres" 37 + tcredis "github.com/testcontainers/testcontainers-go/modules/redis" 34 38 "github.com/testcontainers/testcontainers-go/wait" 35 39 36 40 _ "github.com/golang-migrate/migrate/v4/source/file" 37 41 ) 38 42 39 43 type ( 40 - stopDBFunc func() 44 + stopFunc func() 41 45 AppTestSuite struct { 42 46 suite.Suite 43 47 ··· 45 49 require *require.Assertions 46 50 47 51 postgresDB *psqlutil.DB 48 - stopPostgres stopDBFunc 52 + stopPostgres stopFunc 53 + 54 + redisDB *rdb.DB 55 + stopRedis stopFunc 49 56 50 57 router http.Handler 51 58 hasher hasher.Hasher ··· 72 79 e.ctx = context.Background() 73 80 e.require = e.Require() 74 81 75 - db, stop, err := e.prepPostgres() 76 - e.require.NoError(err) 77 - 78 - e.postgresDB = db 79 - e.stopPostgres = stop 82 + e.postgresDB, e.stopPostgres = e.prepPostgres() 83 + e.redisDB, e.stopRedis = e.prepRedis() 80 84 81 85 e.initDeps() 82 86 } 83 87 84 88 func (e *AppTestSuite) TearDownSuite() { 85 89 e.stopPostgres() 90 + e.stopRedis() 86 91 } 87 92 88 93 // initDeps initializes the dependencies for the app ··· 103 108 vertokrepo := vertokrepo.New(e.postgresDB) 104 109 105 110 userepo := userepo.New(e.postgresDB) 111 + usercache := usercache.New(e.redisDB, cfg.CacheUsersTTL) 106 112 usersrv := usersrv.New( 107 113 userepo, 108 114 sessionrepo, ··· 110 116 e.hasher, 111 117 e.jwtTokenizer, 112 118 e.mailer, 119 + usercache, 113 120 cfg.JwtRefreshTokenTTL, 114 121 cfg.VerificationTokenTTL, 115 122 cfg.AppURL, ··· 129 136 e.router = handler.Handler() 130 137 } 131 138 132 -func (e *AppTestSuite) prepPostgres() (*psqlutil.DB, stopDBFunc, error) { 139 +func (e *AppTestSuite) prepPostgres() (*psqlutil.DB, stopFunc) { 133 140 dbCredential := "testing" 134 - postgresContainer, err := postgres.Run(e.ctx, 141 + postgresContainer, err := tcpostgres.Run(e.ctx, 135 142 "postgres:16-alpine", 136 - postgres.WithUsername(dbCredential), 137 - postgres.WithPassword(dbCredential), 138 - postgres.WithDatabase(dbCredential), 143 + tcpostgres.WithUsername(dbCredential), 144 + tcpostgres.WithPassword(dbCredential), 145 + tcpostgres.WithDatabase(dbCredential), 139 146 testcontainers.WithWaitStrategy(wait.ForListeningPort("5432/tcp"))) 140 147 e.require.NoError(err) 141 148 142 - stop := func() { 143 - err = postgresContainer.Terminate(e.ctx) 144 - e.require.NoError(err) 145 - } 149 + stop := func() { e.require.NoError(postgresContainer.Terminate(e.ctx)) } 146 150 147 151 // connect to the db 148 152 host, err := postgresContainer.Host(e.ctx) ··· 151 155 port, err := postgresContainer.MappedPort(e.ctx, "5432/tcp") 152 156 e.require.NoError(err) 153 157 154 - db, err := psqlutil.Connect( 155 - e.ctx, 156 - fmt.Sprintf( //nolint:nosprintfhostport 157 - "postgres://%s:%s@%s:%s/%s", 158 - dbCredential, 159 - dbCredential, 160 - host, 161 - port.Port(), 162 - dbCredential, 163 - ), 164 - ) 158 + db, err := psqlutil.Connect(e.ctx, fmt.Sprintf( //nolint:nosprintfhostport 159 + "postgres://%s:%s@%s:%s/%s", 160 + dbCredential, 161 + dbCredential, 162 + host, 163 + port.Port(), 164 + dbCredential, 165 + )) 165 166 e.require.NoError(err) 166 167 167 168 // run migrations ··· 175 176 ) 176 177 e.require.NoError(err) 177 178 178 - err = m.Up() 179 + e.require.NoError(m.Up()) 180 + e.require.NoError(driver.Close()) 181 + 182 + return db, stop 183 +} 184 + 185 +func (e *AppTestSuite) prepRedis() (*rdb.DB, stopFunc) { 186 + redisContainer, err := tcredis.Run(e.ctx, "redis:7.4-alpine") 179 187 e.require.NoError(err) 180 188 181 - return db, stop, driver.Close() 189 + stop := func() { e.require.NoError(redisContainer.Terminate(e.ctx)) } 190 + 191 + uri, err := redisContainer.ConnectionString(e.ctx) 192 + e.require.NoError(err) 193 + 194 + connOpts, err := redis.ParseURL(uri) 195 + e.require.NoError(err) 196 + 197 + redis, err := rdb.Connect(e.ctx, connOpts.Addr, connOpts.Password, connOpts.DB) 198 + e.require.NoError(err) 199 + 200 + return redis, stop 182 201 } 183 202 184 203 func (e *AppTestSuite) getConfig() *config.Config { ··· 194 213 LogShowLine: os.Getenv("LOG_SHOW_LINE") == "true", 195 214 LogFormat: "text", 196 215 LogLevel: "debug", 216 + CacheUsersTTL: time.Second, 197 217 } 198 218 }
M
go.mod
··· 12 12 github.com/jackc/pgx/v5 v5.7.1 13 13 github.com/mailgun/mailgun-go/v4 v4.16.0 14 14 github.com/prometheus/client_golang v1.20.5 15 + github.com/redis/go-redis/v9 v9.7.0 15 16 github.com/stretchr/testify v1.9.0 16 - github.com/testcontainers/testcontainers-go v0.33.0 17 - github.com/testcontainers/testcontainers-go/modules/postgres v0.33.0 17 + github.com/testcontainers/testcontainers-go v0.34.0 18 + github.com/testcontainers/testcontainers-go/modules/postgres v0.34.0 19 + github.com/testcontainers/testcontainers-go/modules/redis v0.34.0 18 20 golang.org/x/time v0.7.0 19 21 ) 20 22 ··· 32 34 github.com/cloudwego/iasm v0.2.0 // indirect 33 35 github.com/containerd/log v0.1.0 // indirect 34 36 github.com/containerd/platforms v0.2.1 // indirect 35 - github.com/cpuguy83/dockercfg v0.3.1 // indirect 37 + github.com/cpuguy83/dockercfg v0.3.2 // indirect 36 38 github.com/davecgh/go-spew v1.1.1 // indirect 39 + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 37 40 github.com/distribution/reference v0.6.0 // indirect 38 41 github.com/docker/docker v27.2.0+incompatible // indirect 39 42 github.com/docker/go-connections v0.5.0 // indirect
M
go.sum
··· 13 13 github.com/ahmetb/go-linq v3.0.0+incompatible/go.mod h1:PFffvbdbtw+QTB0WKRP0cNht7vnCfnGlEpak/DVg5cY= 14 14 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 15 15 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 16 +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= 17 +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= 18 +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= 19 +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 16 20 github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= 17 21 github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= 18 22 github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= ··· 33 37 github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= 34 38 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 35 39 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 36 -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= 37 -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= 40 +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= 41 +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= 38 42 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 39 43 github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= 40 44 github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 41 45 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 42 46 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 43 47 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 48 +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 49 +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 44 50 github.com/dhui/dktest v0.4.3 h1:wquqUxAFdcUgabAVLvSCOKOlag5cIZuaOjYIBOWdsR0= 45 51 github.com/dhui/dktest v0.4.3/go.mod h1:zNK8IwktWzQRm6I/l2Wjp7MakiyaFWv4G1hjmodmMTs= 46 52 github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= ··· 84 90 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 85 91 github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= 86 92 github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 93 +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= 94 +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= 87 95 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 88 96 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 89 97 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= ··· 256 264 github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= 257 265 github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 258 266 github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 267 +github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= 268 +github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= 259 269 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 260 270 github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 261 271 github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= ··· 281 291 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 282 292 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 283 293 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 294 +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 284 295 github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 285 296 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 286 297 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= ··· 293 304 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 294 305 github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 295 306 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 296 -github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5UMw/kRiISng037Gxdw= 297 -github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8= 298 -github.com/testcontainers/testcontainers-go/modules/postgres v0.33.0 h1:c+Gt+XLJjqFAejgX4hSpnHIpC9eAhvgI/TFWL/PbrFI= 299 -github.com/testcontainers/testcontainers-go/modules/postgres v0.33.0/go.mod h1:I4DazHBoWDyf69ByOIyt3OdNjefiUx372459txOpQ3o= 307 +github.com/testcontainers/testcontainers-go v0.34.0 h1:5fbgF0vIN5u+nD3IWabQwRybuB4GY8G2HHgCkbMzMHo= 308 +github.com/testcontainers/testcontainers-go v0.34.0/go.mod h1:6P/kMkQe8yqPHfPWNulFGdFHTD8HB2vLq/231xY2iPQ= 309 +github.com/testcontainers/testcontainers-go/modules/postgres v0.34.0 h1:c51aBXT3v2HEBVarmaBnsKzvgZjC5amn0qsj8Naqi50= 310 +github.com/testcontainers/testcontainers-go/modules/postgres v0.34.0/go.mod h1:EWP75ogLQU4M4L8U+20mFipjV4WIR9WtlMXSB6/wiuc= 311 +github.com/testcontainers/testcontainers-go/modules/redis v0.34.0 h1:HkkKZPi6W2I+ywqplvnKOYRBKXQgpdxErBbdgx8F8nw= 312 +github.com/testcontainers/testcontainers-go/modules/redis v0.34.0/go.mod h1:iUkbN75F4E8WC5C1MfHbGOHOuKU7gOJfHjtwMT8G9QE= 300 313 github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= 301 314 github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= 302 315 github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
M
internal/config/config.go
··· 8 8 ) 9 9 10 10 type Config struct { 11 - AppEnv string 12 - AppURL string 13 - ServerPort string 11 + AppEnv string 12 + AppURL string 13 + ServerPort string 14 + 14 15 PostgresDSN string 15 16 PasswordSalt string 17 + 18 + RedisAddr string 19 + RedisPassword string 20 + RedisDB int 21 + 22 + CacheUsersTTL time.Duration 16 23 17 24 JwtSigningKey string 18 25 JwtAccessTokenTTL time.Duration ··· 37 44 38 45 func NewConfig() *Config { 39 46 return &Config{ 40 - AppEnv: getenvOrDefault("APP_ENV", "debug"), 41 - AppURL: getenvOrDefault("APP_URL", ""), 42 - ServerPort: getenvOrDefault("SERVER_PORT", "3000"), 47 + AppEnv: getenvOrDefault("APP_ENV", "debug"), 48 + AppURL: getenvOrDefault("APP_URL", ""), 49 + ServerPort: getenvOrDefault("SERVER_PORT", "3000"), 50 + 43 51 PostgresDSN: getenvOrDefault("POSTGRESQL_DSN", ""), 44 52 PasswordSalt: getenvOrDefault("PASSWORD_SALT", ""), 53 + 54 + RedisAddr: getenvOrDefault("REDIS_ADDR", ""), 55 + RedisPassword: getenvOrDefault("REDIS_PASSWORD", ""), 56 + RedisDB: mustGetenvOrDefaultInt(getenvOrDefault("REDIS_DB", "0"), 0), 57 + 58 + CacheUsersTTL: mustParseDuration(getenvOrDefault("CACHE_USERS_TTL", "1h")), 45 59 46 60 JwtSigningKey: getenvOrDefault("JWT_SIGNING_KEY", ""), 47 61 JwtAccessTokenTTL: mustParseDuration(
M
internal/service/usersrv/usersrv.go
··· 3 3 import ( 4 4 "context" 5 5 "errors" 6 + "log/slog" 6 7 "time" 7 8 8 9 "github.com/gofrs/uuid/v5" ··· 14 15 "github.com/olexsmir/onasty/internal/store/psql/sessionrepo" 15 16 "github.com/olexsmir/onasty/internal/store/psql/userepo" 16 17 "github.com/olexsmir/onasty/internal/store/psql/vertokrepo" 18 + "github.com/olexsmir/onasty/internal/store/rdb/usercache" 17 19 "github.com/olexsmir/onasty/internal/transport/http/reqid" 18 20 ) 19 21 ··· 43 45 hasher hasher.Hasher 44 46 jwtTokenizer jwtutil.JWTTokenizer 45 47 mailer mailer.Mailer 48 + cache usercache.UserCacheer 46 49 47 50 refreshTokenTTL time.Duration 48 51 verificationTokenTTL time.Duration ··· 56 59 hasher hasher.Hasher, 57 60 jwtTokenizer jwtutil.JWTTokenizer, 58 61 mailer mailer.Mailer, 62 + cache usercache.UserCacheer, 59 63 refreshTokenTTL, verificationTokenTTL time.Duration, 60 64 appURL string, 61 65 ) *UserSrv { ··· 66 70 hasher: hasher, 67 71 jwtTokenizer: jwtTokenizer, 68 72 mailer: mailer, 73 + cache: cache, 69 74 refreshTokenTTL: refreshTokenTTL, 70 75 verificationTokenTTL: verificationTokenTTL, 71 76 appURL: appURL, ··· 227 232 } 228 233 229 234 func (u UserSrv) CheckIfUserExists(ctx context.Context, id uuid.UUID) (bool, error) { 230 - return u.userstore.CheckIfUserExists(ctx, id) 235 + if r, err := u.cache.GetIsExists(ctx, id.String()); err == nil { 236 + return r, nil 237 + } else { //nolint:revive 238 + slog.ErrorContext(ctx, "usercache", "err", err) 239 + } 240 + 241 + isExists, err := u.userstore.CheckIfUserExists(ctx, id) 242 + if err != nil { 243 + return false, err 244 + } 245 + 246 + if err := u.cache.SetIsExists(ctx, id.String(), isExists); err != nil { 247 + slog.Error("usercache", "err", err) 248 + } 249 + 250 + return isExists, nil 231 251 } 232 252 233 253 func (u UserSrv) CheckIfUserIsActivated(ctx context.Context, userID uuid.UUID) (bool, error) { 234 - return u.userstore.CheckIfUserIsActivated(ctx, userID) 254 + if r, err := u.cache.GetIsActivated(ctx, userID.String()); err == nil { 255 + return r, nil 256 + } else { //nolint:revive 257 + slog.ErrorContext(ctx, "usercache", "err", err) 258 + } 259 + 260 + isActivated, err := u.userstore.CheckIfUserExists(ctx, userID) 261 + if err != nil { 262 + return false, err 263 + } 264 + 265 + if err := u.cache.SetIsActivated(ctx, userID.String(), isActivated); err != nil { 266 + slog.Error("usercache", "err", err) 267 + } 268 + 269 + return isActivated, nil 235 270 } 236 271 237 272 func (u UserSrv) getTokens(userID uuid.UUID) (dtos.TokensDTO, error) {
A
internal/store/rdb/rdb.go
··· 1 +package rdb 2 + 3 +import ( 4 + "context" 5 + 6 + "github.com/redis/go-redis/v9" 7 +) 8 + 9 +type DB struct{ *redis.Client } 10 + 11 +func Connect(ctx context.Context, addr, password string, db int) (*DB, error) { 12 + client := redis.NewClient(&redis.Options{ //nolint:exhaustruct 13 + Addr: addr, 14 + Password: password, 15 + DB: db, 16 + }) 17 + 18 + _, err := client.Ping(ctx).Result() 19 + if err != nil { 20 + return nil, err 21 + } 22 + 23 + return &DB{Client: client}, nil 24 +}
A
internal/store/rdb/usercache/usercache.go
··· 1 +package usercache 2 + 3 +import ( 4 + "context" 5 + "strings" 6 + "time" 7 + 8 + "github.com/olexsmir/onasty/internal/store/rdb" 9 +) 10 + 11 +type UserCacheer interface { 12 + SetIsExists(ctx context.Context, userID string, isExists bool) error 13 + GetIsExists(ctx context.Context, userID string) (isExists bool, err error) 14 + 15 + SetIsActivated(ctx context.Context, userID string, isActivated bool) error 16 + GetIsActivated(ctx context.Context, userID string) (isActivated bool, err error) 17 +} 18 + 19 +var _ UserCacheer = (*UserCache)(nil) 20 + 21 +type UserCache struct { 22 + rdb *rdb.DB 23 + ttl time.Duration 24 +} 25 + 26 +func New(rdb *rdb.DB, ttl time.Duration) *UserCache { 27 + return &UserCache{ 28 + rdb: rdb, 29 + ttl: ttl, 30 + } 31 +} 32 + 33 +func (u *UserCache) SetIsExists(ctx context.Context, userID string, val bool) error { 34 + _, err := u.rdb. 35 + Set(ctx, getKey("exists", userID), val, u.ttl). 36 + Result() 37 + return err 38 +} 39 + 40 +func (u *UserCache) GetIsExists(ctx context.Context, userID string) (bool, error) { 41 + res, err := u.rdb.Get(ctx, getKey(userID, "exists")).Bool() 42 + if err != nil { 43 + return false, err 44 + } 45 + 46 + return res, nil 47 +} 48 + 49 +func (u *UserCache) SetIsActivated(ctx context.Context, userID string, val bool) error { 50 + _, err := u.rdb. 51 + Set(ctx, getKey("activated", userID), val, u.ttl). 52 + Result() 53 + return err 54 +} 55 + 56 +func (u *UserCache) GetIsActivated(ctx context.Context, userID string) (bool, error) { 57 + res, err := u.rdb.Get(ctx, getKey(userID, "activated")).Bool() 58 + if err != nil { 59 + return false, err 60 + } 61 + return res, nil 62 +} 63 + 64 +// getKey return a key for redis in this format user:<userID>:<key> 65 +func getKey(userID, key string) string { 66 + var sb strings.Builder 67 + sb.WriteString("user:") 68 + sb.WriteString(userID) 69 + sb.WriteString(":") 70 + sb.WriteString(key) 71 + return sb.String() 72 +}