MADCAP IDEA PART 7 : REGISTRATION/LOGIN BACKEND

 

Last Time

 

So let me start with an apology that it has taken me so long to get this new post up since last time. I have snuck in a holiday and been on a training course which really only left me 1 week of train rides to do this in, lame excuse but there you have it.

Anyway last time we looked at doing all the react markup for the screens, this time we will be doing the backend registration and login functions which will make use of reactive mongo for the play side of things.

 

PreAmble

Just as a reminder this is part of my ongoing set of posts which I talk about here :

https://sachabarbs.wordpress.com/2017/05/01/madcap-idea/, where we will be building up to a point where we have a full app using lots of different stuff, such as these

 

  • WebPack
  • React.js
  • React Router
  • TypeScript
  • Babel.js
  • Akka
  • Scala
  • Play (Scala Http Stack)
  • MySql
  • SBT
  • Kafka
  • Kafka Streams

 

So I think the best way to cover this post is just dive in to the 2 main topics covered

 

Registration

 

Passenger Registration 

 

As a reminder this is what the Passenger Registration screen looks like

 

image

 

And this is the relevant code that deals with the “Register” button being clicked

 

_handleValidSubmit = (values) => {
    var driver = values;
    var self = this;

    $.ajax({
        type: 'POST',
        url: 'registration/save/driver',
        data: JSON.stringify(driver),
        contentType: "application/json; charset=utf-8",
        dataType: 'json'
    })
    .done(function (jdata, textStatus, jqXHR) {
        var redactedDriver = driver;
        redactedDriver.password = "";
        console.log("redacted ${redactedDriver}");
        console.log(redactedDriver);
        console.log("Auth Service");
        console.log(self.props.authService);
        self.props.authService.storeUser(redactedDriver);
        self.setState(
            {
                okDialogHeaderText: 'Registration Successful',
                okDialogBodyText: 'You are now registered',
                okDialogOpen: true,
                okDialogKey: Math.random()
            });
    })
    .fail(function (jqXHR, textStatus, errorThrown) {
        self.setState(
            {
                okDialogHeaderText: 'Error',
                okDialogBodyText: jqXHR.responseText,
                okDialogOpen: true,
                okDialogKey: Math.random()
            });
    });
}

_okDialogCallBack = () => {
    this.setState(
        {
            okDialogOpen: false
        });
    if (this.state.wasSuccessful) {
        hashHistory.push('/');
    }
}

 

 

There are a couple of things of note there

  • That we use a standard POST that will post the registration data as JSON to the Play API backend code
  • That we also show a standard Boostrap OkDialog which we looked at last time, which when the Ok button is clicked will use the React Router to navigate to the route page

 

Let’s now turn our attention to the Play API backend code that goes with the “Passenger Registration”

 

JSON Read/Write support

The first thing we need to do is to set up support for reading JSON into Scala objects from a JSON string, and also allowing Scala objects to be turned back into a JSON string. For a “Passenger Registration” the Play API Json support comes via 3 Traits namely

  • Reads : Which allows reading a JSON string into a Scala object
  • Writes : Which allows a Scala object to be turned into a JSON string
  • Format : is just a mix of the Reads and Writes Traits and can be used for implicit conversion in place of its components.

The recommendation for both of these is that they are exposed as implicit vals you can read more about it here  : https://www.playframework.com/documentation/2.6.x/ScalaJsonCombinators

For the “Passenger Registration” it looks like this

package Entities

import play.api.libs.json._
import play.api.libs.functional.syntax._

case class PassengerRegistration(
  fullName: String,
  email: String,
  password: String)

object PassengerRegistration {
  implicit val formatter = Json.format[PassengerRegistration]
}

object PassengerRegistrationJsonFormatters {

  implicit val passengerRegistrationWrites = new Writes[PassengerRegistration] {
    def writes(passengerRegistration: PassengerRegistration) = Json.obj(
      "fullname" -> passengerRegistration.fullName,
      "email" -> passengerRegistration.email,
      "password" -> passengerRegistration.password
    )
  }

  implicit val passengerRegistrationReads: Reads[PassengerRegistration] = (
    (JsPath \ "fullname").read[String] and
      (JsPath \ "email").read[String] and
      (JsPath \ "password").read[String]
    )(PassengerRegistration.apply _)
}

 

Once we have that in place we need to turn our attention to the actual endpoint to support the POST of a PassengerRegistration object. We first need to set up the route in the conf/routes file as follows:

 

