0

I'm trying to build a user registration based on the tutorial from Amigoscode Youtube: Java Tutorial, Git Repository, but I make it reactive with webflux, postgresql and R2DBC.

Now I'm struggling on saving the user data into the database. If I do it like it is in the tutorial, where the return types of the registration methods are String, it wont save the data at all. So I changed the return types to ApplicationUser and put the save() into the return, but now, if I call the endpoint with postman, it will save the data, but it will also return all the user data including the encrypted password to postman. I'm not sure how much of a problem this is but I would rather like to avoid this behaviour. What to do?

This is the version which saves to database, but returns all the data:
ApplicationUserService.java

@RequiredArgsConstructor
@Service
public class ApplicationUserService implements ReactiveUserDetailsService {

    private final static String USER_NOT_FOUND_MSG = "User with email %s not found";

    private final UserRepository userRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    public Mono<ApplicationUser> signUpUser(ApplicationUser applicationUser){

        String encodedPassword = bCryptPasswordEncoder.encode(applicationUser.getPassword());

        applicationUser.setPassword(encodedPassword);

        return userRepository.findByEmail(applicationUser.getEmail())
                // .flatMap(t -> { throw new IllegalStateException("Email already exists"); })
                .switchIfEmpty(userRepository.save(applicationUser));             
    }

Postman Output


This version doesn't return the data, but also doesn't save to the database:
Needs changing return types from Mono<ApplicationUser> to Mono<String> in other functions.
ApplicationUserService.java

@RequiredArgsConstructor
@Service
public class ApplicationUserService implements ReactiveUserDetailsService {

    private final static String USER_NOT_FOUND_MSG = "User with email %s not found";

    private final UserRepository userRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    public Mono<String> signUpUser(ApplicationUser applicationUser){

        String encodedPassword = bCryptPasswordEncoder.encode(applicationUser.getPassword());

        applicationUser.setPassword(encodedPassword);

        userRepository.findByEmail(applicationUser.getEmail())
                .flatMap(t -> { throw new IllegalStateException("Email already exists"); })
                .switchIfEmpty(userRepository.save(applicationUser)); 

        return Mono.just("Sign up works, maybe");           
    }

This returns "Sign up works,maybe" and thats it. No data is saved.

Remaining functions

ApplicationUser.java

@Data
@NoArgsConstructor
@Table("users")
public class ApplicationUser implements UserDetails { 
 
    @Id
    private long id;
    private String username;  
    private String password;
    private String email;
    private UserRole userRole;
    private boolean isAccountNonExpired = true;
    private boolean locked = false;
    private boolean isCredentialsNonExpired = true;
    private boolean isEnabled = false;

    public ApplicationUser(String username, String password, String email,UserRole userRole) {
                               this.username = username;
                               this.password = password;
                               this.email = email;
                               this.userRole = userRole;
                           }
      
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        SimpleGrantedAuthority authority = new SimpleGrantedAuthority(userRole.name());
        return Collections.singletonList(authority);
    }

    @Override
    public String getPassword() {
        // TODO Auto-generated method stub
        return password;
    }

    @Override
    public String getUsername() {
        // TODO Auto-generated method stub
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        // TODO Auto-generated method stub
        return isAccountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        // TODO Auto-generated method stub
        return locked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        // TODO Auto-generated method stub
        return isCredentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        // TODO Auto-generated method stub
        return isEnabled;
    }

    
}

UserRole.java

public enum UserRole {

    USER,
    ADMIN,
    MODERATOR
}

UserRepository.java

@Repository
@Transactional(readOnly = true)
public interface UserRepository extends ReactiveCrudRepository<ApplicationUser, Long>{

    Mono<ApplicationUser> findByEmail(String email);
    
}

RegistrationController.java

@RestController
@RequestMapping(path = "/registration")
@AllArgsConstructor
public class RegistrationController {

    private RegistrationService registrationService;

    @PostMapping
    public Mono<ApplicationUser> register(@RequestBody RegistrationRequest request){ 
        return registrationService.reg(request);
    }
    
}

RegistrationService.java

@Data
@Service
@AllArgsConstructor
public class RegistrationService {

    private EmailValidator emailValidator;
    private ApplicationUserService applicationUserService;
  
    public Mono<ApplicationUser> reg(RegistrationRequest request) { 

        Boolean isValidEmail = emailValidator.test(request.getEmail());

        if(!isValidEmail) {
            throw new IllegalStateException("Email not valid");
        }

        return applicationUserService.signUpUser(
            new ApplicationUser(
                request.getUsername(),
                request.getPassword(),
                request.getEmail(),              
                UserRole.USER

             )
        );
    }
    
}

RegistrationRequest.java

@Data
@AllArgsConstructor
@ToString
public class RegistrationRequest {

    private String username;
    private String password;
    private String email;
    private UserRole userRole;   
}
Macster
  • 81
  • 2
  • 9

1 Answers1

2

If you'd like to return a String after saving into the database, you just have to make sure you don't break the reactive chain:

public Mono<String> signUpUser(ApplicationUser applicationUser){
    String encodedPassword = bCryptPasswordEncoder.encode(applicationUser.getPassword());

    applicationUser.setPassword(encodedPassword);

    return userRepository.findByEmail(applicationUser.getEmail())
            .flatMap(t -> { throw new IllegalStateException("Email already exists"); })
            .switchIfEmpty(userRepository.save(applicationUser))
            .thenReturn("Sign up works, for sure");        
}

Otherwise, if you broke the chain, then the rule "Nothing happens until you subscribe" will come to bite you. That's why you only saw the String returned but not the user saved. As nothing subscribed to the save operation, it was not executed.

Martin Tarjányi
  • 8,863
  • 2
  • 31
  • 49
  • Alright, yes. Thats working too. Great thanks. But it's not like this first sends the userdata and then sending the String right? I couldn't observe that, but just to be sure. – Macster Jun 05 '21 at 13:52
  • 1
    It's a Mono with a single value, so only the String is returned in the end. – Martin Tarjányi Jun 05 '21 at 18:20