Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Building a Twitter Clone With Neo4j: Part VII

DZone's Guide to

Building a Twitter Clone With Neo4j: Part VII

Let’s switch gears and get to work on the front-end. Learn how to register and login with your user and wire up your framework and Grittier service.

· Database Zone
Free Resource

Traditional relational databases weren’t designed for today’s customers. Learn about the world’s first NoSQL Engagement Database purpose-built for the new era of customer experience.

Alright, we’ve had enough back-end work on our Twitter Clone. Let’s switch gears and get to work on the front-end. I’ve decided I’m going to use a Java micro framework for my front-end, but if your language of choice is Ruby, Python, Go, or whatever, find an alternative library and follow along.

I want to be fully honest and admit that I’m not much of a front-end developer.

Image title

I ended up finding a series on YouTube on building your own Twitter clone, from Andrew Hoffman and decided to borrow the CSS and styling with a small update since I’ll be using the latest Twitter Bootstrap framework.

We’re going to be using Jooby once again. I gave you an introduction to this framework on a previous blog post when we started out building our own multi-model database. If you want to follow along, open up the source code on GitHub on another tab and flip back and forth.

Jooby makes it really easy to get started, you can create a project using a Maven Archetype and it will produce a skeleton application for you that just says Hello:

public class App extends Jooby { // 1.
 
  {
    // 2. add a route
    get("/", () -> "Hello");
  }
 
  public static void main(String[] args) {
    // 3. run my app
    run(App::new, args);
  }
}

Not a bad start, but we need to add a few things. In development mode, it is very useful to add the Whoops module so we can see where we screwed up. I’m also going to be doing a ton of JSON, so I’ll grab Jackson, and for my template engine I am experimenting with a new one called Rocker by Fizzed because it has ridiculous performance claims and I’m a sucker for high-speed libraries (~250% faster than Freemarker while also requiring orders-of-magnitude less memory). The “by” at the end is the standard for Jooby plugins. If we were to build one for Neo4j, it would be Neo4jby.

// Debug friendly error messages
on("dev", () -> use(new Whoops()));
 
// Configure Jackson
use(new Jackson().doWith(mapper -> {
    mapper.setTimeZone(TimeZone.getTimeZone("UTC"));
}));
 
// Setup Template Engine
use(new Rockerby());

We need more than just “Hello”, so we’ll replace that with a call to the index page, and then go build one: get("/", index::template).

I’ll spare you the HTML code, but this is what it ends up looking like more or less:

I’ve added a Register and Login button. Since we need an account before we can log on to the system, let’s start there with a new route: get("/register", register::template);.

…and a new user page which ended up looking like:

When the user clicks on the Register button, we need to capture the input fields of the form and submit them to our Grittier service. Let’s add a User model to our project.

@Data
public class User {
    private String username;
    private String name;
    private String email;
    private String password;
    private String hash;
    private Long time;
}

See that @Data annotation on top of User? I’m using Lombok to make my life easier and auto-magically create methods so we don’t have to.

To access the Unmanaged Extension code we’ve built on top of Neo4j, we’re going to need an HTTP Client. I looked around at various options and decided on Retrofit from Square. It’s not just a client, it really helps keep things organized and sane. The way it works is that we specify an interface for our Neo4j service with the paths, parameters and expected results. This is what that looks like for Grittier:

public interface GrittierService {
 
    @GET("users/{username}")
    Call<User> getUser(@Path("username") String username);
 
    @GET("users/{username}/profile")
    Call<User> getProfile(@Path("username") String username, @Query("username2") String username2);
 
    @POST("users")
    Call<User> createUser(@Body User user);
 
    @GET("users/{username}/followers")
    Call<List<User>> getFollowers(@Path("username") String username);
    ...

In order to use it in our code, we need to create a new service, but before we can do that we need to grab the username/password for Neo4j as well as the URL of the server and wire it all together when our application starts:

Config conf = require(Config.class);
 