# Registration page

POST  /registration/save/passenger             controllers.RegistrationController.savePassengerRegistration()

 

Reactive Mongo

Now that the route is in place its just a standard Play controller that we need. However I have chosen to use Reactive Mongo as my storage mechanism for registration/login data. This means we have a few things that we need to install

  1. Mongo itself which you can just grab from here : https://www.mongodb.com/download-center
  2. We then need to provision the actual Reactive Mongo Scala library, which we can do using the standard build.sbt file, where we specify the following dependency

 

libraryDependencies ++= Seq(
  "org.reactivemongo" %% "play2-reactivemongo" % "0.11.12"
)

 

Ensure Mongod.exe Is Running

In order to run the app we must ensure that the Mongo server is actually running which for my installation of Mongo means starting “Mongod.exe” from “C:\Program Files\MongoDB\Server\3.5\bin\mongod.exe” before I run the app

 

The Registration Controller

So now that we have talked about the JSON Reads/Writes and we know that we need Mongo downloaded and running, lets see what the actual controller looks like shall we. Here is the FULL code for the “Passenger Registration”

 

package controllers

import javax.inject.Inject
import play.api.mvc.{Action, Controller, Result}
import Entities._
import Entities.DriverRegistrationJsonFormatters._
import Entities.PassengerRegistrationJsonFormatters._
import scala.concurrent.{ExecutionContext, Future}
import play.modules.reactivemongo._
import play.api.Logger
import utils.Errors
import play.api.libs.json._
import reactivemongo.api.ReadPreference
import reactivemongo.play.json._
import collection._

class RegistrationController @Inject()(val reactiveMongoApi: ReactiveMongoApi)
  (implicit ec: ExecutionContext)
  extends Controller with MongoController with ReactiveMongoComponents {

  def passRegistrationFuture: Future[JSONCollection] = 
	database.map(_.collection[JSONCollection]("passenger-registrations"))

  def savePassengerRegistration = Action.async(parse.json) { request =>
    Json.fromJson[PassengerRegistration](request.body) match {
      case JsSuccess(newPassRegistration, _) =>

        //https://github.com/ReactiveMongo/ReactiveMongo-Extensions/blob/0.10.x/guide/dsl.md
        val query = Json.obj("email" -> Json.obj("$eq" -> newPassRegistration.email))

        dealWithRegistration[PassengerRegistration](
          newPassRegistration,
          passRegistrationFuture,
          query,
          PassengerRegistration.formatter)
      case JsError(errors) =>
        Future.successful(BadRequest(
			"Could not build a PassengerRegistration from the json provided. " +
			Errors.show(errors)))
    }
  }

  private def dealWithRegistration[T](
          incomingRegistration: T,
          jsonCollectionFuture: Future[JSONCollection],
          query: JsObject,
          formatter: OFormat[T])
          (implicit ec: ExecutionContext): Future[Result] = {

    def hasExistingRegistrationFuture = jsonCollectionFuture.flatMap {
        //http://reactivemongo.org/releases/0.11/documentation/advanced-topics/collection-api.html
        _.find(query)
        .cursor[JsObject](ReadPreference.primary)
        .collect[List]()
      }.map(_.length match {
          case 0 => false
          case _ => true
      }
    )

    hasExistingRegistrationFuture.flatMap {
      case false => {
        for {
          registrations <- jsonCollectionFuture
          writeResult <- registrations.insert(incomingRegistration)(formatter,ec)
        } yield {
          Logger.debug(s"Successfully inserted with LastError: $writeResult")
          Ok(Json.obj())
        }
      }
      case true => Future(BadRequest("Registration already exists"))
    }
  }
}

 

Lets break this down into chunks

 

  • The controller constructor
    • This takes a ReactiveMongoApi (this is mandatory to satisfy the base trait MongoController requirements)
    • Inherits from MongoController which provides a lot of use functionality
    • It also inherits from ReactiveMongoComponents in order to allow the cake pattern/self typing requirements of the base MongoController  which expects a ReactiveMongoComponents
  • The use of JSONCollection
    • There is a Future[JSONCollection] that represents the passenger collection in Mongo. This is a collection that stores JSON. When using reactive Mongo you have a choice about whether to use the standard BSON collections of JSON. I opted for JSON
  • The Guts Of The Logic
    • So now we have discussed the controller constructor and the Mongo collections. We just need to talk about the actual work that happens on registration. In a nutshell it works like this
    • The incoming JSON string is turned into a PassengerRegistration object via Play
    • We then create a new JSON query object to query the Mongo JSONCollection to see if a registration already exists
    • If a registration already exists we exit with a BadRequest output
    • If a registration does NOT already exist we insert the new registration details into the Mongo JSONCollection, and the we return an Ok output

 

