From feac5b037a573bed7705e75c843624256543e444 Mon Sep 17 00:00:00 2001 From: Johann Sell Date: Thu, 25 Feb 2021 20:36:04 +0100 Subject: [PATCH 1/9] prepare local development --- build.sbt | 1 + conf/application.conf | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index cf174a0b..2a90d40d 100644 --- a/build.sbt +++ b/build.sbt @@ -13,6 +13,7 @@ resolvers ++= Seq( "scalaz-bintray" at "http://dl.bintray.com/scalaz/releases", Resolver.sonatypeRepo("snapshots") ) +resolvers += Resolver.jcenterRepo pipelineStages := Seq(digest,gzip) diff --git a/conf/application.conf b/conf/application.conf index 78b15d67..4537d745 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -14,12 +14,13 @@ mongo-async-driver { slick.dbs.default.driver = "slick.driver.MySQLDriver$" slick.dbs.default.db.driver = "com.mysql.jdbc.Driver" -slick.dbs.default.db.url = "jdbc:mysql://172.2.1.2/drops" -#slick.dbs.default.db.user = "root" -#slick.dbs.default.db.password = "admin" -#slick.dbs.default.db.url = "jdbc:mysql://localhost:3306/drops" -slick.dbs.default.db.user = "drops" +#slick.dbs.default.db.url = "jdbc:mysql://172.2.1.2/drops" +slick.dbs.default.db.user = "root" slick.dbs.default.db.password = "drops" +slick.dbs.default.db.url = "jdbc:mysql://172.2.200.1:3306/drops" +slick.dbs.default.db.connectionTimeout = 30s +#slick.dbs.default.db.user = "drops" +#slick.dbs.default.db.password = "drops" play.evolutions.enabled = true play.evolutions.db.default.autoApply = true From 6df6b6f978e1744138fc1b7c3157e9cae5e0fe8f Mon Sep 17 00:00:00 2001 From: Johann Sell Date: Tue, 2 Mar 2021 14:19:54 +0100 Subject: [PATCH 2/9] change organization name --- build.sbt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index 2a90d40d..03f5f9fa 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,7 @@ import com.typesafe.sbt.packager.docker.Cmd name := """Drops""" +organization := "net.soteto" version := "0.36.63" @@ -60,10 +61,11 @@ excludeFilter in (Assets, LessKeys.less) := "_*.less" // setting a maintainer which is used for all packaging types maintainer in Docker := "Johann Sell" - // exposing the play ports dockerExposedPorts := Seq(9000, 9443) - -dockerRepository := Some("vivaconagua") -version in Docker := "latest" - +dockerRepository := Some("soteto") +//dockerUsername := Some("soteto") +//dockerUpdateLatest := true +routesGenerator := InjectedRoutesGenerator +packageName in Docker := packageName.value +version in Docker := version.value \ No newline at end of file From 6da96f2927371e68ff08a33329edfe529d1d8c9e Mon Sep 17 00:00:00 2001 From: Johann Sell Date: Tue, 9 Mar 2021 12:22:00 +0100 Subject: [PATCH 3/9] #1: fixed --- app/controllers/webapp/Auth.scala | 15 ++++++++++++++- app/models/Address.scala | 3 +++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/controllers/webapp/Auth.scala b/app/controllers/webapp/Auth.scala index 2673a8ac..74476b8c 100644 --- a/app/controllers/webapp/Auth.scala +++ b/app/controllers/webapp/Auth.scala @@ -139,7 +139,20 @@ class Auth @Inject() ( case Some(_) => Future.successful(WebAppResult.Bogus(request, "error.userExists", List(signUpData.email), "AuthProvider.SignUp.UserExists", Json.toJson(Map[String, String]())).getResult) case None => - val profile = Profile(loginInfo, signUpData.email, signUpData.firstName, signUpData.lastName, signUpData.mobilePhone, signUpData.placeOfResidence, signUpData.birthday, signUpData.gender, signUpData.address) + val profile = Profile( + loginInfo, + signUpData.email, + signUpData.firstName, + signUpData.lastName, + signUpData.mobilePhone, + signUpData.placeOfResidence, + signUpData.birthday, + signUpData.gender, + signUpData.address.flatMap(a => a.isEmpty match { + case true => None + case _ => Some(a) + }) + ) for { avatarUrl <- avatarService.retrieveURL(signUpData.email) user <- userService.save(User(id = UUID.randomUUID(), List(profile), updated = System.currentTimeMillis(), created = System.currentTimeMillis())) diff --git a/app/models/Address.scala b/app/models/Address.scala index 966f9b42..d4aef0dc 100644 --- a/app/models/Address.scala +++ b/app/models/Address.scala @@ -29,6 +29,9 @@ case class AddressStub ( ) extends AddressBase { def toAddress: Address = Address(Some(UUID.randomUUID()), street, additional, zip, city, country) + + def isEmpty: Boolean = + street == "" && additional.map(_ == "").getOrElse(true) && zip == "" && city == "" && country == "" } /** From 60300b699facd689edd585dcb3732855c8f1de14 Mon Sep 17 00:00:00 2001 From: Johann Sell Date: Tue, 9 Mar 2021 13:13:44 +0100 Subject: [PATCH 4/9] #1 increase version number --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 03f5f9fa..c150138f 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ import com.typesafe.sbt.packager.docker.Cmd name := """Drops""" organization := "net.soteto" -version := "0.36.63" +version := "0.36.64" lazy val root = (project in file(".")).enablePlugins(PlayScala).enablePlugins(DockerPlugin) From ed9c920145ae80f734ed5b910f4c5944e99f1fb1 Mon Sep 17 00:00:00 2001 From: Johann Sell Date: Fri, 9 Apr 2021 16:54:39 +0200 Subject: [PATCH 5/9] Updated hint to missing email server --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a253ccbb..3f2467ff 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ docker rm drops > *Notice:* -All server generated output will be written to the `server-output` file. +All server generated output will be written to the command line (logs in the case of a docker image). This is also needed to confirm users since the production server also uses a mock mail server. > From 8010547b1ebeb6ea7fa2fcbd4b35a3b4cdc7e15d Mon Sep 17 00:00:00 2001 From: Johann Sell Date: Wed, 5 May 2021 20:40:49 +0200 Subject: [PATCH 6/9] #4 replace insert of new users by transaction --- README.md | 2 + app/controllers/webapp/Auth.scala | 8 +-- app/daos/UserDao.scala | 83 ++++++++++++++++++++++++++++--- app/services/UserService.scala | 25 ++++++---- build.sbt | 2 +- 5 files changed, 97 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index a253ccbb..6830d3c3 100644 --- a/README.md +++ b/README.md @@ -365,6 +365,8 @@ and your service. ChangeLog ========= +## Version 0.36.65 (2021-5-5) +* [[B] #4 - UserDAO does not execute inserts as a transaction](https://github.com/SOTETO/drops/issues/4) ## Version 0.36.61 (2019-4-16) diff --git a/app/controllers/webapp/Auth.scala b/app/controllers/webapp/Auth.scala index 74476b8c..75ef2a7d 100644 --- a/app/controllers/webapp/Auth.scala +++ b/app/controllers/webapp/Auth.scala @@ -157,16 +157,16 @@ class Auth @Inject() ( avatarUrl <- avatarService.retrieveURL(signUpData.email) user <- userService.save(User(id = UUID.randomUUID(), List(profile), updated = System.currentTimeMillis(), created = System.currentTimeMillis())) pw <- authInfoRepository.add(loginInfo, passwordHasher.hash(signUpData.password)) - token <- userTokenService.save(UserToken.create(user.id, signUpData.email, true)) + token <- userTokenService.save(UserToken.create(user.get.id, signUpData.email, true)) } yield { getWebApp match { case Left(message) => WebAppResult.NotFound(request, message._1, Nil, "AuthProvider.SignUp.MissingConfig", Map[String, String]()).getResult case Right(webapp) => { - mailer.welcome(profile, link = webapp.getAbsoluteSignUpTokenEndpoint(token.id.toString)) - WebAppResult.Ok(request, "signup.created", Nil, "AuthProvider.SignUp.Success", Json.toJson(profile)).getResult - } + mailer.welcome(profile, link = webapp.getAbsoluteSignUpTokenEndpoint(token.id.toString)) + WebAppResult.Ok(request, "signup.created", Nil, "AuthProvider.SignUp.Success", Json.toJson(profile)).getResult } } + } } } ) diff --git a/app/daos/UserDao.scala b/app/daos/UserDao.scala index 1325b889..0c716aa1 100644 --- a/app/daos/UserDao.scala +++ b/app/daos/UserDao.scala @@ -27,7 +27,7 @@ trait UserDao extends ObjectIdResolver with CountResolver{ // def getObjectId(userId: UUID):Future[Option[ObjectIdWrapper]] def find(loginInfo:LoginInfo):Future[Option[User]] def find(userId:UUID):Future[Option[User]] - def save(user:User):Future[User] + def save(user:User):Future[Option[User]] def replace(user:User):Future[User] def confirm(loginInfo:LoginInfo):Future[User] def link(user:User, profile:Profile):Future[User] @@ -110,14 +110,23 @@ class MariadbUserDao @Inject()(val crewDao: MariadbCrewDao) extends UserDao { * @param user * @return */ - override def save(user: User): Future[User]={ + override def save(user: User): Future[Option[User]]={ val userDBObj = UserDB(user) - dbConfig.db.run((users returning users.map(_.id)) += userDBObj).flatMap(id => { - findDBUserModel(id) - }).flatMap(userObj => { - addProfiles(userObj, user.profiles) - }) + val crewIds : Future[Map[String, Option[Long]]] = Future.sequence(user.profiles.map(profile => + profile.supporter.crew match { + // if supporter has a crew, find the CrewDB.id by UUID crew.id + case Some(crew) => crewDao.findDBCrewModel(crew.id).map(_ match { + case Some(c) => (profile.loginInfo.providerKey, Some(c.id)) + case None => (profile.loginInfo.providerKey, None) + }) + //else None + case _ => Future.successful((profile.loginInfo.providerKey, None)) + } + )).map(_.toMap) + crewIds.flatMap(c_ids => + dbConfig.db.run(insertUser(userDBObj, user.profiles, c_ids)) + ).flatMap(id => find(id)) } override def replace(updatedUser: User): Future[User] = { @@ -298,6 +307,66 @@ class MariadbUserDao @Inject()(val crewDao: MariadbCrewDao) extends UserDao { dbConfig.db.run(users.filter(_.id === id).result).map(r => r.head) } + /** + * Creates an DBIOAction that inserts a complete user as a transaction that inserts into multiple columns. + * + * @author Johann Sell + * @param userDB + * @param userProfiles + * @param crewDBids + * @return + */ + private def insertUser(userDB: UserDB, userProfiles: List[Profile], crewDBids: Map[String, Option[Long]]) : DBIOAction[Long,slick.dbio.NoStream,slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Transactional with slick.dbio.Effect.Transactional] = { + + // Assign a Supporter to an Crew via SupporterCrew. Each SupporterCrew Object contains one Role. + def supporterCrewAssignment(crewDBiD: Option[Long], s: Long, r: Option[Role]) = crewDBiD match { + case Some(crewId) => (supporterCrews returning supporterCrews.map(_.supporterId)) += (SupporterCrewDB(s, crewId, r, None, None, None)) + case _ => DBIO.successful(false) + } + + // Assign Address to Supporter. + def addressAssignment(address: Option[Address], supporterId: Long) = address match { + case Some(a) => (addresses returning addresses.map(_.id)) += AddressDB(0, a, supporterId) + case _ => DBIO.successful(false) + } + + (for { + u <- (users returning users.map(_.id)) += userDB + _ <- DBIO.sequence(userProfiles.map(profile => (for { + // insert Profile and return long id as p + p <- (profiles returning profiles.map(_.id)) += ProfileDB(profile, u) + // insert Supporter with profile_id = p and return long id as s + s <- (supporters returning supporters.map(_.id)) += SupporterDB(0, profile.supporter, p) + //insert Addresses with supporter_id = s. + _ <- profile.supporter.address match { + // if Addresses is a list and not Empty we insert every address in the list and return a DBIO.seq with all id's + case list if list.nonEmpty => list.tail.foldLeft(DBIO.seq(addressAssignment(list.headOption, s)))( + (seq, address) => DBIO.seq(seq, addressAssignment(Some(address), s)) + ) + // else return Database false + case _ => DBIO.successful(false) + } + // same as address for all roles + _ <- profile.supporter.roles match { + case list if list.nonEmpty => list.tail.foldLeft(DBIO.seq(supporterCrewAssignment(crewDBids.getOrElse(profile.loginInfo.providerKey, None), s, list.headOption)))( + (seq, role) => DBIO.seq(seq, supporterCrewAssignment(crewDBids.getOrElse(profile.loginInfo.providerKey, None), s, Some(role))) + ) + case _ => DBIO.seq(supporterCrewAssignment(crewDBids.getOrElse(profile.loginInfo.providerKey, None), s, None)) + } + // insert the LoginInfo PasswordInfo and OAuthInfo Models + _ <- (loginInfos returning loginInfos.map(_.id)) += LoginInfoDB(0, profile.loginInfo, p) + _ <- (profile.passwordInfo match { + case Some(passwordInfo) => (passwordInfos returning passwordInfos.map(_.id)) += PasswordInfoDB(0, passwordInfo, p) + case None => DBIO.successful(false) + }) + _ <- (profile.oauth1Info match { + case Some(oAuth1Info) => (oauth1Infos returning oauth1Infos.map(_.id)) += OAuth1InfoDB(oAuth1Info, p) + case None => DBIO.successful(false) + }) + } yield ()).transactionally)) + } yield u).transactionally + } + //ToDo: Export Profile DB Operations to ProfileDAO. This has to be protected, becaus it should not used outside the DAO Package /** * internal function to add a list of profiles for one user to the database diff --git a/app/services/UserService.scala b/app/services/UserService.scala index f8b7501b..55cc87f1 100644 --- a/app/services/UserService.scala +++ b/app/services/UserService.scala @@ -34,7 +34,7 @@ class UserService @Inject() ( def retrieve(loginInfo:LoginInfo):Future[Option[User]] = userDao.find(loginInfo) def save(user:User) = { userDao.save(user).map(user => { - nats.publishCreate("USER", user.id) + user.map(u => nats.publishCreate("USER", u.id)) user }) } @@ -100,22 +100,25 @@ class UserService @Inject() ( } def save(socialProfile:CommonSocialProfile) = { - def onSuccess(user: User, create: Boolean) = { - if(create) { - nats.publishCreate("USER", user.id) - poolService.save(user) // Todo: Consider result?! - } else { - nats.publishUpdate("USER", user.id) - poolService.update(user) // Todo: Consider result?! - } + def onCreate(user: Option[User]) = { + user.map(u => { + nats.publishCreate("USER", u.id) + poolService.save(u) + }) + user + } + + def onUpdate(user: User) = { + nats.publishUpdate("USER", user.id) + poolService.update(user) user } val profile = Profile(socialProfile) userDao.find(profile.loginInfo).flatMap { case None => userDao.save(User(UUID.randomUUID(), List(profile), System.currentTimeMillis(), System.currentTimeMillis())) - .map(onSuccess(_, true)) + .map(onCreate(_)) case Some(user) => userDao.update(profile) - .map(onSuccess(_, false)) + .map(onUpdate(_)) } } diff --git a/build.sbt b/build.sbt index c150138f..19b123e8 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ import com.typesafe.sbt.packager.docker.Cmd name := """Drops""" organization := "net.soteto" -version := "0.36.64" +version := "0.36.65" lazy val root = (project in file(".")).enablePlugins(PlayScala).enablePlugins(DockerPlugin) From 1e35ec17bed26df744844b097c1826eff67b6084 Mon Sep 17 00:00:00 2001 From: Johann Sell Date: Wed, 5 May 2021 21:08:13 +0200 Subject: [PATCH 7/9] #2 split insert of complete user and insert of just its profiles into two functions --- app/daos/UserDao.scala | 84 ++++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 36 deletions(-) diff --git a/app/daos/UserDao.scala b/app/daos/UserDao.scala index 0c716aa1..86d099cf 100644 --- a/app/daos/UserDao.scala +++ b/app/daos/UserDao.scala @@ -308,7 +308,7 @@ class MariadbUserDao @Inject()(val crewDao: MariadbCrewDao) extends UserDao { } /** - * Creates an DBIOAction that inserts a complete user as a transaction that inserts into multiple columns. + * Creates a [[DBIOAction]] that inserts a complete user as a transaction that inserts into multiple columns. * * @author Johann Sell * @param userDB @@ -317,7 +317,22 @@ class MariadbUserDao @Inject()(val crewDao: MariadbCrewDao) extends UserDao { * @return */ private def insertUser(userDB: UserDB, userProfiles: List[Profile], crewDBids: Map[String, Option[Long]]) : DBIOAction[Long,slick.dbio.NoStream,slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Transactional with slick.dbio.Effect.Transactional] = { + (for { + u <- (users returning users.map(_.id)) += userDB + _ <- insertProfiles(u, userProfiles, crewDBids) + } yield u).transactionally + } + /** + * Creates a [[DBIOAction]] that inserts all given profiles as a transaction and assigns them to a given users ID. + * + * @author Johann Sell + * @param userID + * @param userProfiles + * @param crewDBids + * @return + */ + private def insertProfiles(userID: Long, userProfiles: List[Profile], crewDBids: Map[String, Option[Long]]): DBIOAction[List[Long],slick.dbio.NoStream,slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Transactional] = { // Assign a Supporter to an Crew via SupporterCrew. Each SupporterCrew Object contains one Role. def supporterCrewAssignment(crewDBiD: Option[Long], s: Long, r: Option[Role]) = crewDBiD match { case Some(crewId) => (supporterCrews returning supporterCrews.map(_.supporterId)) += (SupporterCrewDB(s, crewId, r, None, None, None)) @@ -330,41 +345,38 @@ class MariadbUserDao @Inject()(val crewDao: MariadbCrewDao) extends UserDao { case _ => DBIO.successful(false) } - (for { - u <- (users returning users.map(_.id)) += userDB - _ <- DBIO.sequence(userProfiles.map(profile => (for { - // insert Profile and return long id as p - p <- (profiles returning profiles.map(_.id)) += ProfileDB(profile, u) - // insert Supporter with profile_id = p and return long id as s - s <- (supporters returning supporters.map(_.id)) += SupporterDB(0, profile.supporter, p) - //insert Addresses with supporter_id = s. - _ <- profile.supporter.address match { - // if Addresses is a list and not Empty we insert every address in the list and return a DBIO.seq with all id's - case list if list.nonEmpty => list.tail.foldLeft(DBIO.seq(addressAssignment(list.headOption, s)))( - (seq, address) => DBIO.seq(seq, addressAssignment(Some(address), s)) - ) - // else return Database false - case _ => DBIO.successful(false) - } - // same as address for all roles - _ <- profile.supporter.roles match { - case list if list.nonEmpty => list.tail.foldLeft(DBIO.seq(supporterCrewAssignment(crewDBids.getOrElse(profile.loginInfo.providerKey, None), s, list.headOption)))( - (seq, role) => DBIO.seq(seq, supporterCrewAssignment(crewDBids.getOrElse(profile.loginInfo.providerKey, None), s, Some(role))) - ) - case _ => DBIO.seq(supporterCrewAssignment(crewDBids.getOrElse(profile.loginInfo.providerKey, None), s, None)) - } - // insert the LoginInfo PasswordInfo and OAuthInfo Models - _ <- (loginInfos returning loginInfos.map(_.id)) += LoginInfoDB(0, profile.loginInfo, p) - _ <- (profile.passwordInfo match { - case Some(passwordInfo) => (passwordInfos returning passwordInfos.map(_.id)) += PasswordInfoDB(0, passwordInfo, p) - case None => DBIO.successful(false) - }) - _ <- (profile.oauth1Info match { - case Some(oAuth1Info) => (oauth1Infos returning oauth1Infos.map(_.id)) += OAuth1InfoDB(oAuth1Info, p) - case None => DBIO.successful(false) - }) - } yield ()).transactionally)) - } yield u).transactionally + DBIO.sequence(userProfiles.map(profile => (for { + // insert Profile and return long id as p + p <- (profiles returning profiles.map(_.id)) += ProfileDB(profile, userID) + // insert Supporter with profile_id = p and return long id as s + s <- (supporters returning supporters.map(_.id)) += SupporterDB(0, profile.supporter, p) + //insert Addresses with supporter_id = s. + _ <- profile.supporter.address match { + // if Addresses is a list and not Empty we insert every address in the list and return a DBIO.seq with all id's + case list if list.nonEmpty => list.tail.foldLeft(DBIO.seq(addressAssignment(list.headOption, s)))( + (seq, address) => DBIO.seq(seq, addressAssignment(Some(address), s)) + ) + // else return Database false + case _ => DBIO.successful(false) + } + // same as address for all roles + _ <- profile.supporter.roles match { + case list if list.nonEmpty => list.tail.foldLeft(DBIO.seq(supporterCrewAssignment(crewDBids.getOrElse(profile.loginInfo.providerKey, None), s, list.headOption)))( + (seq, role) => DBIO.seq(seq, supporterCrewAssignment(crewDBids.getOrElse(profile.loginInfo.providerKey, None), s, Some(role))) + ) + case _ => DBIO.seq(supporterCrewAssignment(crewDBids.getOrElse(profile.loginInfo.providerKey, None), s, None)) + } + // insert the LoginInfo PasswordInfo and OAuthInfo Models + _ <- (loginInfos returning loginInfos.map(_.id)) += LoginInfoDB(0, profile.loginInfo, p) + _ <- (profile.passwordInfo match { + case Some(passwordInfo) => (passwordInfos returning passwordInfos.map(_.id)) += PasswordInfoDB(0, passwordInfo, p) + case None => DBIO.successful(false) + }) + _ <- (profile.oauth1Info match { + case Some(oAuth1Info) => (oauth1Infos returning oauth1Infos.map(_.id)) += OAuth1InfoDB(oAuth1Info, p) + case None => DBIO.successful(false) + }) + } yield p).transactionally)) } //ToDo: Export Profile DB Operations to ProfileDAO. This has to be protected, becaus it should not used outside the DAO Package From 7d25e02533d8bc405a25f526082dca590286bc1e Mon Sep 17 00:00:00 2001 From: Johann Sell Date: Wed, 5 May 2021 21:28:53 +0200 Subject: [PATCH 8/9] close #4 --- app/daos/UserDao.scala | 72 ++++++++++++++++++++++++++++------ app/services/UserService.scala | 19 ++++++--- 2 files changed, 72 insertions(+), 19 deletions(-) diff --git a/app/daos/UserDao.scala b/app/daos/UserDao.scala index 86d099cf..dc148c21 100644 --- a/app/daos/UserDao.scala +++ b/app/daos/UserDao.scala @@ -28,10 +28,10 @@ trait UserDao extends ObjectIdResolver with CountResolver{ def find(loginInfo:LoginInfo):Future[Option[User]] def find(userId:UUID):Future[Option[User]] def save(user:User):Future[Option[User]] - def replace(user:User):Future[User] + def replace(user:User):Future[Option[User]] def confirm(loginInfo:LoginInfo):Future[User] - def link(user:User, profile:Profile):Future[User] - def update(profile:Profile):Future[User] + def link(user:User, profile:Profile):Future[Option[User]] + def update(profile:Profile):Future[Option[User]] def list : Future[List[User]] def listOfStubs : Future[List[UserStub]] def delete (userId:UUID):Future[Boolean] @@ -129,7 +129,7 @@ class MariadbUserDao @Inject()(val crewDao: MariadbCrewDao) extends UserDao { ).flatMap(id => find(id)) } - override def replace(updatedUser: User): Future[User] = { + override def replace(updatedUser: User): Future[Option[User]] = { findDBUserModel(updatedUser.id).flatMap(user => { //Delete Profiles val deleteProfile = profiles.filter(_.userId === user.id) @@ -141,9 +141,25 @@ class MariadbUserDao @Inject()(val crewDao: MariadbCrewDao) extends UserDao { dbConfig.db.run( (deletePasswordInfo.delete andThen deleteLoginInfo.delete andThen deleteAddress.delete andThen deleteSupporterCrew.delete andThen deleteSupporter.delete andThen deleteProfile.delete).transactionally - ).flatMap(_ => - addProfiles(user, updatedUser.profiles) - ) + ).flatMap(_ => { + // now insert the new profiles + // start with retrieving the crew IDs of the assigned crews + val crewIds: Future[Map[String, Option[Long]]] = Future.sequence(updatedUser.profiles.map(profile => + profile.supporter.crew match { + // if supporter has a crew, find the CrewDB.id by UUID crew.id + case Some(crew) => crewDao.findDBCrewModel(crew.id).map(_ match { + case Some(c) => (profile.loginInfo.providerKey, Some(c.id)) + case None => (profile.loginInfo.providerKey, None) + }) + //else None + case _ => Future.successful((profile.loginInfo.providerKey, None)) + } + )).map(_.toMap) + crewIds.flatMap(c_ids => + dbConfig.db.run(insertProfiles(user.id, updatedUser.profiles, c_ids)) + ).flatMap(_ => find(user.id)) + //addProfiles(user, updatedUser.profiles) + }) }) } @@ -160,10 +176,25 @@ class MariadbUserDao @Inject()(val crewDao: MariadbCrewDao) extends UserDao { * @param profile * @return */ - override def link(user: User, profile: Profile): Future[User] = { + override def link(user: User, profile: Profile): Future[Option[User]] = { find(profile.loginInfo).flatMap(user => { findDBUserModel(user.get.id).flatMap(userDB => { - addProfiles(userDB, List(profile)) + // now insert the new profiles + // start with retrieving the crew IDs of the assigned crews + val crewIds : Future[Map[String, Option[Long]]] = + profile.supporter.crew match { + // if supporter has a crew, find the CrewDB.id by UUID crew.id + case Some(crew) => crewDao.findDBCrewModel(crew.id).map(_ match { + case Some(c) => Map(profile.loginInfo.providerKey -> Some(c.id)) + case None => Map(profile.loginInfo.providerKey -> None) + }) + //else None + case _ => Future.successful(Map(profile.loginInfo.providerKey -> None)) + } + crewIds.flatMap(c_ids => + dbConfig.db.run(insertProfiles(userDB.id, List(profile), c_ids)) + ).flatMap(_ => find(userDB.id)) + //addProfiles(userDB, List(profile)) }) }) } @@ -175,7 +206,7 @@ class MariadbUserDao @Inject()(val crewDao: MariadbCrewDao) extends UserDao { * @param profile * @return */ - override def update(profile: Profile): Future[User] = { + override def update(profile: Profile): Future[Option[User]] = { find(profile.loginInfo).flatMap(user => { findDBUserModel(user.get.id).flatMap(userDB => { //Delete Profile @@ -188,9 +219,24 @@ class MariadbUserDao @Inject()(val crewDao: MariadbCrewDao) extends UserDao { dbConfig.db.run( (deletePasswordInfo.delete andThen deleteLoginInfo.delete andThen deleteAddress.delete andThen deleteSupporterCrew.delete andThen deleteSupporter.delete andThen deleteProfile.delete).transactionally - ).flatMap(_ => - addProfiles(userDB, List(profile)) - ) + ).flatMap(_ => { + // now insert the new profiles + // start with retrieving the crew IDs of the assigned crews + val crewIds: Future[Map[String, Option[Long]]] = + profile.supporter.crew match { + // if supporter has a crew, find the CrewDB.id by UUID crew.id + case Some(crew) => crewDao.findDBCrewModel(crew.id).map(_ match { + case Some(c) => Map(profile.loginInfo.providerKey -> Some(c.id)) + case None => Map(profile.loginInfo.providerKey -> None) + }) + //else None + case _ => Future.successful(Map(profile.loginInfo.providerKey -> None)) + } + crewIds.flatMap(c_ids => + dbConfig.db.run(insertProfiles(userDB.id, List(profile), c_ids)) + ).flatMap(_ => find(userDB.id)) + //addProfiles(userDB, List(profile)) + }) }) }) } diff --git a/app/services/UserService.scala b/app/services/UserService.scala index 55cc87f1..146af7b9 100644 --- a/app/services/UserService.scala +++ b/app/services/UserService.scala @@ -34,7 +34,10 @@ class UserService @Inject() ( def retrieve(loginInfo:LoginInfo):Future[Option[User]] = userDao.find(loginInfo) def save(user:User) = { userDao.save(user).map(user => { - user.map(u => nats.publishCreate("USER", u.id)) + user.map(u => { + nats.publishCreate("USER", u.id) + poolService.save(u) + }) user }) } @@ -42,8 +45,10 @@ class UserService @Inject() ( def update(updatedUser: User) = { userDao.replace(updatedUser).map(user => { - nats.publishUpdate("USER", updatedUser.id) - poolService.update(user) // Todo: Consider result?! + user.map(u => { + nats.publishUpdate("USER", u.id) + poolService.update(u) // Todo: Consider result?! + }) user }) } @@ -108,9 +113,11 @@ class UserService @Inject() ( user } - def onUpdate(user: User) = { - nats.publishUpdate("USER", user.id) - poolService.update(user) + def onUpdate(user: Option[User]) = { + user.map(u => { + nats.publishUpdate("USER", u.id) + poolService.update(u) + }) user } val profile = Profile(socialProfile) From d0e31d2379b9ef552bfd7d9cb93bd27c41f196c6 Mon Sep 17 00:00:00 2001 From: Johann Sell Date: Thu, 6 May 2021 09:13:03 +0200 Subject: [PATCH 9/9] close #6 --- README.md | 3 ++- app/controllers/Application.scala | 1 + app/daos/UserDao.scala | 2 +- app/models/DummyUser.scala | 7 +++++++ build.sbt | 2 +- 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9179540a..a8e1632d 100644 --- a/README.md +++ b/README.md @@ -365,8 +365,9 @@ and your service. ChangeLog ========= -## Version 0.36.65 (2021-5-5) +## Version 0.36.66 (2021-5-6) * [[B] #4 - UserDAO does not execute inserts as a transaction](https://github.com/SOTETO/drops/issues/4) +* [[B] #6 - DummyUser creation throws an error on Insert](https://github.com/SOTETO/drops/issues/6) ## Version 0.36.61 (2019-4-16) diff --git a/app/controllers/Application.scala b/app/controllers/Application.scala index 9d38b3f2..e6ad537f 100644 --- a/app/controllers/Application.scala +++ b/app/controllers/Application.scala @@ -76,6 +76,7 @@ class Application @Inject() ( // save user in database and return a future of the saved user userService.save(crewSupporter) }) + //Future.sequence(users).map((list) => Ok(Json.toJson(list))) Future.sequence(users).map((list) => Ok(Json.toJson(list))) }) ) diff --git a/app/daos/UserDao.scala b/app/daos/UserDao.scala index dc148c21..5b35d7a6 100644 --- a/app/daos/UserDao.scala +++ b/app/daos/UserDao.scala @@ -381,7 +381,7 @@ class MariadbUserDao @Inject()(val crewDao: MariadbCrewDao) extends UserDao { private def insertProfiles(userID: Long, userProfiles: List[Profile], crewDBids: Map[String, Option[Long]]): DBIOAction[List[Long],slick.dbio.NoStream,slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Write with slick.dbio.Effect.Transactional] = { // Assign a Supporter to an Crew via SupporterCrew. Each SupporterCrew Object contains one Role. def supporterCrewAssignment(crewDBiD: Option[Long], s: Long, r: Option[Role]) = crewDBiD match { - case Some(crewId) => (supporterCrews returning supporterCrews.map(_.supporterId)) += (SupporterCrewDB(s, crewId, r, None, None, None)) + case Some(crewId) => (supporterCrews returning supporterCrews.map(_.id)) += (SupporterCrewDB(s, crewId, r, None, None, None)) case _ => DBIO.successful(false) } diff --git a/app/models/DummyUser.scala b/app/models/DummyUser.scala index 1af20650..2a7b7ffc 100644 --- a/app/models/DummyUser.scala +++ b/app/models/DummyUser.scala @@ -40,6 +40,13 @@ object DummyUser { ) }, Set(), + //Set(AddressStub( + // (json \ "location" \ "street" \ "name").as[String], + // None, + // (json \ "location" \ "postcode").as[Int].toString, + // (json \ "location" \ "city").as[String], + // (json \ "location" \ "country").as[String] + //).toAddress), Some("active"), None ) diff --git a/build.sbt b/build.sbt index 19b123e8..65917329 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ import com.typesafe.sbt.packager.docker.Cmd name := """Drops""" organization := "net.soteto" -version := "0.36.65" +version := "0.36.66" lazy val root = (project in file(".")).enablePlugins(PlayScala).enablePlugins(DockerPlugin)