   // Define the interceptor, add authentication headers
   String credentials = Credentials.basic(conf.getString("neo4j.username"), conf.getString("neo4j.password"));
   Interceptor interceptor = chain -> {
       Request newRequest = chain.request().newBuilder().addHeader("Authorization", credentials).build();
       return chain.proceed(newRequest);
   };
 
   // Add the interceptor to OkHttpClient
   OkHttpClient.Builder builder = new OkHttpClient.Builder();
   builder.interceptors().add(interceptor);
   OkHttpClient client = builder.build();
 
   Retrofit retrofit = new Retrofit.Builder()
           .client(client)
           .baseUrl("http://" + conf.getString("neo4j.url") + conf.getString("neo4j.prefix") +  "/")
           .addConverterFactory(JacksonConverterFactory.create())
           .build();
 
   service = retrofit.create(GrittierService.class);

Yes, that is a little messy, but it will make our life easier later on. Now back to the Registration form, when the user fills out the form and clicks on the magic Register button, the form is submitted and it gets processed in Jooby. The plain text password from the form can’t stay unencrypted, so let’s grab the BCrypt library and add it to our project, we’ll use this to secure the password:

<dependency>
  <groupId>org.mindrot</groupId>
  <artifactId>jbcrypt</artifactId>
  <version>0.4</version>
</dependency>

Here, we create a user from the form parameters using the User class, set the password to a legitimate password, and call our Grittier service to create the user.

post("/register", (req, rsp) -> {
    User user = req.form(User.class);
    user.setPassword(BCrypt.hashpw(user.getPassword(), BCrypt.gensalt()));
    Response<User> response = service.createUser(user).execute();
    if (response.isSuccessful()) {
        Results.redirect("/login");
    } else {
        throw new Err(Status.CONFLICT, "There was a problem with your registration.");
    }
});

If we take a look at the nodes in our Graph Database now, we can see our newly created node and its properties:

If we stored the plain text password, we’d see “swordfish” in the password property. Instead, we see an encrypted string. We now need to build functionality to allow our newly created user to login. First we need a login route, get("/login", login::template_;.

…and a new template.

To handle security, we will be using the Pac4j module for Jooby. We can use the Jooby use method, but we will need to write our own custom Authenticator.

use(newAuth().form("*", ServiceAuthenticator.class));

We need to override the validate method and have it use the Grittier service. We request the credentials of the user and use BCrypt.checkpw to make sure they match. If they do match, then we set the user profile and also set a session ID to be their username.

public class ServiceAuthenticator implements Authenticator<UsernamePasswordCredentials> {
    private static final Logger logger = LoggerFactory.getLogger(ServiceAuthenticator.class);
 
    @Override
    public void validate(UsernamePasswordCredentials credentials, WebContext webContext) throws HttpAction {
        Response<User> response;
        try {
            response = service.getUser(username).execute();
            User user = response.body();
            if (!BCrypt.checkpw(credentials.getPassword(), user.getPassword())){
                String message = "Bad credentials for: " + username;
                logger.error(message);
                throw new BadCredentialsException(message);
            } else {
                CommonProfile profile = new CommonProfile();
                profile.setId(username);
                profile.addAttribute("name", user.getName());
                profile.addAttribute("email", user.getEmail());
                credentials.setUserProfile(profile);
                webContext.setSessionAttribute("id", username);
            }
        } catch (IOException e) {
            String message = "No account found for: " + username;
            logger.error(message);
            throw new AccountNotFoundException(message);
        }
    }

There is a variable in the Jooby config file on /conf/application.conf we need to set which tells Jooby where to redirect the user after a successful login attempt. In our case, we’ll send them to the home page: auth.login.redirectTo = "/home".

Any route that is under the use(new Auth()…) method will be authenticated. To make sure it worked, we’ll add a route, grab the id from the session and say hello: get("/home", req -> { return"Hello "+ req.get("id");} );.

…and it works. We can now register and login with our user. We have wired up our framework and Grittier service. Stay tuned for more updates in the near future.

Learn how the world’s first NoSQL Engagement Database delivers unparalleled performance at any scale for customer experience innovation that never ends.

Topics:
database ,tutorial ,twitter ,neo4j

Published at DZone with permission of Max De Marzi, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}