Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ jobs:
build:
docker:
- image: cimg/go:1.18

- image: circleci/redis:4.0.9-alpine
- image: cimg/mysql:8.0
environment:
MYSQL_DATABASE: test_db
Expand Down Expand Up @@ -49,6 +49,9 @@ jobs:
done
echo Failed waiting for MySQL && exit 1

- run:
name: Waiting for Redis primary to be ready
command: dockerize -wait tcp://localhost:6379 -timeout 1m
- run:
name: Initialize firebase service account key file
command: |
Expand Down
45 changes: 43 additions & 2 deletions api/controllers/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ import (
"boilerplate-api/infrastructure"
"boilerplate-api/responses"
"boilerplate-api/url_query"
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"

"github.com/gin-gonic/gin"

"gorm.io/gorm"
)
Expand All @@ -20,6 +23,7 @@ type UserController struct {
logger infrastructure.Logger
userService services.UserService
env infrastructure.Env
redisClient infrastructure.Redis
validator validators.UserValidator
}

Expand All @@ -29,12 +33,14 @@ func NewUserController(
userService services.UserService,
env infrastructure.Env,
validator validators.UserValidator,
redisClient infrastructure.Redis,
) UserController {
return UserController{
logger: logger,
userService: userService,
env: env,
validator: validator,
redisClient: redisClient,
}
}

Expand Down Expand Up @@ -122,6 +128,24 @@ func (cc UserController) GetAllUsers(c *gin.Context) {
return
}

// Redis Cache
endpoint := c.Request.URL

cachedKey := endpoint.String()

response := map[string]interface{}{
"data": users,
"count": count,
}

mr, _ := json.Marshal(&response)

cacheErr := cc.redisClient.RedisClient.Set(cachedKey, mr, cc.env.RedisCacheTime).Err()
if cacheErr != nil {
responses.HandleError(c, cacheErr)
return
}

responses.JSONCount(c, http.StatusOK, users, count)
}

Expand All @@ -145,5 +169,22 @@ func (cc UserController) GetUserProfile(c *gin.Context) {
return
}

responses.JSON(c, http.StatusOK, user)
// Redis Cache
endpoint := c.Request.URL

cachedKey := endpoint.String()

response := map[string]interface{}{
"data": user,
}

mr, _ := json.Marshal(&response)

cacheErr := cc.redisClient.RedisClient.Set(cachedKey, mr, 10*time.Second).Err()
if cacheErr != nil {
responses.HandleError(c, cacheErr)
return
}

responses.JSON(c, http.StatusOK, response)
}
1 change: 1 addition & 0 deletions api/middlewares/middlewares.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var Module = fx.Options(
fx.Provide(NewDBTransactionMiddleware),
fx.Provide(NewJWTAuthMiddleWare),
fx.Provide(NewRateLimitMiddleware),
fx.Provide(NewRedisMiddleware),
)

// IMiddleware middleware interface
Expand Down
52 changes: 52 additions & 0 deletions api/middlewares/redis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package middlewares

import (
"boilerplate-api/infrastructure"
"boilerplate-api/responses"
"encoding/json"
"net/http"

"github.com/gin-gonic/gin"
)

type RedisMiddleware struct {
logger infrastructure.Logger
env infrastructure.Env
redisClient infrastructure.Redis
}

func NewRedisMiddleware(
logger infrastructure.Logger,
env infrastructure.Env,
redisClient infrastructure.Redis,
) RedisMiddleware {
return RedisMiddleware{
logger: logger,
env: env,
redisClient: redisClient,
}
}

// Verify Redis Cache
func (m RedisMiddleware) VerifyRedisCache() gin.HandlerFunc {
return func(c *gin.Context) {

endpoint := c.Request.URL

cachedKey := endpoint.String()

val, err := m.redisClient.RedisClient.Get(cachedKey).Bytes()
if err != nil {
c.Next()
return
}
responseBody := map[string]interface{}{}

json.Unmarshal(val, &responseBody)

responses.InterfaceJson(c, http.StatusOK, responseBody)
// Abort other chained middlewares since we already get the response here.
c.Abort()
}

}
7 changes: 5 additions & 2 deletions api/routes/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type UserRoutes struct {
jwtMiddleware middlewares.JWTAuthMiddleWare
trxMiddleware middlewares.DBTransactionMiddleware
rateLimitMiddleware middlewares.RateLimitMiddleware
redisMiddleware middlewares.RedisMiddleware
}

