From ef95e3bfbead59b8d2b23f09a04b6032dfb9076d Mon Sep 17 00:00:00 2001 From: pahmiudahgede Date: Wed, 22 Jan 2025 12:20:51 +0700 Subject: [PATCH] feat: add caching control with redis --- config/connection.go | 29 ++++++++ go.mod | 3 + go.sum | 6 ++ internal/api/routes.go | 1 + internal/controllers/auth.go | 44 +++++++++++++ internal/controllers/userpin.go | 110 ++++++++++++++++++++++++------- internal/middleware/auth.go | 26 +++++++- internal/repositories/address.go | 39 ++++++++++- internal/repositories/auth.go | 82 +++++++++++++++++++++-- internal/repositories/userpin.go | 34 ++++++++-- internal/services/address.go | 37 ++++++++++- internal/services/auth.go | 5 -- internal/services/userpin.go | 18 ++--- presentation/main.go | 1 + 14 files changed, 382 insertions(+), 53 deletions(-) diff --git a/config/connection.go b/config/connection.go index 934ad96..027aedb 100644 --- a/config/connection.go +++ b/config/connection.go @@ -1,10 +1,12 @@ package config import ( + "context" "fmt" "log" "os" + "github.com/go-redis/redis/v8" "github.com/pahmiudahgede/senggoldong/domain" "gorm.io/driver/postgres" "gorm.io/gorm" @@ -21,6 +23,12 @@ var ( APIKey string ServerHost string ServerPort string + + RedisClient *redis.Client + RedisHost string + RedisPort string + RedisPassword string + RedisDB int ) func InitConfig() { @@ -32,6 +40,10 @@ func InitConfig() { DBUser = os.Getenv("DB_USER") DBPassword = os.Getenv("DB_PASSWORD") APIKey = os.Getenv("API_KEY") + RedisHost = os.Getenv("REDIS_HOST") + RedisPort = os.Getenv("REDIS_PORT") + RedisPassword = os.Getenv("REDIS_PASSWORD") + RedisDB = 0 if ServerHost == "" || ServerPort == "" || DBHost == "" || DBPort == "" || DBName == "" || DBUser == "" || DBPassword == "" || APIKey == "" { log.Fatal("Error: environment variables yang dibutuhkan tidak ada") @@ -77,3 +89,20 @@ func InitDatabase() { fmt.Println("Koneksi ke database berhasil dan migrasi dilakukan") } + +func InitRedis() { + InitConfig() + + RedisClient = redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:%s", RedisHost, RedisPort), + Password: RedisPassword, + DB: RedisDB, + }) + + _, err := RedisClient.Ping(context.Background()).Result() + if err != nil { + log.Fatal("Gagal terhubung ke Redis:", err) + } + + fmt.Println("Koneksi ke Redis berhasil") +} diff --git a/go.mod b/go.mod index 532c615..634b41e 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,13 @@ go 1.23.3 require ( github.com/andybalholm/brotli v1.0.5 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.23.0 // indirect + github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/gofiber/fiber/v2 v2.52.5 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/google/uuid v1.5.0 // indirect diff --git a/go.sum b/go.sum index 4620fde..cf521f9 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,10 @@ github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +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/davecgh/go-spew v1.1.0/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.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -9,6 +13,8 @@ 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.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +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/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= diff --git a/internal/api/routes.go b/internal/api/routes.go index 6a833d0..fc8bdda 100644 --- a/internal/api/routes.go +++ b/internal/api/routes.go @@ -42,6 +42,7 @@ func AppRouter(app *fiber.App) { // # authentication # api.Post("/register", controllers.Register) api.Post("/login", controllers.Login) + api.Post("/logout", controllers.Logout) // # userinfo # api.Get("/user", middleware.AuthMiddleware, controllers.GetUserInfo) diff --git a/internal/controllers/auth.go b/internal/controllers/auth.go index 8f94b9c..1a52e60 100644 --- a/internal/controllers/auth.go +++ b/internal/controllers/auth.go @@ -1,7 +1,12 @@ package controllers import ( + "context" + "strings" + "time" + "github.com/gofiber/fiber/v2" + "github.com/pahmiudahgede/senggoldong/config" "github.com/pahmiudahgede/senggoldong/dto" "github.com/pahmiudahgede/senggoldong/internal/repositories" "github.com/pahmiudahgede/senggoldong/internal/services" @@ -86,6 +91,16 @@ func Login(c *fiber.Ctx) error { )) } + ctx := context.Background() + err = config.RedisClient.Set(ctx, "auth_token:"+token, credentials.Identifier, time.Hour*24).Err() + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( + fiber.StatusInternalServerError, + "Failed to store session", + nil, + )) + } + user, err := repositories.GetUserByEmailUsernameOrPhone(credentials.Identifier, "") if err != nil { return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse( @@ -248,3 +263,32 @@ func UpdatePassword(c *fiber.Ctx) error { }, )) } + +func Logout(c *fiber.Ctx) error { + tokenString := c.Get("Authorization") + tokenString = strings.TrimPrefix(tokenString, "Bearer ") + + if tokenString == "" { + return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse( + fiber.StatusUnauthorized, + "Token is required", + nil, + )) + } + + ctx := context.Background() + err := config.RedisClient.Del(ctx, "auth_token:"+tokenString).Err() + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( + fiber.StatusInternalServerError, + "Failed to delete session", + nil, + )) + } + + return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( + fiber.StatusOK, + "Logout successful", + nil, + )) +} diff --git a/internal/controllers/userpin.go b/internal/controllers/userpin.go index 9111bcd..a92f4aa 100644 --- a/internal/controllers/userpin.go +++ b/internal/controllers/userpin.go @@ -1,7 +1,11 @@ package controllers import ( + "time" + + "github.com/go-redis/redis/v8" "github.com/gofiber/fiber/v2" + "github.com/pahmiudahgede/senggoldong/config" "github.com/pahmiudahgede/senggoldong/dto" "github.com/pahmiudahgede/senggoldong/internal/services" "github.com/pahmiudahgede/senggoldong/utils" @@ -27,8 +31,16 @@ func CreatePin(c *fiber.Ctx) error { userID := c.Locals("userID").(string) - existingPin, err := services.GetPinByUserID(userID) - if err == nil && existingPin.ID != "" { + redisPin, err := config.RedisClient.Get(c.Context(), "pin:"+userID).Result() + if err != nil && err != redis.Nil { + return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( + fiber.StatusInternalServerError, + "Failed to check PIN from Redis", + nil, + )) + } + + if redisPin != "" { return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse( fiber.StatusBadRequest, "PIN sudah ada, tidak perlu dibuat lagi", @@ -45,10 +57,18 @@ func CreatePin(c *fiber.Ctx) error { )) } + err = config.RedisClient.Set(c.Context(), "pin:"+userID, pin.Pin, time.Minute*30).Err() + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( + fiber.StatusInternalServerError, + "Failed to save PIN to Redis", + nil, + )) + } + formattedCreatedAt := utils.FormatDateToIndonesianFormat(pin.CreatedAt) pinResponse := dto.PinResponse{ - CreatedAt: formattedCreatedAt, } @@ -62,25 +82,43 @@ func CreatePin(c *fiber.Ctx) error { func GetPinStatus(c *fiber.Ctx) error { userID := c.Locals("userID").(string) - pin, err := services.GetPinByUserID(userID) - if err != nil { + _, err := config.RedisClient.Get(c.Context(), "pin:"+userID).Result() + if err == redis.Nil { - return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse( - fiber.StatusNotFound, - "Anda belum membuat PIN", + pin, err := services.GetPinByUserID(userID) + if err != nil { + return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse( + fiber.StatusNotFound, + "Anda belum membuat PIN", + nil, + )) + } + + formattedCreatedAt := utils.FormatDateToIndonesianFormat(pin.CreatedAt) + formattedUpdatedAt := utils.FormatDateToIndonesianFormat(pin.UpdatedAt) + + return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( + fiber.StatusOK, + "PIN sudah dibuat", + map[string]interface{}{ + "createdAt": formattedCreatedAt, + "updatedAt": formattedUpdatedAt, + }, + )) + } else if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( + fiber.StatusInternalServerError, + "Failed to fetch PIN from Redis", nil, )) } - formattedCreatedAt := utils.FormatDateToIndonesianFormat(pin.CreatedAt) - formattedUpdatedAt := utils.FormatDateToIndonesianFormat(pin.UpdatedAt) - return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( fiber.StatusOK, "PIN sudah dibuat", map[string]interface{}{ - "createdAt": formattedCreatedAt, - "updatedAt": formattedUpdatedAt, + "createdAt": "PIN ditemukan di Redis", + "updatedAt": "PIN ditemukan di Redis", }, )) } @@ -96,19 +134,45 @@ func GetPin(c *fiber.Ctx) error { } userID := c.Locals("userID").(string) - pin, err := services.GetPinByUserID(userID) - if err != nil { - return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse( - fiber.StatusNotFound, - "Sepertinya anda belum membuat pin", + + redisPin, err := config.RedisClient.Get(c.Context(), "pin:"+userID).Result() + if err == redis.Nil { + + pin, err := services.GetPinByUserID(userID) + if err != nil { + return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse( + fiber.StatusNotFound, + "Sepertinya anda belum membuat pin", + nil, + )) + } + + isPinValid := services.CheckPin(pin.Pin, input.Pin) + if isPinValid { + + config.RedisClient.Set(c.Context(), "pin:"+userID, pin.Pin, time.Minute*30) + return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( + fiber.StatusOK, + "PIN benar", + true, + )) + } + + return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse( + fiber.StatusUnauthorized, + "PIN salah", + false, + )) + } else if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( + fiber.StatusInternalServerError, + "Failed to fetch PIN from Redis", nil, )) } - isPinValid := services.CheckPin(pin.Pin, input.Pin) - + isPinValid := services.CheckPin(redisPin, input.Pin) if isPinValid { - return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( fiber.StatusOK, "PIN benar", @@ -145,7 +209,6 @@ func UpdatePin(c *fiber.Ctx) error { updatedPin, err := services.UpdatePin(userID, input.OldPin, input.NewPin) if err != nil { - if err.Error() == "PIN lama salah" { return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse( fiber.StatusUnauthorized, @@ -161,6 +224,9 @@ func UpdatePin(c *fiber.Ctx) error { )) } + config.RedisClient.Del(c.Context(), "pin:"+userID) + config.RedisClient.Set(c.Context(), "pin:"+userID, updatedPin.Pin, time.Minute*30) + formattedUpdatedAt := utils.FormatDateToIndonesianFormat(updatedPin.UpdatedAt) return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index 6c957f3..7c716f2 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -1,12 +1,15 @@ package middleware import ( + "context" "errors" "os" "strings" + "time" "github.com/gofiber/fiber/v2" "github.com/golang-jwt/jwt/v5" + "github.com/pahmiudahgede/senggoldong/config" "github.com/pahmiudahgede/senggoldong/utils" ) @@ -23,6 +26,16 @@ func RoleRequired(roles ...string) fiber.Handler { tokenString = strings.TrimPrefix(tokenString, "Bearer ") + ctx := context.Background() + cachedToken, err := config.RedisClient.Get(ctx, "auth_token:"+tokenString).Result() + if err != nil || cachedToken == "" { + return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse( + fiber.StatusUnauthorized, + "Invalid or expired token", + nil, + )) + } + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, errors.New("unexpected signing method") @@ -91,6 +104,14 @@ func AuthMiddleware(c *fiber.Ctx) error { }) } + ctx := context.Background() + cachedToken, err := config.RedisClient.Get(ctx, "auth_token:"+tokenString).Result() + if err != nil || cachedToken == "" { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "message": "Invalid or expired token", + }) + } + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { return []byte(os.Getenv("API_KEY")), nil }) @@ -109,8 +130,9 @@ func AuthMiddleware(c *fiber.Ctx) error { } userID := claims["sub"].(string) - c.Locals("userID", userID) + config.RedisClient.Expire(ctx, "auth_token:"+tokenString, time.Hour*24).Err() + return c.Next() -} +} \ No newline at end of file diff --git a/internal/repositories/address.go b/internal/repositories/address.go index 64b1eb4..1699f1a 100644 --- a/internal/repositories/address.go +++ b/internal/repositories/address.go @@ -1,7 +1,11 @@ package repositories import ( + "context" + "encoding/json" "errors" + "fmt" + "time" "github.com/pahmiudahgede/senggoldong/config" "github.com/pahmiudahgede/senggoldong/domain" @@ -12,15 +16,34 @@ func CreateAddress(address *domain.Address) error { if result.Error != nil { return result.Error } + + cacheKey := fmt.Sprintf("address:user:%s", address.UserID) + config.RedisClient.Del(context.Background(), cacheKey) + return nil } func GetAddressesByUserID(userID string) ([]domain.Address, error) { + ctx := context.Background() + cacheKey := fmt.Sprintf("address:user:%s", userID) + + cachedAddresses, err := config.RedisClient.Get(ctx, cacheKey).Result() + if err == nil { + var addresses []domain.Address + if json.Unmarshal([]byte(cachedAddresses), &addresses) == nil { + return addresses, nil + } + } + var addresses []domain.Address - err := config.DB.Where("user_id = ?", userID).Find(&addresses).Error + err = config.DB.Where("user_id = ?", userID).Find(&addresses).Error if err != nil { return nil, err } + + addressesJSON, _ := json.Marshal(addresses) + config.RedisClient.Set(ctx, cacheKey, addressesJSON, time.Hour).Err() + return addresses, nil } @@ -36,13 +59,25 @@ func UpdateAddress(address domain.Address) (domain.Address, error) { if err := config.DB.Save(&address).Error; err != nil { return address, err } + + cacheKey := fmt.Sprintf("address:user:%s", address.UserID) + config.RedisClient.Del(context.Background(), cacheKey) + return address, nil } func DeleteAddress(addressID string) error { var address domain.Address - if err := config.DB.Where("id = ?", addressID).Delete(&address).Error; err != nil { + if err := config.DB.Where("id = ?", addressID).First(&address).Error; err != nil { return err } + + if err := config.DB.Delete(&address).Error; err != nil { + return err + } + + cacheKey := fmt.Sprintf("address:user:%s", address.UserID) + config.RedisClient.Del(context.Background(), cacheKey) + return nil } diff --git a/internal/repositories/auth.go b/internal/repositories/auth.go index c4fb68f..c865c5e 100644 --- a/internal/repositories/auth.go +++ b/internal/repositories/auth.go @@ -1,17 +1,31 @@ package repositories import ( + "context" + "encoding/json" "errors" "fmt" + "log" + "time" "github.com/pahmiudahgede/senggoldong/config" "github.com/pahmiudahgede/senggoldong/domain" ) func IsEmailExist(email, roleId string) bool { + ctx := context.Background() + cacheKey := fmt.Sprintf("email:%s", email) + cachedRole, err := config.RedisClient.Get(ctx, cacheKey).Result() + if err == nil && cachedRole == roleId { + return true + } + var user domain.User if err := config.DB.Where("email = ?", email).First(&user).Error; err == nil { if user.RoleID == roleId { + if err := config.RedisClient.Set(ctx, cacheKey, roleId, 24*time.Hour).Err(); err != nil { + log.Printf("Redis Set error: %v", err) + } return true } } @@ -19,9 +33,19 @@ func IsEmailExist(email, roleId string) bool { } func IsUsernameExist(username, roleId string) bool { + ctx := context.Background() + cacheKey := fmt.Sprintf("username:%s", username) + cachedRole, err := config.RedisClient.Get(ctx, cacheKey).Result() + if err == nil && cachedRole == roleId { + return true + } + var user domain.User if err := config.DB.Where("username = ?", username).First(&user).Error; err == nil { if user.RoleID == roleId { + if err := config.RedisClient.Set(ctx, cacheKey, roleId, 24*time.Hour).Err(); err != nil { + log.Printf("Redis Set error: %v", err) + } return true } } @@ -29,9 +53,19 @@ func IsUsernameExist(username, roleId string) bool { } func IsPhoneExist(phone, roleId string) bool { + ctx := context.Background() + cacheKey := fmt.Sprintf("phone:%s", phone) + cachedRole, err := config.RedisClient.Get(ctx, cacheKey).Result() + if err == nil && cachedRole == roleId { + return true + } + var user domain.User if err := config.DB.Where("phone = ?", phone).First(&user).Error; err == nil { if user.RoleID == roleId { + if err := config.RedisClient.Set(ctx, cacheKey, roleId, 24*time.Hour).Err(); err != nil { + log.Printf("Redis Set error: %v", err) + } return true } } @@ -56,8 +90,20 @@ func CreateUser(username, name, email, phone, password, roleId string) error { } func GetUserByEmailUsernameOrPhone(identifier, roleId string) (domain.User, error) { + ctx := context.Background() + cacheKey := fmt.Sprintf("user:%s", identifier) var user domain.User - err := config.DB.Where("email = ? OR username = ? OR phone = ?", identifier, identifier, identifier).First(&user).Error + + cachedUser, err := config.RedisClient.Get(ctx, cacheKey).Result() + if err == nil { + if err := json.Unmarshal([]byte(cachedUser), &user); err == nil { + if roleId == "" || user.RoleID == roleId { + return user, nil + } + } + } + + err = config.DB.Where("email = ? OR username = ? OR phone = ?", identifier, identifier, identifier).First(&user).Error if err != nil { return user, errors.New("user not found") } @@ -66,28 +112,45 @@ func GetUserByEmailUsernameOrPhone(identifier, roleId string) (domain.User, erro return user, errors.New("identifier found but role does not match") } + userJSON, _ := json.Marshal(user) + if err := config.RedisClient.Set(ctx, cacheKey, userJSON, 1*time.Hour).Err(); err != nil { + log.Printf("Redis Set error: %v", err) + } return user, nil } func GetUserByID(userID string) (domain.User, error) { + ctx := context.Background() + cacheKey := fmt.Sprintf("user:%s", userID) var user domain.User - if err := config.DB. - Preload("Role"). - Where("id = ?", userID). - First(&user).Error; err != nil { + + cachedUser, err := config.RedisClient.Get(ctx, cacheKey).Result() + if err == nil { + if err := json.Unmarshal([]byte(cachedUser), &user); err == nil { + return user, nil + } + } + + if err := config.DB.Preload("Role").Where("id = ?", userID).First(&user).Error; err != nil { return user, errors.New("user not found") } - fmt.Printf("User ID: %s, Role: %v\n", user.ID, user.Role) + userJSON, _ := json.Marshal(user) + if err := config.RedisClient.Set(ctx, cacheKey, userJSON, 1*time.Hour).Err(); err != nil { + log.Printf("Redis Set error: %v", err) + } return user, nil } func UpdateUser(user *domain.User) error { - if err := config.DB.Save(user).Error; err != nil { return errors.New("failed to update user") } + cacheKey := fmt.Sprintf("user:%s", user.ID) + if err := config.RedisClient.Del(context.Background(), cacheKey).Err(); err != nil { + log.Printf("Redis Del error: %v", err) + } return nil } @@ -104,5 +167,10 @@ func UpdateUserPassword(userID, newPassword string) error { return errors.New("failed to update password") } + cacheKey := fmt.Sprintf("user:%s", userID) + if err := config.RedisClient.Del(context.Background(), cacheKey).Err(); err != nil { + log.Printf("Redis Del error: %v", err) + } + return nil } diff --git a/internal/repositories/userpin.go b/internal/repositories/userpin.go index 6f1a344..5ce2160 100644 --- a/internal/repositories/userpin.go +++ b/internal/repositories/userpin.go @@ -1,7 +1,10 @@ package repositories import ( + "context" "errors" + "fmt" + "time" "github.com/pahmiudahgede/senggoldong/config" "github.com/pahmiudahgede/senggoldong/domain" @@ -17,12 +20,30 @@ func CreatePin(pin *domain.UserPin) error { } func GetPinByUserID(userID string) (domain.UserPin, error) { - var pin domain.UserPin - err := config.DB.Where("user_id = ?", userID).First(&pin).Error - if err != nil { - return pin, errors.New("PIN tidak ditemukan") + + ctx := context.Background() + redisClient := config.RedisClient + + redisKey := fmt.Sprintf("user_pin:%s", userID) + + pin, err := redisClient.Get(ctx, redisKey).Result() + if err == nil { + + return domain.UserPin{ + UserID: userID, + Pin: pin, + }, nil } - return pin, nil + + var dbPin domain.UserPin + err = config.DB.Where("user_id = ?", userID).First(&dbPin).Error + if err != nil { + return dbPin, errors.New("PIN tidak ditemukan") + } + + redisClient.Set(ctx, redisKey, dbPin.Pin, 5*time.Minute) + + return dbPin, nil } func UpdatePin(userID string, newPin string) (domain.UserPin, error) { @@ -44,5 +65,8 @@ func UpdatePin(userID string, newPin string) (domain.UserPin, error) { return pin, err } + redisClient := config.RedisClient + redisClient.Del(context.Background(), fmt.Sprintf("user_pin:%s", userID)) + return pin, nil } diff --git a/internal/services/address.go b/internal/services/address.go index db18d1d..bc62090 100644 --- a/internal/services/address.go +++ b/internal/services/address.go @@ -1,8 +1,13 @@ package services import ( + "context" + "encoding/json" "errors" + "fmt" + "time" + "github.com/pahmiudahgede/senggoldong/config" "github.com/pahmiudahgede/senggoldong/domain" "github.com/pahmiudahgede/senggoldong/dto" "github.com/pahmiudahgede/senggoldong/internal/repositories" @@ -25,15 +30,32 @@ func CreateAddress(userID string, input dto.AddressInput) (domain.Address, error return domain.Address{}, err } + cacheKey := fmt.Sprintf("address:user:%s", userID) + config.RedisClient.Del(context.Background(), cacheKey) + return address, nil } func GetAllAddressesByUserID(userID string) ([]domain.Address, error) { + ctx := context.Background() + cacheKey := fmt.Sprintf("address:user:%s", userID) + + cachedAddresses, err := config.RedisClient.Get(ctx, cacheKey).Result() + if err == nil { + var addresses []domain.Address + if json.Unmarshal([]byte(cachedAddresses), &addresses) == nil { + return addresses, nil + } + } addresses, err := repositories.GetAddressesByUserID(userID) if err != nil { return nil, err } + + addressesJSON, _ := json.Marshal(addresses) + config.RedisClient.Set(ctx, cacheKey, addressesJSON, time.Hour).Err() + return addresses, nil } @@ -46,7 +68,6 @@ func GetAddressByID(addressID string) (domain.Address, error) { } func UpdateAddress(addressID string, input dto.AddressInput) (domain.Address, error) { - address, err := repositories.GetAddressByID(addressID) if err != nil { return address, errors.New("address not found") @@ -65,13 +86,25 @@ func UpdateAddress(addressID string, input dto.AddressInput) (domain.Address, er return updatedAddress, errors.New("failed to update address") } + cacheKey := fmt.Sprintf("address:user:%s", address.UserID) + config.RedisClient.Del(context.Background(), cacheKey) + return updatedAddress, nil } func DeleteAddress(addressID string) error { - err := repositories.DeleteAddress(addressID) + address, err := repositories.GetAddressByID(addressID) + if err != nil { + return errors.New("address not found") + } + + err = repositories.DeleteAddress(addressID) if err != nil { return errors.New("failed to delete address") } + + cacheKey := fmt.Sprintf("address:user:%s", address.UserID) + config.RedisClient.Del(context.Background(), cacheKey) + return nil } diff --git a/internal/services/auth.go b/internal/services/auth.go index 6d0712e..500b0f9 100644 --- a/internal/services/auth.go +++ b/internal/services/auth.go @@ -12,7 +12,6 @@ import ( ) func RegisterUser(username, name, email, phone, password, confirmPassword, roleId string) error { - if password != confirmPassword { return errors.New("password dan confirm password tidak cocok") } @@ -48,7 +47,6 @@ func LoginUser(identifier, password string) (string, error) { } const roleId = "" - user, err := repositories.GetUserByEmailUsernameOrPhone(identifier, roleId) if err != nil { return "", errors.New("invalid email/username/phone or password") @@ -71,7 +69,6 @@ func generateJWT(userID, role string) string { } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - t, err := token.SignedString([]byte(os.Getenv("API_KEY"))) if err != nil { return "" @@ -89,7 +86,6 @@ func GetUserByID(userID string) (domain.User, error) { } func UpdateUser(userID, email, username, name, phone string) error { - user, err := repositories.GetUserByID(userID) if err != nil { return errors.New("user not found") @@ -129,7 +125,6 @@ func UpdateUser(userID, email, username, name, phone string) error { } func UpdatePassword(userID, oldPassword, newPassword string) error { - user, err := repositories.GetUserByID(userID) if err != nil { return errors.New("user not found") diff --git a/internal/services/userpin.go b/internal/services/userpin.go index 464f6cd..bbe7b12 100644 --- a/internal/services/userpin.go +++ b/internal/services/userpin.go @@ -9,14 +9,6 @@ import ( "golang.org/x/crypto/bcrypt" ) -func GetPinByUserID(userID string) (domain.UserPin, error) { - pin, err := repositories.GetPinByUserID(userID) - if err != nil { - return pin, errors.New("PIN tidak ditemukan") - } - return pin, nil -} - func CreatePin(userID string, input dto.PinInput) (domain.UserPin, error) { hashedPin, err := bcrypt.GenerateFromPassword([]byte(input.Pin), bcrypt.DefaultCost) @@ -37,7 +29,17 @@ func CreatePin(userID string, input dto.PinInput) (domain.UserPin, error) { return pin, nil } +func GetPinByUserID(userID string) (domain.UserPin, error) { + + pin, err := repositories.GetPinByUserID(userID) + if err != nil { + return pin, errors.New("PIN tidak ditemukan") + } + return pin, nil +} + func UpdatePin(userID string, oldPin string, newPin string) (domain.UserPin, error) { + pin, err := repositories.GetPinByUserID(userID) if err != nil { return pin, errors.New("PIN tidak ditemukan") diff --git a/presentation/main.go b/presentation/main.go index ec2679d..c95c8b1 100644 --- a/presentation/main.go +++ b/presentation/main.go @@ -18,6 +18,7 @@ func init() { config.InitConfig() config.InitDatabase() + config.InitRedis() }