Learn

User Roles & Secondary Indexes

Brian Sam-Bodden
Author
Brian Sam-Bodden, Developer Advocate at Redis

Objectives#

Agenda#

Loading Users#

{
  "password": "9yNvIO4GLBdboI",
  "name": "Georgia Spencer",
  "id": -5035019007718357598,
  "email": "[email protected]"
}

User Repository#

package com.redislabs.edu.redi2read.repositories;

import com.redislabs.edu.redi2read.models.User;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends CrudRepository<User, String> {
  User findFirstByEmail(String email);
}

Prerequisites#

Password Encoding#

@Bean
public BCryptPasswordEncoder passwordEncoder() {
  return new BCryptPasswordEncoder();
}
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

Secondary Indexes: Finding Roles by Name#

@Data
@Builder
@RedisHash
public class Role {
  @Id
  private String id;

  @Indexed
  private String name;
}
import org.springframework.data.redis.core.index.Indexed;
127.0.0.1:6379> KEYS com.redislabs.edu.redi2read.models.Role*
1) "com.redislabs.edu.redi2read.models.Role:c4219654-0b79-4ee6-b928-cb75909c4464"
2) "com.redislabs.edu.redi2read.models.Role:9d383baf-35a0-4d20-8296-eedc4bea134a"
3) "com.redislabs.edu.redi2read.models.Role"
127.0.0.1:6379> DEL "com.redislabs.edu.redi2read.models.Role:c4219654-0b79-4ee6-b928-cb75909c4464" "com.redislabs.edu.redi2read.models.Role:9d383baf-35a0-4d20-8296-eedc4bea134a" "com.redislabs.edu.redi2read.models.Role"
(integer) 3
127.0.0.1:6379>
@Repository
public interface RoleRepository extends CrudRepository<Role, String> {
  Role findFirstByName(String role);
}

CreateUsers CommandLineRunner#

package com.redislabs.edu.redi2read.boot;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.redislabs.edu.redi2read.models.Role;
import com.redislabs.edu.redi2read.models.User;
import com.redislabs.edu.redi2read.repositories.RoleRepository;
import com.redislabs.edu.redi2read.repositories.UserRepository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Component
@Order(2)
@Slf4j
public class CreateUsers implements CommandLineRunner {

  @Autowired
  private RoleRepository roleRepository;

  @Autowired
  private UserRepository userRepository;

  @Autowired
  private BCryptPasswordEncoder passwordEncoder;

  @Override
  public void run(String... args) throws Exception {
    if (userRepository.count() == 0) {
      // load the roles
      Role admin = roleRepository.findFirstByname("admin");
      Role customer = roleRepository.findFirstByname("customer");

      try {
        // create a Jackson object mapper
        ObjectMapper mapper = new ObjectMapper();
        // create a type definition to convert the array of JSON into a List of Users
        TypeReference<List<User>> typeReference = new TypeReference<List<User>>() {
        };
        // make the JSON data available as an input stream
        InputStream inputStream = getClass().getResourceAsStream("/data/users/users.json");
        // convert the JSON to objects
        List<User> users = mapper.readValue(inputStream, typeReference);

        users.stream().forEach((user) -> {
          user.setPassword(passwordEncoder.encode(user.getPassword()));
          user.addRole(customer);
          userRepository.save(user);
        });
        log.info(">>>> " + users.size() + " Users Saved!");
      } catch (IOException e) {
        log.info(">>>> Unable to import users: " + e.getMessage());
      }

      User adminUser = new User();
      adminUser.setName("Adminus Admistradore");
      adminUser.setEmail("[email protected]");
      adminUser.setPassword(passwordEncoder.encode("Reindeer Flotilla"));//
      adminUser.addRole(admin);

      userRepository.save(adminUser);
      log.info(">>>> Loaded User Data and Created users...");
    }
  }
}
2021-04-03 10:05:04.222  INFO 40386 --- [  restartedMain] c.r.edu.redi2read.Redi2readApplication   : Started Redi2readApplication in 2.192 seconds (JVM running for 2.584)
2021-04-03 10:05:04.539  INFO 40386 --- [  restartedMain] c.r.edu.redi2read.boot.CreateRoles       : >>>> Created admin and customer roles...
2021-04-03 10:06:27.292  INFO 40386 --- [  restartedMain] c.r.edu.redi2read.boot.CreateUsers       : >>>> 1000 Users Saved!
2021-04-03 10:06:27.373  INFO 40386 --- [  restartedMain] c.r.edu.redi2read.boot.CreateUsers       : >>>> Loaded User Data and Created users...

