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
44 changes: 44 additions & 0 deletions api/handlers/user_handler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package handlers

import (
"context"
"net/http"

"api-shiners/pkg/user"
Expand Down Expand Up @@ -42,3 +43,46 @@ func (ctrl *UserController) GetUserByID(c *fiber.Ctx) error {

return utils.Success(c, http.StatusOK, "Get user by ID successfully", user, nil)
}

func (ctrl *UserController) SetUserRole(c *fiber.Ctx) error {
ctx := context.Background()

// 🔹 Ambil user_id dari URL
userIDParam := c.Params("id")
userID, err := uuid.Parse(userIDParam)
if err != nil {
return utils.Error(c, http.StatusBadRequest, "Invalid user ID format", "InvalidUUID", nil)
}

// 🔹 Ambil nama role dari body
var req struct {
Role string `json:"role"`
}

if err := c.BodyParser(&req); err != nil {
return utils.Error(c, http.StatusBadRequest, "Invalid request body", "BadRequestException", nil)
}

if req.Role == "" {
return utils.Error(c, http.StatusBadRequest, "Role name is required", "BadRequestException", nil)
}

// 🔹 Jalankan service → ambil user hasil update
updatedUser, err := ctrl.userService.SetUserRole(ctx, userID, req.Role)
if err != nil {
return utils.Error(c, http.StatusBadRequest, err.Error(), "SetRoleException", nil)
}

// 🔹 Siapkan response data
data := fiber.Map{
"user_id": updatedUser.ID,
"name": updatedUser.Name,
"email": updatedUser.Email,
"role": req.Role,
}

// 🔹 Response sukses
return utils.Success(c, http.StatusOK, "User role assigned successfully", data, nil)
}


2 changes: 2 additions & 0 deletions api/routes/user_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ func UserRoutes(app *fiber.App, userController *handlers.UserController) {
api := app.Group("/api")
api.Get("/users", middleware.AdminMiddleware, userController.GetAllUsers)
api.Get("/users/:id", middleware.AuthMiddleware, userController.GetUserByID)

api.Post("/users/:id/role", middleware.AdminMiddleware, userController.SetUserRole)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

untuk user profile kasih /profile sendiri agar terpisah dan dia mengambil user id yg login otomatis

}
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import (
"api-shiners/api/handlers"
"api-shiners/api/routes"
"api-shiners/pkg/auth"
"api-shiners/pkg/user"
"api-shiners/pkg/config"
"api-shiners/pkg/user"

_ "api-shiners/docs"

Expand Down Expand Up @@ -65,7 +65,7 @@ func main() {
healthController := handlers.NewHealthController()

userRepo := user.NewUserRepository(config.DB)
userService := user.NewUserService(userRepo)
userService := user.NewUserService(userRepo, authRepo)
userController := handlers.NewUserController(userService)

routes.UserRoutes(app, userController)
Expand Down
29 changes: 27 additions & 2 deletions pkg/auth/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import (
"gorm.io/gorm"
)

type UserRepository interface {
type AuthRepository interface {
FindByEmail(ctx context.Context, email string) (*entities.User, error)
CreateUser(ctx context.Context, user *entities.User) error
FindRoleByName(ctx context.Context, name string) (*entities.Role, error)
AssignUserRole(ctx context.Context, userRole *entities.UserRole) error
UpdateUser(ctx context.Context, user *entities.User) error
RemoveAllRolesFromUser(ctx context.Context, userID uuid.UUID) error

// 🔹 Tambahan untuk Forgot & Reset Password
SaveResetToken(ctx context.Context, userID uuid.UUID, token string, expiresAt time.Time) error
Expand All @@ -28,7 +29,7 @@ type userRepository struct {
db *gorm.DB
}

func NewUserRepository(db *gorm.DB) UserRepository {
func NewUserRepository(db *gorm.DB) AuthRepository {
return &userRepository{db: db}
}

Expand Down Expand Up @@ -62,9 +63,33 @@ func (r *userRepository) FindRoleByName(ctx context.Context, name string) (*enti
}

func (r *userRepository) AssignUserRole(ctx context.Context, userRole *entities.UserRole) error {
// Cek apakah user sudah punya role ini
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minta ai nya jgn ada komen-komen bhs indo kek begini, klo pun perlu pke bahas inggris.. berlau di kode-kode lain yak..

var existing entities.UserRole
err := r.db.WithContext(ctx).
Where("user_id = ? AND role_id = ?", userRole.UserID, userRole.RoleID).
First(&existing).Error

if err == nil {
// Sudah ada, tidak perlu insert ulang
return nil
}

if !errors.Is(err, gorm.ErrRecordNotFound) {
// Error lain (misal DB error)
return err
}

// Belum ada → insert baru
return r.db.WithContext(ctx).Create(userRole).Error
}

func (r *userRepository) RemoveAllRolesFromUser(ctx context.Context, userID uuid.UUID) error {
return r.db.WithContext(ctx).
Where("user_id = ?", userID).
Delete(&entities.UserRole{}).Error
}


func (r *userRepository) UpdateUser(ctx context.Context, user *entities.User) error {
return r.db.WithContext(ctx).Save(user).Error
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/auth/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ type AuthService interface {
}

type authService struct {
userRepo UserRepository
userRepo AuthRepository
}

func NewAuthService(userRepo UserRepository) AuthService {
func NewAuthService(userRepo AuthRepository) AuthService {
return &authService{userRepo: userRepo}
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/test/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,8 @@ func TestResetPassword_InvalidToken(t *testing.T) {
assert.Error(t, err)
assert.EqualError(t, err, "invalid or expired token")
}

func (m *MockUserRepo) RemoveAllRolesFromUser(ctx context.Context, userID uuid.UUID) error {
args := m.Called(ctx, userID)
return args.Error(0)
}
4 changes: 4 additions & 0 deletions pkg/user/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ func (r *userRepository) GetByID(id uuid.UUID) (entities.User, error) {
err := r.db.Preload("Roles").First(&user, "id = ?", id).Error
return user, err
}




84 changes: 60 additions & 24 deletions pkg/user/service.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
package user

import (
"api-shiners/pkg/auth"
"api-shiners/pkg/config"
"api-shiners/pkg/entities"
"context"
"encoding/json"
"errors"
"fmt"
"log"
"time"

"api-shiners/pkg/config"
"api-shiners/pkg/entities"

"github.com/google/uuid"
)

type UserService interface {
GetAllUsers() ([]entities.User, error)
GetUserByID(id uuid.UUID) (entities.User, error)
SetUserRole(ctx context.Context, userID uuid.UUID, roleName string) (*entities.User, error)
}

type userService struct {
userRepo UserRepository
authRepo auth.AuthRepository
}

func NewUserService(userRepo UserRepository) UserService {
return &userService{userRepo: userRepo}
// ✅ Constructor tunggal — wajib dipakai
func NewUserService(userRepo UserRepository, authRepo auth.AuthRepository) UserService {
return &userService{
userRepo: userRepo,
authRepo: authRepo,
}
}

// ===== GET ALL USERS (dengan caching Redis opsional) =====
Expand All @@ -33,15 +39,13 @@ func (s *userService) GetAllUsers() ([]entities.User, error) {

var users []entities.User

// 🔹 Coba ambil dari Redis (jika aktif)
// 🔹 Coba ambil dari Redis
if config.RedisClient != nil {
val, err := config.RedisClient.Get(ctx, cacheKey).Result()
if err == nil && val != "" {
if err := json.Unmarshal([]byte(val), &users); err == nil {
return users, nil
}
} else if err != nil && err.Error() != "redis: client is closed" {
log.Println("⚠️ Redis not available or not running, skip caching...")
}
}

Expand All @@ -51,51 +55,83 @@ func (s *userService) GetAllUsers() ([]entities.User, error) {
return nil, err
}

// 🔹 Simpan ke cache (jika Redis aktif)
// 🔹 Simpan ke Redis
if config.RedisClient != nil {
data, _ := json.Marshal(users)
err := config.RedisClient.Set(ctx, cacheKey, data, 5*time.Minute).Err()
if err != nil {
log.Println("⚠️ Failed to cache users:", err)
}
config.RedisClient.Set(ctx, cacheKey, data, 5*time.Minute)
}

return users, nil
}

// ===== GET USER BY ID (dengan caching Redis opsional) =====
// ===== GET USER BY ID =====
func (s *userService) GetUserByID(id uuid.UUID) (entities.User, error) {
ctx := context.Background()
cacheKey := fmt.Sprintf("user:%s", id.String())

var user entities.User

// 🔹 Coba ambil dari Redis (jika aktif)
if config.RedisClient != nil {
val, err := config.RedisClient.Get(ctx, cacheKey).Result()
if err == nil && val != "" {
if err := json.Unmarshal([]byte(val), &user); err == nil {
return user, nil
}
} else if err != nil && err.Error() != "redis: client is closed" {
log.Println("⚠️ Redis not available or not running, skip caching...")
}
}

// 🔹 Ambil dari DB
user, err := s.userRepo.GetByID(id)
if err != nil {
return entities.User{}, err
}

// 🔹 Simpan ke Redis (jika aktif)
if config.RedisClient != nil {
data, _ := json.Marshal(user)
err := config.RedisClient.Set(ctx, cacheKey, data, 10*time.Minute).Err()
if err != nil {
log.Println("⚠️ Failed to cache user:", err)
}
config.RedisClient.Set(ctx, cacheKey, data, 10*time.Minute)
}

return user, nil
}

// ===== SET USER ROLE =====
func (s *userService) SetUserRole(ctx context.Context, userID uuid.UUID, roleName string) (*entities.User, error) {
// Cek dependency dulu
if s.userRepo == nil || s.authRepo == nil {
return nil, errors.New("userRepo atau authRepo belum diinisialisasi dengan benar")
}

// 1. Cari user berdasarkan ID
user, err := s.userRepo.GetByID(userID)
if err != nil {
return nil, errors.New("user not found")
}

// 2. Cari role berdasarkan nama
role, err := s.authRepo.FindRoleByName(ctx, roleName)
if err != nil {
return nil, errors.New("role not found")
}

// 3. Hapus semua role lama user
if err := s.authRepo.RemoveAllRolesFromUser(ctx, user.ID); err != nil {
return nil, fmt.Errorf("failed to clear old roles: %v", err)
}

// 4. Tambahkan role baru
userRole := &entities.UserRole{
UserID: user.ID,
RoleID: role.ID,
}
if err := s.authRepo.AssignUserRole(ctx, userRole); err != nil {
return nil, err
}

// 5. Ambil ulang user untuk dikembalikan (dengan role terbaru)
updatedUser, err := s.userRepo.GetByID(userID)
if err != nil {
return nil, err
}

return &updatedUser, nil
}