fix: fixing code and optimizing

This commit is contained in:
pahmiudahgede 2025-02-01 05:58:40 +07:00
parent aed5f9126a
commit 53328a0bde
15 changed files with 207 additions and 176 deletions

View File

@ -27,18 +27,6 @@ type RegisterDTO struct {
RoleID string `json:"roleId,omitempty"`
}
type UserResponseDTO struct {
ID string `json:"id"`
Username string `json:"username"`
Name string `json:"name"`
Phone string `json:"phone"`
Email string `json:"email"`
EmailVerified bool `json:"emailVerified"`
RoleName string `json:"role"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
func (l *LoginDTO) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)

13
dto/user_dto.go Normal file
View File

@ -0,0 +1,13 @@
package dto
type UserResponseDTO struct {
ID string `json:"id"`
Username string `json:"username"`
Name string `json:"name"`
Phone string `json:"phone"`
Email string `json:"email"`
EmailVerified bool `json:"emailVerified"`
RoleName string `json:"role"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}

View File

@ -1,14 +1,12 @@
package handler
import (
"errors"
"log"
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
"golang.org/x/crypto/bcrypt"
)
type UserHandler struct {
@ -32,36 +30,12 @@ func (h *UserHandler) Login(c *fiber.Ctx) error {
user, err := h.UserService.Login(loginDTO)
if err != nil {
if err.Error() == "akun dengan role tersebut belum terdaftar" {
return utils.GenericErrorResponse(c, fiber.StatusNotFound, "akun dengan role tersebut belum terdaftar")
}
if err.Error() == "password yang anda masukkan salah" {
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "password yang anda masukkan salah")
}
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "password yang anda masukkan salah")
}
return utils.GenericErrorResponse(c, fiber.StatusNotFound, "akun tidak ditemukan")
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, err.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(&registerDTO); err != nil {

View File

@ -0,0 +1,30 @@
package handler
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
type UserProfileHandler struct {
UserProfileService services.UserProfileService
}
func NewUserProfileHandler(userProfileService services.UserProfileService) *UserProfileHandler {
return &UserProfileHandler{UserProfileService: userProfileService}
}
func (h *UserProfileHandler) GetUserProfile(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found")
}
userProfile, err := h.UserProfileService.GetUserProfile(userID)
if err != nil {
return utils.GenericErrorResponse(c, fiber.StatusNotFound, err.Error())
}
return utils.LogResponse(c, userProfile, "User profile retrieved successfully")
}

View File

@ -11,7 +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
}
@ -23,15 +23,6 @@ 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

View File

@ -0,0 +1,27 @@
package repositories
import (
"github.com/pahmiudahgede/senggoldong/model"
"gorm.io/gorm"
)
type UserProfileRepository interface {
FindByID(userID string) (*model.User, error)
}
type userProfileRepository struct {
DB *gorm.DB
}
func NewUserProfileRepository(db *gorm.DB) UserProfileRepository {
return &userProfileRepository{DB: db}
}
func (r *userProfileRepository) 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
}

View File