And that is how the “Passenger Registration” works.

 

 

Driver Registration

The driver registration works in much the same way as described above, its just slightly different JSON, but it does share the same core logic/controller as the “Passenger Registration”

 

 

Login

Although the Login share some of the same ideas as Registration it has to do slightly different work, we will talk about what is different along the way, but this is what the Login screen looks like

image

 

And this is what the relevant code on the client looks like to send across the JSON payload.

_handleValidSubmit = (values) => {
    var logindetails = values;
    var self = this;

    $.ajax({
        type: 'POST',
        url: 'login/validate',
        data: JSON.stringify(logindetails),
        contentType: "application/json; charset=utf-8",
        dataType: 'json'
    })
    .done(function (jdata, textStatus, jqXHR) {

        console.log("result of login");
        console.log(jqXHR.responseText);
        let currentUser = jqXHR.responseText;
        self._authService.storeUser(currentUser);

        self.setState(
            {
                okDialogHeaderText: 'Login Successful',
                okDialogBodyText: 'You are now logged in',
                okDialogOpen: true,
                okDialogKey: Math.random()
            });
    })
    .fail(function (jqXHR, textStatus, errorThrown) {
        self.setState(
            {
                okDialogHeaderText: 'Error',
                okDialogBodyText: jqXHR.responseText,
                okDialogOpen: true,
                okDialogKey: Math.random()
            });
    });
}

 

What is different this time is that when the Login is successful we make use of an injected AuthService which we push out a true or false to indicate login status on a Rx subject, which all the other screens/components listen to, where each component can changes its own state/rendering depending on the login status. Here is the full code for the AuthService

 

import { injectable, inject } from "inversify";
import { TYPES } from "../types";
import Rx from 'rx';  

@injectable()
export class AuthService {

    private _isAuthenticated: boolean;
    private _authenticatedSubject = new Rx.Subject<boolean>();

    constructor() {

    }

    clearUser = () => {
        this._isAuthenticated = false;
        sessionStorage.removeItem('currentUserProfile');
        this._authenticatedSubject.onNext(false);
    }

    storeUser = (currentUser) => {
        this._isAuthenticated = true;
        sessionStorage.setItem('currentUserProfile', currentUser);
        this._authenticatedSubject.onNext(true);
    }

    userName = () => {
        var user = JSON.parse(sessionStorage.getItem('currentUserProfile'));
        return user.fullName;
    }

    isAuthenticated = () => {
        return this._isAuthenticated;
    }

    getAuthenticationStream = () => {
        return this._authenticatedSubject.asObservable();
    }
}

 

 

And here is example of how a React component may listen to this Rx subject to affect its own rendering

 

import * as React from "react";
import * as ReactDOM from "react-dom";

....
....

let authService = ContainerOperations.getInstance().container.get<AuthService>(TYPES.AuthService);

export interface MainNavProps {
    authService: AuthService;
}

export interface MainNavState {
    isLoggedIn: boolean;
}

class MainNav extends React.Component<MainNavProps, MainNavState> {

    private _subscription: any;


    constructor(props: any) {
        super(props);
        console.log(props);
        this.state = {
            isLoggedIn: false
        };
    }

    componentWillMount() {
        this._subscription = this.props.authService.getAuthenticationStream().subscribe(isAuthenticated => {
            this.state = {
                isLoggedIn: isAuthenticated
            };
            if (this.state.isLoggedIn) {
                hashHistory.push('/createjob');
            }
            else {
                hashHistory.push('/');
            }
        });
    }

    componentWillUnmount() {
        this._subscription.dispose();
    }

    render() {
       ....
       ....
    }
}


Now moving on to the server side Play API code, as before we need a Play backend route for the Login

 

# Login page

POST  /login/validate                          controllers.LoginController.validateLogin()

 

And as before we also have a Reactive Mongo enabled Play controller. I won’t go over the common stuff again, but here is the guts of the LoginController, and shown below is a bullet point list of what it does

 

package controllers

import javax.inject.Inject