// NewUserRoutes creates new user controller
Expand All @@ -27,6 +28,7 @@ func NewUserRoutes(
jwtMiddleware middlewares.JWTAuthMiddleWare,
trxMiddleware middlewares.DBTransactionMiddleware,
rateLimitMiddleware middlewares.RateLimitMiddleware,
redisMiddleware middlewares.RedisMiddleware,
) UserRoutes {
return UserRoutes{
router: router,
Expand All @@ -36,6 +38,7 @@ func NewUserRoutes(
jwtMiddleware: jwtMiddleware,
trxMiddleware: trxMiddleware,
rateLimitMiddleware: rateLimitMiddleware,
redisMiddleware: redisMiddleware,
}
}

Expand All @@ -44,8 +47,8 @@ func (i UserRoutes) Setup() {
i.logger.Zap.Info(" Setting up user routes")
users := i.router.Gin.Group("/users").Use(i.rateLimitMiddleware.HandleRateLimit(constants.BasicRateLimit, constants.BasicPeriod))
{
users.GET("", i.userController.GetAllUsers)
users.GET("", i.redisMiddleware.VerifyRedisCache(), i.userController.GetAllUsers)
users.POST("", i.trxMiddleware.DBTransactionHandle(), i.userController.CreateUser)
}
i.router.Gin.GET("/profile", i.jwtMiddleware.Handle(), i.userController.GetUserProfile)
i.router.Gin.GET("/profile", i.jwtMiddleware.Handle(), i.redisMiddleware.VerifyRedisCache(), i.userController.GetUserProfile)
}
13 changes: 12 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
version: "3.3"

services:
redis:
container_name: "redis"
image: redis:alpine
# Specify the redis.conf file to use and add a password.
command: redis-server /usr/local/etc/redis/redis.conf --requirepass ${REDIS_PASSWORD}
ports:
- "6379:6379"
# map the volumes to that redis has the custom conf file from this project.
volumes:
- $PWD/redis.conf:/usr/local/etc/redis/redis.conf

web:
build:
context: .
Expand Down Expand Up @@ -28,7 +39,7 @@ services:
[
"--character-set-server=utf8mb4",
"--collation-server=utf8mb4_unicode_ci",
"--default-authentication-plugin=mysql_native_password",
"--default-authentication-plugin=mysql_native_password"
]
ports:
- 33066:3306
Expand Down
2 changes: 2 additions & 0 deletions docker/web.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ RUN go install github.com/go-delve/delve/cmd/dlv@latest

COPY . /clean_web

RUN go get github.com/go-redis/redis

ARG VERSION="4.13.0"

RUN set -x \
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ require (
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-redis/redis v6.15.9+incompatible // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,8 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
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 v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
Expand Down
3 changes: 3 additions & 0 deletions infrastructure/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ type Env struct {
BudgetDisplayName string `mapstructure:"BUDGET_DISPLAY_NAME"`
BudgetAmount int64 `mapstructure:"BUDGET_AMOUNT"`
SetBudget int `mapstructure:"SET_BUDGET"`

RedisPassword string `mapstructure:"REDIS_PASSWORD"`
RedisCacheTime time.Duration `mapstructure:"REDIS_CACHE_TIME"`
}

// NewEnv creates a new environment
Expand Down
1 change: 1 addition & 0 deletions infrastructure/infrastructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ var Module = fx.Options(
fx.Provide(NewAWSConfig),
fx.Provide(NewS3Client),
fx.Provide(NewGCPBilling),
fx.Provide(NewRedis),
)
27 changes: 27 additions & 0 deletions infrastructure/redis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package infrastructure

import (
"github.com/go-redis/redis"
)

// Redis
type Redis struct {
RedisClient redis.Client
}

func NewRedis(logger Logger, env Env) Redis {

var client = redis.NewClient(&redis.Options{
// Container name + port since we are using docker
Addr: "redis:6379",
Password: env.RedisPassword,
})

if client == nil {
logger.Zap.Fatalf("Cannot run redis")
}

return Redis{
RedisClient: *client,
}
}
7 changes: 7 additions & 0 deletions redis.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Required
##########

# Set a memory usage limit to the specified amount of bytes.
# When the memory limit is reached Redis will try to remove keys
# according to the eviction policy selected (see maxmemory-policy).
maxmemory 41943040