1

I would like to conditionally validate certain things on my models in my Spring Boot application based upon which route the model is being validated by.

I have two routes, one for signing up for logging in. The one for logging in simply requires a username and password. The one for signing up requires an email, username and password. I would like to use the same model using javax.validation.constraints on both routes.

User.java

public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Column(nullable=false,unique = true)
    @NotBlank(message = "email is blank")
    @Email(message = "invalid email")
    private String email;
    @Column(nullable=false,updatable = false,unique = true)
    @Size(min = 8, max = 32, message = "username must be between 8 and 32 characters")
    private String username;
    @Column(nullable=false)
    @NotBlank(message = "missing password")
    @Size(min = 8, max = 32, message = "password must be between 8 and 32 characters")
    private String password;
    @Column(columnDefinition = "text")
    private String about;
    @Column(nullable=false,updatable = false)
    @CreationTimestamp
    private LocalDateTime createdAt;
    private LocalDateTime passwordChangedAt;
    private LocalDateTime aboutChangedAt;

UserController.java

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/auth")
public class UserController {
  private final AuthenticationManager authenticationManager;
  private final UserRepository userRepository;
  private final RoleRepository roleRepository;
  private final PasswordEncoder encoder;
  private final JwtUtils jwtUtils;

  public UserController(AuthenticationManager authenticationManager, UserRepository userRepository, RoleRepository roleRepository, PasswordEncoder encoder, JwtUtils jwtUtils) {
    this.authenticationManager = authenticationManager;
    this.userRepository = userRepository;
    this.roleRepository = roleRepository;
    this.encoder = encoder;
    this.jwtUtils = jwtUtils;
  }
  @PostMapping("/signin")
  public ResponseEntity<?> authenticateUser(@Valid @RequestBody User loginRequest) {

    Authentication authentication = authenticationManager.authenticate(
        new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));

    SecurityContextHolder.getContext().setAuthentication(authentication);
    String jwt = jwtUtils.generateJwtToken(authentication);
    
    UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();    
    List<String> roles = userDetails.getAuthorities().stream()
        .map(item -> item.getAuthority())
        .collect(Collectors.toList());

    return ResponseEntity.ok(new JwtResponse(jwt, 
                         userDetails.getId(), 
                         userDetails.getUsername(), 
                         userDetails.getEmail(), 
                         roles));
  }

  @PostMapping("/signup")
  public ResponseEntity<?> registerUser(@Valid @RequestBody User signUpRequest) {
    if (userRepository.existsByUsername(signUpRequest.getUsername())) {
      return ResponseEntity
          .badRequest()
          .body(new MessageResponse("Error: Username is already taken!"));
    }

    if (userRepository.existsByEmail(signUpRequest.getEmail())) {
      return ResponseEntity
          .badRequest()
          .body(new MessageResponse("Error: Email is already in use!"));
    }

    // Create new user's account
    User user = new User(signUpRequest.getUsername(), 
               signUpRequest.getEmail(),
               encoder.encode(signUpRequest.getPassword()));

    Set<String> strRoles = signUpRequest.getRole();
    Set<Role> roles = new HashSet<>();

    if (strRoles == null) {
      Role userRole = roleRepository.findByName(ERole.ROLE_USER)
          .orElseThrow(() -> new RuntimeException("Error: Role is not found."));
      roles.add(userRole);
    } else {
      strRoles.forEach(role -> {
        switch (role) {
        case "admin":
          Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN)
              .orElseThrow(() -> new RuntimeException("Error: Role is not found."));
          roles.add(adminRole);

          break;
        case "mod":
          Role modRole = roleRepository.findByName(ERole.ROLE_MODERATOR)
              .orElseThrow(() -> new RuntimeException("Error: Role is not found."));
          roles.add(modRole);

          break;
        default:
          Role userRole = roleRepository.findByName(ERole.ROLE_USER)
              .orElseThrow(() -> new RuntimeException("Error: Role is not found."));
          roles.add(userRole);
        }
      });
    }

    user.setRoles(roles);
    userRepository.save(user);

    return ResponseEntity.ok(new MessageResponse("User registered successfully!"));
  }
}

I would like to use the same User model/entity for both. Is there a way to do this?

I know I can create different request templates that I would then map to a model, however I am curious if there is a more concise way to do this using annotations.

Toni
  • 3,296
  • 2
  • 13
  • 34
vw0389
  • 51
  • 1
  • 6
  • Hi and welcome to stack overflow. What is the thought behind wanting to use the same model for both routes? – hooknc Dec 27 '22 at 19:44
  • Less redudant code, reusability. – vw0389 Dec 27 '22 at 19:45
  • I see... I would urge you to focus on solving the problems at hand without worrying too much about redundant code and reusability. Those two issues should become apparent as you write your code. I would recommend breaking your two apis into separate controller classes and have a separate model for each controller with their individual validation logic. It will be much simpler to read/understand in the long run. – hooknc Dec 27 '22 at 19:49
  • ok. It's worth noting I'm doing this project for prospective employers, so I didn't want to look silly. I'll do that. Thank you!. – vw0389 Dec 27 '22 at 19:52
  • Sounds good. I do not believe you would look silly by keeping things simple and easy to read. Best of luck with your future work endeavors! – hooknc Dec 27 '22 at 19:54

1 Answers1

0

While you annotate your Model method parameter with @Valid, you can instead use Spring's @Validated annotation.

This is similar to already answered StackOverflow question.

You can read on using validation Groups from this baeldung blog.

Preet
  • 111
  • 4