Scala rewriting asynchronous / callback code

A simple code that should check the user for skipping, the user is active after this last time of the last login.

def authenticate() = Action.async { implicit request => loginForm.bindFromRequest.fold( errors => Future.successful(BadRequest(views.html.logon(errors))), usersData =>{ val cursor = this.collection.find(BSONDocument("name" -> usersData._1)).one[Account].map(_.filter(p=>p.password == hashedPass(usersData._2, usersData._1))) cursor.flatMap(p => p match { case None => Future.successful(BadRequest(views.html.logon(loginForm.withGlobalError("user/pass incorect!!!")))) case Some(user) => { if(!user.active) Future.successful(BadRequest(views.html.logon(loginForm.withGlobalError("inactive!!!")))) else collection.update(BSONDocument("_id" -> user.id), BSONDocument("$set" -> BSONDocument("lastLogin" -> BSONDateTime(new org.joda.time.DateTime().getMillis())))) .flatMap(x => gotoLoginSucceeded(user.id.stringify)) } }) }) } 

How to transfer it to less flat cards / spaghetti cards?

Another solution

 def authenticate() = AsyncStack { implicit request => loginForm.bindFromRequest.fold( errors => Future.successful(BadRequest(views.html.logon(errors))), usersData =>{ for{ user <- this.collection.find(BSONDocument("name" -> usersData._1)).one[Account].map(_.filter(p=>p.password == hashedPass(usersData._2, usersData._1))) update <- { lazy val update = collection.update(BSONDocument("_id" -> user.get.id), BSONDocument("$set" -> BSONDocument("lastLogin" -> BSONDateTime(new org.joda.time.DateTime().getMillis())))) update } result <- { lazy val result = gotoLoginSucceeded(user.get.id.stringify) result } } yield if(user.isEmpty) BadRequest(views.html.logon(loginForm.withGlobalError("login\pass mismatch"))) else if(!user.get.active) BadRequest(views.html.logon(loginForm.withGlobalError("inactive"))) else if(update.err.isEmpty) result else InternalServerError(views.html.logon(loginForm.withGlobalError("server error"))) }) 

}

+6
source share
2 answers

I would probably reorganize the code into something like this:

 def authenticate() = Action.async { implicit request => loginForm.bindFromRequest.fold( hasErrors = displayFormWithErrors, success = loginUser) } private def displayFormWithErrors[T](errors:Form[T]) = Future.successful(BadRequest(views.html.logon(errors))) private def loginUser(userData:(String, String)) = { val (username, password) = userData findUser(username, password) .flatMap { case None => showLoginFormWithError("user/pass incorect!!!") case Some(user) if (!user.active) => showLoginFormWithError("inactive!!!") case Some(user) => updateUserAndRedirect(user) } } private def findUser(username:String, password:String) = this.collection .find(BSONDocument("name" -> username)) .one[Account] .map(_.filter(_.password == hashedPass(password, username))) private def showLoginFormWithError(error:String) = Future.successful(BadRequest( views.html.logon(loginForm.withGlobalError(error)))) private def updateUserAndRedirect(user:Account) = updateLastLogin(user) .flatMap(_ => gotoLoginSucceeded(user.id.stringify)) private def updateLastLogin(user:Account) = collection .update(BSONDocument("_id" -> user.id), BSONDocument("$set" -> BSONDocument("lastLogin" -> BSONDateTime(new JodaDateTime().getMillis())))) 
+5
source

I prefer to do password and user verification in form validation suggestions - there will be something like this (untested, but you get the idea):

 private val loginForm = Form( mapping( "name" -> nonEmptyText, "password" -> nonEmptyText ){ (name, password) => ( this.collection.find(BSONDocument("name" -> name)).one[Account], password) }{ data => Some((data._1.name, data._2)) }.verifying(new Constraint(None, Seq())({ data: (Option[Account], String) => data match { case (Some(account: Account), _) if !account.active => Invalid(ValidationError("inactive")) case (Some(account: Account), password) if account.password==hashedPass(account.name, password) => Valid case _ => Invalid(ValidationError("login/pass mismatch")) } })) ) 

And then the controller becomes much simpler:

 def authenticate() = Action.async { implicit request => loginForm.bindFromRequest.fold( errors => Future.successful(BadRequest(views.html.logon(errors))), usersData =>{ collection.update(BSONDocument("_id" -> usersData._1.id), BSONDocument("$set" -> BSONDocument("lastLogin" -> BSONDateTime(new org.joda.time.DateTime().getMillis())))) .flatMap(x => gotoLoginSucceeded(user.id.stringify)) } ) } 
0
source

All Articles