This is my implementation of the UserService class. It's a slightly modified version of this implementation (http://www.shrikar.com/blog/2013/10/26/playframework-securesocial-and-mongodb/)
I use it with the default securesocial FacebookProvider. Everything works fine except for the fact that it duplicates user entries everytime i log in. I would like to persist the user only once. I thought this was handled by the plugin itself but i'm not sure it is the case. Is it up to me to check if the user exists and insert the logged user only if there are no other entries for the same user? Or there is something else that is wrong?
class MongoUserService(application: Application) extends UserServicePlugin(application) with Controller with MongoController {
def collection: JSONCollection = db.collection[JSONCollection]("users")
def tokens: JSONCollection = db.collection[JSONCollection]("tokens")
val outPutUser = (__ \ "id").json.prune
def retIdentity(json: JsObject): Identity = {
val userid = (json \ "userid").as[String]
val provider = (json \ "provider").as[String]
val firstname = (json \ "firstname").as[String]
val lastname = (json \ "lastname").as[String]
val email = (json \ "email").as[String]
val avatar = (json \ "avatar").as[String]
val hash = (json \ "password" \ "hasher").as[String]
val password = (json \ "password" \ "password").as[String]
println("password : " + password)
val salt = (json \ "password" \ "salt").asOpt[String]
val authmethod = (json \ "authmethod").as[String]
val identity: IdentityId = new IdentityId(userid, authmethod)
val authMethod: AuthenticationMethod = new AuthenticationMethod(authmethod)
val pwdInfo: PasswordInfo = new PasswordInfo(hash, password)
val serial:Integer=((json\"serial").as[Long]).toInt
val user: NWOUser = new NWOUser(identity, firstname, lastname, firstname, Some(email), Some(avatar), authMethod, None, None, Some(pwdInfo),serial)
user
}
def generateSerial():Integer={
val collection = db[JSONCollection]("serial")
val cursor = collection.find(Json.obj()).cursor[JsObject]
val futureserial = cursor.headOption.map {
case Some(i) => i
case None => 0
}
val jobj = Await.result(futureserial, 5 seconds)
val newSerial=jobj match {
case x: Boolean => 0
case _ =>retSerial(jobj.asInstanceOf[JsObject])+1
}
collection.update(Json.obj(), Json.obj("$set"->Json.obj("serial"->newSerial))).onComplete {
case _=>println("updated")
}
newSerial
}
def retSerial(json: JsObject): Integer = {
println(json)
val serial=(json \ "serial").as[Long]
serial.toInt
}
def findByEmailAndProvider(email: String, providerId: String): Option[Identity] = {
val cursor = collection.find(Json.obj("userid" -> email, "provider" -> providerId)).cursor[JsObject]
val futureuser = cursor.headOption.map {
case Some(user) => user
case None => false
}
val jobj = Await.result(futureuser, 5 seconds)
jobj match {
case x: Boolean => None
case _ => Some(retIdentity(jobj.asInstanceOf[JsObject]))
}
}
def save(user: Identity): Identity = {
val email = user.email match {
case Some(email) => email
case _ => "N/A"
}
val avatar = user.avatarUrl match {
case Some(url) => url
case _ => "N/A"
}
val savejson = Json.obj(
"userid" -> user.identityId.userId,
"provider" -> user.identityId.providerId,
"firstname" -> user.firstName,
"lastname" -> user.lastName,
"email" -> email,
"avatar" -> avatar,
"authmethod" -> user.authMethod.method,
"serial"->this.generateSerial.toLong,
user.passwordInfo match {
case None =>"password" -> Json.obj( "hasher" -> "",
"password" -> "",
"salt" -> "")
case x =>"password" -> Json.obj(
"hasher" -> x.get.hasher,
"password" -> x.get.password,
"salt" -> x.get.salt)
},
"created_at" -> Json.obj("$date" -> new Date()),
"updated_at" -> Json.obj("$date" -> new Date()))
println(Json.toJson(savejson))
collection.insert(savejson)
user
}
def find(id: IdentityId): Option[Identity] = {
findByEmailAndProvider(id.userId, id.providerId)
}
def save(token: Token) {
val tokentosave = Json.obj(
"uuid" -> token.uuid,
"email" -> token.email,
"creation_time" -> Json.obj("$date" -> token.creationTime),
"expiration_time" -> Json.obj("$date" -> token.expirationTime),
"isSignUp" -> token.isSignUp)
tokens.save(tokentosave)
}
def findToken(token: String): Option[Token] = {
val cursor = tokens.find(Json.obj("uuid" -> token)).cursor[JsObject]
val futureuser = cursor.headOption.map {
case Some(user) => user
case None => false
}
val jobj = Await.result(futureuser, 5 seconds)
jobj match {
case x: Boolean => None
case obj: JsObject => {
println(obj)
val uuid = (obj \ "uuid").as[String]
val email = (obj \ "email").as[String]
val created = (obj \ "creation_time" \ "$date").as[Long]
val expire = (obj \ "expiration_time" \ "$date").as[Long]
val signup = (obj \ "isSignUp").as[Boolean]
val df = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")
Some(new Token(uuid, email, new DateTime(created), new DateTime(expire), signup))
}
}
}
def deleteToken(uuid: String) {}
def deleteExpiredTokens() {}
}
You can solve it by using db.collection.[update][1]
with upsert=true
, rather than insert
. With this flag, it will insert an object identified by the first element (query) in the call when it doesn't exist, and update the existing one.
So, instead of
collection.insert(savejson)
try
collection.update(
Json.obj("userid" -> user.identityId.userId),
savejson,
Json.obj("upsert" -> true)
)