@ -1,7 +1,6 @@
package services
import (
"context"
"errors"
"fmt"
"time"
@ -17,7 +16,6 @@ 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 {
@ -31,6 +29,7 @@ func NewUserService(userRepo repositories.UserRepository, roleRepo repositories.
}
func (s *userService) Login(credentials dto.LoginDTO) (*dto.UserResponseWithToken, error) {
if credentials.RoleID == "" {
return nil, errors.New("roleId is required")
}
@ -49,7 +48,6 @@ 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,
@ -57,7 +55,7 @@ func (s *userService) Login(credentials dto.LoginDTO) (*dto.UserResponseWithToke
"roleName": user.Role.RoleName,
}
err = utils.SetJSONData(ctx, sessionKey, sessionData, time.Hour*24)
err = utils.SetJSONData(sessionKey, sessionData, time.Hour*24)
if err != nil {
return nil, err
}
@ -91,76 +89,8 @@ 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")
}

View File

@ -0,0 +1,73 @@
package services
import (
"encoding/json"
"errors"
"fmt"
"time"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/repositories"
"github.com/pahmiudahgede/senggoldong/utils"
)
type UserProfileService interface {
GetUserProfile(userID string) (*dto.UserResponseDTO, error)
}
type userProfileService struct {
UserProfileRepo repositories.UserProfileRepository
}
func NewUserProfileService(userProfileRepo repositories.UserProfileRepository) UserProfileService {
return &userProfileService{UserProfileRepo: userProfileRepo}
}
func (s *userProfileService) GetUserProfile(userID string) (*dto.UserResponseDTO, error) {
cacheKey := fmt.Sprintf("userProfile:%s", userID)
cachedData, err := utils.GetJSONData(cacheKey)
if err == nil && cachedData != nil {
userResponse := &dto.UserResponseDTO{}
if data, ok := cachedData["data"].(string); ok {
if err := json.Unmarshal([]byte(data), userResponse); err != nil {
return nil, err
}
return userResponse, nil
}
}
user, err := s.UserProfileRepo.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,
}
cacheData := map[string]interface{}{
"data": userResponse,
}
err = utils.SetJSONData(cacheKey, cacheData, time.Hour*24)
if err != nil {
fmt.Printf("Error caching user profile to Redis: %v\n", err)
}
return userResponse, nil
}

View File

@ -1,7 +1,6 @@
package middleware
import (
"log"
"os"
"github.com/gofiber/fiber/v2"
@ -9,15 +8,14 @@ import (
)
func APIKeyMiddleware(c *fiber.Ctx) error {
apiKey := c.Get("x-api-key")
if apiKey == "" {
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: API key is required")
}
validAPIKey := os.Getenv("API_KEY")
if apiKey != validAPIKey {
log.Printf("Invalid API Key: %s", apiKey)
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: api key yang anda masukkan tidak valid")
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: Invalid API key")
}
return c.Next()

View File

@ -1,8 +1,6 @@
package middleware
import (
"context"
"log"
"os"
"github.com/gofiber/fiber/v2"
@ -23,7 +21,6 @@ func AuthMiddleware(c *fiber.Ctx) error {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(os.Getenv("SECRET_KEY")), nil
})
if err != nil || !token.Valid {
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: Invalid token")
}
@ -33,25 +30,20 @@ func AuthMiddleware(c *fiber.Ctx) error {
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: Invalid token claims")
}
userID, ok := claims["sub"].(string)
if !ok || userID == "" {
log.Println("Invalid userID format in token")
userID := claims["sub"].(string)
if userID == "" {
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: Invalid user session")
}
ctx := context.Background()
sessionKey := "session:" + userID
sessionData, err := utils.GetJSONData(ctx, sessionKey)
sessionData, err := utils.GetJSONData(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")
}
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")
}

View File

@ -1,18 +1,20 @@
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 {
if len(allowedRoles) == 0 {
return utils.GenericErrorResponse(c, fiber.StatusForbidden, "Forbidden: No roles specified")
}
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")
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: Role not found")
}
for _, role := range allowedRoles {
@ -21,7 +23,6 @@ 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")
}
}

View File

@ -27,5 +27,5 @@ func AuthRouter(api fiber.Router) {
api.Post("/login", userHandler.Login)
api.Post("/register", userHandler.Register)
api.Post("/logout", middleware.AuthMiddleware, userHandler.Logout)
api.Get("/user", middleware.AuthMiddleware, userHandler.GetUserProfile)
}

View File

@ -0,0 +1,18 @@
package presentation
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/internal/handler"
"github.com/pahmiudahgede/senggoldong/internal/repositories"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/middleware"
)
func UserProfileRouter(api fiber.Router) {
userProfileRepo := repositories.NewUserProfileRepository(config.DB)
userProfileService := services.NewUserProfileService(userProfileRepo)
userProfileHandler := handler.NewUserProfileHandler(userProfileService)
api.Get("/user", middleware.AuthMiddleware, userProfileHandler.GetUserProfile)
}

View File

@ -10,4 +10,5 @@ func SetupRoutes(app *fiber.App) {
api := app.Group("/apirijikid")
api.Use(middleware.APIKeyMiddleware)
presentation.AuthRouter(api)
presentation.UserProfileRouter(api)
}

View File

@ -12,30 +12,35 @@ import (
var ctx = context.Background()
func SetData(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
const defaultExpiration = 1 * time.Hour
func SetData[T any](key string, value T, expiration time.Duration) error {
if expiration == 0 {
expiration = defaultExpiration
}
jsonData, err := json.Marshal(value)
if err != nil {
log.Printf("Error marshaling JSON data: %v", err)
return err
return logAndReturnError("Error marshaling data to JSON", err)
}
err = config.RedisClient.Set(ctx, key, jsonData, expiration).Err()
if err != nil {
log.Printf("Error setting JSON data to Redis: %v", err)
return err
return logAndReturnError("Error setting data in Redis", err)
}
log.Printf("JSON Data stored in Redis with key: %s", key)
log.Printf("Data stored in Redis with key: %s", key)
return nil
}
func GetData(ctx context.Context, key string) (string, error) {
func GetData(key string) (string, error) {
val, err := config.RedisClient.Get(ctx, key).Result()
if err == redis.Nil {
return "", nil
} else if err != nil {
log.Printf("Error getting data from Redis: %v", err)
return "", err
return "", logAndReturnError("Error retrieving data from Redis", err)
}
return val, nil
}
@ -43,41 +48,26 @@ func GetData(ctx context.Context, key string) (string, error) {
func DeleteData(key string) error {
err := config.RedisClient.Del(ctx, key).Err()
if err != nil {
log.Printf("Error deleting data from Redis: %v", err)
return err
return logAndReturnError("Error deleting data from Redis", err)
}
log.Printf("Data deleted from Redis with key: %s", key)
return nil
}
func CheckKeyExists(ctx context.Context, key string) (bool, error) {
func CheckKeyExists(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
return false, logAndReturnError("Error checking if key exists in Redis", err)
}
return val > 0, nil
}
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
func SetJSONData[T any](key string, value T, expiration time.Duration) error {
return SetData(key, value, 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
}
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)
func GetJSONData(key string) (map[string]interface{}, error) {
val, err := GetData(key)
if err != nil || val == "" {
return nil, err
}
@ -96,3 +86,8 @@ func DeleteSessionData(userID string) error {
sessionKey := "session:" + userID
return DeleteData(sessionKey)
}
func logAndReturnError(message string, err error) error {
log.Printf("%s: %v", message, err)
return err
}