Phoenix error adding user to REST service - rest

I made this call:
curl -X POST -H "Content-Type: application/json" -d '{
"user": {
"email": "leo.hetsch#testapp.com",
"first_name": "Léo",
"last_name": "Hetsch",
"password": "notsosecure",
"username": "test1"
}
}' "http://localhost:4000/api/users"
And on the server I get:
[info] POST /api/users
[debug] Processing by VirtualTeams.UserController.create/2
Parameters: %{"user" => %{"email" => "leo.hetsch#testapp.com", "first_name" => "Léo", "last_name" => "Hetsch", "password" => "[FILTERED]", "username" => "test1"}}
Pipelines: [:api]
This is in my user.ex file:
schema "users" do
field :email, :string
field :password, :string
field :first_name, :string
field :last_name, :string
field :api_token, :string
field :username, :string
timestamps()
end
#doc """
Builds a changeset based on the `struct` and `params`.
"""
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:email, :password, :first_name, :last_name, :api_token, :username])
|> validate_required([:email, :password, :first_name, :last_name, :api_token, :username])
|> unique_constraint(:email)
|> unique_constraint(:username)
end
def create(params) do
changeset(%VirtualTeams.User{}, params)
|> put_change(:password, hashed_password(params["password"]))
|> put_change(:api_token, :base64.encode(:crypto.strong_rand_bytes(24)))
|> VirtualTeams.Repo.insert()
end
defp hashed_password(password) do
Comeonin.Pbkdf2.hashpwsalt(password)
end
Just to verify I did:
mix ecto.migrate
00:40:35.074 [info] Already up
Why am I getting an error?
UPDATE:
Forgot the error:
{"error":"error creating user"}
In my controller I have this code, which has the error:
def create(conn, %{"user" => user_params}) do
case User.create(user_params) do
{:ok, user} ->
conn
|> put_status(:created)
|> render("user.json", user: user)
{:error, changeset} ->
conn
|> put_status(:bad_request)
|> json(%{error: "error creating user"})
end

The problem appears to be that :api_token is required by changeset/2, but not present in the params from the request.
In general, it's useful to get a look at the changeset in {:error, changeset} (returned by create/1 in this case) in order to see what validation failed.

Related

Play Framework JSON automated mapping with custom validation

Hi guys I'm new in the Scala world and I want to create a simple REST API using Play Framework. I'm working on JSON mappings to Case Classes, and I have maybe a stupid question.
I have a case class that represents a UserDto (it is used for user registration, and getting some basic info about the user)
case class UserDto(id: Option[Int], email: String, username: String, password: Option[String], confirmedPassword: Option[String])
So as it says in the Play Framework docs, there are multiple ways to map this to JSON, and I'm looking at the automated mapping part
The way I've done it is
val userDtoWrites: OWrites[UserDto] = Json.writes[UserDto]
val userDtoReads: Reads[UserDto] = Json.reads[UserDto]
Now I want to add some validation to my UserDto, for example, to check if the Email is correct.
So my question is, is it somehow possible to create a Read for checking only the email field and add it to the automated generated Read?
I tried it with composeWith, but after running the second Read it only returns the email part.
val checkEmail: String = (JsPath \ "email").read[String](email)
val checkEmailJsString: JsString = checkEmail.map(s => JsString(s))
val newRead = userDtoReads.composeWith(checkEmailJsString))
For example, when a user wants to register:
{"email": "user#email.com", "username": "user","password": "password1234", "confirmedPassword": "password1234"}
That should map to:
UserDto(None, user#email.com, user, password1234, password1234)
if everything is correct. If the password does not match or the email has a bad format is should return 400 for example.
If you want to have instances of UserDto which only has valid email then you should consider using Refined types (using e.g. https://github.com/avdv/play-json-refined to provide support for Play JSON).
import eu.timepit.refined._
import eu.timepit.refined.auto._
import eu.timepit.refined.api._
import play.api.libs.json._
import de.cbley.refined.play.json._
type EmailPattern = "^\S+#\S+\.\S+$" // Scala 2.13 version
type EmailPattern = W.`"^\S+#\S+\.\S+$"`.T // before 2.13
type Email = String Refined string.MatchesRegex[EmailPattern]
case class UserDto(
id: Option[Int],
email: Email, // instead of plain String
username: String,
password: Option[String],
confirmedPassword: Option[String]
)
object UserDto {
implicit val userDtoWrites: OWrites[UserDto] = Json.writes[UserDto]
implicit val userDtoReads: Reads[UserDto] = Json.reads[UserDto]
}
If you don't want to use another library, you can implement your own Reads[UserDto]:
case class UserDto(id: Option[Int], email: String, username: String, password: Option[String], confirmedPassword: Option[String])
implicit val reads: Reads[UserDto] = (
(__ \ "id").readNullable[Int] and
(__ \ "email").read[String].filter(JsonValidationError("email is not valid"))(isValid) and
(__ \ "username").read[String] and
(__ \ "password").readNullable[String] and
(__ \ "confirmedPassword").readNullable[String]
) (UserDto).filter(
JsonValidationError("password and confirmedPassword must be equal")
) { userDto =>
userDto.password == userDto.confirmedPassword
}
Then you can use it:
val successfulJsonString = """{"email": "user#email.com", "username": "user", "password": "password1234", "confirmedPassword": "password1234"}"""
val emailJsonString = """{"email": "user", "username": "user","password": "password1234", "confirmedPassword": "password1234"}"""
val passwordsJsonString = """{"email": "user#email.com", "username": "user","password": "password1234", "confirmedPassword": "1234"}"""
Json.parse(successfulJsonString).validate[UserDto].fold(println, println)
Json.parse(emailJsonString).validateOpt[UserDto].fold(println, println)
Json.parse(passwordsJsonString).validate[UserDto].fold(println, println)
And get the output:
UserDto(None,user#email.com,user,Some(password1234),Some(password1234))
List((/email,List(JsonValidationError(List(email is not valid),List()))))
List((,List(JsonValidationError(List(field1 and field2 must be equal),List()))))
I took references from Scala play json combinators for validating equality and how to add custom ValidationError in Json Reads in PlayFramework to construct that solution.
Code run at Scastie.

Doobie. Set connection timeout

How to set connection timeout using Doobie?
For now, I am creating a new hikari transactor, and then configuring it:
def buildTransactor(driver: String, uri: String,
user: String, pwd: String,
timeout: Long) = for {
ce <- ExecutionContexts.fixedThreadPool[Task](10)
te <- ExecutionContexts.cachedThreadPool[Task]
xa <- HikariTransactor.newHikariTransactor[Task](
driver, uri, user, pwd, ce, te)
_ <- configure(xa, timeout) // Configure transactor
} yield xa
def configure(xa: HikariTransactor[Task], timeout: Long) = Resource.liftF(
xa.configure(ds => Task(ds.setConnectionTimeout(timeout)))
)
I am not sure it is ok. Docs says nothing.

ReactiveMongo Find One Document Error

With the following code bit, I'm trying to fetch a single document based on a user's email where email is part of the stored document:
def userByEmail(encryptedEmail: String): Future[Either[ServiceError, User]] = async {
println(s"finding a user for email $encryptedEmail")
val inlandDb = dbConn.db(dbName)
val userColl = inlandDb[BSONCollection](userCollection)
val found = await(
userColl.find(BSONDocument(emailKey -> BSONString(encryptedEmail))).one[User]
)
println(s"found a user $found")
found match {
case Some(user) => Right(user)
case None => Left(ServiceError("user not found"))
}
}
The user for the given email exists as I verified it in the mongo console. Is there anything wrong? Why is that I'm not able to get the user back for my search query.
Should I have any index defined on email in my user document so that it is searchable?
I get the following error:
finding a user for email Ctkiaw/cbW8DxtRIxbtUYADq5bp6uW7tVryhpT57lKU=
failed java.lang.RuntimeException: None.get
None.get
java.lang.RuntimeException: None.get
at scala.sys.package$.error(package.scala:27)
at play.api.libs.iteratee.Iteratee$$anonfun$run$1.apply(Iteratee.scala:396)
at play.api.libs.iteratee.Iteratee$$anonfun$run$1.apply(Iteratee.scala:389)
at play.api.libs.iteratee.StepIteratee$$anonfun$fold$2.apply(Iteratee.scala:706)
at play.api.libs.iteratee.StepIteratee$$anonfun$fold$2.apply(Iteratee.scala:706)
at scala.concurrent.impl.Future$PromiseCompletingRunnable.liftedTree1$1(Future.scala:24)
at scala.concurrent.impl.Future$PromiseCompletingRunnable.run(Future.scala:24)
at play.api.libs.iteratee.Execution$trampoline$.executeScheduled(Execution.scala:109)
at play.api.libs.iteratee.Execution$trampoline$.execute(Execution.scala:71)
at scala.concurrent.impl.CallbackRunnable.executeWithValue(Promise.scala:40)
at scala.concurrent.impl.Promise$DefaultPromise.tryComplete(Promise.scala:248)
at scala.concurrent.Promise$class.complete(Promise.scala:55)
at scala.concurrent.impl.Promise$DefaultPromise.complete(Promise.scala:153)
at scala.concurrent.impl.Future$PromiseCompletingRunnable.run(Future.scala:23)
at scala.concurrent.impl.ExecutionContextImpl$AdaptedForkJoinTask.exec(ExecutionContextImpl.scala:121)
at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.pollAndExecAll(ForkJoinPool.java:1253)
at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1346)
at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
Here is my User model:
case class User(
_id: Option[String],
firstName: String,
lastName: String,
email: String,
pass: String,
address: Address,
createDate: DateTime,
activateDate: Option[DateTime],
isUserActivated: Boolean,
verificationDate: Option[DateTime]
)
Here is how I do the transformation:
implicit object UserBSONHandler
extends BSONDocumentReader[User] with BSONDocumentWriter[User] {
def read(doc: BSONDocument): User = {
User(
_id = doc.getAs[String]("_id"),
createDate = doc.getAs[BSONDateTime](createDateKey).map(dt => new DateTime(dt.value, DateTimeZone.UTC)).get,
activateDate = doc.getAs[BSONDateTime](activateDateKey).map(dt => new DateTime(dt.value, DateTimeZone.UTC)),
verificationDate = doc.getAs[BSONDateTime](verificationDateKey).map(dt => new DateTime(dt.value, DateTimeZone.UTC)),
firstName = doc.getAs[String](firstNameKey).get,
lastName = doc.getAs[String](lastNameKey).get,
email = doc.getAs[String](emailKey).get,
pass = doc.getAs[String](passKey).get,
address = doc.getAs[Address](addressKey).get,
isUserActivated = doc.getAs[Boolean](isUserActivatedKey).get
)
}
def write(user: User): BSONDocument = {
BSONDocument(
firstNameKey -> user.firstName,
lastNameKey -> user.lastName,
emailKey -> user.email,
passKey -> user.pass,
addressKey -> user.address,
createDateKey -> user.createDate.toString(Iso8601DateFormatter),
activateDateKey -> user.activateDate.map(dt => dt.toString(Iso8601DateFormatter)),
verificationDateKey -> user.verificationDate.map(dt => dt.toString(Iso8601DateFormatter)),
isUserActivatedKey -> user.isUserActivated
)
}
}
From the logs it looks like something is off in your Reads the original code posted above is syntactically OK but the error message along with the logging code helped me zero in on further checking out you BSONDocumentReader
With help from "Barry", I was able to nail down where the problem was. I had to change the read method as below:
def read(doc: BSONDocument): User = {
User(
_id = doc.getAs[String]("_id"),
createDate = doc.getAs[String](createDateKey).map(dt => new DateTime(dt, DateTimeZone.UTC)).get,
activateDate = doc.getAs[String](activateDateKey).map(dt => new DateTime(dt, DateTimeZone.UTC)),
verificationDate = doc.getAs[String](verificationDateKey).map(dt => new DateTime(dt, DateTimeZone.UTC)),
firstName = doc.getAs[String](firstNameKey).get,
lastName = doc.getAs[String](lastNameKey).get,
email = doc.getAs[String](emailKey).get,
pass = doc.getAs[String](passKey).get,
address = doc.getAs[Address](addressKey).get,
isUserActivated = doc.getAs[Boolean](isUserActivatedKey).get
)
}

Adding JsValue to Mongo

Let's say I have a JsValue.
val dbo = MongoDBObject("id" -> "0001", "name" -> "Kevin", "age" -> "100")
val json: JsValue = Json.parse(dbo.toString)
I tried to insert json via:
val obj = MongoDBObject("key" -> json)
collection.insert(obj)
However, there are many brackets [ and ] added to the json part when I do a db.collection.findOne() from the Mongo shell.
How can I properly add a JsValue into Mongo in Casbah?

play scala json of map with list

I've two classes, User & Address.
case class User(
id: Pk[Long] = NotAssigned,
name: String = "",
email: String = "",
addresses: Seq[Address])
case class Address(
id: Pk[Long] = NotAssigned,
userId: Long,
city: String)
From my controller I've to send all users along with their addresses, like Map[User, List[Address]]. I could able to extract them using anorm (mysql) but then I need to send them as json. Could you please help on how to implement the writes & reads for the above Map[User, List[Address]], Thanks.
That should help
import anorm._
import play.api.libs.json._
import play.api.libs.functional.syntax._
case class User(
id: Pk[Long] = NotAssigned,
name: String = "",
email: String = "",
addresses: Seq[Address])
case class Address(
id: Pk[Long] = NotAssigned,
userId: Long,
city: String)
// Play does not provide Format[Pk[A]], so you need to define it
implicit def pkReads[A](implicit r: Reads[Option[A]]): Reads[Pk[A]] = r.map { _.map(Id(_)).getOrElse(NotAssigned) }
implicit def pkWrites[A](implicit w: Writes[Option[A]]): Writes[Pk[A]] = Writes(id => w.writes(id.toOption))
implicit val addrFormat = Json.format[Address]
implicit val userFormat = Json.format[User]
Now you can easily serialize a user:
val as = Seq(Address(Id(2), 1, "biim"))
val u = User(Id(1), "jto", "jto#foo.bar", as)
scala> Json.toJson(u)
res6: play.api.libs.json.JsValue = {"id":1,"name":"jto","email":"jto#foo.bar","addresses":[{"id":2,"userId":1,"city":"biim"}]}
As Julien says, you can't just serialize a Map[User, Seq[Address]]. It just does not make sense since User can't be a key in a Json Object.
You can solve this problem by transforming your Map[User, List[Address]] to a List[User], and the JsonWriter will became easy to write.
Something like:
list.map {
case (user, address) => user.copy(addresses = address.toSeq)
}
The "addresses" in User contains the address so you don't really need to send Map[User, List[Address]] back to client. The Json would be an array of serialized user objects and addresses is part of that. If you do want to send back a Map then the type Map[String, List[Address]] makes more sense in Json serialization context. Here is the code to generate Json for List[User]. The output looks like this
[
{
"id": 1,
"name": "John Doe",
"email": "john#email.com",
"addresses": [
{
"id": 1001,
"userId": 1,
"city": "Chicago"
},
{
"id": 1002,
"userId": 1,
"city": "New York"
}
]
},
{
"id": 2,
"name": "Jane Doe",
"email": "jane#email.com",
"addresses": [
{
"id": 1012,
"userId": 1,
"city": "Dallas"
}
]
}
]
Here is the code the would be in you controller. It has implicit Json formatters that are used by Json.toJson.
implicit object PkWrites extends Writes[Pk[Long]] {
def writes(key: Pk[Long]) = Json.toJson(key.toOption)
}
implicit object PkReads extends Reads[Pk[Long]] {
def reads(json: JsValue) = json match {
case l: JsNumber => JsSuccess(Id(l.value.toLong))
case _ => JsSuccess(NotAssigned)
}
}
implicit val AddressWrites: Writes[Address] = (
(JsPath \ "id").write[Pk[Long]] and
(JsPath \ "userId").write[Long] and
(JsPath \ "city").write[String]
)(unlift(Address.unapply))
implicit val AddressReads: Reads[Address] = (
(JsPath \ "id").read[Pk[Long]] and
(JsPath \ "userId").read[Long] and
(JsPath \ "city").read[String]
)(Address.apply _)
implicit val UserWrites: Writes[User] = (
(JsPath \ "id").write[Pk[Long]] and
(JsPath \ "name").write[String] and
(JsPath \ "email").write[String] and
(JsPath \ "addresses").write[List[Address]]
)(unlift(User.unapply))
def makeJson() = Action {
val johnAddr1 = Address(Id(1001), 1, "Chicago")
val johnAddr2 = Address(Id(1002), 1, "New York")
val janeAddr1 = Address(Id(1012), 1, "Dallas")
val john = User(Id(1), "John Doe", "john#email.com", List(johnAddr1, johnAddr2))
val jane = User(Id(2), "Jane Doe", "jane#email.com", List(janeAddr1))
Ok(Json.toJson(List(john, jane)))
// Ok(Json.toJson(map))
}