import Entities.DriverRegistrationJsonFormatters._
import Entities.PassengerRegistrationJsonFormatters._
import Entities._
import play.api.Logger
import play.api.libs.json._
import play.api.mvc.{Action, Controller, Result}
import play.modules.reactivemongo._
import reactivemongo.api.ReadPreference
import reactivemongo.play.json._
import reactivemongo.play.json.collection._
import utils.Errors


import scala.concurrent.{ExecutionContext, Future}

class LoginController @Inject()(val reactiveMongoApi: ReactiveMongoApi)
                               (implicit ec: ExecutionContext)
  extends Controller with MongoController with ReactiveMongoComponents {

  def passRegistrationFuture: Future[JSONCollection] = database.map(_.collection[JSONCollection]("passenger-registrations"))
  def driverRegistrationFuture: Future[JSONCollection] = database.map(_.collection[JSONCollection]("driver-registrations"))


  def validateLogin = Action.async(parse.json) { request =>
    Json.fromJson[Login](request.body) match {
      case JsSuccess(newLoginDetails, _) =>
        newLoginDetails.isDriver match {
          case false => {
            val maybePassengerReg = extractExistingRegistration(
              passRegistrationFuture.flatMap {
                _.find(Json.obj("email" -> Json.obj("$eq" -> newLoginDetails.email))).
                  cursor[JsObject](ReadPreference.primary).
                  collect[List]()
              })
            returnRedactedRegistration[PassengerRegistration](
              maybePassengerReg,
              (reg: PassengerRegistration) => Ok(Json.toJson(reg.copy(password = "")))
            )

          }
          case true => {
            val maybeDriverReg = extractExistingRegistration(
              driverRegistrationFuture.flatMap {
                _.find(Json.obj("email" -> Json.obj("$eq" -> newLoginDetails.email))).
                  cursor[JsObject](ReadPreference.primary).
                  collect[List]()
              })
            returnRedactedRegistration[DriverRegistration](
              maybeDriverReg,
              (reg: DriverRegistration) => Ok(Json.toJson(reg.copy(password = "")))
            )
          }
        }
      case JsError(errors) =>
        Future.successful(BadRequest("Could not build a Login from the json provided. " +
          Errors.show(errors)))
    }
  }

  private def returnRedactedRegistration[T]
  (
    maybeDriverRegFuture: Future[Option[JsObject]],
    redactor : T => Result
  )(implicit reads: Reads[T]): Future[Result] = {
    maybeDriverRegFuture.map {
      case Some(json) => {
        val reg = Json.fromJson[T](json)
        reg match {
          case JsSuccess(reg, _) => {
            redactor(reg)
          }
          case _ => BadRequest("Registration already exists")
        }
      }
      case None => BadRequest("Could not find registration")
    }
  }

  private def extractExistingRegistration[T]
  (incomingRegistrations: Future[List[T]])
  (implicit writes: Writes[T], ec: ExecutionContext): Future[Option[T]] = {
    incomingRegistrations.map(matchedRegistrations =>
      matchedRegistrations.length match {
        case 0 => None
        case _ => Some(matchedRegistrations(0))
      }
    )
  }
}

 

Essentially the code above does the following:

 

  • Takes the deserialized JSON Login information, and decides whether it’s a Driver or a Passenger that is trying to login based on the IsDriver boolean.
  • Once we know if it’s a Driver or a Passenger that we are dealing with we continue to check if there is a registration for someone that has the login email
  • If we find a registered Driver or Passenger we obtain the registration that was previously stored and redact the password, and then convert it to JSON and send it back over the wire to the React code where it is stored in Local Storage and exposes out to the JS code using the AuthService.ts code above, and will notify any Rx subscribers of the login status, such that any listening React components can adjust their state/render information

 

 

Conclusion

I hope you have enjoyed this post, I know I have had fun with this one, I like the Reactive Mongo stuff, and I like how its all Future based which makes for a nice non-blocking workflow. We are now past the 1st phase of this project, the next phase will be to start with the Kafka messaging, where I may end up doing some more experimental/self contained code, rather than trying to shoe horn things into the main app in one go. I will still get it into the main app, but I am just thinking the Kafka posts may be better as stand alone things. I’ll see how it goes, at any rate the Kafka Streams stuff is phase 2, and we will be looking at that next

Advertisements

2 thoughts on “MADCAP IDEA PART 7 : REGISTRATION/LOGIN BACKEND

  1. Thanks on your marvelous posting! I seriously enjoyed reading it, you happen to be a great author. I will be sure to bookmark your blog and definitely will come back down the road. I want to encourage you to ultimately continue your great job, have a nice weekend!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: