Scala write collection of objects to HttpResponse - scala

I'm scala beginner and for now I'm trying to build a basic play/slick app (sort of user database).
It seems I've been able to build up all the stuff but data transferring to the front-end.
Here is what I have:
UserDAO.scala
class UserDao #Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends BaseDao {
import driver.api._
def entities = TableQuery[UsersTable]
def all(): Future[Seq[User]] = {
db.run(entities.result)
}
class UsersTable(tag: Tag) extends BaseTable(tag, "USER") {
def email = column[String]("email")
def password = column[String]("password")
def * = (id, email, password) <> (User.tupled, User.unapply)
}
}
Application.scala
Application #Inject()(userDAO: UserDao) extends Controller {
def users = Action.async {
val userList = userDAO.all()
userList
.map { list => Ok(list.map(elem => Json.toJson(elem : UserDto))) }
.recover { case _ => InternalServerError }
}
}
UserDTO.scala
case class UserDto(id: Long, login: String)
object UserDto {
implicit val userWriter = Json.writes[UserDto]
implicit def from(user: User): UserDto = UserDto(user.id, user.login)
}
What I don't understand is why compiler complains about .map { list => Ok(list.map(elem => Json.toJson(elem : UserDto))) } in Application.scala. It seems that I provided everything required for conversion to json. Could please, anybody, show what I'm doing wrong?

Replace Ok(list.map(elem => Json.toJson(elem : UserDto))) with Json.toJson(list: Seq[UserDto])
Application #Inject()(userDAO: UserDao) extends Controller {
def users = Action.async {
val userList = userDAO.all()
userList
.map { list => Ok(Json.toJson(list: Seq[UserDto])) }
.recover { case _ => InternalServerError }
}
}

Related

Reusablecode for db operations with dependency injection

I have multiple actors managing data models that are written to a mongo db.
object LevelManager {
val collectionName = "levels"
}
#Singleton
class LevelManager #Inject()(
val reactiveMongoApi: ReactiveMongoApi) extends Actor with ActorLogging with InjectedActorSupport {
def collection: Future[JSONCollection] = reactiveMongoApi.database.map(_.collection[JSONCollection](LevelManager.collectionName))
override def receive: Receive = {
case msg:GetById =>
var level = collection.flatMap(c => c.find(Json.obj("_id" -> msg.id), Option.empty[JsObject]).one[LevelModel].map {
result =>
logger.info( result )
}
}
}
This works fine, but this db code is used in every actor and i did not manage to have it only once. I'm not sure if this is even a clever way, too. It derived from older scala times without dependency injection, where everything was put in an object trait.
So i'm looking for a trait or something, with basic db io handling
Edit: Before dependency injection i was able to use a trait like this:
trait BaseModel[T] {
val collectionName: String
val db = ReactiveMongoPlugin.db
def load(id: Long)(implicit fmt: Format[T]) = {
val coll = db.collection[JSONCollection](collectionName)
coll.find(Json.obj("_id" -> id)).one[T]
}
def loadAll()(implicit fmt: Format[T]) = {
val coll = db.collection[JSONCollection](collectionName)
coll.find(Json.obj()).cursor[T].collect[Vector]()
}
def save(id: Long, model: T)(implicit fmt: Format[T]) = {
val coll = db.collection[JSONCollection](collectionName)
val doc = Json.toJson(model).as[JsObject] + ("_id" -> Json.toJson(id))
coll.save(doc).map { lastError =>
if (!lastError.ok) Logger.error(lastError.message)
lastError.ok
}
}
I ended in creating a trait with def collection: Future[JSONCollection] and i'm now able to access the db my favorite db functions. This was my goal and makes life so much better. But i'm unsettled from the recent feedback here, if this has any disadvantages.
trait DBStuff[T] {
def collection: Future[JSONCollection]
def log: LoggingAdapter
def save(id: String, model: T)(implicit fmt: Format[T]) = {
val doc:JsObject = Json.toJson(model).as[JsObject] + ("_id" -> Json.toJson(id))
collection.flatMap(_.update.one(Json.obj("_id" -> id), doc, true)).map(lastError => if (!lastError.ok) log.warning(s"Mongo LastError: %s".format(lastError)))
}
def loadOne(id: String)(implicit fmt: Format[T]): Future[Option[T]] = loadOne( Json.obj("_id" -> id) )
def loadOne(obj: JsObject, projection:Option[JsObject] = None )(implicit fmt: Format[T]): Future[Option[T]] = {
collection.flatMap(_.find( obj, projection).one[T].map {
result =>
result
}.recover {
case err => log.error(s"DB Loading Error: $err")
None
})
}
def loadAll()(implicit fmt: Format[T]):Future[Vector[T]] = {
loadAll(Json.obj(), None )
}
def loadAll( obj: JsObject, projection:Option[JsObject] = None)(implicit fmt: Format[T]):Future[Vector[T]] = {
collection.flatMap(_.find(obj, projection ).cursor[T]().collect[Vector](Int.MaxValue, Cursor.FailOnError()).map {
result => result
}.recover {
case err =>
log.error(s"DB Loading Error: $err")
Vector.empty
})
}
...
}
#Singleton
class TaskManager #Inject()(
val reactiveMongoApi: ReactiveMongoApi
) extends Actor with ActorLogging with InjectedActorSupport with DBStuff[TaskModel] {
def collection: Future[JSONCollection] = reactiveMongoApi.database.map(_.collection[JSONCollection](TaskManager.collectionName))
override def preStart() = {
loadAll() map {
result =>
//What ever
}
}

How to create slick projection for list of nested case class?

I am using play 2.6.6 , scala 2.12.3 and slick 3.0.0.
I had following case class structure initially where there was a nested case class:
case class Device(id: Int, deviceUser: Option[DeviceUser] =None)
case class DeviceUser(name: Option[String] = None)
So, I had created following projection for Device class:
class DevicesTable(tag: Tag) extends Table[Device](tag, "DEVICES") {
def id = column[Int]("ID", O.PrimaryKey)
def name = column[Option[String]]("NAME")
def deviceUser = name.<>[Option[DeviceUser]](
{
(param: Option[String]) => {
param match {
case Some(name) => Some(DeviceUser(Some(name)))
case None => None
}
}
},
{
(t: Option[DeviceUser]) =>
{
t match {
case Some(user) => Some(user.name)
case None => None
}
}
}
)
def * = (id, deviceUser).<>(Device.tupled, Device.unapply)
}
The above setup was working fine. I could easily store and retrieve data using the above projection. But now, my requirement has changed and I need to store list of nested case class. So, the class structure is now as follow :
case class Device(id: Int, deviceUser: Option[List[DeviceUser]] =None)
case class DeviceUser(name: Option[String] = None)
Is there some way where I could define projection for the field deviceUser: Option[List[DeviceUser]] ?
Update : I am looking for more of a non-relational approach here.
Since, no body has suggested a solution so far, I am sharing the approach that I am using right now. It works but of course is not the best solution. Specially, I want to avoid using Await here and would like to develop a generic implicit parser.
ALso, I had to create a separate DeviceUsersTable.
case class DeviceUser(id: Int,name: Option[String] = None)
class DeviceUserRepo #Inject()(protected val dbConfigProvider: DatabaseConfigProvider) {
val dbConfig = dbConfigProvider.get[JdbcProfile]
val db = dbConfig.db
import dbConfig.profile.api._
val DeviceUsers = TableQuery[DeviceUserTable]
private def _findById(id: Int): DBIO[Option[DeviceUser]] =
DeviceUsers.filter(_.id === id).result.headOption
def findById(id: Int): Future[Option[DeviceUser]] =
db.run(_findById(id))
def all: Future[List[DeviceUser]] =
db.run(DeviceUsers.to[List].result)
def create(deviceUser: DeviceUser): Future[Int] = {
db.run(DeviceUsers returning DeviceUsers.map(_.id) += deviceUser)
}
class DeviceUserTable(tag: Tag) extends Table[DeviceUser](tag, "DEVICE_USERS") {
def id = column[Int]("ID", O.PrimaryKey)
def name = column[Option[String]]("NAME")
def * = (id, name).<>(DeviceUser.tupled, DeviceUser.unapply)
}
}
And the original DevicesTable now looks like this :
class DevicesTable(tag: Tag) extends Table[Device](tag, "DEVICES") {
implicit val deviceUserConverter = MappedColumnType.base[Option[List[DeviceUser]], String](
deviceUsersOpt => {
deviceUsersOpt match {
case Some(users:List[DeviceUser]) =>val listOfId = users.map{
k => val res = deviceUserRepo.create(k)
Await.result(res, 10 seconds)
}
listOfId.mkString(",")
case None => ""
}
},
str =>{
val listOfIds = (str split "," map Integer.parseInt).toList.filterNot(k => k.equals(""))
if(listOfIds.nonEmpty){
val users = listOfIds.map{ k =>
val res = deviceUserRepo.findById(k)
Await.result(res, 10 seconds)
}
Some(users.flatten)
} else {
None
}
}
)
def id = column[Int]("ID", O.PrimaryKey)
def deviceUser = column[Option[List[DeviceUser]]]("DEVICE_USERS")
def * = (id, deviceUser).<>(Device.tupled, Device.unapply)
}

Slick 3.1 - Convert Slick Table Row Object to case Class

I've used the Slick 3.1 code generator to create the default object and trait Tables.scala
The below method works however I would like to implicitly or explicitly convert UserRow and PasswordsRow to User and UserPassword.
Working method:
override def getUser(email: String): Future[Option[(Tables.UsersRow, Tables.PasswordsRow)]] = db.run {
(for {
user <- users if user.email === email
password <- passwords if password.id === user.id
} yield (user, password)).result.headOption
}
Desired method:
override def getUser(email: String): Future[Option[(User, UserPassword)]] = db.run {
(for {
user <- users if user.email === email
password <- passwords if password.id === user.id
} yield (user, password)).result.headOption
}
User.scala
package model
import com.wordnik.swagger.annotations.{ ApiModel, ApiModelProperty }
import slick.jdbc.GetResult
import spray.json.DefaultJsonProtocol
import scala.annotation.meta.field
case class User(
id: Int,
email: String,
name: Option[String] = None,
surname: Option[String] = None,
passwordId: Option[Int] = None
)
object User extends DefaultJsonProtocol{
implicit val getUserResult = GetResult(r => User(r.<<, r.<<, r.<<, r.<<, r.<<))
implicit val userFormat = jsonFormat5(User.apply)
}
UserPassword.scala
package model
import com.github.t3hnar.bcrypt.{Password, generateSalt}
import slick.jdbc.GetResult
case class UserPassword(id: Int, hashedPassword: Option[String], salt: String = generateSalt) {
def passwordMatches(password: String): Boolean = hashedPassword.contains(password.bcrypt(salt))
}
object UserPassword {
implicit val getUserPasswordResult = GetResult(r => UserPassword(r.<<, r.<<, r.<<))
def newWithPassword(password: String) = {
val salt = generateSalt
new UserPassword(0, Some(password.bcrypt(salt)), salt)
}
}
Someting like this maybe?
val futureUserRowAndPwdRow = getUser(email)
val futureUser: Future[Option[(User, UserPassword)]] = futureUserRowAndPwdRow map {
maybeUserRow => maybeUserRow map {
case (userRow, pwdRow) => (User(userRow.whatever....), UserPassword(..))
}
}

Upload file Scala Spray

Looking to create functionality to upload a file using multipart/form-data. However I cannot grasp how to change the MultipartFormData and store it in the file system. Below is what I have so far.
trait Service extends HttpService {
private final lazy val fileWorker = actorRefFactory.actorOf(Props[FileServicesActor])
implicit def executionContext = actorRefFactory.dispatcher
val demoRoute: Route = {
path("file") {
post {
respondWithMediaType(`application/json`) {
entity(as[MultipartFormData]) { formData =>
uploadFile(formData)
}
}
}
}
}
private def uploadFile(data: MultipartFormData)= {
val response = (fileWorker ? UploadFile(data)).mapTo[Any].map { case t: Success => t
case e: Error => Error.outputError(e)
case _ => Failure(_)
}.recover { case e: Exception => Failure(e)
}
complete(response)
}
}
The function resolves to this
def uploadFile(data: MultipartFormData) = {
val file = data.get("file")
//not sure what to do with data here...
}

Play-silhouette-slick-seed; SecuredAction not executing with change of TableQuery

I'm using the play-silhouette-slick seed for a project and I need to add users to different tables depending on their type. However, if I change the TableQuery used to insertUpdate the user in UserDAOImpl.save, a SecuredAction further down the line (for ApplicationController.index) does not execute, suggesting that the user hasn't been authenticated. So by swapping this line of code;
_ <- slickUsers.insertOrUpdate(dbUser)
To this;
_ <-slickAdministrators.insertOrUpdate(dbUser)
My SecuredAction doesn't execute. Can anyone point me in the right direction please? More of my code is below;
class UserDAOImpl extends UserDAO with DAOSlick {
import driver.api._
def find(loginInfo: LoginInfo) = {
getUserTypeAndID(loginInfo) match {
case Some((id, userType)) if userType.equals("administrator") => val userQuery = for {
dbUser <- slickUsers.filter(_.userID === id)
} yield dbUser
db.run(userQuery.result.headOption).map { dbUserOption =>
dbUserOption.map { user =>
Administrator(UUID.fromString(user.userID), loginInfo, user.title, user.firstName, user.lastName, user.email)
}
}
case None => val userQuery = for {
dbLoginInfo <- loginInfoQuery(loginInfo)
dbUser <- slickUsers.filter(_.userID === dbLoginInfo.userID)
} yield dbUser
db.run(userQuery.result.headOption).map { dbUserOption =>
dbUserOption.map { user =>
Administrator(UUID.fromString(user.userID), loginInfo, user.title, user.firstName, user.lastName, user.email)
}
}
}
def getUserTypeAndID(loginInfo: LoginInfo) = {
val userQuery = for {
dbLoginInfo <- loginInfoQuery(loginInfo)
} yield dbLoginInfo
val dbresult = db.run(userQuery.result.headOption)
val userOption = Await.result(dbresult, 5 second)
userOption map {info => (info.userID, info.userType)}
}
def save(user: User) = {
val dbUser = DBUser(user.userID.toString, user.title, user.firstName, user.lastName, user.email)
val userType = user.getClass.getTypeName match {
case "models.Administrator" => "administrator"
}
val loginInfoAction = {
val retrieveLoginInfo = slickLoginInfos.filter(
info => info.providerID === user.loginInfo.providerID &&
info.providerKey === user.loginInfo.providerKey).result.headOption
val insertLoginInfo = slickLoginInfos += DBLoginInfo(dbUser.userID, user.loginInfo.providerID, user.loginInfo.providerKey, userType)
for {
loginInfoOption <- retrieveLoginInfo
loginInfo <- loginInfoOption.map(DBIO.successful(_)).getOrElse(insertLoginInfo)
} yield loginInfo
}
val actions = (for {
_ <- slickAdministrators.insertOrUpdate(dbUser)
loginInfo <- loginInfoAction
} yield ()).transactionally
// run actions and return user afterwards
db.run(actions).map(_ => user)
}
}
DBTableDefinitions
trait DBTableDefinitions {
protected val driver: JdbcProfile
import driver.api._
case class DBUser (
userID: String,
title: Option[String],
firstName: Option[String],
lastName: Option[String],
email: Option[String]
)
class Users(tag: Tag) extends Table[DBUser](tag, "user") {
def userID = column[String]("userid", O.PrimaryKey)
def title = column[Option[String]]("title")
def firstName = column[Option[String]]("firstname")
def lastName = column[Option[String]]("lastname")
def email = column[Option[String]]("email")
def * = (userID, title, firstName, lastName, email) <> (DBUser.tupled, DBUser.unapply)
}
class Administrators(tag: Tag) extends Table[DBUser](tag, "administrators") {
def userID = column[String]("userid", O.PrimaryKey)
def title = column[Option[String]]("title")
def firstName = column[Option[String]]("firstname")
def lastName = column[Option[String]]("lastname")
def email = column[Option[String]]("email")
def * = (userID, title, firstName, lastName, email) <> (DBUser.tupled, DBUser.unapply)
}
case class DBLoginInfo (
userID: String,
providerID: String,
providerKey: String,
userType: String
)
class LoginInfos(tag: Tag) extends Table[DBLoginInfo](tag, "logininfo") {
def userID = column[String]("userid", O.PrimaryKey)
def providerID = column[String]("providerid")
def providerKey = column[String]("providerkey")
def userType = column[String]("usertype")
def * = (userID, providerID, providerKey, userType) <> (DBLoginInfo.tupled, DBLoginInfo.unapply)
}
case class DBPasswordInfo (
hasher: String,
password: String,
userID: String
)
class PasswordInfos(tag: Tag) extends Table[DBPasswordInfo](tag, "passwordinfo") {
def hasher = column[String]("hasher")
def password = column[String]("password")
def userID = column[String]("userid")
def * = (hasher, password, userID) <> (DBPasswordInfo.tupled, DBPasswordInfo.unapply)
}
// table query definitions
val slickUsers = TableQuery[Users]
val slickAdministrators = TableQuery[Administrators]
val slickLoginInfos = TableQuery[LoginInfos]
val slickPasswordInfos = TableQuery[PasswordInfos]
// queries used in multiple places
def loginInfoQuery(loginInfo: LoginInfo) =
slickLoginInfos.filter(dbLoginInfo => dbLoginInfo.providerID === loginInfo.providerID && dbLoginInfo.providerKey === loginInfo.providerKey)
}
SignUpController
class SignUpController #Inject() (
val messagesApi: MessagesApi,
val env: Environment[User, CookieAuthenticator],
userService: UserService,
authInfoRepository: AuthInfoRepository,
passwordHasher: PasswordHasher)
extends Silhouette[User, CookieAuthenticator] {
/**
* Registers a new user.
*
* #return The result to display.
*/
def signUp(userType: String) = Action.async { implicit request =>
SignUpForm.form.bindFromRequest.fold(
form => Future.successful(BadRequest(views.html.signUp(form))),
data => {
val loginInfo = LoginInfo(CredentialsProvider.ID, data.email)
userService.retrieve(loginInfo).flatMap {
case Some(user) =>
Future.successful(Redirect(routes.ApplicationController.signUp()).flashing("error" -> Messages("user.exists")))
case None =>
val authInfo = passwordHasher.hash(data.password)
val user = getUser(userType, data, loginInfo)
for {
user <- userService.save(user)
authInfo <- authInfoRepository.add(loginInfo, authInfo)
//shouldn't need below data -> it creates cookie info to continue as the user added
authenticator <- env.authenticatorService.create(loginInfo)
value <- env.authenticatorService.init(authenticator)
result <- env.authenticatorService.embed(value, Redirect(routes.ApplicationController.index()))
} yield {
env.eventBus.publish(SignUpEvent(user, request, request2Messages))
env.eventBus.publish(LoginEvent(user, request, request2Messages))
result
}
}
}
)
}
/**
* Creates a new user according to the specified userType.
*
* #param userType the type of user required
* #param data the data from the SignUpForm
* #param loginInfo the users loginInfo
* #return an instance of a User.
*/
def getUser(userType: String, data: SignUpForm.Data, loginInfo: LoginInfo): User = userType match{
case "administrator" => Administrator(
userID = UUID.randomUUID(),
loginInfo = loginInfo,
title = Some(data.title),
firstName = Some(data.firstName),
lastName = Some(data.lastName),
email = Some(data.email)
)
}
}
ApplicationController
class ApplicationController #Inject() (
val messagesApi: MessagesApi,
val env: Environment[User, CookieAuthenticator])
extends Silhouette[User, CookieAuthenticator] {
/**
* Handles the index action.
*
* #return The result to display.
*/
def index = SecuredAction.async { implicit request =>
Future.successful(Ok(views.html.home(request.identity)))
}
/**
* Handles the Sign In action.
*
* #return The result to display.
*/
def signIn = UserAwareAction.async { implicit request =>
request.identity match {
case Some(user) => Future.successful(Redirect(routes.ApplicationController.index()))
case None => Future.successful(Ok(views.html.signIn(SignInForm.form)))
}
}
/**
* Handles the Sign Up action.
*
* #return The result to display.
*/
def signUp = UserAwareAction.async { implicit request =>
request.identity match {
case Some(user) => Future.successful(Redirect(routes.ApplicationController.index()))
case None => Future.successful(Ok(views.html.signUp(SignUpForm.form)))
}
}
/**
* Handles the Sign Out action.
*
* #return The result to display.
*/
def signOut = SecuredAction.async { implicit request =>
val result = Redirect(routes.ApplicationController.index())
env.eventBus.publish(LogoutEvent(request.identity, request, request2Messages))
env.authenticatorService.discard(request.authenticator, result)
}
}
The code above works if I change that single line in UserDAOImpl.save.
The problem was that I had not implemented a method in UserDAOImpl to find a user in the administrators table according to their LoginInfo. So by adding this;
def find(loginInfo: LoginInfo) = {
getUserTypeAndID(loginInfo) match {
case Some((id, userType)) if userType.equals("administrator") => val userQuery = for {
dbUser <- slickAdministrators.filter(_.userID === id)
} yield dbUser
db.run(userQuery.result.headOption).map { dbUserOption =>
dbUserOption.map { user =>
Administrator(UUID.fromString(user.userID), loginInfo, user.title, user.firstName, user.lastName, user.email)
}
}
case None => val userQuery = for {
dbLoginInfo <- loginInfoQuery(loginInfo)
dbUser <- slickUsers.filter(_.userID === dbLoginInfo.userID)
} yield dbUser
db.run(userQuery.result.headOption).map { dbUserOption =>
dbUserOption.map { user =>
Administrator(UUID.fromString(user.userID), loginInfo, user.title, user.firstName, user.lastName, user.email)
}
}
}
ie, the line
dbUser <- slickAdministrators.filter(_.userID === id)
The SecuredAction now executes as expected for a valid user. I didn't realise that this method was called for authentication for a SecuredAction (or maybe I'm wrong on this and it's being called from somewhere else), so if anyone can explain further it would be really helpful - this has taken me all day to sort!