fix: redis caching and setup route management
This commit is contained in:
parent
16bfb40f95
commit
a824de7ba0
|
@ -4,7 +4,7 @@ import (
|
|||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/pahmiudahgede/senggoldong/config"
|
||||
"github.com/pahmiudahgede/senggoldong/middleware"
|
||||
"github.com/pahmiudahgede/senggoldong/presentation"
|
||||
"github.com/pahmiudahgede/senggoldong/router"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -12,6 +12,8 @@ func main() {
|
|||
|
||||
app := fiber.New()
|
||||
app.Use(middleware.APIKeyMiddleware)
|
||||
presentation.AuthRouter(app)
|
||||
|
||||
router.SetupRoutes(app)
|
||||
|
||||
config.StartServer(app)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package handler
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/pahmiudahgede/senggoldong/dto"
|
||||
|
@ -46,6 +47,21 @@ func (h *UserHandler) Login(c *fiber.Ctx) error {
|
|||
return utils.LogResponse(c, user, "Login successful")
|
||||
}
|
||||
|
||||
func (h *UserHandler) GetUserProfile(c *fiber.Ctx) error {
|
||||
userID, ok := c.Locals("userID").(string)
|
||||
if !ok || userID == "" {
|
||||
log.Println("Unauthorized access: User ID not found in session")
|
||||
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found")
|
||||
}
|
||||
|
||||
user, err := h.UserService.GetUserProfile(userID)
|
||||
if err != nil {
|
||||
return utils.ErrorResponse(c, "User not found")
|
||||
}
|
||||
|
||||
return utils.LogResponse(c, user, "User profile retrieved successfully")
|
||||
}
|
||||
|
||||
func (h *UserHandler) Register(c *fiber.Ctx) error {
|
||||
var registerDTO dto.RegisterDTO
|
||||
if err := c.BodyParser(®isterDTO); err != nil {
|
||||
|
@ -81,14 +97,13 @@ func (h *UserHandler) Register(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
func (h *UserHandler) Logout(c *fiber.Ctx) error {
|
||||
|
||||
token := c.Get("Authorization")
|
||||
if token == "" {
|
||||
|
||||
return utils.ErrorResponse(c, "No token provided")
|
||||
userID, ok := c.Locals("userID").(string)
|
||||
if !ok || userID == "" {
|
||||
log.Println("Unauthorized access: User ID not found in session")
|
||||
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found")
|
||||
}
|
||||
|
||||
err := utils.DeleteData(token)
|
||||
err := utils.DeleteSessionData(userID)
|
||||
if err != nil {
|
||||
return utils.InternalServerErrorResponse(c, "Error logging out")
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ type UserRepository interface {
|
|||
FindByUsername(username string) (*model.User, error)
|
||||
FindByPhoneAndRole(phone, roleID string) (*model.User, error)
|
||||
FindByEmailAndRole(email, roleID string) (*model.User, error)
|
||||
FindByID(userID string) (*model.User, error)
|
||||
Create(user *model.User) error
|
||||
}
|
||||
|
||||
|
@ -22,6 +23,15 @@ func NewUserRepository(db *gorm.DB) UserRepository {
|
|||
return &userRepository{DB: db}
|
||||
}
|
||||
|
||||
func (r *userRepository) FindByID(userID string) (*model.User, error) {
|
||||
var user model.User
|
||||
err := r.DB.Preload("Role").Where("id = ?", userID).First(&user).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) FindByIdentifierAndRole(identifier, roleID string) (*model.User, error) {
|
||||
var user model.User
|
||||
err := r.DB.Preload("Role").Where("(email = ? OR username = ? OR phone = ?) AND role_id = ?", identifier, identifier, identifier, roleID).First(&user).Error
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
@ -16,6 +17,7 @@ import (
|
|||
type UserService interface {
|
||||
Login(credentials dto.LoginDTO) (*dto.UserResponseWithToken, error)
|
||||
Register(user dto.RegisterDTO) (*model.User, error)
|
||||
GetUserProfile(userID string) (*dto.UserResponseDTO, error)
|
||||
}
|
||||
|
||||
type userService struct {
|
||||
|
@ -47,6 +49,7 @@ func (s *userService) Login(credentials dto.LoginDTO) (*dto.UserResponseWithToke
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
sessionKey := fmt.Sprintf("session:%s", user.ID)
|
||||
sessionData := map[string]interface{}{
|
||||
"userID": user.ID,
|
||||
|
@ -54,7 +57,7 @@ func (s *userService) Login(credentials dto.LoginDTO) (*dto.UserResponseWithToke
|
|||
"roleName": user.Role.RoleName,
|
||||
}
|
||||
|
||||
err = utils.SetJSONData(sessionKey, sessionData, time.Hour*24)
|
||||
err = utils.SetJSONData(ctx, sessionKey, sessionData, time.Hour*24)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -88,6 +91,75 @@ func CheckPasswordHash(password, hashedPassword string) bool {
|
|||
return err == nil
|
||||
}
|
||||
|
||||
func (s *userService) GetUserProfile(userID string) (*dto.UserResponseDTO, error) {
|
||||
ctx := context.Background()
|
||||
cacheKey := "user:profile:" + userID
|
||||
|
||||
if exists, _ := utils.CheckKeyExists(ctx, cacheKey); exists {
|
||||
cachedUser, _ := utils.GetJSONData(ctx, cacheKey)
|
||||
if cachedUser != nil {
|
||||
if userDTO, ok := mapToUserResponseDTO(cachedUser); ok {
|
||||
return userDTO, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user, err := s.UserRepo.FindByID(userID)
|
||||
if err != nil {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
|
||||
createdAt, _ := utils.FormatDateToIndonesianFormat(user.CreatedAt)
|
||||
updatedAt, _ := utils.FormatDateToIndonesianFormat(user.UpdatedAt)
|
||||
|
||||
userResponse := &dto.UserResponseDTO{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Name: user.Name,
|
||||
Phone: user.Phone,
|
||||
Email: user.Email,
|
||||
EmailVerified: user.EmailVerified,
|
||||
RoleName: user.Role.RoleName,
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
}
|
||||
|
||||
err = utils.SetJSONData(ctx, cacheKey, userResponse, 5*time.Minute)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to cache user data")
|
||||
}
|
||||
|
||||
return userResponse, nil
|
||||
}
|
||||
|
||||
func mapToUserResponseDTO(data map[string]interface{}) (*dto.UserResponseDTO, bool) {
|
||||
id, idOk := data["id"].(string)
|
||||
username, usernameOk := data["username"].(string)
|
||||
name, nameOk := data["name"].(string)
|
||||
phone, phoneOk := data["phone"].(string)
|
||||
email, emailOk := data["email"].(string)
|
||||
emailVerified, emailVerifiedOk := data["emailVerified"].(bool)
|
||||
roleName, roleNameOk := data["roleName"].(string)
|
||||
createdAt, createdAtOk := data["createdAt"].(string)
|
||||
updatedAt, updatedAtOk := data["updatedAt"].(string)
|
||||
|
||||
if !(idOk && usernameOk && nameOk && phoneOk && emailOk && emailVerifiedOk && roleNameOk && createdAtOk && updatedAtOk) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &dto.UserResponseDTO{
|
||||
ID: id,
|
||||
Username: username,
|
||||
Name: name,
|
||||
Phone: phone,
|
||||
Email: email,
|
||||
EmailVerified: emailVerified,
|
||||
RoleName: roleName,
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
}, true
|
||||
}
|
||||
|
||||
func (s *userService) Register(user dto.RegisterDTO) (*model.User, error) {
|
||||
if user.Password != user.ConfirmPassword {
|
||||
return nil, fmt.Errorf("password and confirm password do not match")
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
@ -14,6 +16,10 @@ func AuthMiddleware(c *fiber.Ctx) error {
|
|||
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: No token provided")
|
||||
}
|
||||
|
||||
if len(tokenString) > 7 && tokenString[:7] == "Bearer " {
|
||||
tokenString = tokenString[7:]
|
||||
}
|
||||
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(os.Getenv("SECRET_KEY")), nil
|
||||
})
|
||||
|
@ -23,19 +29,35 @@ func AuthMiddleware(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
if !ok || claims["sub"] == nil {
|
||||
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: Invalid token claims")
|
||||
}
|
||||
|
||||
sessionKey := "session:" + claims["sub"].(string)
|
||||
sessionData, err := utils.GetJSONData(sessionKey)
|
||||
if err != nil {
|
||||
userID, ok := claims["sub"].(string)
|
||||
if !ok || userID == "" {
|
||||
log.Println("Invalid userID format in token")
|
||||
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: Invalid user session")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
sessionKey := "session:" + userID
|
||||
sessionData, err := utils.GetJSONData(ctx, sessionKey)
|
||||
if err != nil || sessionData == nil {
|
||||
log.Println("Session expired or invalid for userID:", userID)
|
||||
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Session expired or invalid")
|
||||
}
|
||||
|
||||
c.Locals("userID", sessionData["userID"])
|
||||
c.Locals("roleID", sessionData["roleID"])
|
||||
c.Locals("roleName", sessionData["roleName"])
|
||||
roleID, roleOK := sessionData["roleID"].(string)
|
||||
roleName, roleNameOK := sessionData["roleName"].(string)
|
||||
|
||||
if !roleOK || !roleNameOK {
|
||||
log.Println("Invalid session data for userID:", userID)
|
||||
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: Invalid session data")
|
||||
}
|
||||
|
||||
c.Locals("userID", userID)
|
||||
c.Locals("roleID", roleID)
|
||||
c.Locals("roleName", roleName)
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/pahmiudahgede/senggoldong/utils"
|
||||
)
|
||||
|
||||
func RoleMiddleware(allowedRoles ...string) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
roleID, exists := c.Locals("roleID").(string)
|
||||
if !exists || roleID == "" {
|
||||
roleID, ok := c.Locals("roleID").(string)
|
||||
if !ok || roleID == "" {
|
||||
log.Println("Unauthorized access: Role not found in session")
|
||||
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: Role not found in session")
|
||||
}
|
||||
|
||||
|
@ -18,6 +21,7 @@ func RoleMiddleware(allowedRoles ...string) fiber.Handler {
|
|||
}
|
||||
}
|
||||
|
||||
log.Println("Access denied for role:", roleID)
|
||||
return utils.GenericErrorResponse(c, fiber.StatusForbidden, "Access Denied: You don't have permission to access this resource")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Address struct {
|
||||
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
|
||||
UserID string `gorm:"not null" json:"userId"`
|
||||
User User `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"user"`
|
||||
Province string `gorm:"not null" json:"province"`
|
||||
District string `gorm:"not null" json:"district"`
|
||||
Subdistrict string `gorm:"not null" json:"subdistrict"`
|
||||
PostalCode int `gorm:"not null" json:"postalCode"`
|
||||
Village string `gorm:"not null" json:"village"`
|
||||
Detail string `gorm:"not null" json:"detail"`
|
||||
Geography string `gorm:"not null" json:"geography"`
|
||||
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
|
||||
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package presentation
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
@ -8,14 +9,14 @@ import (
|
|||
"github.com/pahmiudahgede/senggoldong/internal/handler"
|
||||
"github.com/pahmiudahgede/senggoldong/internal/repositories"
|
||||
"github.com/pahmiudahgede/senggoldong/internal/services"
|
||||
"github.com/pahmiudahgede/senggoldong/middleware"
|
||||
)
|
||||
|
||||
func AuthRouter(app *fiber.App) {
|
||||
api := app.Group("/apirijikid")
|
||||
|
||||
func AuthRouter(api fiber.Router) {
|
||||
secretKey := os.Getenv("SECRET_KEY")
|
||||
if secretKey == "" {
|
||||
panic("SECRET_KEY is not set in the environment variables")
|
||||
log.Fatal("SECRET_KEY is not set in the environment variables")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
userRepo := repositories.NewUserRepository(config.DB)
|
||||
|
@ -25,5 +26,6 @@ func AuthRouter(app *fiber.App) {
|
|||
|
||||
api.Post("/login", userHandler.Login)
|
||||
api.Post("/register", userHandler.Register)
|
||||
api.Post("/logout", userHandler.Logout)
|
||||
api.Post("/logout", middleware.AuthMiddleware, userHandler.Logout)
|
||||
api.Get("/user", middleware.AuthMiddleware, userHandler.GetUserProfile)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/pahmiudahgede/senggoldong/presentation"
|
||||
)
|
||||
|
||||
func SetupRoutes(app *fiber.App) {
|
||||
api := app.Group("/apirijikid")
|
||||
|
||||
presentation.AuthRouter(api)
|
||||
}
|
|
@ -10,31 +10,38 @@ import (
|
|||
"github.com/pahmiudahgede/senggoldong/config"
|
||||
)
|
||||
|
||||
func SetData(key string, value interface{}, expiration time.Duration) error {
|
||||
err := config.RedisClient.Set(context.Background(), key, value, expiration).Err()
|
||||
var ctx = context.Background()
|
||||
|
||||
func SetData(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
|
||||
jsonData, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
log.Printf("Error setting data to Redis: %v", err)
|
||||
log.Printf("Error marshaling JSON data: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Printf("Data stored in Redis with key: %s", key)
|
||||
|
||||
err = config.RedisClient.Set(ctx, key, jsonData, expiration).Err()
|
||||
if err != nil {
|
||||
log.Printf("Error setting JSON data to Redis: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("JSON Data stored in Redis with key: %s", key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetData(key string) (string, error) {
|
||||
val, err := config.RedisClient.Get(context.Background(), key).Result()
|
||||
func GetData(ctx context.Context, key string) (string, error) {
|
||||
val, err := config.RedisClient.Get(ctx, key).Result()
|
||||
if err == redis.Nil {
|
||||
log.Printf("No data found for key: %s", key)
|
||||
return "", nil
|
||||
} else if err != nil {
|
||||
log.Printf("Error getting data from Redis: %v", err)
|
||||
return "", err
|
||||
}
|
||||
log.Printf("Data retrieved from Redis for key: %s", key)
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func DeleteData(key string) error {
|
||||
err := config.RedisClient.Del(context.Background(), key).Err()
|
||||
err := config.RedisClient.Del(ctx, key).Err()
|
||||
if err != nil {
|
||||
log.Printf("Error deleting data from Redis: %v", err)
|
||||
return err
|
||||
|
@ -43,18 +50,8 @@ func DeleteData(key string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func SetDataWithExpire(key string, value interface{}, expiration time.Duration) error {
|
||||
err := config.RedisClient.Set(context.Background(), key, value, expiration).Err()
|
||||
if err != nil {
|
||||
log.Printf("Error setting data with expiration to Redis: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Printf("Data stored in Redis with key: %s and expiration: %v", key, expiration)
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckKeyExists(key string) (bool, error) {
|
||||
val, err := config.RedisClient.Exists(context.Background(), key).Result()
|
||||
func CheckKeyExists(ctx context.Context, key string) (bool, error) {
|
||||
val, err := config.RedisClient.Exists(ctx, key).Result()
|
||||
if err != nil {
|
||||
log.Printf("Error checking if key exists in Redis: %v", err)
|
||||
return false, err
|
||||
|
@ -62,17 +59,25 @@ func CheckKeyExists(key string) (bool, error) {
|
|||
return val > 0, nil
|
||||
}
|
||||
|
||||
func SetJSONData(key string, value interface{}, expiration time.Duration) error {
|
||||
func SetJSONData(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
|
||||
jsonData, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
log.Printf("Error marshaling JSON data: %v", err)
|
||||
return err
|
||||
}
|
||||
return SetData(key, jsonData, expiration)
|
||||
|
||||
err = config.RedisClient.Set(ctx, key, jsonData, expiration).Err()
|
||||
if err != nil {
|
||||
log.Printf("Error setting JSON data to Redis: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetJSONData(key string) (map[string]interface{}, error) {
|
||||
val, err := GetData(key)
|
||||
log.Printf("JSON Data stored in Redis with key: %s", key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetJSONData(ctx context.Context, key string) (map[string]interface{}, error) {
|
||||
val, err := GetData(ctx, key)
|
||||
if err != nil || val == "" {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -86,3 +91,8 @@ func GetJSONData(key string) (map[string]interface{}, error) {
|
|||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func DeleteSessionData(userID string) error {
|
||||
sessionKey := "session:" + userID
|
||||
return DeleteData(sessionKey)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue