From e35edf31723c9457cbf95be83b5abcb98810aaa3 Mon Sep 17 00:00:00 2001 From: areeqakbr Date: Thu, 19 Jun 2025 19:35:02 +0700 Subject: [PATCH] adding new feature for updating new roles --- .../controller/user_management_controller.go | 274 ++++++++++++++---- delivery/server.go | 1 + repository/users_repo.go | 10 + usecase/users_usecase.go | 42 +++ 4 files changed, 277 insertions(+), 50 deletions(-) diff --git a/delivery/controller/user_management_controller.go b/delivery/controller/user_management_controller.go index 209915d..a3638b8 100644 --- a/delivery/controller/user_management_controller.go +++ b/delivery/controller/user_management_controller.go @@ -1,89 +1,263 @@ package controller import ( - "net/http" - "users_management/m/middleware" - "users_management/m/usecase" - "users_management/m/utils/common" - - "github.com/gin-gonic/gin" + "errors" + "net/http" + "users_management/m/middleware" + "users_management/m/usecase" + "users_management/m/utils/common" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" ) -type UserManagementController struct { +type UserRoleManagementController struct { userUC usecase.UsersUsecase rg *gin.RouterGroup } -func NewUserManagementController(userUC usecase.UsersUsecase, rg *gin.RouterGroup) *UserManagementController { - return &UserManagementController{ +func NewUserRoleManagementController(userUC usecase.UsersUsecase, rg *gin.RouterGroup) *UserRoleManagementController { + return &UserRoleManagementController{ userUC: userUC, rg: rg, } } -func (c *UserManagementController) Route() { - users := c.rg.Group("/user-management") +func (c *UserRoleManagementController) Route() { + userRole := c.rg.Group("/user-role-management") + userRole.Use(middleware.RequireAdminRole()) // Only Admin and Super Admin can access { - // Only superadmin can manage user roles - users.PUT("/role", middleware.RequireSuperAdminRole(), c.updateUserRole) - - // Admins and superadmins can view all users - users.GET("/users", middleware.RequireAdminRole(), c.getAllUsers) - - // Users can view their own profile - users.GET("/profile", c.getMyProfile) + userRole.PUT("/update/:id", c.updateUserRoleByID) + userRole.PUT("/update-by-username", c.updateUserRoleByUsername) + userRole.GET("/users", c.getAllUsersWithRoles) + userRole.GET("/available-roles", c.getAvailableRoles) } } -type UpdateRoleRequest struct { - NomorInduk string `json:"nomor_induk" binding:"required"` - RoleName string `json:"role_name" binding:"required"` +type UpdateUserRoleByIDRequest struct { + NewRole string `json:"new_role" binding:"required,oneof=Teknisi Admin"` } -func (c *UserManagementController) updateUserRole(ctx *gin.Context) { - var req UpdateRoleRequest +type UpdateUserRoleByUsernameRequest struct { + Username string `json:"username" binding:"required"` + NewRole string `json:"new_role" binding:"required,oneof=Teknisi Admin"` +} + +func (c *UserRoleManagementController) updateUserRoleByID(ctx *gin.Context) { + // Get user ID from URL parameter + userIDStr := ctx.Param("id") + userID, err := uuid.Parse(userIDStr) + if err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid user ID format") + return + } + + var req UpdateUserRoleByIDRequest if err := ctx.ShouldBindJSON(&req); err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } - err := c.userUC.UpdateUserRole(req.NomorInduk, req.RoleName) - if err != nil { - common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error()) - return - } - - common.SingleResponses(ctx, "User role updated successfully", nil) -} - -func (c *UserManagementController) getAllUsers(ctx *gin.Context) { - users, err := c.userUC.GetAllUsers() - if err != nil { - common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error()) - return - } - - common.SingleResponses(ctx, "Users retrieved successfully", users) -} - -func (c *UserManagementController) getMyProfile(ctx *gin.Context) { - userID, exists := ctx.Get("userID") + // Get current user role to check permissions + currentUserRole, exists := ctx.Get("userRole") if !exists { - common.ErrorResponses(ctx, http.StatusUnauthorized, "User ID not found") + common.ErrorResponses(ctx, http.StatusUnauthorized, "User role not found") return } - nomorInduk, ok := userID.(string) + currentUserRoleStr, ok := currentUserRole.(string) if !ok { - common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid user ID") + common.ErrorResponses(ctx, http.StatusUnauthorized, "Invalid user role format") return } - user, err := c.userUC.GetUserByNomorInduk(nomorInduk) + // Get target user to check current role and prevent invalid operations + targetUser, err := c.userUC.GetUserByID(userID) if err != nil { common.ErrorResponses(ctx, http.StatusNotFound, "User not found") return } - common.SingleResponses(ctx, "User profile retrieved successfully", user) + // Validate role change permissions + if err := c.validateRoleChange(currentUserRoleStr, targetUser.Role.Name, req.NewRole); err != nil { + common.ErrorResponses(ctx, http.StatusForbidden, err.Error()) + return + } + + // Update user role + err = c.userUC.UpdateUserRoleByID(userID, req.NewRole) + if err != nil { + common.ErrorResponses(ctx, http.StatusInternalServerError, "Failed to update user role: "+err.Error()) + return + } + + // Get updated user info + updatedUser, err := c.userUC.GetUserByID(userID) + if err != nil { + // Role was updated but couldn't fetch updated info + common.SingleResponses(ctx, "User role updated successfully", gin.H{ + "user_id": userID, + "new_role": req.NewRole, + }) + return + } + + response := gin.H{ + "user_id": updatedUser.ID, + "username": updatedUser.Username, + "name": updatedUser.Name, + "previous_role": targetUser.Role.Name, + "new_role": updatedUser.Role.Name, + "updated_by": ctx.GetString("userName"), + } + + common.SingleResponses(ctx, "User role updated successfully", response) +} + +func (c *UserRoleManagementController) updateUserRoleByUsername(ctx *gin.Context) { + var req UpdateUserRoleByUsernameRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) + return + } + + // Get current user role to check permissions + currentUserRole, exists := ctx.Get("userRole") + if !exists { + common.ErrorResponses(ctx, http.StatusUnauthorized, "User role not found") + return + } + + currentUserRoleStr, ok := currentUserRole.(string) + if !ok { + common.ErrorResponses(ctx, http.StatusUnauthorized, "Invalid user role format") + return + } + + // Get target user + targetUser, err := c.userUC.GetUserByUsernameWithStatus(req.Username) + if err != nil { + common.ErrorResponses(ctx, http.StatusNotFound, "User not found") + return + } + + // Validate role change permissions + if err := c.validateRoleChange(currentUserRoleStr, targetUser.Role.Name, req.NewRole); err != nil { + common.ErrorResponses(ctx, http.StatusForbidden, err.Error()) + return + } + + // Update user role by username + err = c.userUC.UpdateUserRoleByUsername(req.Username, req.NewRole) + if err != nil { + common.ErrorResponses(ctx, http.StatusInternalServerError, "Failed to update user role: "+err.Error()) + return + } + + // Get updated user info + updatedUser, err := c.userUC.GetUserByUsernameWithStatus(req.Username) + if err != nil { + // Role was updated but couldn't fetch updated info + common.SingleResponses(ctx, "User role updated successfully", gin.H{ + "username": req.Username, + "new_role": req.NewRole, + }) + return + } + + response := gin.H{ + "user_id": updatedUser.ID, + "username": updatedUser.Username, + "name": updatedUser.Name, + "previous_role": targetUser.Role.Name, + "new_role": updatedUser.Role.Name, + "updated_by": ctx.GetString("userName"), + } + + common.SingleResponses(ctx, "User role updated successfully", response) +} + +func (c *UserRoleManagementController) getAllUsersWithRoles(ctx *gin.Context) { + users, err := c.userUC.GetAllUsers() + if err != nil { + common.ErrorResponses(ctx, http.StatusInternalServerError, "Failed to get users: "+err.Error()) + return + } + + // Filter and format response + var userList []gin.H + for _, user := range users { + // Only show approved users + if user.Status == "approved" { + userInfo := gin.H{ + "id": user.ID, + "username": user.Username, + "name": user.Name, + "nomor_induk": user.NomorInduk, + "role": user.Role.Name, + "status": user.Status, + "created_at": user.CreatedAt, + "updated_at": user.UpdatedAt, + } + userList = append(userList, userInfo) + } + } + + response := gin.H{ + "users": userList, + "total": len(userList), + } + + common.SingleResponses(ctx, "Users retrieved successfully", response) +} + +func (c *UserRoleManagementController) getAvailableRoles(ctx *gin.Context) { + roles := []gin.H{ + { + "name": "Teknisi", + "description": "Technical user with limited administrative access", + "can_be_assigned_by": []string{"Admin", "Super Admin"}, + }, + { + "name": "Admin", + "description": "Administrative user with full system access except user management", + "can_be_assigned_by": []string{"Admin", "Super Admin"}, + }, + } + + currentUserRole := ctx.GetString("userRole") + + response := gin.H{ + "available_roles": roles, + "your_role": currentUserRole, + "note": "You can update users between Teknisi and Admin roles only", + } + + common.SingleResponses(ctx, "Available roles for assignment", response) +} + +// Helper function to validate role change permissions +func (c *UserRoleManagementController) validateRoleChange(currentUserRole, targetCurrentRole, newRole string) error { + // Super Admin can change any role to Teknisi or Admin + if currentUserRole == "Super Admin" { + return nil + } + + // Admin can change roles but with some restrictions + if currentUserRole == "Admin" { + // Admin cannot change Super Admin roles + if targetCurrentRole == "Super Admin" { + return errors.New("cannot modify Super Admin users") + } + + // Admin cannot assign Super Admin role + if newRole == "Super Admin" { + return errors.New("cannot assign Super Admin role") + } + + return nil + } + + // Only Admin and Super Admin can change roles + return errors.New("insufficient permissions to change user roles") } \ No newline at end of file diff --git a/delivery/server.go b/delivery/server.go index 021874a..41a6eb8 100644 --- a/delivery/server.go +++ b/delivery/server.go @@ -78,6 +78,7 @@ func (s *Server) setupController() { protectedRegistration.PUT("/approve/:id", middleware.RequireAdminRole(), controller.NewUserRegistrationController(s.ucManager.NewUserUsecase(), protected).ApproveUser) protectedRegistration.PUT("/reject/:id", middleware.RequireAdminRole(), controller.NewUserRegistrationController(s.ucManager.NewUserUsecase(), protected).RejectUser) } + controller.NewUserRoleManagementController(s.ucManager.NewUserUsecase(), protected).Route() controller.NewUsersController(s.ucManager.NewUserUsecase(), s.ucManager.NewAuthUsecase(), protected).Route() controller.NewCountAssetsController(s.ucManager.NewCountAssetsUsecase(), protected).Route() controller.NewDeviceController(s.ucManager.NewDeviceUsecase(), protected, s.cfg).Route() diff --git a/repository/users_repo.go b/repository/users_repo.go index 9e72816..cb1258c 100644 --- a/repository/users_repo.go +++ b/repository/users_repo.go @@ -21,6 +21,8 @@ type UsersRepo interface { GetUserByID(userID uuid.UUID) (entity.User, error) GetUserByUsernameWithStatus(username string) (entity.User, error) CreateSuperAdminBypass(user entity.User) error + UpdateUserRoleByID(userID uuid.UUID, roleID uuid.UUID) error + UpdateUserRoleByUsername(username string, roleID uuid.UUID) error } type usersRepo struct { @@ -33,6 +35,14 @@ func NewUsersRepo(db *gorm.DB) UsersRepo { } } +func (r *usersRepo) UpdateUserRoleByID(userID uuid.UUID, roleID uuid.UUID) error { + return r.db.Model(&entity.User{}).Where("id = ?", userID).Update("role_id", roleID).Error +} + +func (r *usersRepo) UpdateUserRoleByUsername(username string, roleID uuid.UUID) error { + return r.db.Model(&entity.User{}).Where("username = ?", username).Update("role_id", roleID).Error +} + func (r *usersRepo) CreateSuperAdminBypass(user entity.User) error { return r.db.Create(&user).Error } diff --git a/usecase/users_usecase.go b/usecase/users_usecase.go index d49de6f..641dbb9 100644 --- a/usecase/users_usecase.go +++ b/usecase/users_usecase.go @@ -25,6 +25,9 @@ type UsersUsecase interface { RejectUser(userID uuid.UUID) error GetUserByUsernameWithStatus(username string) (entity.User, error) CreateSuperAdminBypass(user entity.User) error + UpdateUserRoleByID(userID uuid.UUID, newRoleName string) error + UpdateUserRoleByUsername(username, newRoleName string) error + GetUserByID(userID uuid.UUID) (entity.User, error) } @@ -45,6 +48,45 @@ func (u *usersUsecase) CreateSuperAdminBypass(user entity.User) error { return u.userRepo.CreateSuperAdminBypass(user) } +// Add these implementations to the usersUsecase struct +func (u *usersUsecase) UpdateUserRoleByID(userID uuid.UUID, newRoleName string) error { + // Get the new role ID + newRoleID, err := u.GetRoleByDepartment(newRoleName) + if err != nil { + return errors.New("invalid role name: " + err.Error()) + } + + // Check if user exists + _, err = u.userRepo.GetUserByID(userID) + if err != nil { + return errors.New("user not found") + } + + // Update user role + return u.userRepo.UpdateUserRoleByID(userID, newRoleID) +} + +func (u *usersUsecase) UpdateUserRoleByUsername(username, newRoleName string) error { + // Get the new role ID + newRoleID, err := u.GetRoleByDepartment(newRoleName) + if err != nil { + return errors.New("invalid role name: " + err.Error()) + } + + // Check if user exists + _, err = u.userRepo.GetUserByUsernameWithStatus(username) + if err != nil { + return errors.New("user not found") + } + + // Update user role by username + return u.userRepo.UpdateUserRoleByUsername(username, newRoleID) +} + +func (u *usersUsecase) GetUserByID(userID uuid.UUID) (entity.User, error) { + return u.userRepo.GetUserByID(userID) +} + func (u *usersUsecase) RegisterUser(registerDTO dto.UserRegisterDTO) error { // Validate input if err := u.validate.Struct(registerDTO); err != nil {