Skip to content

Commit

Permalink
Merge pull request #554 from playframework/mergify/bp/2.9.x/pr-553
Browse files Browse the repository at this point in the history
[2.9.x] Sample update/play scala isolated slick example  (backport #553) by @kristileka
  • Loading branch information
mkurz authored Feb 28, 2024
2 parents 6588f57 + 94fae15 commit d7f1e04
Show file tree
Hide file tree
Showing 13 changed files with 375 additions and 50 deletions.

This file was deleted.

140 changes: 140 additions & 0 deletions play-scala-isolated-slick-example/app/controllers/UserController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package controllers

import com.example.user.{User, UserDAO}
import models.UserRequest
import play.api.i18n.{Lang, Messages}
import play.api.libs.json.Json
import play.api.mvc._

import java.time.Instant
import java.util.UUID
import javax.inject.{Inject, Singleton}
import scala.concurrent.ExecutionContext

@Singleton
class UserController @Inject() (userDAO: UserDAO, cc: ControllerComponents)(
implicit ec: ExecutionContext
) extends AbstractController(cc) {
/**
* An implicit for default Languages for the Inputs to default to english.
*/
implicit val defaultLanguage: Messages = cc.messagesApi.preferred(Seq(Lang("en")))

/**
* GET - Get the List of all the users from the userDao
* @return Html View of Index with all the users
*/
def index: Action[AnyContent] = Action.async { implicit request =>
userDAO.all.map { users =>
Ok(views.html.index(users))
}
}

/**
* GET - Find all Users and return them on a String array ids.
* @return List[String] of all the user ids.
*/
def findAll: Action[AnyContent] = Action.async{implicit request =>
userDAO.all.map{ users=>
Ok(Json.toJson(users.map(_.id)))
}
}

/**
* GET - Gets the Create page loading to create a new User
* @return The Html Page for the Create page
*/
def create: Action[AnyContent] = Action {
val emptyForm = UserRequest.form
Ok(views.html.create(emptyForm))
}


/**
* POST - Create a new user from the UserRequest.form validated
* @return Redirect into the index view to see the new user
*/
def save: Action[AnyContent] = Action { implicit request =>
UserRequest.form
.bindFromRequest()
.fold(
formWithErrors => BadRequest(views.html.create(formWithErrors)),
formData => {
userDAO.create(
User(
UUID.randomUUID().toString,
formData.email,
createdAt = Instant.now(),
updatedAt = Option(Instant.now())
)
)
Redirect(routes.UserController.index)
}
)
}

/**
* GET - Opens the Edit Page for the new user with the form filled from userDAO.lookup
* @param id The Id Parameter of the edited user.
* @return The Html page of the edit filled with the user email to be edited.
*/
def edit(id: String): Action[AnyContent] = Action.async { implicit request =>
userDAO.lookup(id).map { userData =>
userData.fold(
Redirect(routes.UserController.index)
) { user =>
val filledForm = UserRequest.form
.fill(UserRequest(id = Option(user.id), email = user.email))
Ok(views.html.update(filledForm))
}
}
}

/**
* POST - Update the user via the validated form to the id requested if that exists.
* @param id The id parameter of the user to edit
* @return Redirect to html page of index with the updated user on it.
*/
def update(id: String): Action[AnyContent] = Action.async {
implicit request =>
userDAO.lookup(id).map { userData =>
userData.fold(
Redirect(routes.UserController.index)
) { user =>
UserRequest.form
.bindFromRequest()
.fold(
formWithErrors => BadRequest(views.html.create(formWithErrors)),
formData => {
userDAO.update(
User(
id,
formData.email,
user.createdAt,
updatedAt = Option(Instant.now())
)
)
Redirect(routes.UserController.index)
}
)
}
}
}

/**
* GET - Delete a user from the database based on id - Done GET due to href link
* @param id The user to delete
* @return Redirects to index page where the new user doesnt exist.
*/
def delete(id: String): Action[AnyContent] = Action.async {
implicit unused =>
userDAO.lookup(id).map { userData =>
userData.fold(
Redirect(routes.UserController.index)
) { _ =>
userDAO.delete(id)
Redirect(routes.UserController.index)
}
}
}
}
23 changes: 23 additions & 0 deletions play-scala-isolated-slick-example/app/models/UserRequest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package models

import play.api.data.Forms._
import play.api.data.Form

/**
* A UserRequest model to get the form to update/create users.
* @param id The Optional User Id to counter add/edit actions
* @param email The Email to be registered/edited
*/
case class UserRequest(id: Option[String], email: String)

/**
* The Companion object of the form
*/
object UserRequest {
val form: Form[UserRequest] = Form(
mapping(
"id" -> optional(nonEmptyText),
"email" -> email
)(UserRequest.apply)(UserRequest.unapply)
)
}
13 changes: 13 additions & 0 deletions play-scala-isolated-slick-example/app/views/create.scala.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@(userForm: Form[UserRequest])(implicit messages: Messages)

@import views.html.helper._

@main("Create User") {

<h1 >Create a new User</h1>

@form(action = routes.UserController.save) {
@inputText(userForm("email"))
<button type="submit">Create</button>
}
}
46 changes: 28 additions & 18 deletions play-scala-isolated-slick-example/app/views/index.scala.html
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
@(users: Seq[User])

@main("Title Page") {
<h2>Users</h2>

<table>
<tr>
<th>Id</th>
<th>Email</th>
<th>Created At</th>
<th>Updated At</th>
</tr>
@for(user <- users){
@import helper._
@main("Title Page") {
<h2>Users</h2>
<a href="@routes.UserController.create" class="add-button">Create User</a>
<br>
<br>
<br>
<table>
<tr>
<td>@user.id</td>
<td>@user.email</td>
<td>@user.createdAt</td>
<td>@user.updatedAt</td>
<th>Id</th>
<th>Email</th>
<th>Created At</th>
<th>Updated At</th>
<th>Actions</th>
</tr>
}
</table>
}
@for(user <- users) {
<tr>
<td>@user.id</td>
<td>@user.email</td>
<td>@user.createdAt</td>
<td>@user.updatedAt</td>
<td>
<a href="@routes.UserController.edit(user.id)" class="edit-button">Edit</a>
<a href="@routes.UserController.delete(user.id)" class="delete-button">Delete</a>

</td>
</tr>
}
</table>
}
4 changes: 3 additions & 1 deletion play-scala-isolated-slick-example/app/views/main.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
<title>@title</title>
<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
<link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">

<script src="@routes.Assets.versioned("javascripts/hello.js")" type="text/javascript"></script>

</head>
<body>
@content
@content
</body>
</html>
16 changes: 16 additions & 0 deletions play-scala-isolated-slick-example/app/views/update.scala.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@(userForm: Form[UserRequest])(implicit messages: Messages)

@import views.html.helper._

@main("Update User") {

<h1>Update User</h1>

@form(routes.UserController.update(userForm("id").value.get)) {

@inputText(userForm("email"))

<button type="submit">Update</button>

}
}
14 changes: 12 additions & 2 deletions play-scala-isolated-slick-example/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET / controllers.HomeController.index
# User Routes

GET / controllers.UserController.index
GET /users/all controllers.UserController.findAll

GET /users/new controllers.UserController.create
POST /users controllers.UserController.save

GET /users/:id/edit controllers.UserController.edit(id: String)
POST /users/:id controllers.UserController.update(id: String)

GET /users/:id/delete controllers.UserController.delete(id: String)

# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@

//Disabling to allow posts from forms.
play.filters.disabled += play.filters.csrf.CSRFFilter

myapp = {
database = {
driver = org.h2.Driver
Expand Down
77 changes: 77 additions & 0 deletions play-scala-isolated-slick-example/public/stylesheets/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
body {
font-family: Arial, sans-serif;
}

table {
width: 100%;
border-collapse: collapse;
}

th, td {
border: 1px solid #ddd;
text-align: left;
padding: 8px;
}

th {
background-color: #f2f2f2;
}

tr:nth-child(even) {
background-color: #f9f9f9;
}

.add-button, .edit-button, .delete-button {
text-decoration: none;
padding: 5px 10px;
border-radius: 5px;
color: white;
margin-right: 5px;
}

.add-button {
background-color: #4CAF50; /* Green */
}

.edit-button {
background-color: #2196F3; /* Blue */
}

.delete-button {
background-color: #f44336; /* Red */
}

input[type="text"], input[type="email"], input[type="password"] {
width: calc(100% - 20px);
padding: 10px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}

button[type="submit"] {
width: 100%;
background-color: #4CAF50;
color: white;
padding: 14px 20px;
margin: 8px 0;
border: none;
border-radius: 4px;
cursor: pointer;
}

button[type="submit"]:hover {
background-color: #45a049;
}

form {
margin: auto;
width: 50%;
padding: 10px;
}

label[for="email"] {
display: none;
}
2 changes: 1 addition & 1 deletion play-scala-isolated-slick-example/scripts/test-sbt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ echo "+----------------------------+"
echo "| Executing tests using sbt |"
echo "+----------------------------+"
rm -f test.mv.db test.trace.db
sbt ++$MATRIX_SCALA clean flyway/flywayMigrate slickCodegen test
sbt ++$MATRIX_SCALA clean reload flyway/flywayMigrate slickCodegen test
Loading

0 comments on commit d7f1e04

Please sign in to comment.