Exploring the loaded Users#

127.0.0.1:6379> KEYS "com.redislabs.edu.redi2read.models.User"
1) "com.redislabs.edu.redi2read.models.User"
127.0.0.1:6379> TYPE "com.redislabs.edu.redi2read.models.User"
set
127.0.0.1:6379> SCARD "com.redislabs.edu.redi2read.models.User"
(integer) 1001
127.0.0.1:6379> SRANDMEMBER "com.redislabs.edu.redi2read.models.User"
"-1848761758049653394"
127.0.0.1:6379> HGETALL "com.redislabs.edu.redi2read.models.User:-1848761758049653394"
 1) "id"
 2) "-1848761758049653394"
 3) "_class"
 4) "com.redislabs.edu.redi2read.models.User"
 5) "roles.[0]"
 6) "com.redislabs.edu.redi2read.models.Role:a9f9609f-c173-4f48-a82d-ca88b0d62d0b"
 7) "name"
 8) "Janice Garza"
 9) "email"
10) "[email protected]"
11) "password"
12) "$2a$10$/UHTESWIqcl6HZmGpWSUHexNymIgM7rzOsWc4tcgqh6W5OVO4O46."

Building the Redi2Read API#

package com.redislabs.edu.redi2read.controllers;

import com.redislabs.edu.redi2read.models.User;
import com.redislabs.edu.redi2read.repositories.UserRepository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/users")
public class UserController {

  @Autowired
  private UserRepository userRepository;

  @GetMapping
  public Iterable<User> all() {
    return userRepository.findAll();
  }
}
$ curl --location --request GET 'http://localhost:8080/api/users/'
[
   {
       "id": "-1180251602608130769",
       "name": "Denise Powell",
       "email": "[email protected]",
       "password": "$2a$10$pMJjQ2bFAUGlBTX9cHsx/uGrbbl3JZmmiR.vG5xaVwQodQyLaj52a",
       "passwordConfirm": null,
       "roles": [
           {
               "id": "a9f9609f-c173-4f48-a82d-ca88b0d62d0b",
               "name": "customer"
           }
       ]
   },
...
]
@JsonIgnoreProperties(value = { "password", "passwordConfirm" }, allowSetters = true)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@ToString(onlyExplicitlyIncluded = true)
@Data
@RedisHash
public class User {
...
}
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
[
    {
        "id": "-1180251602608130769",
        "name": "Denise Powell",
        "email": "[email protected]",
        "roles": [
            {
                "id": "a9f9609f-c173-4f48-a82d-ca88b0d62d0b",
                "name": "customer"
            }
        ]
    },
...
]
 @GetMapping
 public Iterable<User> all(@RequestParam(defaultValue = "") String email) {
   if (email.isEmpty()) {
     return userRepository.findAll();
   } else {
     Optional<User> user = Optional.ofNullable(userRepository.findFirstByEmail(email));
     return user.isPresent() ? List.of(user.get()) : Collections.emptyList();
   }
 }
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.springframework.web.bind.annotation.RequestParam;
curl --location --request GET 'http://localhost:8080/api/users/[email protected]'
[
  {
    "id": "-1266125356844480724",
    "name": "Donald Gibson",
    "email": "[email protected]",
    "roles": [
      {
        "id": "a9f9609f-c173-4f48-a82d-ca88b0d62d0b",
        "name": "customer"
      }
    ]
  }
]