Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Database error on insert address #361

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
>
Expand Down Expand Up @@ -365,6 +365,10 @@ and your service.
ChangeLog
=========

## Version 0.36.67 (2021-5-10)
* [[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)
* [[B] #8 - Database error on inserting Address](https://github.com/SOTETO/drops/issues/8)

## Version 0.36.61 (2019-4-16)

Expand Down
1 change: 1 addition & 0 deletions app/controllers/Application.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
})
)
Expand Down
23 changes: 18 additions & 5 deletions app/controllers/webapp/Auth.scala
Original file line number Diff line number Diff line change
Expand Up @@ -139,21 +139,34 @@ 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()))
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
}
}
}
}
}
)
Expand Down
2 changes: 1 addition & 1 deletion app/daos/ProfileDao.scala
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class MariadbProfileDao @Inject()(val crewDao: MariadbCrewDao) extends ProfileDa
s <- supporters.filter(s => s.profileId === p.id)
} yield s
dbConfig.db.run(action.result).flatMap(result => {
dbConfig.db.run((addresses returning addresses.map(_.id)) += AddressDB(0, Some(UUID.randomUUID()), profile.supporter.address.head, result.head.id))
dbConfig.db.run((addresses returning addresses.map(_.id)) += AddressDB(0, UUID.randomUUID(), profile.supporter.address.head, result.head.id))
})
}
// def getSupporterCrewDB(sc: SupporterCrewDB) : Future[SupporterCrewDB] = dbConfig.db.run(
Expand Down
193 changes: 166 additions & 27 deletions app/daos/UserDao.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ 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 replace(user:User):Future[User]
def save(user:User):Future[Option[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]
Expand Down Expand Up @@ -60,12 +60,12 @@ class MariadbUserDao @Inject()(val crewDao: MariadbCrewDao) extends UserDao {
val oauthTokens = TableQuery[OauthTokenTableDef]
val addresses = TableQuery[AddressTableDef]

def uuidFromString(uuid: Option[String]) = {
uuid match {
case Some(id) => Some(UUID.fromString(id))
case _ => None
}
}
// def uuidFromString(uuid: Option[String]) = {
// uuid match {
// case Some(id) => Some(UUID.fromString(id))
// case _ => None
// }
// }

implicit val getUserResult = GetResult(r => UserDB(r.nextLong, UUID.fromString(r.nextString), r.nextString, r.nextLong, r.nextLong))
implicit val getProfileResult = GetResult(r => ProfileDB(r.nextLong, r.nextBoolean, r.nextString, r.nextLong))
Expand All @@ -74,7 +74,19 @@ class MariadbUserDao @Inject()(val crewDao: MariadbCrewDao) extends UserDao {
implicit val getSupporterInfoResult = GetResult(r => SupporterDB(r.nextLong, r.nextStringOption, r.nextStringOption, r.nextStringOption, r.nextStringOption, r.nextStringOption, r.nextLongOption, r.nextStringOption, r.nextLong))
implicit val getSupporterCrewInfoResult = GetResult(r => Some(SupporterCrewDB(r.nextLong, r.nextLong, r.nextLong, r.nextStringOption, r.nextStringOption, r.nextLong, r.nextLong, r.nextStringOption, r.nextLongOption)))
implicit val getOauth1InfoResult = GetResult(r => Some(OAuth1InfoDB(r.nextLong, r.nextString, r.nextString, r.nextLong)))
implicit val getAddressInfoResult = GetResult(r => Some(AddressDB(r.nextLong, uuidFromString(r.nextStringOption), r.nextString, r.nextStringOption, r.nextString, r.nextString, r.nextString, r.nextLong)))
implicit val getAddressInfoResult = GetResult(r => {
val id = r.nextLong
val uuidOption = r.nextStringOption
val street = r.nextString
val additional = r.nextStringOption
val zip = r.nextString
val city = r.nextString
val country = r.nextString
val supporter_id = r.nextLong
uuidOption.map(uuidString =>
AddressDB(id, UUID.fromString(uuidString), street, additional, zip, city, country, supporter_id)
)
})



Expand Down Expand Up @@ -110,17 +122,26 @@ 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] = {
override def replace(updatedUser: User): Future[Option[User]] = {
findDBUserModel(updatedUser.id).flatMap(user => {
//Delete Profiles
val deleteProfile = profiles.filter(_.userId === user.id)
Expand All @@ -132,9 +153,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)
})
})
}

Expand All @@ -151,10 +188,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))
})
})
}
Expand All @@ -166,7 +218,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
Expand All @@ -179,9 +231,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))
})
})
})
}
Expand Down Expand Up @@ -298,6 +365,78 @@ class MariadbUserDao @Inject()(val crewDao: MariadbCrewDao) extends UserDao {
dbConfig.db.run(users.filter(_.id === id).result).map(r => r.head)
}

/**
* Creates a [[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] = {
(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(_.id)) += (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(a, supporterId)
case _ => DBIO.successful(false)
}

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
/**
* internal function to add a list of profiles for one user to the database
Expand Down
2 changes: 1 addition & 1 deletion app/daos/schema/AddressTableDef.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class AddressTableDef(tag: Tag) extends Table[AddressDB](tag, "Address") {
def supporterId = column[Long]("supporter_id")

def * =
(id, publicId.?, street, additional.?, zip, city, country, supporterId) <> (AddressDB.tupled, AddressDB.unapply)
(id, publicId, street, additional.?, zip, city, country, supporterId) <> (AddressDB.tupled, AddressDB.unapply)

def supporterKey = foreignKey("supporter_id", supporterId, TableQuery[SupporterTableDef])(_.id, onUpdate = ForeignKeyAction.Cascade)

Expand Down
7 changes: 5 additions & 2 deletions app/models/Address.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ case class AddressStub (
country: String

) extends AddressBase {
def toAddress: Address = Address(Some(UUID.randomUUID()), street, additional, zip, city, country)
def toAddress: Address = Address(UUID.randomUUID(), street, additional, zip, city, country)

def isEmpty: Boolean =
street == "" && additional.map(_ == "").getOrElse(true) && zip == "" && city == "" && country == ""
}

/**
Expand All @@ -42,7 +45,7 @@ case class AddressStub (
* @param toAddressStub convert the Address to AddressStub
*/
case class Address (
publicId: Option[UUID],
publicId: UUID,
override val street: String,
override val additional: Option[String],
override val zip: String,
Expand Down
8 changes: 7 additions & 1 deletion app/models/DummyUser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@ object DummyUser {
(res, i) => res + pillars.toList(i)
)
},
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
)
Expand Down
Loading