Home > Software design >  Edit the roles of an user using checkboxes
Edit the roles of an user using checkboxes

Time:03-31

I am trying to edit the Roles of an user using html and thymeleaf. The point is that everything works ok, I can edit the username, the password, etc but when it comes to the roles, I get null when saving the List. I can't find anything useful on the internet and I'm struggling with this for a couple of weeks now, I need a way to edit those roles, so the page will return a List containing those roles that are checked.

Here I have the UserDetails:

@Getter
@Setter
public class MyUserDetails implements UserDetails {
    private final User user;

    public MyUserDetails(User user) {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return user.getRoles().stream()
                .map(role->new SimpleGrantedAuthority(role.getName()))
                .collect(Collectors.toList());
    }

    public int getId(){return this.user.getId();}

    public User getUser(){
        return this.user;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return user.isEnabled();
    }
}

Here is the User:

@Builder
@Getter
@Setter
@Entity
@Table(name="user",schema = "public")
@NoArgsConstructor
@AllArgsConstructor
public class User {

    @Id
    @Column(name="user_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @NotEmpty(message = "You must provide a username!")
    @Size(min=5, max=30)
    private String username;

   @NotEmpty(message = "You must provide a password!")
    @ValidPassword
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private String password;

    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private boolean enabled;

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name="user_roles",
            joinColumns = @JoinColumn(name="user_id"),
            inverseJoinColumns = @JoinColumn(name = "role_id")
            )
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private List<Role> roles;

    @JoinColumn(name="last_played")
    private LocalDate lastPlayed;

    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    public List<String> getRoleNames(){
      return roles.stream()
                .map(Role::getName)
                .collect(Collectors.toList());
    }

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<Reward> rewards;

    @Override
    public String toString() {
        return "User{"  
                "id="   id  
                ", username='"   username   '\''  
                ", password='"   password   '\''  
                ", enabled="   enabled  
                ", roles="   roles  
                ", lastPlayed="   lastPlayed  
                ", rewards="   rewards  
                '}';
    }
}

Here is the Role class:

@Getter
@Setter
@Entity
@Table(name="role",schema = "public")
public class Role {

    @Id
    @Column(name="role_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
}

The controller:

@Controller
@RequiredArgsConstructor
public class EditUserController {

    private final IUserDetailsService userService;
    private final RoleService roleService;

        @PostMapping(USERS_SAVE)
    public String saveEdit( @ModelAttribute(AttributeNames.USER) User user,
                            BindingResult bindingResult){
        if(bindingResult.hasErrors()){
            return EDIT_USER;
        }
        userService.updateUser(user);
        return REDIRECT  USERS;
    }

    @GetMapping("/users/{id}")
    public String editUser(@PathVariable int id, Model model){

        List<Role> roleList = roleService.findAll();
        UserDetails user = userService.findUserById(id);

        model.addAttribute(AttributeNames.USER, user);
        model.addAttribute(AttributeNames.ROLE_LIST, roleList);
        return EDIT_USER;
    }

    @DeleteMapping(Mappings.USERS_DELETE_ID)
    public String deleteUser(@PathVariable int id){
            userService.deleteUser(id);
            return REDIRECT USERS;
}
}

And finally the html part for the roles :

 <div >
            <label >Roles</label>
            <div >
               <th:block th:each="role:${roleList}">
                <input type="checkbox"
                       name="roles"
                       th:field="*{user.roles}"
                       th:value="${role.id}"
                       th:text="${role.name}" />
               </th:block>
            </div>
        </div>

Here is a picture with the form, this user has both roles of user and admin, and they are both checked when entering the page, but if I go for the update, the roles will be modified to null. Can you help me figure this out?

https://i.stack.imgur.com/DWpRw.png

CodePudding user response:

The mapping does not need to be many to many, Use a uni directional as this:

@OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.PERSIST)
@JoinTable(
        name = "user_role",
        joinColumns = @JoinColumn(
                name = "user_id",
                referencedColumnName = "id"
        ),
        inverseJoinColumns = @JoinColumn(
                name = "role_id",
                referencedColumnName = "id"
        ))
private List<Role> roles;

Because a role does not need to have references for corresponding users it's being used by. Then have the table created 'user_role'. After that if you change roles and save the user it should be updated in user_role table.

UPDATE Could you try this when returning user:

@GetMapping(path = "/test")
@ResponseStatus(HttpStatus.OK)
public User test() {
    User user = // get user here by id
    //set roles there

    return user;
}

CodePudding user response:

Apparently my problem was that I was adding the MyUserInterface class to the Model insted Of User. So in the Controller I added this:

MyUserDetails userDetails=userService.findUserById(id);
User user = userDetails.getUser();

And now thymeleaf can get access to the list of the User directly.

 <th:block th:each="role:${roleList}">
                <input type="checkbox"
                       name="roles"
                       th:field="*{roles}"
                       th:value="${role.id}"
                       th:text="${role.name}" />
               </th:block>
  • Related