New to spray and scala. Been struggling to get it right for couple of days now.
I am trying to merge facebook oauth2 login + user login details into the database in case the same user logs in by different ways(user/pass or fb login).
Pasting below spray routing snippet.
path("facebook") {
post{
entity(as[JObject]) { json =>
val fb: FacebookAuthModel = json.extract[FacebookAuthModel]
complete {
//Get user details from fb oauth2
val fbUser = fbAuth.getIdentity(fb) match {
case Right(user: User) => user
case Left(error: Failure) => throw new FailureException(error)
}
//Check if user is already present either by fb id or email
val userFuture = userRepo(FetchUserByFacebook(fbUser.facebook.get,fbUser.email))
userFuture.map {
case u: User => {
//user present but fb id not attached yet
if (u.facebook.isEmpty) {
//update fb id for the user - fire to actor and forget, i.e no callback to sender
userRepo(UpdateFacebookId(u.id.get, fbUser.facebook.get))
}
//complete request with a token - request(1)
AuthToken(token=jwt.createToken(u))
}
case None => {
//first time user using fb login
userRepo(CreateUser(fbUser)).map {
//complete request with the token - request(2)
case createdUser: User => AuthToken(token=jwt.createToken(createdUser))
case None => throw new FailureException(Failure("Not able to CreateUser", FailureType.Unauthorized))
}
}
}
}
}
}
}
Everything works fine except in case of first time user using fb login (refer request(2)).
Request gets completed with empty response before the nest future could complete.
I tried flatMapping the result from userFuture and then using onComplete on it to give the appropriate response, but it din't work.
Any idea how I could successfully complete the request(request(2)) with the token?
If one of the two branches in your code execution path could result in a Future
, then you have to code to this as the lowest common denominator when dealing with userFuture
. That means flatMap
on userFuture
and using Future.successful
in the case where you don't have an explicit second Future
to deal with. Something along this line:
def handleUserResult(a:Any):Future[AuthToken] = a match{
case u:User =>
if (u.facebook.isEmpty) {
userRepo(UpdateFacebookId(u.id.get, fbUser.facebook.get))
}
Future.successful(AuthToken(token=jwt.createToken(u)))
case None =>
userRepo(CreateUser(fbUser)).map {
case createdUser: User =>
AuthToken(token=jwt.createToken(createdUser))
case None =>
throw new FailureException(Failure("Not able to CreateUser", FailureType.Unauthorized))
}
}
Once you define that method, you can use it on userResult
as follows:
userResult.flatMap(handleUserResult)
I didn't check this code for compilation issues. I was more trying to show the general approach of flatMap
used to handle two cases, one that produces another second Future
and one that does not.