Skip to content

Commit

Permalink
Upgrade to play3 (#5)
Browse files Browse the repository at this point in the history
* Upgraded to play 3 and scala 3, updated unit test mocks, reformatted all code with scalafmt

* migrated code using scala compiler

* upgraded scala and play

* upgraded to use newer CSP filter.
  • Loading branch information
chrisnappin authored Mar 12, 2024
1 parent fc9e201 commit e2a8feb
Show file tree
Hide file tree
Showing 15 changed files with 642 additions and 555 deletions.
21 changes: 15 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
/bin
/logs
/.bsp
/.settings
/target
/project/metals.sbt
/project/.bloop
/project/target
/project/project
target/
/.bloop
/.metals
/.vscode
/.settings
/.cache
/.classpath
/.project
.idea/
/logs
/.sbtserver/
/.cache-main
/.idea
/bin/
/.cache-tests
/.bsp
3 changes: 3 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
version = "3.7.15"
runner.dialect = scala3
maxColumn = 100
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ This is an example web application showing how to use the [play-recaptcha](https

![reCAPTCHA version 2](recaptcha-example-v2.png "reCAPTCHA version 2")

Please download the latest example code - [version 2.6](https://github.com/chrisnappin/play-recaptcha-v2-example/releases/tag/release-2.6) which works with [play-recaptcha v2.6](https://github.com/chrisnappin/play-recaptcha/releases/tag/release-2.6).
Please download the latest example code - [version 3.0](https://github.com/chrisnappin/play-recaptcha-v2-example/releases/tag/release-3.0) which works with [play-recaptcha v3.0](https://github.com/chrisnappin/play-recaptcha/releases/tag/release-3.0).

## License
The Play reCAPTCHA Module, and this example app, are copyright Chris Nappin, and are released under the [Apache 2 License](http://www.apache.org/licenses/LICENSE-2.0).
Expand Down
69 changes: 41 additions & 28 deletions app/controllers/ExampleForm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,44 +28,58 @@ import scala.concurrent.ExecutionContext

// case class used to bind data from the form
case class UserRegistration(username: String, email: Option[String], agree: Boolean)
object UserRegistration:
def unapply(u: UserRegistration): Option[(String, Option[String], Boolean)] =
Some(u.username, u.email, u.agree)

/**
* Example form with a recaptcha field.
/** Example form with a recaptcha field.
*
* @param formTemplate The form template to use
* @param nonceAction Adds a nonce to the request, and CSP header to the response
* @param verifier The recaptcha verifier to use
* @param cc The controller components to use
* @param executionContext The execution context used to run futures
* @param formTemplate
* The form template to use
* @param nonceAction
* Adds a nonce to the request, and CSP header to the response
* @param verifier
* The recaptcha verifier to use
* @param cc
* The controller components to use
* @param executionContext
* The execution context used to run futures
*/
class ExampleForm @Inject()(formTemplate: views.html.form, nonceAction: NonceActionBuilder,
verifier: RecaptchaVerifier, cc: ControllerComponents)(implicit executionContext: ExecutionContext)
extends AbstractController(cc) with I18nSupport {
class ExampleForm @Inject() (
formTemplate: views.html.form,
nonceAction: NonceActionBuilder,
verifier: RecaptchaVerifier,
cc: ControllerComponents
)(implicit executionContext: ExecutionContext)
extends AbstractController(cc)
with I18nSupport:

/** The logger to use. */
private val logger = Logger(this.getClass)

/** Form data mapping and validation. */
val userForm = Form(mapping(
"username" -> nonEmptyText,
"email" -> optional(email),
"agree" -> boolean
)(UserRegistration.apply)(UserRegistration.unapply))

/**
* Shows the form.
val userForm = Form(
mapping(
"username" -> nonEmptyText,
"email" -> optional(email),
"agree" -> boolean
)(UserRegistration.apply)(UserRegistration.unapply)
)

/** Shows the form.
*
* @return The form
* @return
* The form
*/
def show = nonceAction { implicit request: Request[AnyContent] =>
Ok(formTemplate(userForm))
}

/**
* Handles a form submission.
* @return The success redirect, or the form with error messages
/** Handles a form submission.
* @return
* The success redirect, or the form with error messages
*/
def submitForm = nonceAction.async { implicit request: Request[AnyContent] =>
def submitForm = nonceAction.async { implicit request: Request[AnyContent] =>
verifier.bindFromRequestAndVerify(userForm).map { form =>
form.fold(
// validation or captcha test failed
Expand Down Expand Up @@ -97,14 +111,13 @@ class ExampleForm @Inject()(formTemplate: views.html.form, nonceAction: NonceAct
}
}

/**
* Show the result message.
* @return The success result page
/** Show the result message.
* @return
* The success result page
*/
def result = Action { implicit request: Request[AnyContent] =>
def result = Action { implicit request: Request[AnyContent] =>
// success message after POST-Redirect-GET
// use flash scope to pass any once-off messages
Ok(views.html.result())
}

}
15 changes: 7 additions & 8 deletions app/controllers/Index.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,20 @@ import play.api.Logger
import play.api.i18n.I18nSupport
import play.api.mvc.{AbstractController, AnyContent, ControllerComponents, Request}

/**
* Renders the index page.
* @param cc The controller components to use
/** Renders the index page.
* @param cc
* The controller components to use
*/
class Index @Inject()(cc: ControllerComponents) extends AbstractController(cc) with I18nSupport {
class Index @Inject() (cc: ControllerComponents) extends AbstractController(cc) with I18nSupport:

/** The logger to use. */
private val logger: Logger = Logger(this.getClass)

/**
* Show the index page.
* @return The index page
/** Show the index page.
* @return
* The index page
*/
def index = Action { implicit request: Request[AnyContent] =>
logger.debug("accept languages are: " + request.acceptLanguages)
Ok(views.html.index())
}
}
132 changes: 77 additions & 55 deletions app/controllers/InvisibleForm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,91 +21,113 @@ import javax.inject.Inject
import play.api.Logger
import play.api.data.Form
import play.api.data.Forms._
import play.api.mvc.{AbstractController, AnyContent, ControllerComponents, MessagesActionBuilder, MessagesRequest}
import play.api.mvc.{
AbstractController,
AnyContent,
ControllerComponents,
MessagesActionBuilder,
MessagesRequest
}

import scala.concurrent.ExecutionContext

// case class used to bind data from the form
case class InvisibleUserRegistration(username: String, email: Option[String], agree: Boolean)
object InvisibleUserRegistration:
def unapply(i: InvisibleUserRegistration): Option[(String, Option[String], Boolean)] =
Some(i.username, i.email, i.agree)

/**
* Example form with an invisible recaptcha button.
/** Example form with an invisible recaptcha button.
*
* @param messagesAction Wraps the request with i18n messages
* @param nonceAction Adds a nonce to the request, and CSP header to the response
* @param formTemplate The form template to use
* @param verifier The recaptcha verifier to use
* @param cc The controller components
* @param executionContext The execution context used to run futures
* @param messagesAction
* Wraps the request with i18n messages
* @param nonceAction
* Adds a nonce to the request, and CSP header to the response
* @param formTemplate
* The form template to use
* @param verifier
* The recaptcha verifier to use
* @param cc
* The controller components
* @param executionContext
* The execution context used to run futures
*/
class InvisibleForm @Inject()(messagesAction: MessagesActionBuilder, nonceAction: NonceActionBuilder,
formTemplate: views.html.invisibleForm, verifier: RecaptchaVerifier, cc: ControllerComponents)(
implicit executionContext: ExecutionContext) extends AbstractController(cc) {
class InvisibleForm @Inject() (
messagesAction: MessagesActionBuilder,
nonceAction: NonceActionBuilder,
formTemplate: views.html.invisibleForm,
verifier: RecaptchaVerifier,
cc: ControllerComponents
)(implicit executionContext: ExecutionContext)
extends AbstractController(cc):

/** The logger to use. */
private val logger = Logger(this.getClass)

/** Form data mapping and validation. */
val userForm = Form(mapping(
"username" -> nonEmptyText,
"email" -> optional(email),
"agree" -> boolean
)(InvisibleUserRegistration.apply)(InvisibleUserRegistration.unapply))
val userForm = Form(
mapping(
"username" -> nonEmptyText,
"email" -> optional(email),
"agree" -> boolean
)(InvisibleUserRegistration.apply)(InvisibleUserRegistration.unapply)
)

/**
* Shows the form.
/** Shows the form.
*
* @return The form
* @return
* The form
*/
def show = nonceAction { messagesAction { implicit request: MessagesRequest[AnyContent] =>
Ok(formTemplate(userForm))
} }
def show = nonceAction:
messagesAction { implicit request: MessagesRequest[AnyContent] =>
Ok(formTemplate(userForm))
}

/**
* Handles a form submission.
/** Handles a form submission.
*
* @return The success redirect, or the form with error messages
* @return
* The success redirect, or the form with error messages
*/
def submitForm = nonceAction { messagesAction.async { implicit request: MessagesRequest[AnyContent] =>
verifier.bindFromRequestAndVerify(userForm).map { form =>
form.fold(
// validation or captcha test failed
errors => {
// re-renders the form, with validation error messages etc
logger.info("form validation or captcha test failed")
BadRequest(formTemplate(errors))
},
def submitForm = nonceAction:
messagesAction.async { implicit request: MessagesRequest[AnyContent] =>
verifier.bindFromRequestAndVerify(userForm).map { form =>
form.fold(
// validation or captcha test failed
errors => {
// re-renders the form, with validation error messages etc
logger.info("form validation or captcha test failed")
BadRequest(formTemplate(errors))
},

// validation and captcha test succeeded
success => {
logger.info("User is " + success)
// validation and captcha test succeeded
success => {
logger.info("User is " + success)

// TODO: process the validated form
// TODO: process the validated form

// only store simple message in flash
val saveMessage = "User " + success.username + " has been registered"
// only store simple message in flash
val saveMessage = "User " + success.username + " has been registered"

// use POST-Redirect-GET to avoid repeated form submissions on browser refresh
Redirect(routes.InvisibleForm.result).flashing("save.message" -> saveMessage)
// use POST-Redirect-GET to avoid repeated form submissions on browser refresh
Redirect(routes.InvisibleForm.result).flashing("save.message" -> saveMessage)

/*
* This process uses GET redirect to a page with a message passed via flash scope.
* Alternatives could be passing an id as request parameter to a page that then loads the data
* again, or saving the model object in cache then reading from that...
*/
}
)
/*
* This process uses GET redirect to a page with a message passed via flash scope.
* Alternatives could be passing an id as request parameter to a page that then loads the data
* again, or saving the model object in cache then reading from that...
*/
}
)
}
}
} }

/**
* Show the result message.
/** Show the result message.
*
* @return The success result page
* @return
* The success result page
*/
def result = messagesAction { implicit request: MessagesRequest[AnyContent] =>
// success message after POST-Redirect-GET
// use flash scope to pass any once-off messages
Ok(views.html.result())
}
}
Loading

0 comments on commit e2a8feb

Please sign in to comment.