├── chapter06 ├── data │ ├── .keep │ ├── manual.pdf │ ├── strategy.jpg │ └── multipart-message.data ├── project │ ├── build.properties │ ├── plugins.sbt │ └── build.scala ├── .gitignore └── src │ ├── main │ ├── webapp │ │ └── WEB-INF │ │ │ ├── layouts │ │ │ └── default.scaml │ │ │ ├── web.xml │ │ │ └── views │ │ │ └── index.scaml │ ├── scala │ │ ├── ScalatraBootstrap.scala │ │ └── org │ │ │ └── scalatra │ │ │ └── book │ │ │ └── chapter06 │ │ │ └── store.scala │ └── resources │ │ └── logback.xml │ └── test │ └── scala │ └── org │ └── scalatra │ └── book │ └── chapter06 │ └── FilesSpec.scala ├── chapter13 ├── src │ ├── main │ │ ├── webapp │ │ │ ├── img │ │ │ │ └── .gitkeep │ │ │ ├── WEB-INF │ │ │ │ ├── templates │ │ │ │ │ └── views │ │ │ │ │ │ ├── hackers │ │ │ │ │ │ ├── index.ssp │ │ │ │ │ │ ├── show.ssp │ │ │ │ │ │ └── new.ssp │ │ │ │ │ │ └── sessions │ │ │ │ │ │ └── new.ssp │ │ │ │ └── web.xml │ │ │ └── js │ │ │ │ └── foundation │ │ │ │ └── foundation.alerts.js │ │ ├── scala │ │ │ ├── com │ │ │ │ └── constructiveproof │ │ │ │ │ └── hackertracker │ │ │ │ │ ├── HackersSwagger.scala │ │ │ │ │ ├── stacks │ │ │ │ │ ├── HackerCoreStack.scala │ │ │ │ │ ├── ApiStack.scala │ │ │ │ │ └── BrowserStack.scala │ │ │ │ │ ├── auth │ │ │ │ │ ├── utils │ │ │ │ │ │ └── HmacUtils.scala │ │ │ │ │ ├── strategies │ │ │ │ │ │ └── OurBasicAuthStrategy.scala │ │ │ │ │ ├── AuthenticationSupport.scala │ │ │ │ │ └── OurBasicAuthenticationSupport.scala │ │ │ │ │ ├── init │ │ │ │ │ ├── DatabaseSessionSupport.scala │ │ │ │ │ └── DatabaseInit.scala │ │ │ │ │ ├── DatabaseSetupController.scala │ │ │ │ │ ├── SessionsController.scala │ │ │ │ │ ├── HackersController.scala │ │ │ │ │ └── models │ │ │ │ │ └── Models.scala │ │ │ └── ScalatraBootstrap.scala │ │ └── resources │ │ │ └── logback.xml │ └── test │ │ └── scala │ │ └── com │ │ └── constructiveproof │ │ └── hackertracker │ │ └── HackersControllerSpec.scala ├── project │ ├── build.properties │ └── plugins.sbt ├── README.md └── .gitignore ├── chapter01 ├── project │ ├── build.properties │ └── plugins.sbt ├── src │ ├── main │ │ ├── webapp │ │ │ └── WEB-INF │ │ │ │ ├── templates │ │ │ │ ├── views │ │ │ │ │ └── hello-scalate.jade │ │ │ │ └── layouts │ │ │ │ │ └── default.jade │ │ │ │ └── web.xml │ │ ├── scala │ │ │ ├── ScalatraBootstrap.scala │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── app │ │ │ │ ├── MyScalatraServlet.scala │ │ │ │ └── MyScalatraWebAppStack.scala │ │ └── resources │ │ │ └── logback.xml │ └── test │ │ └── scala │ │ └── com │ │ └── example │ │ └── app │ │ └── MyScalatraServletSpec.scala └── README.md ├── chapter03 ├── project │ ├── build.properties │ ├── plugins.sbt │ └── build.scala ├── src │ ├── main │ │ ├── webapp │ │ │ └── WEB-INF │ │ │ │ ├── views │ │ │ │ └── hello-scalate.scaml │ │ │ │ ├── layouts │ │ │ │ └── default.scaml │ │ │ │ └── web.xml │ │ ├── scala │ │ │ ├── org │ │ │ │ └── scalatra │ │ │ │ │ └── book │ │ │ │ │ └── chapter03 │ │ │ │ │ ├── Album.scala │ │ │ │ │ └── Artist.scala │ │ │ └── ScalatraBootstrap.scala │ │ └── resources │ │ │ └── logback.xml │ └── test │ │ └── scala │ │ └── org │ │ └── scalatra │ │ └── book │ │ └── chapter03 │ │ └── RecordStoreSpec.scala └── README.md ├── chapter05 ├── project │ ├── build.properties │ ├── plugins.sbt │ └── build.scala ├── .gitignore ├── README.md └── src │ ├── main │ ├── scala │ │ ├── ScalatraBootstrap.scala │ │ └── org │ │ │ └── scalatra │ │ │ └── book │ │ │ └── chapter06 │ │ │ ├── MyJsonpRoutes.scala │ │ │ ├── MyJsonApp.scala │ │ │ ├── recipes.scala │ │ │ ├── MyFoodRoutes.scala │ │ │ ├── json4s_basics.scala │ │ │ ├── MyJsonScalazRoutes.scala │ │ │ ├── MyJsonRoutes.scala │ │ │ └── json4s_custom_serializers.scala │ ├── resources │ │ └── logback.xml │ └── webapp │ │ └── WEB-INF │ │ └── web.xml │ └── test │ └── scala │ └── org │ └── scalatra │ └── book │ └── chapter06 │ └── MyJsonAppSpec.scala ├── chapter07 ├── project │ ├── build.properties │ ├── plugins.sbt │ └── build.scala ├── src │ └── main │ │ ├── webapp │ │ └── WEB-INF │ │ │ ├── layouts │ │ │ └── default.scaml │ │ │ ├── views │ │ │ ├── greeter_dry.scaml │ │ │ └── greeter.scaml │ │ │ └── web.xml │ │ ├── scala │ │ ├── ScalatraBootstrap.scala │ │ └── org │ │ │ └── scalatra │ │ │ └── book │ │ │ └── chapter07 │ │ │ ├── GreeterServlet.scala │ │ │ └── MyScalatraWebappStack.scala │ │ └── resources │ │ └── logback.xml └── README.md ├── chapter08 ├── project │ ├── build.properties │ ├── plugins.sbt │ └── build.scala ├── README.md └── src │ ├── main │ ├── scala │ │ ├── org │ │ │ └── scalatra │ │ │ │ └── book │ │ │ │ └── chapter08 │ │ │ │ ├── MyScalatraServlet.scala │ │ │ │ ├── FoodServlet.scala │ │ │ │ └── NukeLauncherServlet.scala │ │ └── ScalatraBootstrap.scala │ ├── resources │ │ └── logback.xml │ └── webapp │ │ └── WEB-INF │ │ └── web.xml │ └── test │ └── scala │ └── org │ └── scalatra │ └── book │ └── chapter08 │ ├── JsonBodySupport.scala │ ├── MyScalatraServletWordSpec.scala │ ├── MyScalatraServletSpec.scala │ ├── FoodServletWordSpec.scala │ ├── FoodServletSpec.scala │ └── NukeLauncherSpec.scala ├── chapter09 ├── project │ ├── build.properties │ └── plugins.sbt ├── src │ ├── main │ │ ├── webapp │ │ │ ├── static.txt │ │ │ └── WEB-INF │ │ │ │ ├── templates │ │ │ │ └── views │ │ │ │ │ └── hello-scalate.jade │ │ │ │ └── web.xml │ │ ├── resources │ │ │ ├── application.conf │ │ │ └── logback.xml │ │ └── scala │ │ │ ├── org │ │ │ └── scalatra │ │ │ │ └── book │ │ │ │ └── chapter09 │ │ │ │ ├── UrlShortener.scala │ │ │ │ ├── Chapter09.scala │ │ │ │ └── AppConfig.scala │ │ │ └── ScalatraBootstrap.scala │ └── test │ │ └── scala │ │ └── org │ │ └── scalatra │ │ └── book │ │ └── chapter09 │ │ └── Chapter09Spec.scala └── .gitignore ├── chapter02 ├── project │ ├── build.properties │ └── plugins.sbt ├── src │ ├── main │ │ ├── webapp │ │ │ ├── img │ │ │ │ ├── glyphicons-halflings.png │ │ │ │ └── glyphicons-halflings-white.png │ │ │ └── WEB-INF │ │ │ │ ├── templates │ │ │ │ ├── views │ │ │ │ │ └── pages │ │ │ │ │ │ └── show.ssp │ │ │ │ └── layouts │ │ │ │ │ └── default.ssp │ │ │ │ └── web.xml │ │ ├── scala │ │ │ ├── ScalatraBootstrap.scala │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── cms │ │ │ │ └── ScalatraCmsStack.scala │ │ └── resources │ │ │ └── logback.xml │ └── test │ │ └── scala │ │ └── com │ │ └── example │ │ └── cms │ │ └── PagesControllerSpec.scala ├── README.md └── .gitignore ├── chapter04 ├── project │ ├── build.properties │ └── plugins.sbt ├── src │ ├── main │ │ ├── webapp │ │ │ └── WEB-INF │ │ │ │ ├── templates │ │ │ │ ├── views │ │ │ │ │ └── hello-scalate.jade │ │ │ │ └── layouts │ │ │ │ │ └── default.jade │ │ │ │ └── web.xml │ │ ├── scala │ │ │ ├── ScalatraBootstrap.scala │ │ │ └── com │ │ │ │ └── constructiveproof │ │ │ │ └── hackertracker │ │ │ │ ├── CookiesExample.scala │ │ │ │ ├── GateController.scala │ │ │ │ └── HackerTrackerStack.scala │ │ └── resources │ │ │ └── logback.xml │ └── test │ │ └── scala │ │ └── com │ │ └── constructiveproof │ │ └── hackertracker │ │ └── HackersControllerSpec.scala ├── README.md └── .gitignore ├── chapter09-sbt ├── project │ ├── build.properties │ └── plugins.sbt ├── src │ ├── main │ │ ├── webapp │ │ │ ├── static.txt │ │ │ └── WEB-INF │ │ │ │ └── web.xml │ │ ├── resources │ │ │ ├── application.conf │ │ │ └── logback.xml │ │ └── scala │ │ │ ├── org │ │ │ └── scalatra │ │ │ │ └── book │ │ │ │ └── chapter09 │ │ │ │ ├── UrlShortener.scala │ │ │ │ ├── Chapter09.scala │ │ │ │ └── AppConfig.scala │ │ │ └── ScalatraBootstrap.scala │ └── test │ │ └── scala │ │ └── org │ │ └── scalatra │ │ └── book │ │ └── chapter09 │ │ └── Chapter09Spec.scala ├── build.sbt └── .gitignore ├── chapter10 ├── project │ ├── build.properties │ ├── plugins.sbt │ └── build.scala ├── .gitignore └── src │ ├── main │ ├── webapp │ │ └── WEB-INF │ │ │ ├── views │ │ │ ├── areas.jade │ │ │ ├── area.jade │ │ │ └── routes.jade │ │ │ ├── layouts │ │ │ └── default.jade │ │ │ └── web.xml │ ├── scala │ │ ├── org │ │ │ └── scalatra │ │ │ │ └── book │ │ │ │ └── chapter10 │ │ │ │ ├── domain.scala │ │ │ │ └── scalaz.scala │ │ └── ScalatraBootstrap.scala │ └── resources │ │ └── logback.xml │ └── test │ └── scala │ └── org │ └── scalatra │ └── book │ └── chapter10 │ └── AppSpec.scala ├── chapter12 ├── project │ ├── build.properties │ └── plugins.sbt ├── src │ ├── main │ │ ├── webapp │ │ │ └── WEB-INF │ │ │ │ ├── templates │ │ │ │ ├── views │ │ │ │ │ └── hello-scalate.jade │ │ │ │ └── layouts │ │ │ │ │ └── default.jade │ │ │ │ └── web.xml │ │ ├── resources │ │ │ └── logback.xml │ │ └── scala │ │ │ ├── com │ │ │ └── constructiveproof │ │ │ │ └── crawler │ │ │ │ ├── AkkaCrawler.scala │ │ │ │ ├── CrawlerStack.scala │ │ │ │ ├── actors │ │ │ │ └── GrabActor.scala │ │ │ │ ├── SparkExampleController.scala │ │ │ │ └── CrawlController.scala │ │ │ └── ScalatraBootstrap.scala │ └── test │ │ └── scala │ │ └── com │ │ └── constructiveproof │ │ └── crawler │ │ └── AkkaCrawlerSpec.scala ├── README.md └── .gitignore ├── chapter07-twirl ├── project │ ├── build.properties │ ├── plugins.sbt │ └── build.scala ├── README.md └── src │ ├── main │ ├── scala │ │ ├── ScalatraBootstrap.scala │ │ └── org │ │ │ └── scalatra │ │ │ └── book │ │ │ └── chapter07 │ │ │ └── GreeterServlet.scala │ ├── resources │ │ └── logback.xml │ ├── twirl │ │ └── greeting.scala.html │ └── webapp │ │ └── WEB-INF │ │ └── web.xml │ └── test │ └── scala │ └── org │ └── scalatra │ └── book │ └── chapter07 │ └── GreeterServletSpec.scala ├── chapter09-docker ├── src │ ├── main │ │ ├── webapp │ │ │ ├── static.txt │ │ │ └── WEB-INF │ │ │ │ ├── templates │ │ │ │ └── views │ │ │ │ │ └── hello-scalate.jade │ │ │ │ └── web.xml │ │ ├── scala │ │ │ ├── org │ │ │ │ └── scalatra │ │ │ │ │ └── book │ │ │ │ │ └── chapter09 │ │ │ │ │ ├── UrlShortener.scala │ │ │ │ │ └── Chapter09.scala │ │ │ ├── ScalatraBootstrap.scala │ │ │ └── ScalatraLauncher.scala │ │ └── resources │ │ │ ├── application.conf │ │ │ └── logback.xml │ └── test │ │ └── scala │ │ └── org │ │ └── scalatra │ │ └── book │ │ └── chapter09 │ │ └── Chapter09Spec.scala ├── project │ ├── build.properties │ └── plugins.sbt ├── stop.sh ├── README.md ├── start.sh ├── conf │ ├── application.conf │ └── logback.xml └── .gitignore ├── chapter09-sbtweb ├── src │ ├── main │ │ ├── public │ │ │ ├── static.txt │ │ │ ├── WEB-INF │ │ │ │ ├── templates │ │ │ │ │ └── views │ │ │ │ │ │ └── hello-scalate.jade │ │ │ │ └── web.xml │ │ │ └── index.html │ │ ├── assets │ │ │ └── css │ │ │ │ ├── bars │ │ │ │ └── bar.less │ │ │ │ └── main.less │ │ ├── resources │ │ │ ├── application.conf │ │ │ └── logback.xml │ │ └── scala │ │ │ ├── org │ │ │ └── scalatra │ │ │ │ └── book │ │ │ │ └── chapter09 │ │ │ │ ├── UrlShortener.scala │ │ │ │ ├── Chapter09.scala │ │ │ │ └── AppConfig.scala │ │ │ └── ScalatraBootstrap.scala │ └── test │ │ └── scala │ │ └── org │ │ └── scalatra │ │ └── book │ │ └── chapter09 │ │ └── Chapter09Spec.scala ├── project │ ├── build.properties │ └── plugins.sbt ├── README.md └── .gitignore ├── chapter09-standalone ├── project │ ├── build.properties │ └── plugins.sbt ├── src │ ├── main │ │ ├── webapp │ │ │ ├── static.txt │ │ │ └── WEB-INF │ │ │ │ ├── templates │ │ │ │ └── views │ │ │ │ │ └── hello-scalate.jade │ │ │ │ └── web.xml │ │ ├── scala │ │ │ ├── org │ │ │ │ └── scalatra │ │ │ │ │ └── book │ │ │ │ │ └── chapter09 │ │ │ │ │ ├── UrlShortener.scala │ │ │ │ │ └── Chapter09.scala │ │ │ ├── ScalatraBootstrap.scala │ │ │ └── ScalatraLauncher.scala │ │ └── resources │ │ │ ├── application.conf │ │ │ ├── logback.xml │ │ │ └── logback.production.xml │ └── test │ │ └── scala │ │ └── org │ │ └── scalatra │ │ └── book │ │ └── chapter09 │ │ └── Chapter09Spec.scala ├── README.md └── .gitignore ├── comments-collector ├── project │ ├── build.properties │ └── plugins.sbt ├── src │ ├── main │ │ ├── webapp │ │ │ ├── css │ │ │ │ └── site.css │ │ │ └── WEB-INF │ │ │ │ ├── web.xml │ │ │ │ ├── views │ │ │ │ ├── comments.scaml │ │ │ │ └── index.scaml │ │ │ │ └── layouts │ │ │ │ └── default.scaml │ │ ├── scala │ │ │ ├── comments │ │ │ │ ├── scalaz.scala │ │ │ │ ├── data.scala │ │ │ │ └── frontend.scala │ │ │ └── ScalatraBootstrap.scala │ │ └── resources │ │ │ └── logback.xml │ └── test │ │ └── scala │ │ └── comments │ │ └── CommentsServletSpec.scala ├── README.md └── .gitignore ├── chapter11 ├── 1-hacker-tracker-unprotected │ ├── src │ │ ├── main │ │ │ ├── webapp │ │ │ │ ├── img │ │ │ │ │ └── .gitkeep │ │ │ │ ├── WEB-INF │ │ │ │ │ ├── templates │ │ │ │ │ │ └── views │ │ │ │ │ │ │ └── hackers │ │ │ │ │ │ │ ├── index.ssp │ │ │ │ │ │ │ ├── show.ssp │ │ │ │ │ │ │ └── new.ssp │ │ │ │ │ └── web.xml │ │ │ │ └── js │ │ │ │ │ └── foundation │ │ │ │ │ └── foundation.alerts.js │ │ │ ├── resources │ │ │ │ └── logback.xml │ │ │ └── scala │ │ │ │ ├── ScalatraBootstrap.scala │ │ │ │ └── com │ │ │ │ └── constructiveproof │ │ │ │ └── hackertracker │ │ │ │ ├── init │ │ │ │ ├── DatabaseSessionSupport.scala │ │ │ │ └── DatabaseInit.scala │ │ │ │ ├── DatabaseSetupController.scala │ │ │ │ ├── HackerTrackerStack.scala │ │ │ │ ├── HackersController.scala │ │ │ │ └── models │ │ │ │ └── Models.scala │ │ └── test │ │ │ └── scala │ │ │ └── com │ │ │ └── constructiveproof │ │ │ └── hackertracker │ │ │ └── HackersControllerSpec.scala │ ├── project │ │ ├── build.properties │ │ └── plugins.sbt │ ├── README.md │ └── .gitignore └── 2-hacker-tracker-protected │ ├── src │ ├── main │ │ ├── webapp │ │ │ ├── img │ │ │ │ └── .gitkeep │ │ │ ├── WEB-INF │ │ │ │ ├── templates │ │ │ │ │ └── views │ │ │ │ │ │ ├── hackers │ │ │ │ │ │ ├── index.ssp │ │ │ │ │ │ ├── show.ssp │ │ │ │ │ │ └── new.ssp │ │ │ │ │ │ └── sessions │ │ │ │ │ │ └── new.ssp │ │ │ │ └── web.xml │ │ │ └── js │ │ │ │ └── foundation │ │ │ │ └── foundation.alerts.js │ │ ├── resources │ │ │ └── logback.xml │ │ └── scala │ │ │ ├── ScalatraBootstrap.scala │ │ │ └── com │ │ │ └── constructiveproof │ │ │ └── hackertracker │ │ │ ├── init │ │ │ ├── DatabaseSessionSupport.scala │ │ │ └── DatabaseInit.scala │ │ │ ├── auth │ │ │ ├── strategies │ │ │ │ └── OurBasicAuthStrategy.scala │ │ │ ├── AuthenticationSupport.scala │ │ │ └── OurBasicAuthenticationSupport.scala │ │ │ ├── HackerTrackerStack.scala │ │ │ ├── DatabaseSetupController.scala │ │ │ ├── SessionsController.scala │ │ │ └── models │ │ │ └── Models.scala │ └── test │ │ └── scala │ │ └── com │ │ └── constructiveproof │ │ └── hackertracker │ │ └── HackersControllerSpec.scala │ ├── project │ ├── build.properties │ └── plugins.sbt │ ├── README.md │ └── .gitignore ├── .gitignore └── README.md /chapter06/data/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chapter13/src/main/webapp/img/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /chapter01/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 -------------------------------------------------------------------------------- /chapter03/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 -------------------------------------------------------------------------------- /chapter05/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 -------------------------------------------------------------------------------- /chapter06/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 -------------------------------------------------------------------------------- /chapter07/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 -------------------------------------------------------------------------------- /chapter08/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 -------------------------------------------------------------------------------- /chapter09/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 -------------------------------------------------------------------------------- /chapter02/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | -------------------------------------------------------------------------------- /chapter04/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | -------------------------------------------------------------------------------- /chapter09-sbt/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 -------------------------------------------------------------------------------- /chapter09/src/main/webapp/static.txt: -------------------------------------------------------------------------------- 1 | this is static text! -------------------------------------------------------------------------------- /chapter10/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | -------------------------------------------------------------------------------- /chapter12/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | -------------------------------------------------------------------------------- /chapter13/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | -------------------------------------------------------------------------------- /chapter07-twirl/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | -------------------------------------------------------------------------------- /chapter09-docker/src/main/webapp/static.txt: -------------------------------------------------------------------------------- 1 | this is static text! -------------------------------------------------------------------------------- /chapter09-sbt/src/main/webapp/static.txt: -------------------------------------------------------------------------------- 1 | this is static text! -------------------------------------------------------------------------------- /chapter09-sbtweb/src/main/public/static.txt: -------------------------------------------------------------------------------- 1 | this is static text! -------------------------------------------------------------------------------- /chapter09-docker/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | -------------------------------------------------------------------------------- /chapter09-sbtweb/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | -------------------------------------------------------------------------------- /chapter09-standalone/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | -------------------------------------------------------------------------------- /chapter09-standalone/src/main/webapp/static.txt: -------------------------------------------------------------------------------- 1 | this is static text! -------------------------------------------------------------------------------- /comments-collector/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/src/main/webapp/img/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/webapp/img/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /chapter05/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | target/ 3 | /.lib/ 4 | .idea 5 | .idea_modules -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 -------------------------------------------------------------------------------- /chapter05/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") -------------------------------------------------------------------------------- /chapter09-sbtweb/src/main/assets/css/bars/bar.less: -------------------------------------------------------------------------------- 1 | .fnord { 2 | font-size: 42pt; 3 | } -------------------------------------------------------------------------------- /chapter09/src/main/webapp/WEB-INF/templates/views/hello-scalate.jade: -------------------------------------------------------------------------------- 1 | p= "Hello, Scalate!" -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | -------------------------------------------------------------------------------- /chapter06/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | project/project 3 | project/target 4 | 5 | .idea 6 | 7 | -------------------------------------------------------------------------------- /chapter08/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 2 | -------------------------------------------------------------------------------- /chapter10/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | project/project 3 | project/target 4 | 5 | .idea 6 | 7 | -------------------------------------------------------------------------------- /chapter10/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 2 | -------------------------------------------------------------------------------- /chapter09-docker/src/main/webapp/WEB-INF/templates/views/hello-scalate.jade: -------------------------------------------------------------------------------- 1 | p= "Hello, Scalate!" -------------------------------------------------------------------------------- /chapter09-standalone/src/main/webapp/WEB-INF/templates/views/hello-scalate.jade: -------------------------------------------------------------------------------- 1 | p= "Hello, Scalate!" -------------------------------------------------------------------------------- /chapter06/data/manual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalatra/scalatra-in-action/HEAD/chapter06/data/manual.pdf -------------------------------------------------------------------------------- /chapter06/data/strategy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalatra/scalatra-in-action/HEAD/chapter06/data/strategy.jpg -------------------------------------------------------------------------------- /chapter09-docker/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker stop chapter09-standalone 4 | 5 | docker rm chapter09-standalone 6 | 7 | -------------------------------------------------------------------------------- /chapter09-sbtweb/src/main/assets/css/main.less: -------------------------------------------------------------------------------- 1 | @import "bars/bar"; 2 | 3 | .foo { 4 | .bar { 5 | color: blue; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /chapter07-twirl/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 2 | 3 | addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.4") 4 | -------------------------------------------------------------------------------- /chapter02/src/main/webapp/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalatra/scalatra-in-action/HEAD/chapter02/src/main/webapp/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /chapter01/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.5.0") 2 | 3 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 4 | -------------------------------------------------------------------------------- /chapter02/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.5.0") 2 | 3 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 4 | -------------------------------------------------------------------------------- /chapter03/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.5.0") 2 | 3 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 4 | -------------------------------------------------------------------------------- /chapter04/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.5.0") 2 | 3 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 4 | -------------------------------------------------------------------------------- /chapter06/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.5.0") 2 | 3 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 4 | -------------------------------------------------------------------------------- /chapter07/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.5.0") 2 | 3 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 4 | -------------------------------------------------------------------------------- /chapter12/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.5.0") 2 | 3 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 4 | -------------------------------------------------------------------------------- /chapter13/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.5.0") 2 | 3 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 4 | -------------------------------------------------------------------------------- /chapter02/src/main/webapp/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalatra/scalatra-in-action/HEAD/chapter02/src/main/webapp/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /chapter09-sbt/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.5.0") 2 | 3 | addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.0.4") 4 | -------------------------------------------------------------------------------- /comments-collector/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.4.2") 2 | 3 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 4 | -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.5.0") 2 | 3 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 4 | -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.5.0") 2 | 3 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 4 | -------------------------------------------------------------------------------- /chapter03/src/main/webapp/WEB-INF/views/hello-scalate.scaml: -------------------------------------------------------------------------------- 1 | - attributes("title") = "Scalatra: a tiny, Sinatra-like web framework for Scala" 2 | - attributes("headline") = "Welcome to Scalatra" 3 | 4 | Hello, Scalate! -------------------------------------------------------------------------------- /chapter05/README.md: -------------------------------------------------------------------------------- 1 | # Sample code chapter 5 # 2 | 3 | ## Build & Run ## 4 | 5 | ```sh 6 | $ sbt 7 | > container:start 8 | ``` 9 | 10 | Open [http://localhost:8080/](http://localhost:8080/) in your browser. 11 | -------------------------------------------------------------------------------- /chapter01/src/main/webapp/WEB-INF/templates/views/hello-scalate.jade: -------------------------------------------------------------------------------- 1 | - attributes("title") = "Scalatra: a tiny, Sinatra-like web framework for Scala" 2 | - attributes("headline") = "Welcome to Scalatra" 3 | 4 | p= "Hello, Scalate!" -------------------------------------------------------------------------------- /chapter04/src/main/webapp/WEB-INF/templates/views/hello-scalate.jade: -------------------------------------------------------------------------------- 1 | - attributes("title") = "Scalatra: a tiny, Sinatra-like web framework for Scala" 2 | - attributes("headline") = "Welcome to Scalatra" 3 | 4 | p= "Hello, Scalate!" -------------------------------------------------------------------------------- /chapter12/src/main/webapp/WEB-INF/templates/views/hello-scalate.jade: -------------------------------------------------------------------------------- 1 | - attributes("title") = "Scalatra: a tiny, Sinatra-like web framework for Scala" 2 | - attributes("headline") = "Welcome to Scalatra" 3 | 4 | p= "Hello, Scalate!" -------------------------------------------------------------------------------- /chapter09-sbtweb/src/main/public/WEB-INF/templates/views/hello-scalate.jade: -------------------------------------------------------------------------------- 1 | - attributes("title") = "Scalatra: a tiny, Sinatra-like web framework for Scala" 2 | - attributes("headline") = "Welcome to Scalatra" 3 | 4 | p= "Hello, Scalate!" 5 | -------------------------------------------------------------------------------- /chapter09-docker/README.md: -------------------------------------------------------------------------------- 1 | # Sample code chapter 9 # 2 | 3 | ## Build & Run ## 4 | 5 | ```sh 6 | $ sbt 7 | > container:start 8 | > ~web-stage 9 | ``` 10 | 11 | Open [http://localhost:8080/](http://localhost:8080/) in your browser. 12 | -------------------------------------------------------------------------------- /chapter09-sbtweb/README.md: -------------------------------------------------------------------------------- 1 | # Sample code chapter 9 # 2 | 3 | ## Build & Run ## 4 | 5 | ```sh 6 | $ sbt 7 | > container:start 8 | > ~web-stage 9 | ``` 10 | 11 | Open [http://localhost:8080/](http://localhost:8080/) in your browser. 12 | -------------------------------------------------------------------------------- /chapter09-standalone/README.md: -------------------------------------------------------------------------------- 1 | # Sample code chapter 9 # 2 | 3 | ## Build & Run ## 4 | 5 | ```sh 6 | $ sbt 7 | > container:start 8 | > ~web-stage 9 | ``` 10 | 11 | Open [http://localhost:8080/](http://localhost:8080/) in your browser. 12 | -------------------------------------------------------------------------------- /chapter09/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.5.0") 2 | 3 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 4 | 5 | addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.0.4") 6 | 7 | -------------------------------------------------------------------------------- /chapter07/src/main/webapp/WEB-INF/layouts/default.scaml: -------------------------------------------------------------------------------- 1 | !!! 5 2 | -@ val body: String 3 | -@ val title: String = "foo" 4 | %html 5 | %head 6 | %link(type="text/css" href="/css/style.css" rel="stylesheet") 7 | %title= title 8 | %body 9 | != body -------------------------------------------------------------------------------- /chapter09-standalone/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.5.0") 2 | 3 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 4 | 5 | addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.0.4") 6 | 7 | -------------------------------------------------------------------------------- /chapter03/src/main/webapp/WEB-INF/layouts/default.scaml: -------------------------------------------------------------------------------- 1 | -@ val title: String 2 | -@ val headline: String = title 3 | -@ val body: String 4 | 5 | !!! 6 | %html 7 | %head 8 | %title= title 9 | %body 10 | #content 11 | %h1= headline 12 | != body 13 | -------------------------------------------------------------------------------- /chapter01/src/main/webapp/WEB-INF/templates/layouts/default.jade: -------------------------------------------------------------------------------- 1 | -@ val title: String 2 | -@ val headline: String = title 3 | -@ val body: String 4 | 5 | !!! 6 | html 7 | head 8 | title= title 9 | body 10 | #content 11 | h1= headline 12 | != body 13 | -------------------------------------------------------------------------------- /chapter03/README.md: -------------------------------------------------------------------------------- 1 | # Sample code chapter 5 # 2 | 3 | ## Build & Run ## 4 | 5 | ```sh 6 | $ sbt 7 | > container:start 8 | > browse 9 | ``` 10 | 11 | If `browse` doesn't launch your browser, manually open [http://localhost:8080/](http://localhost:8080/) in your browser. 12 | -------------------------------------------------------------------------------- /chapter04/src/main/webapp/WEB-INF/templates/layouts/default.jade: -------------------------------------------------------------------------------- 1 | -@ val title: String 2 | -@ val headline: String = title 3 | -@ val body: String 4 | 5 | !!! 6 | html 7 | head 8 | title= title 9 | body 10 | #content 11 | h1= headline 12 | != body 13 | -------------------------------------------------------------------------------- /chapter07/README.md: -------------------------------------------------------------------------------- 1 | # Sample code chapter 5 # 2 | 3 | ## Build & Run ## 4 | 5 | ```sh 6 | $ sbt 7 | > container:start 8 | > browse 9 | ``` 10 | 11 | If `browse` doesn't launch your browser, manually open [http://localhost:8080/](http://localhost:8080/) in your browser. 12 | -------------------------------------------------------------------------------- /chapter08/README.md: -------------------------------------------------------------------------------- 1 | # Sample code chapter 5 # 2 | 3 | ## Build & Run ## 4 | 5 | ```sh 6 | $ sbt 7 | > container:start 8 | > browse 9 | ``` 10 | 11 | If `browse` doesn't launch your browser, manually open [http://localhost:8080/](http://localhost:8080/) in your browser. 12 | -------------------------------------------------------------------------------- /chapter09-docker/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE=$(dirname $(readlink -f $0)) 4 | 5 | docker run -d \ 6 | -v $BASE/data:/app/data \ 7 | -v $BASE/conf:/app/conf:ro \ 8 | -p 8080:80 \ 9 | --name chapter09-standalone \ 10 | org.scalatra/chapter09-docker 11 | 12 | -------------------------------------------------------------------------------- /chapter12/src/main/webapp/WEB-INF/templates/layouts/default.jade: -------------------------------------------------------------------------------- 1 | -@ val title: String 2 | -@ val headline: String = title 3 | -@ val body: String 4 | 5 | !!! 6 | html 7 | head 8 | title= title 9 | body 10 | #content 11 | h1= headline 12 | != body 13 | -------------------------------------------------------------------------------- /chapter07-twirl/README.md: -------------------------------------------------------------------------------- 1 | # Sample code chapter 5 # 2 | 3 | ## Build & Run ## 4 | 5 | ```sh 6 | $ sbt 7 | > container:start 8 | > browse 9 | ``` 10 | 11 | If `browse` doesn't launch your browser, manually open [http://localhost:8080/](http://localhost:8080/) in your browser. 12 | -------------------------------------------------------------------------------- /chapter02/src/main/webapp/WEB-INF/templates/views/pages/show.ssp: -------------------------------------------------------------------------------- 1 | <%@ import val page: com.example.cms.Page %> 2 | 3 |
4 |
5 |

<%= page.title %>

6 |

<%= page.summary %>

7 |

<%= page.body %>

8 |
9 |
-------------------------------------------------------------------------------- /chapter06/data/multipart-message.data: -------------------------------------------------------------------------------- 1 | 2 | --a93f5485f279c0 3 | content-disposition: form-data; name="sample"; filename="foobar.txt" 4 | 5 | FOOBAZ 6 | --a93f5485f279c0 7 | content-disposition: form-data; name="description" 8 | 9 | A document about foos. 10 | --a93f5485f279c0-- 11 | -------------------------------------------------------------------------------- /chapter13/src/main/webapp/WEB-INF/templates/views/hackers/index.ssp: -------------------------------------------------------------------------------- 1 | <% import com.constructiveproof.hackertracker.models.Hacker %> 2 | <% import org.squeryl.Query %> 3 | <%@ val allHackers:Query[Hacker] %> 4 | 5 | #for(hacker <- allHackers.toList) 6 |

<%= hacker.firstName %>

7 | #end -------------------------------------------------------------------------------- /chapter02/README.md: -------------------------------------------------------------------------------- 1 | # Scalatra CMS # 2 | 3 | ## Build & Run ## 4 | 5 | ```sh 6 | $ cd Scalatra_CMS 7 | $ ./sbt 8 | > container:start 9 | > browse 10 | ``` 11 | 12 | If `browse` doesn't launch your browser, manually open [http://localhost:8080/](http://localhost:8080/) in your browser. 13 | -------------------------------------------------------------------------------- /chapter04/README.md: -------------------------------------------------------------------------------- 1 | # Hacker Tracker # 2 | 3 | ## Build & Run ## 4 | 5 | ```sh 6 | $ cd Hacker_Tracker 7 | $ ./sbt 8 | > container:start 9 | > browse 10 | ``` 11 | 12 | If `browse` doesn't launch your browser, manually open [http://localhost:8080/](http://localhost:8080/) in your browser. 13 | -------------------------------------------------------------------------------- /chapter12/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 12 - Crawler # 2 | 3 | ## Build & Run ## 4 | 5 | ```sh 6 | $ cd chapter12 7 | $ ./sbt 8 | > container:start 9 | > browse 10 | ``` 11 | 12 | If `browse` doesn't launch your browser, manually open [http://localhost:8080/](http://localhost:8080/) in your browser. 13 | -------------------------------------------------------------------------------- /chapter13/README.md: -------------------------------------------------------------------------------- 1 | # Hacker Tracker # 2 | 3 | ## Build & Run ## 4 | 5 | ```sh 6 | $ cd Hacker_Tracker 7 | $ ./sbt 8 | > container:start 9 | > browse 10 | ``` 11 | 12 | If `browse` doesn't launch your browser, manually open [http://localhost:8080/](http://localhost:8080/) in your browser. 13 | -------------------------------------------------------------------------------- /chapter01/README.md: -------------------------------------------------------------------------------- 1 | # My Scalatra Web App # 2 | 3 | ## Build & Run ## 4 | 5 | ```sh 6 | $ cd My_Scalatra_Web_App 7 | $ ./sbt 8 | > container:start 9 | > browse 10 | ``` 11 | 12 | If `browse` doesn't launch your browser, manually open [http://localhost:8080/](http://localhost:8080/) in your browser. 13 | -------------------------------------------------------------------------------- /chapter09-sbtweb/src/main/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello Scalatra 6 | 7 | 8 | 9 | 10 |

Hello, world!

11 | 12 | 13 | -------------------------------------------------------------------------------- /chapter01/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import com.example.app._ 2 | import org.scalatra._ 3 | import javax.servlet.ServletContext 4 | 5 | class ScalatraBootstrap extends LifeCycle { 6 | override def init(context: ServletContext) { 7 | context.mount(new MyScalatraServlet, "/*") 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter02/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import com.example.cms._ 2 | import org.scalatra._ 3 | import javax.servlet.ServletContext 4 | 5 | class ScalatraBootstrap extends LifeCycle { 6 | override def init(context: ServletContext) { 7 | context.mount(new PagesController, "/*") 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/webapp/WEB-INF/templates/views/hackers/index.ssp: -------------------------------------------------------------------------------- 1 | <% import com.constructiveproof.hackertracker.models.Hacker %> 2 | <% import org.squeryl.Query %> 3 | <%@ val allHackers:Query[Hacker] %> 4 | 5 | #for(hacker <- allHackers.toList) 6 |

<%= hacker.firstName %>

7 | #end -------------------------------------------------------------------------------- /chapter03/src/main/scala/org/scalatra/book/chapter03/Album.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter03 2 | 3 | import scala.xml.Node 4 | import scala.collection.concurrent.TrieMap 5 | 6 | object Albums { 7 | def findByName(artist: String, name: String) = 8 | "Optional exercise: extend model to albums" 9 | } 10 | -------------------------------------------------------------------------------- /chapter09-docker/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.5.0") 2 | 3 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 4 | 5 | addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.0.4") 6 | 7 | addSbtPlugin("se.marcuslonnberg" % "sbt-docker" % "1.2.0") 8 | 9 | -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/src/main/webapp/WEB-INF/templates/views/hackers/index.ssp: -------------------------------------------------------------------------------- 1 | <% import com.constructiveproof.hackertracker.models.Hacker %> 2 | <% import org.squeryl.Query %> 3 | <%@ val allHackers:Query[Hacker] %> 4 | 5 | #for(hacker <- allHackers.toList) 6 |

<%= hacker.firstName %>

7 | #end -------------------------------------------------------------------------------- /chapter07/src/main/webapp/WEB-INF/views/greeter_dry.scaml: -------------------------------------------------------------------------------- 1 | -@ val whom: String 2 | -@ val lucky: List[Int] 3 | - attributes("title") = "Hello, "+whom 4 | 5 | %h1 Congratulations 6 | %p You've created your first Scalate view, #{whom}. 7 | %p Your lucky numbers are: 8 | %ul 9 | - for (number <- lucky) 10 | %li #{number} -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/README.md: -------------------------------------------------------------------------------- 1 | # Hacker Tracker # 2 | 3 | ## Build & Run ## 4 | 5 | ```sh 6 | $ cd Hacker_Tracker 7 | $ ./sbt 8 | > container:start 9 | > browse 10 | ``` 11 | 12 | If `browse` doesn't launch your browser, manually open [http://localhost:8080/](http://localhost:8080/) in your browser. 13 | -------------------------------------------------------------------------------- /chapter07/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import org.scalatra._ 2 | import javax.servlet.ServletContext 3 | 4 | import org.scalatra.book.chapter07._ 5 | 6 | class ScalatraBootstrap extends LifeCycle { 7 | override def init(context: ServletContext) { 8 | context.mount(new GreeterServlet, "/*") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/README.md: -------------------------------------------------------------------------------- 1 | # Hacker Tracker # 2 | 3 | ## Build & Run ## 4 | 5 | ```sh 6 | $ cd Hacker_Tracker 7 | $ ./sbt 8 | > container:start 9 | > browse 10 | ``` 11 | 12 | If `browse` doesn't launch your browser, manually open [http://localhost:8080/](http://localhost:8080/) in your browser. 13 | -------------------------------------------------------------------------------- /chapter05/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import org.scalatra._ 2 | import javax.servlet.ServletContext 3 | 4 | import org.scalatra.book.chapter06.MyJsonApp 5 | 6 | class ScalatraBootstrap extends LifeCycle { 7 | override def init(context: ServletContext) { 8 | context.mount(new MyJsonApp, "/*") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /chapter05/src/main/scala/org/scalatra/book/chapter06/MyJsonpRoutes.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter06 2 | 3 | import org.scalatra._ 4 | import org.scalatra.json._ 5 | 6 | trait MyJsonpRoutes extends ScalatraBase with JacksonJsonSupport { 7 | 8 | override def jsonpCallbackParameterNames = Seq("jsonp") 9 | 10 | } 11 | -------------------------------------------------------------------------------- /chapter07-twirl/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import org.scalatra._ 2 | import javax.servlet.ServletContext 3 | 4 | import org.scalatra.book.chapter07._ 5 | 6 | class ScalatraBootstrap extends LifeCycle { 7 | override def init(context: ServletContext) { 8 | context.mount(new GreeterServlet, "/*") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /chapter09/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | 2 | webBase = "http://dev.example.org" 3 | 4 | // one of: development, test, staging, production 5 | environment = "staging" 6 | 7 | email { 8 | user = "user@example.com" 9 | password = "mypassword" 10 | host = "smtp.example.com" 11 | sender = "User " 12 | } -------------------------------------------------------------------------------- /chapter09-sbt/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | 2 | webBase = "http://dev.example.org" 3 | 4 | // one of: development, test, staging, production 5 | environment = "staging" 6 | 7 | email { 8 | user = "user@example.com" 9 | password = "mypassword" 10 | host = "smtp.example.com" 11 | sender = "User " 12 | } -------------------------------------------------------------------------------- /chapter09-sbtweb/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | 2 | webBase = "http://dev.example.org" 3 | 4 | // one of: development, test, staging, production 5 | environment = "staging" 6 | 7 | email { 8 | user = "user@example.com" 9 | password = "mypassword" 10 | host = "smtp.example.com" 11 | sender = "User " 12 | } -------------------------------------------------------------------------------- /chapter13/src/main/scala/com/constructiveproof/hackertracker/HackersSwagger.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker 2 | 3 | import org.scalatra.ScalatraServlet 4 | import org.scalatra.swagger.{JacksonSwaggerBase, Swagger} 5 | 6 | class HackersSwagger(implicit val swagger: Swagger) 7 | extends ScalatraServlet with JacksonSwaggerBase -------------------------------------------------------------------------------- /comments-collector/src/main/webapp/css/site.css: -------------------------------------------------------------------------------- 1 | 2 | @media (min-width: 768px) { 3 | .container { 4 | max-width: 730px; 5 | } 6 | } 7 | 8 | .header { 9 | border-bottom: 1px solid #e5e5e5; 10 | margin-bottom: 2em; 11 | } 12 | 13 | .comment span.title { 14 | font-weight: bold; 15 | text-decoration: underline; 16 | } 17 | -------------------------------------------------------------------------------- /chapter03/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import org.scalatra._ 2 | import javax.servlet.ServletContext 3 | 4 | import org.scalatra.book.chapter03.RecordStore 5 | 6 | class ScalatraBootstrap extends LifeCycle { 7 | override def init(context: ServletContext) { 8 | context.mount(new RecordStore("/srv/media"), "/*") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /chapter09/src/main/scala/org/scalatra/book/chapter09/UrlShortener.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | object UrlShortener { 4 | val chars = ('a' to 'z') ++ ('A' to 'Z') ++ ('0' to '9') 5 | def randChar = chars(scala.util.Random.nextInt(chars.size)) 6 | 7 | def nextFreeToken = (1 to 8).foldLeft("")((acc, _) => acc + randChar) 8 | } 9 | -------------------------------------------------------------------------------- /chapter08/src/main/scala/org/scalatra/book/chapter08/MyScalatraServlet.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter08 2 | 3 | import org.scalatra.ScalatraServlet 4 | 5 | class MyScalatraServlet extends ScalatraServlet { 6 | get("/") { 7 | 8 | 9 |

Hi, world!

10 | 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /chapter09-sbt/src/main/scala/org/scalatra/book/chapter09/UrlShortener.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | object UrlShortener { 4 | val chars = ('a' to 'z') ++ ('A' to 'Z') ++ ('0' to '9') 5 | def randChar = chars(scala.util.Random.nextInt(chars.size)) 6 | 7 | def nextFreeToken = (1 to 8).foldLeft("")((acc, _) => acc + randChar) 8 | } 9 | -------------------------------------------------------------------------------- /chapter09-docker/src/main/scala/org/scalatra/book/chapter09/UrlShortener.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | object UrlShortener { 4 | val chars = ('a' to 'z') ++ ('A' to 'Z') ++ ('0' to '9') 5 | def randChar = chars(scala.util.Random.nextInt(chars.size)) 6 | 7 | def nextFreeToken = (1 to 8).foldLeft("")((acc, _) => acc + randChar) 8 | } 9 | -------------------------------------------------------------------------------- /chapter09-sbtweb/src/main/scala/org/scalatra/book/chapter09/UrlShortener.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | object UrlShortener { 4 | val chars = ('a' to 'z') ++ ('A' to 'Z') ++ ('0' to '9') 5 | def randChar = chars(scala.util.Random.nextInt(chars.size)) 6 | 7 | def nextFreeToken = (1 to 8).foldLeft("")((acc, _) => acc + randChar) 8 | } 9 | -------------------------------------------------------------------------------- /chapter09-standalone/src/main/scala/org/scalatra/book/chapter09/UrlShortener.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | object UrlShortener { 4 | val chars = ('a' to 'z') ++ ('A' to 'Z') ++ ('0' to '9') 5 | def randChar = chars(scala.util.Random.nextInt(chars.size)) 6 | 7 | def nextFreeToken = (1 to 8).foldLeft("")((acc, _) => acc + randChar) 8 | } 9 | -------------------------------------------------------------------------------- /chapter13/src/main/webapp/WEB-INF/templates/views/hackers/show.ssp: -------------------------------------------------------------------------------- 1 | <% import com.constructiveproof.hackertracker.models.Hacker %> 2 | <% import org.squeryl.Query %> 3 | 4 | <%@ val hacker:Hacker %> 5 | 6 | 7 |

<%= hacker.firstName %> <%= hacker.lastName %> born <%= hacker.birthYear.toString %>

8 | 9 |

“<%= hacker.motto %>”

-------------------------------------------------------------------------------- /chapter08/src/test/scala/org/scalatra/book/chapter08/JsonBodySupport.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter08 2 | 3 | import org.scalatra.test._ 4 | import org.scalatra.test.specs2._ 5 | 6 | import org.json4s.JValue 7 | import org.json4s.jackson.JsonMethods 8 | 9 | trait JsonBodySupport { self: ScalatraTests => 10 | def jsonBody: JValue = JsonMethods.parse(body) 11 | } 12 | -------------------------------------------------------------------------------- /chapter06/src/main/webapp/WEB-INF/layouts/default.scaml: -------------------------------------------------------------------------------- 1 | -@ val title: String 2 | -@ val body: String 3 | 4 | !!! 5 | %html 6 | %head 7 | %title= title 8 | %link(rel="stylesheet" href={uri("css/bootstrap.min.css")}) 9 | %body 10 | 11 | 12 | .container 13 | .row 14 | .col-lg-12 15 | %h2 Document Storage 16 | 17 | != body 18 | 19 | 20 | -------------------------------------------------------------------------------- /chapter13/src/main/scala/com/constructiveproof/hackertracker/stacks/HackerCoreStack.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.stacks 2 | 3 | import org.scalatra.{MethodOverride, ScalatraServlet} 4 | import com.constructiveproof.hackertracker.init.DatabaseSessionSupport 5 | 6 | trait HackerCoreStack extends ScalatraServlet with DatabaseSessionSupport with MethodOverride { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /chapter09-docker/conf/application.conf: -------------------------------------------------------------------------------- 1 | 2 | port = 80 3 | 4 | webBase = "http://www.example.org" 5 | 6 | assetsDirectory = "/app/webapp" 7 | 8 | // one of: development, test, staging, production 9 | environment = "production" 10 | 11 | email { 12 | user = "user@example.com" 13 | password = "mypassword" 14 | host = "smtp.example.com" 15 | sender = "User " 16 | } -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/src/main/webapp/WEB-INF/templates/views/hackers/show.ssp: -------------------------------------------------------------------------------- 1 | <% import com.constructiveproof.hackertracker.models.Hacker %> 2 | <% import org.squeryl.Query %> 3 | 4 | <%@ val hacker:Hacker %> 5 | 6 | 7 |

<%= hacker.firstName %> <%= hacker.lastName %> born <%= hacker.birthYear.toString %>

8 | 9 |

“<%= hacker.motto %>”

-------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/webapp/WEB-INF/templates/views/hackers/show.ssp: -------------------------------------------------------------------------------- 1 | <% import com.constructiveproof.hackertracker.models.Hacker %> 2 | <% import org.squeryl.Query %> 3 | 4 | <%@ val hacker:Hacker %> 5 | 6 | 7 |

<%= hacker.firstName %> <%= hacker.lastName %> born <%= hacker.birthYear.toString %>

8 | 9 |

“<%= hacker.motto %>”

-------------------------------------------------------------------------------- /chapter05/src/main/scala/org/scalatra/book/chapter06/MyJsonApp.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter06 2 | 3 | import org.json4s._ 4 | import org.scalatra._ 5 | 6 | class MyJsonApp extends ScalatraServlet with MyJsonRoutes with MyJsonScalazRoutes with MyFoodRoutes with MyJsonpRoutes { 7 | 8 | implicit lazy val jsonFormats = DefaultFormats + 9 | new NutritionFactsSerializer 10 | 11 | } 12 | -------------------------------------------------------------------------------- /comments-collector/README.md: -------------------------------------------------------------------------------- 1 | # Comments collector # 2 | 3 | ## Install MongoDB 4 | 5 | ### Linux 6 | 7 | $ sudo apt-get install mongodb 8 | 9 | ## Build & Run ## 10 | 11 | ```sh 12 | $ cd comments_collector 13 | $ ./sbt 14 | > container:start 15 | > browse 16 | 17 | If `browse` doesn't launch your browser, manually open [http://localhost:8080/](http://localhost:8080/) in your browser. 18 | -------------------------------------------------------------------------------- /chapter05/src/main/scala/org/scalatra/book/chapter06/recipes.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter06 2 | 3 | case class Recipe(title: String, details: RecipeDetails, 4 | ingredients: List[IngredientLine], steps: List[String]) 5 | 6 | case class RecipeDetails(cuisine: String, vegetarian: Boolean, 7 | diet: Option[String]) 8 | 9 | case class IngredientLine(label: String, quantity: String) 10 | 11 | -------------------------------------------------------------------------------- /chapter01/src/main/scala/com/example/app/MyScalatraServlet.scala: -------------------------------------------------------------------------------- 1 | package com.example.app 2 | 3 | import org.scalatra._ 4 | import scalate.ScalateSupport 5 | 6 | class MyScalatraServlet extends MyScalatraWebAppStack { 7 | 8 | get("/") { 9 | 10 | 11 |

Hello, world!

12 | Say hello to Scalate. 13 | 14 | 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /chapter09-standalone/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | 2 | port = 8080 3 | webBase = "http://dev.example.org" 4 | 5 | assetsDirectory = "target/webapp" // or webapp in production 6 | environment = "staging" // one of: development, test, staging, production 7 | 8 | email { 9 | user = "user@example.com" 10 | password = "mypassword" 11 | host = "smtp.example.com" 12 | sender = "User " 13 | } -------------------------------------------------------------------------------- /chapter07/src/main/webapp/WEB-INF/views/greeter.scaml: -------------------------------------------------------------------------------- 1 | !!! 5 2 | -@ val whom: String 3 | -@ val lucky: List[Int] 4 | %html 5 | %head 6 | %link(type="text/css" href="/css/style.css" rel="stylesheet") 7 | %title Hello, #{whom} 8 | %body 9 | %h1 Congratulations 10 | %p You've created your first Scalate view, #{whom}. 11 | %p Your lucky numbers are: 12 | %ul 13 | - for (number <- lucky) 14 | %li #{number} -------------------------------------------------------------------------------- /chapter04/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import com.constructiveproof.hackertracker._ 2 | import org.scalatra._ 3 | import javax.servlet.ServletContext 4 | 5 | class ScalatraBootstrap extends LifeCycle { 6 | override def init(context: ServletContext) { 7 | context.mount(new CookiesExample, "/cookies/*") 8 | context.mount(new GateController, "/grail/*") 9 | context.mount(new HackersController, "/*") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /chapter07-twirl/src/main/scala/org/scalatra/book/chapter07/GreeterServlet.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter07 2 | 3 | import org.scalatra.ScalatraServlet 4 | 5 | class GreeterServlet extends ScalatraServlet { 6 | get("/greet/:whom") { 7 | contentType = "text/html" 8 | val lucky = 9 | for (i <- (1 to 5).toList) 10 | yield util.Random.nextInt(48) + 1 11 | html.greeting(params("whom"), lucky) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /chapter09-docker/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | 2 | port = 8080 3 | 4 | webBase = "http://dev.example.org" 5 | 6 | assetsDirectory = "target/webapp" // or /app/webapp in production 7 | environment = "staging" // one of: development, test, staging, production 8 | 9 | email { 10 | user = "user@example.com" 11 | password = "mypassword" 12 | host = "smtp.example.com" 13 | sender = "User " 14 | } 15 | -------------------------------------------------------------------------------- /chapter10/src/main/webapp/WEB-INF/views/areas.jade: -------------------------------------------------------------------------------- 1 | - import org.scalatra.book.chapter10._ 2 | -@ val areas: Seq[Area] 3 | 4 | - attributes("title") = "Areas" 5 | 6 | .row 7 | .col-md-8 8 | - for (area <- areas) 9 | .panel.panel-default 10 | .panel-heading 11 | | Area: #{area.name} 12 | a.pull-right(href={"/areas/" + area.id}) show 13 | .panel-body 14 | div= area.description 15 | 16 | -------------------------------------------------------------------------------- /chapter08/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import org.scalatra._ 2 | import javax.servlet.ServletContext 3 | 4 | import org.scalatra.book.chapter08._ 5 | 6 | class ScalatraBootstrap extends LifeCycle { 7 | override def init(context: ServletContext) { 8 | context.mount(new FoodServlet, "/*") 9 | context.mount(new MyScalatraServlet, "/*") 10 | context.mount(new NukeLauncherServlet(RealNukeLauncher), "/nuke/*") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter05/src/test/scala/org/scalatra/book/chapter06/MyJsonAppSpec.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter06 2 | 3 | import org.scalatest.FunSuite 4 | import org.scalatra.test.scalatest.ScalatraSuite 5 | 6 | class MyJsonAppSpec extends FunSuite with ScalatraSuite { 7 | 8 | addServlet(classOf[MyJsonApp], "/*") 9 | 10 | test("Getting foods") { 11 | get("/foods/foo_bar") { 12 | status should equal(200) 13 | } 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /chapter06/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import org.scalatra._ 2 | import javax.servlet.ServletContext 3 | 4 | import org.scalatra.book.chapter06.{DocumentStore, DocumentsApp} 5 | 6 | class ScalatraBootstrap extends LifeCycle { 7 | 8 | override def init(context: ServletContext) { 9 | 10 | val store = DocumentStore("data") 11 | 12 | val app = new DocumentsApp(store) 13 | 14 | context.mount(app, "/*") 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /chapter10/src/main/webapp/WEB-INF/layouts/default.jade: -------------------------------------------------------------------------------- 1 | -@ val title: String 2 | -@ val body: String 3 | 4 | !!! 5 | html 6 | head 7 | title= title 8 | link(rel="stylesheet" href="/css/bootstrap.min.css") 9 | body 10 | 11 | .container 12 | .row 13 | .col-md-8 14 | p 15 | ul.nav.nav-pills 16 | li 17 | a(href="/") Home 18 | 19 | h2= title 20 | 21 | != body -------------------------------------------------------------------------------- /chapter03/src/test/scala/org/scalatra/book/chapter03/RecordStoreSpec.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter03 2 | 3 | import org.scalatest.FunSuite 4 | import org.scalatra.test.scalatest.ScalatraSuite 5 | 6 | class RecordStoreSpec extends FunSuite with ScalatraSuite { 7 | 8 | addServlet(new RecordStore("/"), "/*") 9 | 10 | test("Getting artists") { 11 | get("/artists/?") { 12 | status should equal(200) 13 | } 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /chapter07-twirl/src/test/scala/org/scalatra/book/chapter07/GreeterServletSpec.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter07 2 | 3 | import org.scalatest.FunSuite 4 | import org.scalatra.test.scalatest.ScalatraSuite 5 | 6 | class GreeterServletSpec extends FunSuite with ScalatraSuite { 7 | addServlet(classOf[GreeterServlet], "/*") 8 | 9 | test("simple get") { 10 | get("/greet/dave") { 11 | status should equal(200) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chapter09-sbt/src/main/scala/org/scalatra/book/chapter09/Chapter09.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | import org.scalatra.ScalatraServlet 4 | 5 | class Chapter09(appConfig: AppConfig) extends ScalatraServlet { 6 | 7 | get("/") { 8 | f"Greetings! (isDevelopment = ${isDevelopmentMode}})" 9 | } 10 | 11 | get("/shorten-url") { 12 | val token = UrlShortener.nextFreeToken 13 | f"${appConfig.webBase}/$token" 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /chapter10/src/main/scala/org/scalatra/book/chapter10/domain.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter10 2 | 3 | case class Area( 4 | id: Int, 5 | name: String, 6 | location: String, 7 | latitude: Double, 8 | longitude: Double, 9 | description: String) 10 | 11 | case class Route( 12 | id: Int, 13 | areaId: Int, 14 | mountainName: Option[String], 15 | routeName: String, 16 | latitude: Double, 17 | longitude: Double, 18 | description: String) 19 | -------------------------------------------------------------------------------- /comments-collector/src/main/scala/comments/scalaz.scala: -------------------------------------------------------------------------------- 1 | package comments 2 | 3 | import org.scalatra._ 4 | 5 | import scalaz._, Scalaz._ 6 | 7 | trait ScalazSupport extends ScalatraBase { 8 | 9 | // be able to handle scalaz' \/ as return value, simply unwrap the value from the container 10 | override def renderPipeline: RenderPipeline = ({ 11 | case \/-(r) => r 12 | case -\/(l) => l 13 | }: RenderPipeline) orElse super.renderPipeline 14 | 15 | } 16 | -------------------------------------------------------------------------------- /chapter10/src/main/webapp/WEB-INF/views/area.jade: -------------------------------------------------------------------------------- 1 | - import org.scalatra.book.chapter10._ 2 | -@ val area: Area 3 | -@ val routes: Seq[Route] 4 | - attributes("title") = area.name 5 | 6 | .row 7 | .col-lg-8 8 | h4 Routes 9 | - for (route <- routes) 10 | .panel.panel-default 11 | .panel-heading= route.routeName 12 | .panel-body 13 | p Description: #{route.description} 14 | p Lat: #{route.latitude}°, Long: #{route.longitude}° 15 | 16 | -------------------------------------------------------------------------------- /chapter13/src/main/webapp/WEB-INF/templates/views/hackers/new.ssp: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
-------------------------------------------------------------------------------- /chapter09/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import org.scalatra.book.chapter09._ 2 | import org.scalatra._ 3 | 4 | import javax.servlet.ServletContext 5 | 6 | class ScalatraBootstrap extends LifeCycle { 7 | 8 | override def init(context: ServletContext) { 9 | 10 | val conf = AppConfig.load 11 | sys.props(org.scalatra.EnvironmentKey) = AppEnvironment.asString(conf.env) 12 | 13 | val app = new Chapter09(conf) 14 | context.mount(app, "/*") 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /chapter01/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter02/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter03/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter04/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter05/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter06/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter07/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter08/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter09-sbt/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import org.scalatra.book.chapter09._ 2 | import org.scalatra._ 3 | 4 | import javax.servlet.ServletContext 5 | 6 | class ScalatraBootstrap extends LifeCycle { 7 | 8 | override def init(context: ServletContext) { 9 | 10 | val conf = AppConfig.load 11 | sys.props(org.scalatra.EnvironmentKey) = AppEnvironment.asString(conf.env) 12 | 13 | val app = new Chapter09(conf) 14 | context.mount(app, "/*") 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /chapter09/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter10/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter10/src/main/scala/org/scalatra/book/chapter10/scalaz.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter10 2 | 3 | import org.scalatra._ 4 | 5 | import scalaz._, Scalaz._ 6 | 7 | trait ScalazSupport extends ScalatraBase { 8 | 9 | // be able to handle scalaz' \/ as return value, simply unwrap the value from the container 10 | override def renderPipeline: RenderPipeline = ({ 11 | case \/-(r) => r 12 | case -\/(l) => l 13 | }: RenderPipeline) orElse super.renderPipeline 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /chapter12/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter13/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter07/src/main/scala/org/scalatra/book/chapter07/GreeterServlet.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter07 2 | 3 | import org.scalatra.ScalatraServlet 4 | 5 | class GreeterServlet extends MyScalatraWebappStack { 6 | get("/greet/:whom") { 7 | contentType = "text/html" 8 | val lucky = 9 | for (i <- (1 to 5).toList) 10 | yield util.Random.nextInt(48) + 1 11 | layoutTemplate("greeter_dry.html", 12 | "whom" -> params("whom"), 13 | "lucky" -> lucky) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /chapter09-docker/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import org.scalatra.book.chapter09._ 2 | import org.scalatra.LifeCycle 3 | 4 | import javax.servlet.ServletContext 5 | 6 | class ScalatraBootstrap extends LifeCycle { 7 | 8 | override def init(context: ServletContext) { 9 | 10 | val conf = AppConfig.load 11 | sys.props(org.scalatra.EnvironmentKey) = AppEnvironment.asString(conf.env) 12 | 13 | val app = new Chapter09(conf) 14 | context.mount(app, "/*") 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /chapter09-sbt/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter09-sbtweb/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import org.scalatra.book.chapter09._ 2 | import org.scalatra.LifeCycle 3 | 4 | import javax.servlet.ServletContext 5 | 6 | class ScalatraBootstrap extends LifeCycle { 7 | 8 | override def init(context: ServletContext) { 9 | 10 | val conf = AppConfig.load 11 | sys.props(org.scalatra.EnvironmentKey) = AppEnvironment.asString(conf.env) 12 | 13 | val app = new Chapter09(conf) 14 | context.mount(app, "/*") 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /chapter09-standalone/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import org.scalatra.book.chapter09._ 2 | import org.scalatra.LifeCycle 3 | 4 | import javax.servlet.ServletContext 5 | 6 | class ScalatraBootstrap extends LifeCycle { 7 | 8 | override def init(context: ServletContext) { 9 | 10 | val conf = AppConfig.load 11 | sys.props(org.scalatra.EnvironmentKey) = AppEnvironment.asString(conf.env) 12 | 13 | val app = new Chapter09(conf) 14 | context.mount(app, "/*") 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /chapter07-twirl/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter07-twirl/src/main/twirl/greeting.scala.html: -------------------------------------------------------------------------------- 1 | @(whom: String, lucky: List[Int]) 2 | 3 | 4 | 5 | 6 | Hello, @whom 7 | 8 | 9 |

Congratulations

10 |

You've created your first Twirl view, @whom.

11 |

Your lucky numbers are:

12 |
    13 | @for(number <- lucky) { 14 |
  • @number
  • 15 | } 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /chapter09-docker/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter09-sbtweb/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /comments-collector/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter09-sbtweb/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += Resolver.typesafeRepo("releases") 2 | 3 | addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.5.0") 4 | 5 | addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.5.1") 6 | 7 | addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.0.4") 8 | 9 | addSbtPlugin("com.typesafe.sbt" % "sbt-rjs" % "1.0.7") 10 | 11 | addSbtPlugin("com.typesafe.sbt" % "sbt-less" % "1.0.6") 12 | 13 | addSbtPlugin("com.slidingautonomy.sbt" % "sbt-filter" % "1.0.1") 14 | 15 | -------------------------------------------------------------------------------- /chapter09-standalone/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/src/main/webapp/WEB-INF/templates/views/hackers/new.ssp: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
-------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/webapp/WEB-INF/templates/views/hackers/new.ssp: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
-------------------------------------------------------------------------------- /chapter13/src/main/webapp/WEB-INF/templates/views/sessions/new.ssp: -------------------------------------------------------------------------------- 1 |

Please login

2 | 3 |
4 |

5 | 6 |
7 | 8 |
9 | 10 | 11 |

12 |

13 | 14 |

15 |
-------------------------------------------------------------------------------- /chapter04/src/main/scala/com/constructiveproof/hackertracker/CookiesExample.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker 2 | 3 | import org.scalatra._ 4 | 5 | class CookiesExample extends ScalatraServlet { 6 | 7 | get("/") { 8 | val previous = cookies.get("counter") match { 9 | case Some(v) => v.toInt 10 | case None => 0 11 | } 12 | cookies.update("counter", (previous+1).toString) 13 |

14 | Hi, you have been on this page {previous} times already 15 |

16 | } 17 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | lib_managed/ 3 | project/boot/ 4 | project/build/target/ 5 | project/plugins/project/build.properties 6 | src_managed/ 7 | target/ 8 | *.eml 9 | *.iml 10 | *.ipr 11 | *.iws 12 | .*.sw? 13 | .idea 14 | .DS_Store 15 | .ensime 16 | .target 17 | 18 | # paulp script # 19 | /.lib/ 20 | 21 | # eclipse artifacts 22 | .settings 23 | .project 24 | .classpath 25 | .cache 26 | .ensime_lucene 27 | 28 | # sublime artifacts 29 | *.sublime-* 30 | atlassian-ide-plugin.xml 31 | .lib 32 | 33 | .history 34 | 35 | .jvmopts 36 | -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/webapp/WEB-INF/templates/views/sessions/new.ssp: -------------------------------------------------------------------------------- 1 |

Please login

2 | 3 |
4 |

5 | 6 |
7 | 8 |
9 | 10 | 11 |

12 |

13 | 14 |

15 |
-------------------------------------------------------------------------------- /chapter13/src/main/scala/com/constructiveproof/hackertracker/stacks/ApiStack.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.stacks 2 | 3 | import org.scalatra.json.JacksonJsonSupport 4 | import org.scalatra.swagger.JacksonSwaggerBase 5 | import com.constructiveproof.hackertracker.auth.ApiAuthenticationSupport 6 | import org.json4s.DefaultFormats 7 | 8 | trait ApiStack extends HackerCoreStack with ApiAuthenticationSupport 9 | with JacksonJsonSupport { 10 | 11 | override protected implicit lazy val jsonFormats = DefaultFormats 12 | 13 | } 14 | -------------------------------------------------------------------------------- /chapter02/src/main/scala/com/example/cms/ScalatraCmsStack.scala: -------------------------------------------------------------------------------- 1 | package com.example.cms 2 | 3 | import org.scalatra._ 4 | import scalate.ScalateSupport 5 | 6 | trait ScalatraCmsStack extends ScalatraServlet with ScalateSupport { 7 | 8 | notFound { 9 | // remove content type in case it was set through an action 10 | contentType = null 11 | // Try to render a ScalateTemplate if no route matched 12 | findTemplate(requestPath) map { path => 13 | contentType = "text/html" 14 | layoutTemplate(path) 15 | } orElse serveStaticResource() getOrElse resourceNotFound() 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /chapter08/src/main/scala/org/scalatra/book/chapter08/FoodServlet.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter08 2 | 3 | import org.json4s.DefaultFormats 4 | import org.json4s.JsonDSL._ 5 | import org.scalatra.ScalatraServlet 6 | import org.scalatra.json._ 7 | 8 | class FoodServlet extends ScalatraServlet with JacksonJsonSupport { 9 | 10 | implicit lazy val jsonFormats = DefaultFormats 11 | 12 | get("/foods/potatoes") { 13 | val productJson = 14 | ("name" -> "potatoes") ~ 15 | ("fairTrade" -> true) ~ 16 | ("tags" -> List("vegetable", "tuber")) 17 | 18 | productJson 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /chapter01/src/test/scala/com/example/app/MyScalatraServletSpec.scala: -------------------------------------------------------------------------------- 1 | package com.example.app 2 | 3 | import org.scalatra.test.specs2._ 4 | 5 | // For more on Specs2, see http://etorreborre.github.com/specs2/guide/org.specs2.guide.QuickStart.html 6 | class MyScalatraServletSpec extends ScalatraSpec { def is = 7 | "GET / on MyScalatraServlet" ^ 8 | "should return status 200" ! root200^ 9 | end 10 | 11 | addServlet(classOf[MyScalatraServlet], "/*") 12 | 13 | def root200 = get("/") { 14 | status must_== 200 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /chapter01/src/main/scala/com/example/app/MyScalatraWebAppStack.scala: -------------------------------------------------------------------------------- 1 | package com.example.app 2 | 3 | import org.scalatra._ 4 | import scalate.ScalateSupport 5 | 6 | trait MyScalatraWebAppStack extends ScalatraServlet with ScalateSupport { 7 | 8 | notFound { 9 | // remove content type in case it was set through an action 10 | contentType = null 11 | // Try to render a ScalateTemplate if no route matched 12 | findTemplate(requestPath) map { path => 13 | contentType = "text/html" 14 | layoutTemplate(path) 15 | } orElse serveStaticResource() getOrElse resourceNotFound() 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import com.constructiveproof.hackertracker._ 2 | import com.constructiveproof.hackertracker.init.DatabaseInit 3 | import org.scalatra._ 4 | import javax.servlet.ServletContext 5 | 6 | class ScalatraBootstrap extends LifeCycle with DatabaseInit { 7 | 8 | override def init(context: ServletContext) { 9 | configureDb() 10 | context.mount(new HackersController, "/hackers") 11 | context.mount(new DatabaseSetupController, "/database") 12 | } 13 | 14 | override def destroy(context:ServletContext) { 15 | closeDbConnection() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /chapter04/src/main/scala/com/constructiveproof/hackertracker/GateController.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker 2 | 3 | import org.scalatra.ScalatraServlet 4 | 5 | class GateController extends ScalatraServlet { 6 | 7 | before() { 8 | if (params("name") == "Arthur") { 9 | halt(status = 403, 10 | reason = "Forbidden", 11 | headers = Map("X-Your-Mother-Was-A" -> "hamster", 12 | "X-And-Your-Father-Smelt-Of" -> "Elderberries"), 13 | body =

Go away or I shall taunt you a second time!

) 14 | } 15 | } 16 | 17 | get("/") { 18 | "the holy grail!" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /chapter07/src/main/scala/org/scalatra/book/chapter07/MyScalatraWebappStack.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter07 2 | 3 | import org.scalatra._ 4 | import scalate.ScalateSupport 5 | 6 | trait MyScalatraWebappStack extends ScalatraServlet with ScalateSupport { 7 | notFound { 8 | // remove content type in case it was set through an action 9 | contentType = null 10 | // Try to render a ScalateTemplate if no route matched 11 | findTemplate(requestPath) map { path => 12 | contentType = "text/html" 13 | layoutTemplate(path) 14 | } orElse serveStaticResource() getOrElse resourceNotFound() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /chapter06/src/test/scala/org/scalatra/book/chapter06/FilesSpec.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter06 2 | 3 | import org.scalatra.test.specs2._ 4 | 5 | // For more on Specs2, see http://etorreborre.github.com/specs2/guide/org.specs2.guide.QuickStart.html 6 | class FilesSpec extends ScalatraSpec { def is = 7 | "GET / on DocumentsApp" ^ 8 | "should return status 200" ! root200^ 9 | end 10 | 11 | val store = DocumentStore("data") 12 | 13 | val app = new DocumentsApp(store) 14 | 15 | addServlet(app, "/*") 16 | 17 | def root200 = get("/") { 18 | status must_== 200 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /chapter09-sbt/build.sbt: -------------------------------------------------------------------------------- 1 | 2 | organization := "org.scalatra" 3 | name := "Chapter 9" 4 | version := "0.1.0-SNAPSHOT" 5 | scalaVersion := "2.11.6" 6 | 7 | fork in Test := true 8 | 9 | val ScalatraVersion = "2.4.0" 10 | 11 | libraryDependencies ++= Seq( 12 | "org.scalatra" %% "scalatra" % ScalatraVersion, 13 | "org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test", 14 | "com.typesafe" % "config" % "1.2.1", 15 | "ch.qos.logback" % "logback-classic" % "1.1.3" % "runtime", 16 | "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided" 17 | ) 18 | 19 | enablePlugins(JettyPlugin) 20 | 21 | containerPort in Jetty := 8090 22 | -------------------------------------------------------------------------------- /chapter10/src/main/webapp/WEB-INF/views/routes.jade: -------------------------------------------------------------------------------- 1 | - import org.scalatra.book.chapter10._ 2 | -@ val area: Area 3 | -@ val routes: Seq[Route] 4 | - attributes("title") = area.name 5 | 6 | .row 7 | .col-lg-12 8 | h2 Routes 9 | 10 | - for (route <- routes) 11 | .col-lg-12.comment 12 | span.title= route.routeName 13 | p= route.description 14 | 15 | .col-lg-12 16 | h2 Post comment 17 | form(method="post") 18 | label 19 | Title 20 | input(type="text" name="title") 21 | br 22 | label 23 | Body 24 | textarea(name="body") 25 | br 26 | input(type="submit" value="Submit") -------------------------------------------------------------------------------- /chapter02/src/test/scala/com/example/cms/PagesControllerSpec.scala: -------------------------------------------------------------------------------- 1 | package com.example.cms 2 | 3 | import org.scalatra.test.specs2._ 4 | 5 | class PagesControllerSpec extends ScalatraSpec { def is = 6 | "GET /pages/:slug on PagesController" ^ 7 | "should return status 200" ! pagesWork^ 8 | "shows the word 'Bacon' in the body" ! containsBacon^ 9 | end 10 | 11 | addServlet(classOf[PagesController], "/*") 12 | 13 | def pagesWork = get("/pages/bacon-ipsum") { 14 | status must_== 200 15 | } 16 | 17 | def containsBacon = get("/pages/bacon-ipsum") { 18 | body must contain("Bacon") 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /chapter04/src/test/scala/com/constructiveproof/hackertracker/HackersControllerSpec.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker 2 | 3 | import org.scalatra.test.specs2._ 4 | 5 | // For more on Specs2, see http://etorreborre.github.com/specs2/guide/org.specs2.guide.QuickStart.html 6 | class HackersControllerSpec extends ScalatraSpec { def is = 7 | "GET / on HackersController" ^ 8 | "should return status 200" ! root200^ 9 | end 10 | 11 | addServlet(classOf[HackersController], "/*") 12 | 13 | def root200 = get("/") { 14 | status must_== 200 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository contains the sample code for the Scalatra in Action book. 2 | 3 | Each chapter has a code listing. The instructions for building the listings 4 | boils down to: 5 | 6 | * have a JDK installed 7 | * change into the top-level directory of the application (e.g. `cd chapter02/scalachat`). 8 | * make sure the file `sbt` is executable (i.e. `chmod +x sbt` on *nix), or that you've installed sbt if you're on Windows. 9 | * run sbt: `./sbt` (on *nix) or `sbt` (on Windows). This will download a large number of dependencies. 10 | * type `container:start` at the sbt prompt 11 | * visit the running application in your browser, at http://localhost:8008 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter07/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 12 | org.scalatra.servlet.ScalatraListener 13 | 14 | 15 | -------------------------------------------------------------------------------- /chapter08/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 12 | org.scalatra.servlet.ScalatraListener 13 | 14 | 15 | -------------------------------------------------------------------------------- /chapter04/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | org.scalatra.servlet.ScalatraListener 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter07-twirl/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 12 | org.scalatra.servlet.ScalatraListener 13 | 14 | 15 | -------------------------------------------------------------------------------- /chapter09/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | org.scalatra.servlet.ScalatraListener 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter13/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | org.scalatra.servlet.ScalatraListener 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter09-sbt/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | org.scalatra.servlet.ScalatraListener 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import com.constructiveproof.hackertracker._ 2 | import com.constructiveproof.hackertracker.init.DatabaseInit 3 | import org.scalatra._ 4 | import javax.servlet.ServletContext 5 | 6 | class ScalatraBootstrap extends LifeCycle with DatabaseInit { 7 | 8 | override def init(context: ServletContext) { 9 | configureDb() 10 | context.mount(new HackersController, "/hackers") 11 | context.mount(new DatabaseSetupController, "/database") 12 | context.mount(new SessionsController, "/sessions") 13 | } 14 | 15 | override def destroy(context:ServletContext) { 16 | closeDbConnection() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /chapter02/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | org.scalatra.servlet.ScalatraListener 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter09-docker/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | org.scalatra.servlet.ScalatraListener 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter09-standalone/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | org.scalatra.servlet.ScalatraListener 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter12/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | org.scalatra.servlet.ScalatraListener 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter13/src/main/scala/com/constructiveproof/hackertracker/stacks/BrowserStack.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.stacks 2 | 3 | import org.scalatra._ 4 | import org.scalatra.scalate.ScalateSupport 5 | 6 | trait BrowserStack extends HackerCoreStack with ScalateSupport 7 | with FlashMapSupport { 8 | 9 | notFound { 10 | // remove content type in case it was set through an action 11 | contentType = null 12 | // Try to render a ScalateTemplate if no route matched 13 | findTemplate(requestPath) map { path => 14 | contentType = "text/html" 15 | layoutTemplate(path) 16 | } orElse serveStaticResource() getOrElse resourceNotFound() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /chapter09-sbtweb/src/main/public/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | org.scalatra.servlet.ScalatraListener 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter12/src/test/scala/com/constructiveproof/crawler/AkkaCrawlerSpec.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.crawler 2 | 3 | import akka.actor.{Props, ActorSystem} 4 | import com.constructiveproof.crawler.actors.GrabActor 5 | import org.scalatest.FunSuite 6 | import org.scalatra.test.scalatest.ScalatraSuite 7 | 8 | class AkkaCrawlerSpec extends FunSuite with ScalatraSuite { 9 | 10 | val system = ActorSystem() 11 | val grabActor = system.actorOf(Props[GrabActor]) 12 | 13 | addServlet(new AkkaCrawler(system, grabActor), "/*") 14 | 15 | test("Simple get") { 16 | get("/", Map("url" -> "http://www.google.com")) { 17 | status should equal(200) 18 | } 19 | } 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /chapter05/src/main/scala/org/scalatra/book/chapter06/MyFoodRoutes.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter06 2 | 3 | import org.scalatra._ 4 | import org.scalatra.json._ 5 | 6 | import org.json4s._ 7 | 8 | trait MyFoodRoutes extends ScalatraBase with JacksonJsonSupport { 9 | 10 | get("/foods/foo_bar/facts") { 11 | val facts = NutritionFacts( 12 | Energy(2050), Carbohydrate(36.2), Fat(33.9), Protein(7.9)) 13 | 14 | val factsJson = Extraction.decompose(facts) 15 | 16 | factsJson 17 | } 18 | 19 | post("/foods/:name/facts") { 20 | val facts = parsedBody.extractOpt[NutritionFacts] 21 | println(f"updated facts: $facts") 22 | } 23 | 24 | } 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /comments-collector/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | org.scalatra.servlet.ScalatraListener 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter05/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | org.scalatra.servlet.ScalatraListener 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /chapter10/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | org.scalatra.servlet.ScalatraListener 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /chapter08/src/main/scala/org/scalatra/book/chapter08/NukeLauncherServlet.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter08 2 | 3 | import org.scalatra._ 4 | 5 | class NukeLauncherServlet(launcher: NukeLauncher) extends ScalatraServlet { 6 | val NuclearCode = "password123" 7 | 8 | post("/launch") { 9 | if (params("code") == NuclearCode) 10 | launcher.launch() 11 | else 12 | Forbidden() 13 | } 14 | } 15 | 16 | trait NukeLauncher { 17 | def launch(): Unit 18 | } 19 | 20 | object RealNukeLauncher extends NukeLauncher { 21 | def launch(): Unit = ??? 22 | } 23 | 24 | class StubNukeLauncher extends NukeLauncher { 25 | var isLaunched = false 26 | def launch(): Unit = isLaunched = true 27 | } 28 | -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | org.scalatra.servlet.ScalatraListener 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | org.scalatra.servlet.ScalatraListener 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter01/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | org.scalatra.servlet.ScalatraListener 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter09-docker/conf/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | /app/data/logs/app.log 10 | 11 | 12 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /comments-collector/src/main/webapp/WEB-INF/views/comments.scaml: -------------------------------------------------------------------------------- 1 | - import comments._ 2 | -@ val url: String 3 | -@ val comments: Seq[Comment] 4 | - attributes("title") = s"Comments for ${url}" 5 | 6 | .row 7 | .col-lg-12 8 | %h2 9 | Comments for 10 | %a(href={url})= url 11 | 12 | 13 | - for (comment <- comments) 14 | .col-lg-12.comment 15 | %span.title= comment.title 16 | %p= comment.body 17 | 18 | .col-lg-12 19 | %h2 Post comment 20 | %form(method="post") 21 | %label 22 | Title 23 | %input(type="text" name="title") 24 | %br 25 | %label 26 | Body 27 | %textarea(name="body") 28 | %br 29 | %input(type="submit" value="Submit") -------------------------------------------------------------------------------- /chapter08/src/test/scala/org/scalatra/book/chapter08/MyScalatraServletWordSpec.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter08 2 | 3 | import org.scalatra.test.scalatest._ 4 | 5 | class MyScalatraServletWordSpec extends ScalatraWordSpec { 6 | addServlet(classOf[MyScalatraServlet], "/*") 7 | 8 | "GET / on MyScalatraServlet" must { 9 | "return status 200" in { 10 | get("/") { 11 | status should equal(200) 12 | } 13 | } 14 | 15 | "be HTML" in { 16 | get("/") { 17 | header("Content-Type") should startWith("text/html;") 18 | } 19 | } 20 | 21 | "should say \"Hi, world!\"" in { 22 | get("/") { 23 | body should include("Hi") 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /chapter13/src/main/scala/com/constructiveproof/hackertracker/auth/utils/HmacUtils.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.auth.utils 2 | 3 | import javax.crypto.Mac 4 | import javax.crypto.spec.SecretKeySpec 5 | import sun.misc.BASE64Encoder 6 | 7 | object HmacUtils { 8 | 9 | def verify(secretKey: String, signMe: String, hmac: String): Boolean = { 10 | sign(secretKey, signMe) == hmac 11 | } 12 | 13 | def sign(secretKey: String, signMe: String): String = { 14 | val secret = new SecretKeySpec(secretKey.getBytes(), "HmacSHA1") 15 | val mac = Mac.getInstance("HmacSHA1") 16 | mac.init(secret) 17 | val hmac = mac.doFinal(signMe.getBytes) 18 | new BASE64Encoder().encode(hmac) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /chapter09-standalone/src/main/resources/logback.production.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | logs/app.log 10 | 11 | 12 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /comments-collector/src/main/webapp/WEB-INF/views/index.scaml: -------------------------------------------------------------------------------- 1 | - import comments._ 2 | - import java.net.URLEncoder.encode 3 | -@ val urls: Seq[String] 4 | 5 | :!javascript 6 | $(function() { 7 | $("#create-comment").click(function() { 8 | var url = $("#url").val(); 9 | location.href = "#{uri("/")}" + encodeURIComponent(url); 10 | }); 11 | }) 12 | 13 | .row 14 | .col-lg-12 15 | .jumbotron 16 | %h2 Welcome 17 | %p This is the web interface for the comments collector application. Here you can view existing comments and also create new ones. 18 | %p 19 | Enter url: 20 | %input#url(type="text" value="http://www.google.com") 21 | %a#create-comment.btn.btn-lg.btn-primary(href="#" role="button") Create a comment » 22 | 23 | 24 | -------------------------------------------------------------------------------- /chapter13/src/main/scala/com/constructiveproof/hackertracker/init/DatabaseSessionSupport.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.init 2 | 3 | import org.squeryl.Session 4 | import org.squeryl.SessionFactory 5 | import org.scalatra._ 6 | 7 | object DatabaseSessionSupport { 8 | val key = { 9 | val n = getClass.getName 10 | if (n.endsWith("$")) n.dropRight(1) else n 11 | } 12 | } 13 | 14 | trait DatabaseSessionSupport { this: ScalatraBase => 15 | import DatabaseSessionSupport._ 16 | 17 | def dbSession = request.get(key).orNull.asInstanceOf[Session] 18 | 19 | before() { 20 | request(key) = SessionFactory.newSession 21 | dbSession.bindToCurrentThread 22 | } 23 | 24 | after() { 25 | dbSession.close 26 | dbSession.unbindFromCurrentThread 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /chapter09-sbtweb/src/main/scala/org/scalatra/book/chapter09/Chapter09.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | import org.scalatra.ScalatraServlet 4 | import org.scalatra.scalate.ScalateSupport 5 | 6 | class Chapter09(appConfig: AppConfig) extends ScalatraServlet with ScalateSupport { 7 | 8 | get("/shorten-url") { 9 | val token = UrlShortener.nextFreeToken 10 | f"${appConfig.webBase}/$token" 11 | } 12 | 13 | notFound { 14 | // remove content type in case it was set through an action 15 | contentType = null 16 | // Try to render a ScalateTemplate if no route matched 17 | findTemplate(requestPath) map { path => 18 | contentType = "text/html" 19 | layoutTemplate(path) 20 | } orElse serveStaticResource() getOrElse resourceNotFound() 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /chapter12/src/main/scala/com/constructiveproof/crawler/AkkaCrawler.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.crawler 2 | 3 | import java.net.URL 4 | 5 | import akka.actor.{ActorRef, ActorSystem} 6 | import akka.pattern.ask 7 | import akka.util.Timeout 8 | import org.scalatra.{AsyncResult, FutureSupport} 9 | 10 | import scala.concurrent.ExecutionContext 11 | import scala.concurrent.duration._ 12 | 13 | class AkkaCrawler(system: ActorSystem, grabActor: ActorRef) extends CrawlerStack with FutureSupport { 14 | 15 | protected implicit def executor: ExecutionContext = system.dispatcher 16 | 17 | implicit val defaultTimeout = new Timeout(2 seconds) 18 | 19 | get("/") { 20 | contentType = "text/html" 21 | new AsyncResult { 22 | val is = grabActor ? new URL(params("url")) 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /chapter08/src/test/scala/org/scalatra/book/chapter08/MyScalatraServletSpec.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter08 2 | 3 | import org.scalatra.test.specs2._ 4 | 5 | class MyScalatraServletSpec extends ScalatraSpec { def is = 6 | "GET / on MyScalatraServlet" ^ 7 | "should return status 200" ! root200^ 8 | "should be HTML" ! rootHtml^ 9 | "should say \"Hi, world!\"" ! rootHi^ 10 | end 11 | 12 | addServlet(classOf[MyScalatraServlet], "/*") 13 | 14 | def root200 = get("/") { 15 | status must_== 200 16 | } 17 | 18 | def rootHtml = get("/") { 19 | header("Content-Type") must startWith("text/html;") 20 | } 21 | 22 | def rootHi = get("/") { 23 | body must contain("Hi, world!") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/src/main/scala/com/constructiveproof/hackertracker/init/DatabaseSessionSupport.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.init 2 | 3 | import org.squeryl.Session 4 | import org.squeryl.SessionFactory 5 | import org.scalatra._ 6 | 7 | object DatabaseSessionSupport { 8 | val key = { 9 | val n = getClass.getName 10 | if (n.endsWith("$")) n.dropRight(1) else n 11 | } 12 | } 13 | 14 | trait DatabaseSessionSupport { this: ScalatraBase => 15 | import DatabaseSessionSupport._ 16 | 17 | def dbSession = request.get(key).orNull.asInstanceOf[Session] 18 | 19 | before() { 20 | request(key) = SessionFactory.newSession 21 | dbSession.bindToCurrentThread 22 | } 23 | 24 | after() { 25 | dbSession.close 26 | dbSession.unbindFromCurrentThread 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/scala/com/constructiveproof/hackertracker/init/DatabaseSessionSupport.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.init 2 | 3 | import org.squeryl.Session 4 | import org.squeryl.SessionFactory 5 | import org.scalatra._ 6 | 7 | object DatabaseSessionSupport { 8 | val key = { 9 | val n = getClass.getName 10 | if (n.endsWith("$")) n.dropRight(1) else n 11 | } 12 | } 13 | 14 | trait DatabaseSessionSupport { this: ScalatraBase => 15 | import DatabaseSessionSupport._ 16 | 17 | def dbSession = request.get(key).orNull.asInstanceOf[Session] 18 | 19 | before() { 20 | request(key) = SessionFactory.newSession 21 | dbSession.bindToCurrentThread 22 | } 23 | 24 | after() { 25 | dbSession.close 26 | dbSession.unbindFromCurrentThread 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /chapter12/src/main/scala/com/constructiveproof/crawler/CrawlerStack.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.crawler 2 | 3 | import org.scalatra._ 4 | import scalate.ScalateSupport 5 | import org.fusesource.scalate.{ TemplateEngine, Binding } 6 | import org.fusesource.scalate.layout.DefaultLayoutStrategy 7 | import javax.servlet.http.HttpServletRequest 8 | import collection.mutable 9 | 10 | trait CrawlerStack extends ScalatraServlet with ScalateSupport { 11 | 12 | notFound { 13 | // remove content type in case it was set through an action 14 | contentType = null 15 | // Try to render a ScalateTemplate if no route matched 16 | findTemplate(requestPath) map { path => 17 | contentType = "text/html" 18 | layoutTemplate(path) 19 | } orElse serveStaticResource() getOrElse resourceNotFound() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /chapter09-sbt/src/test/scala/org/scalatra/book/chapter09/Chapter09Spec.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | import org.scalatra.test.specs2._ 4 | 5 | class Chapter09Spec extends MutableScalatraSpec { 6 | 7 | val conf = AppConfig.load 8 | sys.props(org.scalatra.EnvironmentKey) = AppEnvironment.asString(conf.env) 9 | 10 | addServlet(new Chapter09(conf), "/*") 11 | 12 | "/ should should execute an action" in { 13 | get("/") { 14 | status must_== 200 15 | } 16 | } 17 | 18 | "/shorten-url should should execute an action" in { 19 | get("/shorten-url") { 20 | status must_== 200 21 | } 22 | } 23 | 24 | "/static.txt should return static file" in { 25 | get("/static.txt") { 26 | status must_== 200 27 | body must_== "this is static text!" 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /chapter12/src/main/scala/com/constructiveproof/crawler/actors/GrabActor.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.crawler.actors 2 | 3 | import java.net.URL 4 | import java.nio.charset.StandardCharsets 5 | 6 | import akka.actor.Actor 7 | 8 | import scala.io.Source 9 | 10 | 11 | class GrabActor extends Actor { 12 | 13 | def receive = { 14 | case url: URL => evaluate(url) 15 | case _ => sender ! "That wasn't a URL." 16 | } 17 | 18 | def evaluate(url: URL) = { 19 | val content = Source.fromURL( 20 | url, StandardCharsets.UTF_8.name() 21 | ).mkString 22 | 23 | content.contains("Akka") match { 24 | case true => sender ! "It's an Akka-related site, very cool." 25 | case false => sender ! "No Akka here, you've made some sort of " + 26 | "mistake in your reading choices." 27 | } 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /chapter13/src/test/scala/com/constructiveproof/hackertracker/HackersControllerSpec.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker 2 | 3 | import com.constructiveproof.hackertracker.init.DatabaseInit 4 | import com.constructiveproof.hackertracker.models.Db 5 | import org.scalatest.{BeforeAndAfter, BeforeAndAfterAll, FunSuite} 6 | import org.scalatra.test.scalatest.ScalatraSuite 7 | 8 | class HackersControllerSpec extends FunSuite with ScalatraSuite with DatabaseInit with BeforeAndAfter with BeforeAndAfterAll { 9 | 10 | addServlet(classOf[HackersController], "/*") 11 | 12 | before { 13 | configureDb() 14 | Db.init 15 | } 16 | 17 | after { 18 | closeDbConnection() 19 | } 20 | 21 | test("simple get") { 22 | get("/new") { 23 | status should equal(302) // It's protected. Redirect! 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter04/src/main/scala/com/constructiveproof/hackertracker/HackerTrackerStack.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker 2 | 3 | import org.scalatra._ 4 | import scalate.ScalateSupport 5 | import org.fusesource.scalate.{ TemplateEngine, Binding } 6 | import org.fusesource.scalate.layout.DefaultLayoutStrategy 7 | import javax.servlet.http.HttpServletRequest 8 | import collection.mutable 9 | 10 | trait HackerTrackerStack extends ScalatraServlet with ScalateSupport { 11 | 12 | notFound { 13 | // remove content type in case it was set through an action 14 | contentType = null 15 | // Try to render a ScalateTemplate if no route matched 16 | findTemplate(requestPath) map { path => 17 | contentType = "text/html" 18 | layoutTemplate(path) 19 | } orElse serveStaticResource() getOrElse resourceNotFound() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /chapter12/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import _root_.akka.actor.{Props, ActorSystem} 2 | import com.constructiveproof.crawler._ 3 | import com.constructiveproof.crawler.actors.GrabActor 4 | import org.apache.spark.SparkContext 5 | import org.scalatra._ 6 | import javax.servlet.ServletContext 7 | 8 | class ScalatraBootstrap extends LifeCycle { 9 | 10 | val sc = new SparkContext("local[8]", "Spark Demo") 11 | 12 | override def init(context: ServletContext) { 13 | val system = ActorSystem() 14 | val grabActor = system.actorOf(Props[GrabActor]) 15 | 16 | context.mount(new CrawlController, "/*") 17 | context.mount(new AkkaCrawler(system, grabActor), "/akka/*") 18 | context.mount(new SparkExampleController(sc), "/spark/*") 19 | } 20 | 21 | override def destroy(context: ServletContext) { 22 | sc.stop() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /chapter08/src/test/scala/org/scalatra/book/chapter08/FoodServletWordSpec.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter08 2 | 3 | import org.json4s.JString 4 | import org.scalatra.test.scalatest._ 5 | 6 | class FoodServletWordSpec extends ScalatraWordSpec 7 | with JsonBodySupport { 8 | 9 | addServlet(classOf[FoodServlet], "/*") 10 | 11 | "GET /foods/potatoes on FoodServlet" must { 12 | "return status 200" in { 13 | get("/foods/potatoes") { 14 | status should equal(200) 15 | } 16 | } 17 | 18 | "be JSON" in { 19 | get("/foods/potatoes") { 20 | header("Content-Type") should startWith("application/json;") 21 | } 22 | } 23 | 24 | "should have name potatoes" in { 25 | get("/foods/potatoes") { 26 | jsonBody \ "name" should equal(JString("potatoes")) 27 | } 28 | } 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /chapter05/src/main/scala/org/scalatra/book/chapter06/json4s_basics.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter06 2 | 3 | import org.json4s._ 4 | import org.json4s.JsonDSL._ 5 | 6 | object json4s_basics { 7 | 8 | val fooBar = JObject( 9 | "label" -> JString("Foo bar"), 10 | "fairTrade" -> JBool(true), 11 | "tags" -> JArray(List(JString("bio"), JString("chocolate")))) 12 | 13 | // fooBar: org.json4s.JValue = JObject(List((label,JString(Foo bar)), ... 14 | 15 | import org.json4s.jackson.JsonMethods.parse 16 | 17 | val txt = 18 | """{ 19 | | "tags": ["bio","chocolate"], 20 | | "label": "Foo bar", 21 | | "fairTrade": true 22 | |}""".stripMargin 23 | 24 | val parsed = parse(txt) 25 | // parsed: org.json4s.JValue = JObject(List((label,JString(Foo bar)), ... 26 | 27 | fooBar == parsed 28 | // res11: Boolean = true 29 | 30 | } 31 | -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/test/scala/com/constructiveproof/hackertracker/HackersControllerSpec.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker 2 | 3 | import com.constructiveproof.hackertracker.init.DatabaseInit 4 | import com.constructiveproof.hackertracker.models.Db 5 | import org.scalatest.{BeforeAndAfter, BeforeAndAfterAll, FunSuite} 6 | import org.scalatra.test.scalatest.ScalatraSuite 7 | 8 | class HackersControllerSpec extends FunSuite with ScalatraSuite with DatabaseInit with BeforeAndAfter with BeforeAndAfterAll { 9 | 10 | addServlet(classOf[HackersController], "/*") 11 | 12 | before { 13 | wipeDb 14 | configureDb() 15 | Db.init 16 | } 17 | 18 | after { 19 | closeDbConnection() 20 | } 21 | 22 | test("simple get") { 23 | get("/new") { 24 | status should equal(302) // It's protected. Redirect! 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/src/test/scala/com/constructiveproof/hackertracker/HackersControllerSpec.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker 2 | 3 | import java.io.File 4 | 5 | import com.constructiveproof.hackertracker.init.DatabaseInit 6 | import com.constructiveproof.hackertracker.models.Db 7 | import org.scalatest.{BeforeAndAfter, BeforeAndAfterAll, FunSuite} 8 | import org.scalatra.test.scalatest.ScalatraSuite 9 | 10 | class HackersControllerSpec extends FunSuite with ScalatraSuite with DatabaseInit with BeforeAndAfter with BeforeAndAfterAll { 11 | 12 | addServlet(classOf[HackersController], "/*") 13 | 14 | before { 15 | wipeDb 16 | configureDb() 17 | Db.init 18 | } 19 | 20 | after { 21 | closeDbConnection() 22 | } 23 | 24 | test("simple get") { 25 | get("/new") { 26 | status should equal(200) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chapter09/src/main/scala/org/scalatra/book/chapter09/Chapter09.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | import org.scalatra.ScalatraServlet 4 | import org.scalatra.scalate.ScalateSupport 5 | 6 | class Chapter09(appConfig: AppConfig) extends ScalatraServlet with ScalateSupport { 7 | 8 | get("/") { 9 | f"Greetings! (isDevelopment = ${isDevelopmentMode}})" 10 | } 11 | 12 | get("/shorten-url") { 13 | val token = UrlShortener.nextFreeToken 14 | f"${appConfig.webBase}/$token" 15 | } 16 | 17 | notFound { 18 | // remove content type in case it was set through an action 19 | contentType = null 20 | // Try to render a ScalateTemplate if no route matched 21 | findTemplate(requestPath) map { path => 22 | contentType = "text/html" 23 | layoutTemplate(path) 24 | } orElse serveStaticResource() getOrElse resourceNotFound() 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /chapter09-docker/src/main/scala/org/scalatra/book/chapter09/Chapter09.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | import org.scalatra.ScalatraServlet 4 | import org.scalatra.scalate.ScalateSupport 5 | 6 | class Chapter09(appConfig: AppConfig) extends ScalatraServlet with ScalateSupport { 7 | 8 | get("/") { 9 | f"Greetings! (isDevelopment = ${isDevelopmentMode})" 10 | } 11 | 12 | get("/shorten-url") { 13 | val token = UrlShortener.nextFreeToken 14 | f"${appConfig.webBase}/$token" 15 | } 16 | 17 | notFound { 18 | // remove content type in case it was set through an action 19 | contentType = null 20 | // Try to render a ScalateTemplate if no route matched 21 | findTemplate(requestPath) map { path => 22 | contentType = "text/html" 23 | layoutTemplate(path) 24 | } orElse serveStaticResource() getOrElse resourceNotFound() 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /chapter09-standalone/src/main/scala/org/scalatra/book/chapter09/Chapter09.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | import org.scalatra.ScalatraServlet 4 | import org.scalatra.scalate.ScalateSupport 5 | 6 | class Chapter09(appConfig: AppConfig) extends ScalatraServlet with ScalateSupport { 7 | 8 | get("/") { 9 | f"Greetings! (isDevelopment = ${isDevelopmentMode}})" 10 | } 11 | 12 | get("/shorten-url") { 13 | val token = UrlShortener.nextFreeToken 14 | f"${appConfig.webBase}/$token" 15 | } 16 | 17 | notFound { 18 | // remove content type in case it was set through an action 19 | contentType = null 20 | // Try to render a ScalateTemplate if no route matched 21 | findTemplate(requestPath) map { path => 22 | contentType = "text/html" 23 | layoutTemplate(path) 24 | } orElse serveStaticResource() getOrElse resourceNotFound() 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /chapter10/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import javax.servlet.ServletContext 2 | 3 | import org.scalatra._ 4 | import org.scalatra.book.chapter10.{Chapter10App, DbSetup} 5 | import slick.driver.H2Driver.api._ 6 | 7 | import scala.concurrent.Await 8 | import scala.concurrent.duration.Duration 9 | 10 | class ScalatraBootstrap extends LifeCycle { 11 | 12 | val jdbcUrl = "jdbc:h2:mem:chapter10;DB_CLOSE_DELAY=-1" 13 | val jdbcDriverClass = "org.h2.Driver" 14 | val db = Database.forURL(jdbcUrl, driver = jdbcDriverClass) 15 | 16 | val app = new Chapter10App(db) 17 | 18 | override def init(context: ServletContext): Unit = { 19 | 20 | val res = db.run(DbSetup.createDatabase) 21 | 22 | Await.result(res, Duration(5, "seconds")) 23 | 24 | context.mount(app, "/*") 25 | 26 | } 27 | 28 | override def destroy(context: ServletContext): Unit = { 29 | db.close() 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /chapter09-docker/src/main/scala/ScalatraLauncher.scala: -------------------------------------------------------------------------------- 1 | import org.eclipse.jetty.server.Server 2 | 3 | import org.eclipse.jetty.server._ 4 | import org.eclipse.jetty.webapp.WebAppContext 5 | import org.scalatra.book.chapter09.AppConfig 6 | import org.scalatra.servlet.ScalatraListener 7 | 8 | object ScalatraLauncher extends App { 9 | 10 | val appConfig = AppConfig.load 11 | 12 | val server = new Server 13 | server.setStopTimeout(5000) 14 | server.setStopAtShutdown(true) 15 | 16 | val connector = new ServerConnector(server) 17 | connector.setPort(appConfig.port) 18 | 19 | server.addConnector(connector) 20 | 21 | val webAppContext = new WebAppContext 22 | webAppContext.setContextPath("/") 23 | webAppContext.setResourceBase(appConfig.assetsDirectory) 24 | webAppContext.setEventListeners(Array(new ScalatraListener)) 25 | server.setHandler(webAppContext) 26 | 27 | server.start 28 | server.join 29 | 30 | } -------------------------------------------------------------------------------- /chapter09-standalone/src/main/scala/ScalatraLauncher.scala: -------------------------------------------------------------------------------- 1 | import org.eclipse.jetty.server.{Server, ServerConnector} 2 | import org.eclipse.jetty.webapp.WebAppContext 3 | import org.scalatra.book.chapter09.AppConfig 4 | import org.scalatra.servlet.ScalatraListener 5 | 6 | object ScalatraLauncher extends App { 7 | 8 | val conf = AppConfig.load 9 | 10 | val server = new Server 11 | server.setStopTimeout(5000) 12 | server.setStopAtShutdown(true) 13 | 14 | val connector = new ServerConnector(server) 15 | connector.setHost("127.0.0.1") 16 | connector.setPort(conf.port) 17 | 18 | server.addConnector(connector) 19 | 20 | val webAppContext = new WebAppContext 21 | webAppContext.setContextPath("/") 22 | webAppContext.setResourceBase(conf.assetsDirectory) 23 | webAppContext.setEventListeners(Array(new ScalatraListener)) 24 | server.setHandler(webAppContext) 25 | 26 | server.start 27 | server.join 28 | 29 | } -------------------------------------------------------------------------------- /chapter13/src/main/scala/com/constructiveproof/hackertracker/auth/strategies/OurBasicAuthStrategy.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.auth.strategies 2 | 3 | import org.scalatra.auth.strategy.{BasicAuthStrategy} 4 | import org.scalatra.{ScalatraBase} 5 | import javax.servlet.http.{HttpServletResponse, HttpServletRequest} 6 | import com.constructiveproof.hackertracker.models.User 7 | 8 | class OurBasicAuthStrategy(protected override val app: ScalatraBase, realm: String) 9 | extends BasicAuthStrategy[User](app, realm) { 10 | 11 | protected def validate(userName: String, password: String)(implicit request: HttpServletRequest, response: HttpServletResponse): Option[User] = { 12 | if(userName == "scalatra" && password == "scalatra") Some(User("scalatra")) 13 | else None 14 | } 15 | 16 | protected def getUserId(user: User)(implicit request: HttpServletRequest, response: HttpServletResponse): String = user.id 17 | } 18 | 19 | 20 | -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/src/main/scala/com/constructiveproof/hackertracker/DatabaseSetupController.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker 2 | 3 | import com.constructiveproof.hackertracker.models.Db 4 | 5 | class DatabaseSetupController extends HackerTrackerStack { 6 | 7 | 8 | before() { 9 | contentType = "text/html" 10 | } 11 | 12 | /** 13 | * Create a new database. 14 | */ 15 | get("/create") { 16 | Db.init 17 | flash("notice") = "Database created" 18 | redirect("/hackers/new") 19 | } 20 | 21 | /** * 22 | * Print the schema definition for our database out to the console. 23 | */ 24 | get("/dump-schema") { 25 | Db.printDdl 26 | "Take a look in your running console and you'll see the data definition list printout." 27 | } 28 | 29 | /** 30 | * Whack the database. 31 | */ 32 | get("/drop") { 33 | Db.drop 34 | "The database has been dropped." 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /chapter08/src/test/scala/org/scalatra/book/chapter08/FoodServletSpec.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter08 2 | 3 | import org.scalatra.test.specs2._ 4 | 5 | import org.json4s._ 6 | import org.json4s.jackson.JsonMethods 7 | 8 | class FoodServletSpec extends ScalatraSpec with JsonBodySupport { 9 | def is = s2""" 10 | GET /foods/potatoes on FoodServlet 11 | should return status 200 $potatoesOk 12 | should be JSON $potatoesJson 13 | should contain name potatoes $potatoesName 14 | """ 15 | 16 | addServlet(classOf[FoodServlet], "/*") 17 | 18 | def potatoesOk = get("/foods/potatoes") { 19 | status must_== 200 20 | } 21 | 22 | def potatoesJson = get("/foods/potatoes") { 23 | header("Content-Type") must startWith ("application/json;") 24 | } 25 | 26 | def potatoesName = get("/foods/potatoes") { 27 | jsonBody \ "name" must_== JString("potatoes") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chapter03/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 17 | 18 | org.scalatra.servlet.ScalatraListener 19 | 20 | 21 | 22 | 1024 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /chapter05/src/main/scala/org/scalatra/book/chapter06/MyJsonScalazRoutes.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter06 2 | 3 | import org.json4s._ 4 | import org.scalatra._ 5 | import org.scalatra.json._ 6 | 7 | import scalaz._, Scalaz._ 8 | 9 | trait MyJsonScalazRoutes extends ScalatraBase with JacksonJsonSupport { 10 | 11 | // be able to handle scalaz' \/ as return value, simply unwrap the value from the container 12 | override def renderPipeline: RenderPipeline = ({ 13 | case \/-(r) => r 14 | case -\/(l) => l 15 | }: RenderPipeline) orElse super.renderPipeline 16 | 17 | post("/foods_alt") { 18 | 19 | for { 20 | label <- (parsedBody \ "label").extractOpt[String] \/> BadRequest() 21 | fairTrade <- (parsedBody \ "fairTrade").extractOpt[Boolean] \/> BadRequest() 22 | tags <- (parsedBody \ "tags").extractOpt[List[String]] \/> BadRequest() 23 | } yield (label, fairTrade, tags) 24 | 25 | } 26 | 27 | } 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/scala/com/constructiveproof/hackertracker/auth/strategies/OurBasicAuthStrategy.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.auth.strategies 2 | 3 | import org.scalatra.auth.strategy.{BasicAuthStrategy} 4 | import org.scalatra.{ScalatraBase} 5 | import javax.servlet.http.{HttpServletResponse, HttpServletRequest} 6 | import com.constructiveproof.hackertracker.models.User 7 | 8 | class OurBasicAuthStrategy(protected override val app: ScalatraBase, realm: String) 9 | extends BasicAuthStrategy[User](app, realm) { 10 | 11 | protected def validate(username: String, password: String)(implicit request: HttpServletRequest, response: HttpServletResponse): Option[User] = { 12 | if(username == "scalatra" && password == "scalatra") Some(User("scalatra")) 13 | else None 14 | } 15 | 16 | protected def getUserId(user: User)(implicit request: HttpServletRequest, response: HttpServletResponse): String = user.id 17 | } 18 | 19 | 20 | -------------------------------------------------------------------------------- /chapter12/src/main/scala/com/constructiveproof/crawler/SparkExampleController.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.crawler 2 | 3 | import org.apache.spark.SparkContext 4 | import org.scalatra.FutureSupport 5 | import scala.concurrent.{Future, ExecutionContext} 6 | 7 | class SparkExampleController(sc: SparkContext) extends CrawlerStack with FutureSupport { 8 | 9 | protected implicit def executor: ExecutionContext = ExecutionContext.global 10 | 11 | get("/count/:word") { 12 | val word = params("word") 13 | Future { 14 | val occurrenceCount = WordCounter.count(word, sc) 15 | s"Found $occurrenceCount occurrences of $word" 16 | } 17 | } 18 | 19 | } 20 | 21 | object WordCounter { 22 | 23 | def count(word: String, sc: SparkContext) = { 24 | val data = "/path/to/data.csv" // <-- Change this to match your file location and name 25 | val lines = sc.textFile(data) 26 | lines.filter(line => line.contains(word)).cache().count() 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /chapter06/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | org.scalatra.servlet.ScalatraListener 19 | 20 | 21 | 22 | 1024 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /chapter05/src/main/scala/org/scalatra/book/chapter06/MyJsonRoutes.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter06 2 | 3 | import org.scalatra._ 4 | import org.scalatra.json._ 5 | 6 | import org.json4s._ 7 | import org.json4s.JsonDSL._ 8 | 9 | trait MyJsonRoutes extends ScalatraBase with JacksonJsonSupport { 10 | 11 | get("/foods/foo_bar") { 12 | val productJson = 13 | ("label" -> "Foo bar") ~ 14 | ("fairTrade" -> true) ~ 15 | ("tags" -> List("bio", "chocolate")) 16 | 17 | productJson 18 | } 19 | 20 | post("/foods") { 21 | 22 | def parseProduct(jv: JValue): (String, Boolean, List[String]) = { 23 | val label = (jv \ "label").extract[String] 24 | val fairTrade = (jv \ "fairTrade").extract[Boolean] 25 | val tags = (jv \ "tags").extract[List[String]] 26 | 27 | (label, fairTrade, tags) 28 | } 29 | 30 | val product = parseProduct(parsedBody) 31 | println(product) 32 | } 33 | 34 | } 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /chapter06/src/main/webapp/WEB-INF/views/index.scaml: -------------------------------------------------------------------------------- 1 | - import org.scalatra.book.chapter06.Document 2 | 3 | - attributes("title") = "DocumentStorage" 4 | -@ val files: Seq[Document] 5 | 6 | .row 7 | .col-lg-6 8 | %h4 Download a document 9 | %table.table 10 | %tr 11 | %th # 12 | %th Name 13 | %th Description 14 | %th Download 15 | -for (doc <- files) 16 | %tr 17 | %td= doc.id 18 | %td= doc.name 19 | %td= doc.description 20 | %td 21 | %a(href={uri("/documents/" + doc.id)}) download 22 | 23 | .col-lg-3 24 | %h4 Create a new document 25 | %form(enctype="multipart/form-data" method="post" action="/documents") 26 | %div.form-group 27 | %label File: 28 | %input(type="file" name="file") 29 | %div.form-group 30 | %label Description: 31 | %input.form-control(type="text" name="description" value="") 32 | %input.btn.btn-default(type="submit") 33 | -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/scala/com/constructiveproof/hackertracker/HackerTrackerStack.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker 2 | 3 | import org.scalatra._ 4 | import scalate.ScalateSupport 5 | import org.fusesource.scalate.TemplateEngine 6 | import org.fusesource.scalate.layout.DefaultLayoutStrategy 7 | import javax.servlet.http.HttpServletRequest 8 | import collection.mutable 9 | import com.constructiveproof.hackertracker.init.DatabaseSessionSupport 10 | 11 | trait HackerTrackerStack extends ScalatraServlet with ScalateSupport 12 | with DatabaseSessionSupport with FlashMapSupport with MethodOverride { 13 | 14 | notFound { 15 | // remove content type in case it was set through an action 16 | contentType = null 17 | // Try to render a ScalateTemplate if no route matched 18 | findTemplate(requestPath) map { path => 19 | contentType = "text/html" 20 | layoutTemplate(path) 21 | } orElse serveStaticResource() getOrElse resourceNotFound() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/src/main/scala/com/constructiveproof/hackertracker/HackerTrackerStack.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker 2 | 3 | import org.scalatra._ 4 | import scalate.ScalateSupport 5 | import org.fusesource.scalate.TemplateEngine 6 | import org.fusesource.scalate.layout.DefaultLayoutStrategy 7 | import javax.servlet.http.HttpServletRequest 8 | import collection.mutable 9 | import com.constructiveproof.hackertracker.init.DatabaseSessionSupport 10 | 11 | trait HackerTrackerStack extends ScalatraServlet with ScalateSupport 12 | with DatabaseSessionSupport with FlashMapSupport with MethodOverride { 13 | 14 | notFound { 15 | // remove content type in case it was set through an action 16 | contentType = null 17 | // Try to render a ScalateTemplate if no route matched 18 | findTemplate(requestPath) map { path => 19 | contentType = "text/html" 20 | layoutTemplate(path) 21 | } orElse serveStaticResource() getOrElse resourceNotFound() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chapter08/src/test/scala/org/scalatra/book/chapter08/NukeLauncherSpec.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter08 2 | 3 | import org.scalatra.test.specs2._ 4 | import org.specs2.mutable.After 5 | 6 | class NukeLauncherSpec extends MutableScalatraSpec with After { 7 | sequential 8 | 9 | val stubLauncher = new StubNukeLauncher 10 | addServlet(new NukeLauncherServlet(stubLauncher), "/*") 11 | 12 | def after: Any = stubLauncher.isLaunched = false 13 | 14 | def launch[A](code: String)(f: => A): A = post("/launch", "code" -> code) { f } 15 | 16 | "The wrong pass code" should { 17 | "respond with forbidden" in { 18 | launch("wrong") { 19 | status must_== 403 20 | } 21 | } 22 | 23 | "not launch the nukes" in { 24 | launch("wrong") { 25 | stubLauncher.isLaunched must_== false 26 | } 27 | } 28 | } 29 | 30 | "The right pass code" should { 31 | "launch the nukes" in { 32 | launch("password123") { 33 | stubLauncher.isLaunched must_== true 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chapter09/src/test/scala/org/scalatra/book/chapter09/Chapter09Spec.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | import org.scalatra.test.specs2._ 4 | 5 | class Chapter09Spec extends MutableScalatraSpec { 6 | 7 | val conf = AppConfig.load 8 | sys.props(org.scalatra.EnvironmentKey) = AppEnvironment.asString(conf.env) 9 | 10 | addServlet(new Chapter09(conf), "/*") 11 | 12 | "/ should should execute an action" in { 13 | get("/") { 14 | status must_== 200 15 | } 16 | } 17 | 18 | "/shorten-url should should execute an action" in { 19 | get("/shorten-url") { 20 | status must_== 200 21 | } 22 | } 23 | 24 | "/static.txt should return static file" in { 25 | get("/static.txt") { 26 | status must_== 200 27 | body must_== "this is static text!" 28 | } 29 | } 30 | 31 | "/hello-scalate should render a template" in { 32 | get("/hello-scalate") { 33 | status must_== 200 34 | body must_== 35 | """

Hello, Scalate!

36 | |""".stripMargin 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /comments-collector/src/main/scala/comments/data.scala: -------------------------------------------------------------------------------- 1 | package comments 2 | 3 | import com.mongodb.casbah.Imports._ 4 | import org.bson.types.ObjectId 5 | 6 | case class Comment(id: String, url: String, title: String, body: String) 7 | 8 | case class CommentsRepository(collection: MongoCollection) { 9 | 10 | def create(url: String, title: String, body: String): String = { 11 | val m = MongoDBObject("url" -> url, "title" -> title, "body" -> body) 12 | collection += m 13 | m.getAs[ObjectId]("_id").get.toString 14 | } 15 | 16 | def findAll: List[Comment] = { 17 | collection.find.toList flatMap toComment 18 | } 19 | 20 | def findByUrl(url: String): List[Comment] = { 21 | collection.find(MongoDBObject("url" -> url)).toList flatMap toComment 22 | } 23 | 24 | protected def toComment(db: DBObject): Option[Comment] = for { 25 | id <- db.getAs[ObjectId]("_id") 26 | u <- db.getAs[String]("url") 27 | t <- db.getAs[String]("title") 28 | b <- db.getAs[String]("body") 29 | } yield Comment(id.toString, u, t, b) 30 | 31 | } 32 | -------------------------------------------------------------------------------- /chapter09-docker/src/test/scala/org/scalatra/book/chapter09/Chapter09Spec.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | import org.scalatra.test.specs2._ 4 | 5 | class Chapter09Spec extends MutableScalatraSpec { 6 | 7 | val conf = AppConfig.load 8 | sys.props(org.scalatra.EnvironmentKey) = AppEnvironment.asString(conf.env) 9 | 10 | addServlet(new Chapter09(conf), "/*") 11 | 12 | "/ should should execute an action" in { 13 | get("/") { 14 | status must_== 200 15 | } 16 | } 17 | 18 | "/shorten-url should should execute an action" in { 19 | get("/shorten-url") { 20 | status must_== 200 21 | } 22 | } 23 | 24 | "/static.txt should return static file" in { 25 | get("/static.txt") { 26 | status must_== 200 27 | body must_== "this is static text!" 28 | } 29 | } 30 | 31 | "/hello-scalate should render a template" in { 32 | get("/hello-scalate") { 33 | status must_== 200 34 | body must_== 35 | """

Hello, Scalate!

36 | |""".stripMargin 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /chapter09-standalone/src/test/scala/org/scalatra/book/chapter09/Chapter09Spec.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | import org.scalatra.test.specs2._ 4 | 5 | class Chapter09Spec extends MutableScalatraSpec { 6 | 7 | val conf = AppConfig.load 8 | sys.props(org.scalatra.EnvironmentKey) = AppEnvironment.asString(conf.env) 9 | 10 | addServlet(new Chapter09(conf), "/*") 11 | 12 | "/ should should execute an action" in { 13 | get("/") { 14 | status must_== 200 15 | } 16 | } 17 | 18 | "/shorten-url should should execute an action" in { 19 | get("/shorten-url") { 20 | status must_== 200 21 | } 22 | } 23 | 24 | "/static.txt should return static file" in { 25 | get("/static.txt") { 26 | status must_== 200 27 | body must_== "this is static text!" 28 | } 29 | } 30 | 31 | "/hello-scalate should render a template" in { 32 | get("/hello-scalate") { 33 | status must_== 200 34 | body must_== 35 | """

Hello, Scalate!

36 | |""".stripMargin 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /chapter02/.gitignore: -------------------------------------------------------------------------------- 1 | ## generic files to ignore 2 | *~ 3 | *.lock 4 | *.DS_Store 5 | *.swp 6 | *.out 7 | 8 | # rails specific 9 | *.sqlite3 10 | config/database.yml 11 | log/* 12 | tmp/* 13 | 14 | # java specific 15 | *.class 16 | 17 | # python specific 18 | *.pyc 19 | 20 | # xcode/iphone specific 21 | build/* 22 | *.pbxuser 23 | *.mode2v3 24 | *.mode1v3 25 | *.perspective 26 | *.perspectivev3 27 | *~.nib 28 | 29 | # akka specific 30 | logs/* 31 | 32 | # sbt specific 33 | target/ 34 | project/boot 35 | lib_managed/* 36 | project/build/target 37 | project/build/lib_managed 38 | project/build/src_managed 39 | project/plugins/lib_managed 40 | project/plugins/target 41 | project/plugins/src_managed 42 | project/plugins/project 43 | 44 | core/lib_managed 45 | core/target 46 | pubsub/lib_managed 47 | pubsub/target 48 | 49 | # eclipse specific 50 | .metadata 51 | jrebel.lic 52 | .settings 53 | .classpath 54 | .project 55 | 56 | .ensime* 57 | *.sublime-* 58 | .cache 59 | 60 | # intellij 61 | *.eml 62 | *.iml 63 | *.ipr 64 | *.iws 65 | .*.sw? 66 | .idea 67 | 68 | # paulp script 69 | /.lib/ 70 | -------------------------------------------------------------------------------- /chapter04/.gitignore: -------------------------------------------------------------------------------- 1 | ## generic files to ignore 2 | *~ 3 | *.lock 4 | *.DS_Store 5 | *.swp 6 | *.out 7 | 8 | # rails specific 9 | *.sqlite3 10 | config/database.yml 11 | log/* 12 | tmp/* 13 | 14 | # java specific 15 | *.class 16 | 17 | # python specific 18 | *.pyc 19 | 20 | # xcode/iphone specific 21 | build/* 22 | *.pbxuser 23 | *.mode2v3 24 | *.mode1v3 25 | *.perspective 26 | *.perspectivev3 27 | *~.nib 28 | 29 | # akka specific 30 | logs/* 31 | 32 | # sbt specific 33 | target/ 34 | project/boot 35 | lib_managed/* 36 | project/build/target 37 | project/build/lib_managed 38 | project/build/src_managed 39 | project/plugins/lib_managed 40 | project/plugins/target 41 | project/plugins/src_managed 42 | project/plugins/project 43 | 44 | core/lib_managed 45 | core/target 46 | pubsub/lib_managed 47 | pubsub/target 48 | 49 | # eclipse specific 50 | .metadata 51 | jrebel.lic 52 | .settings 53 | .classpath 54 | .project 55 | 56 | .ensime* 57 | *.sublime-* 58 | .cache 59 | 60 | # intellij 61 | *.eml 62 | *.iml 63 | *.ipr 64 | *.iws 65 | .*.sw? 66 | .idea 67 | 68 | # paulp script 69 | /.lib/ 70 | -------------------------------------------------------------------------------- /chapter09/.gitignore: -------------------------------------------------------------------------------- 1 | ## generic files to ignore 2 | *~ 3 | *.lock 4 | *.DS_Store 5 | *.swp 6 | *.out 7 | 8 | # rails specific 9 | *.sqlite3 10 | config/database.yml 11 | log/* 12 | tmp/* 13 | 14 | # java specific 15 | *.class 16 | 17 | # python specific 18 | *.pyc 19 | 20 | # xcode/iphone specific 21 | build/* 22 | *.pbxuser 23 | *.mode2v3 24 | *.mode1v3 25 | *.perspective 26 | *.perspectivev3 27 | *~.nib 28 | 29 | # akka specific 30 | logs/* 31 | 32 | # sbt specific 33 | target/ 34 | project/boot 35 | lib_managed/* 36 | project/build/target 37 | project/build/lib_managed 38 | project/build/src_managed 39 | project/plugins/lib_managed 40 | project/plugins/target 41 | project/plugins/src_managed 42 | project/plugins/project 43 | 44 | core/lib_managed 45 | core/target 46 | pubsub/lib_managed 47 | pubsub/target 48 | 49 | # eclipse specific 50 | .metadata 51 | jrebel.lic 52 | .settings 53 | .classpath 54 | .project 55 | 56 | .ensime* 57 | *.sublime-* 58 | .cache 59 | 60 | # intellij 61 | *.eml 62 | *.iml 63 | *.ipr 64 | *.iws 65 | .*.sw? 66 | .idea 67 | 68 | # paulp script 69 | /.lib/ 70 | -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/scala/com/constructiveproof/hackertracker/DatabaseSetupController.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker 2 | 3 | import com.constructiveproof.hackertracker.models.Db 4 | import com.constructiveproof.hackertracker.auth.OurBasicAuthenticationSupport 5 | 6 | class DatabaseSetupController extends HackerTrackerStack with OurBasicAuthenticationSupport { 7 | 8 | 9 | before() { 10 | contentType = "text/html" 11 | basicAuth 12 | } 13 | 14 | /** 15 | * Create a new database. 16 | */ 17 | get("/create") { 18 | Db.init 19 | flash("notice") = "Database created!" 20 | redirect("/hackers/new") 21 | } 22 | 23 | /** * 24 | * Print the schema definition for our database out to the console. 25 | */ 26 | get("/dump-schema") { 27 | Db.printDdl 28 | "Take a look in your running console and you'll see the data definition list printout." 29 | } 30 | 31 | /** 32 | * Whack the database. 33 | */ 34 | get("/drop") { 35 | Db.drop 36 | "The database has been dropped." 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /chapter12/.gitignore: -------------------------------------------------------------------------------- 1 | ## generic files to ignore 2 | *~ 3 | *.lock 4 | *.DS_Store 5 | *.swp 6 | *.out 7 | 8 | # rails specific 9 | *.sqlite3 10 | config/database.yml 11 | log/* 12 | tmp/* 13 | 14 | # java specific 15 | *.class 16 | 17 | # python specific 18 | *.pyc 19 | 20 | # xcode/iphone specific 21 | build/* 22 | *.pbxuser 23 | *.mode2v3 24 | *.mode1v3 25 | *.perspective 26 | *.perspectivev3 27 | *~.nib 28 | 29 | # akka specific 30 | logs/* 31 | 32 | # sbt specific 33 | target/ 34 | project/boot 35 | lib_managed/* 36 | project/build/target 37 | project/build/lib_managed 38 | project/build/src_managed 39 | project/plugins/lib_managed 40 | project/plugins/target 41 | project/plugins/src_managed 42 | project/plugins/project 43 | 44 | core/lib_managed 45 | core/target 46 | pubsub/lib_managed 47 | pubsub/target 48 | 49 | # eclipse specific 50 | .metadata 51 | jrebel.lic 52 | .settings 53 | .classpath 54 | .project 55 | 56 | .ensime* 57 | *.sublime-* 58 | .cache 59 | 60 | # intellij 61 | *.eml 62 | *.iml 63 | *.ipr 64 | *.iws 65 | .*.sw? 66 | .idea 67 | 68 | # paulp script 69 | /.lib/ 70 | -------------------------------------------------------------------------------- /chapter13/.gitignore: -------------------------------------------------------------------------------- 1 | ## generic files to ignore 2 | *~ 3 | *.lock 4 | *.DS_Store 5 | *.swp 6 | *.out 7 | 8 | # rails specific 9 | *.sqlite3 10 | config/database.yml 11 | log/* 12 | tmp/* 13 | 14 | # java specific 15 | *.class 16 | 17 | # python specific 18 | *.pyc 19 | 20 | # xcode/iphone specific 21 | build/* 22 | *.pbxuser 23 | *.mode2v3 24 | *.mode1v3 25 | *.perspective 26 | *.perspectivev3 27 | *~.nib 28 | 29 | # akka specific 30 | logs/* 31 | 32 | # sbt specific 33 | target/ 34 | project/boot 35 | lib_managed/* 36 | project/build/target 37 | project/build/lib_managed 38 | project/build/src_managed 39 | project/plugins/lib_managed 40 | project/plugins/target 41 | project/plugins/src_managed 42 | project/plugins/project 43 | 44 | core/lib_managed 45 | core/target 46 | pubsub/lib_managed 47 | pubsub/target 48 | 49 | # eclipse specific 50 | .metadata 51 | jrebel.lic 52 | .settings 53 | .classpath 54 | .project 55 | 56 | .ensime* 57 | *.sublime-* 58 | .cache 59 | 60 | # intellij 61 | *.eml 62 | *.iml 63 | *.ipr 64 | *.iws 65 | .*.sw? 66 | .idea 67 | 68 | # paulp script 69 | /.lib/ 70 | -------------------------------------------------------------------------------- /chapter09-sbt/.gitignore: -------------------------------------------------------------------------------- 1 | ## generic files to ignore 2 | *~ 3 | *.lock 4 | *.DS_Store 5 | *.swp 6 | *.out 7 | 8 | # rails specific 9 | *.sqlite3 10 | config/database.yml 11 | log/* 12 | tmp/* 13 | 14 | # java specific 15 | *.class 16 | 17 | # python specific 18 | *.pyc 19 | 20 | # xcode/iphone specific 21 | build/* 22 | *.pbxuser 23 | *.mode2v3 24 | *.mode1v3 25 | *.perspective 26 | *.perspectivev3 27 | *~.nib 28 | 29 | # akka specific 30 | logs/* 31 | 32 | # sbt specific 33 | target/ 34 | project/boot 35 | lib_managed/* 36 | project/build/target 37 | project/build/lib_managed 38 | project/build/src_managed 39 | project/plugins/lib_managed 40 | project/plugins/target 41 | project/plugins/src_managed 42 | project/plugins/project 43 | 44 | core/lib_managed 45 | core/target 46 | pubsub/lib_managed 47 | pubsub/target 48 | 49 | # eclipse specific 50 | .metadata 51 | jrebel.lic 52 | .settings 53 | .classpath 54 | .project 55 | 56 | .ensime* 57 | *.sublime-* 58 | .cache 59 | 60 | # intellij 61 | *.eml 62 | *.iml 63 | *.ipr 64 | *.iws 65 | .*.sw? 66 | .idea 67 | 68 | # paulp script 69 | /.lib/ 70 | -------------------------------------------------------------------------------- /chapter09-sbtweb/.gitignore: -------------------------------------------------------------------------------- 1 | ## generic files to ignore 2 | *~ 3 | *.lock 4 | *.DS_Store 5 | *.swp 6 | *.out 7 | 8 | # rails specific 9 | *.sqlite3 10 | config/database.yml 11 | log/* 12 | tmp/* 13 | 14 | # java specific 15 | *.class 16 | 17 | # python specific 18 | *.pyc 19 | 20 | # xcode/iphone specific 21 | build/* 22 | *.pbxuser 23 | *.mode2v3 24 | *.mode1v3 25 | *.perspective 26 | *.perspectivev3 27 | *~.nib 28 | 29 | # akka specific 30 | logs/* 31 | 32 | # sbt specific 33 | target/ 34 | project/boot 35 | lib_managed/* 36 | project/build/target 37 | project/build/lib_managed 38 | project/build/src_managed 39 | project/plugins/lib_managed 40 | project/plugins/target 41 | project/plugins/src_managed 42 | project/plugins/project 43 | 44 | core/lib_managed 45 | core/target 46 | pubsub/lib_managed 47 | pubsub/target 48 | 49 | # eclipse specific 50 | .metadata 51 | jrebel.lic 52 | .settings 53 | .classpath 54 | .project 55 | 56 | .ensime* 57 | *.sublime-* 58 | .cache 59 | 60 | # intellij 61 | *.eml 62 | *.iml 63 | *.ipr 64 | *.iws 65 | .*.sw? 66 | .idea 67 | 68 | # paulp script 69 | /.lib/ 70 | -------------------------------------------------------------------------------- /chapter09-standalone/.gitignore: -------------------------------------------------------------------------------- 1 | ## generic files to ignore 2 | *~ 3 | *.lock 4 | *.DS_Store 5 | *.swp 6 | *.out 7 | 8 | # rails specific 9 | *.sqlite3 10 | config/database.yml 11 | log/* 12 | tmp/* 13 | 14 | # java specific 15 | *.class 16 | 17 | # python specific 18 | *.pyc 19 | 20 | # xcode/iphone specific 21 | build/* 22 | *.pbxuser 23 | *.mode2v3 24 | *.mode1v3 25 | *.perspective 26 | *.perspectivev3 27 | *~.nib 28 | 29 | # akka specific 30 | logs/* 31 | 32 | # sbt specific 33 | target/ 34 | project/boot 35 | lib_managed/* 36 | project/build/target 37 | project/build/lib_managed 38 | project/build/src_managed 39 | project/plugins/lib_managed 40 | project/plugins/target 41 | project/plugins/src_managed 42 | project/plugins/project 43 | 44 | core/lib_managed 45 | core/target 46 | pubsub/lib_managed 47 | pubsub/target 48 | 49 | # eclipse specific 50 | .metadata 51 | jrebel.lic 52 | .settings 53 | .classpath 54 | .project 55 | 56 | .ensime* 57 | *.sublime-* 58 | .cache 59 | 60 | # intellij 61 | *.eml 62 | *.iml 63 | *.ipr 64 | *.iws 65 | .*.sw? 66 | .idea 67 | 68 | # paulp script 69 | /.lib/ 70 | -------------------------------------------------------------------------------- /comments-collector/.gitignore: -------------------------------------------------------------------------------- 1 | ## generic files to ignore 2 | *~ 3 | *.lock 4 | *.DS_Store 5 | *.swp 6 | *.out 7 | 8 | # rails specific 9 | *.sqlite3 10 | config/database.yml 11 | log/* 12 | tmp/* 13 | 14 | # java specific 15 | *.class 16 | 17 | # python specific 18 | *.pyc 19 | 20 | # xcode/iphone specific 21 | build/* 22 | *.pbxuser 23 | *.mode2v3 24 | *.mode1v3 25 | *.perspective 26 | *.perspectivev3 27 | *~.nib 28 | 29 | # akka specific 30 | logs/* 31 | 32 | # sbt specific 33 | target/ 34 | project/boot 35 | lib_managed/* 36 | project/build/target 37 | project/build/lib_managed 38 | project/build/src_managed 39 | project/plugins/lib_managed 40 | project/plugins/target 41 | project/plugins/src_managed 42 | project/plugins/project 43 | 44 | core/lib_managed 45 | core/target 46 | pubsub/lib_managed 47 | pubsub/target 48 | 49 | # eclipse specific 50 | .metadata 51 | jrebel.lic 52 | .settings 53 | .classpath 54 | .project 55 | 56 | .ensime* 57 | *.sublime-* 58 | .cache 59 | 60 | # intellij 61 | *.eml 62 | *.iml 63 | *.ipr 64 | *.iws 65 | .*.sw? 66 | .idea 67 | 68 | # paulp script 69 | /.lib/ 70 | -------------------------------------------------------------------------------- /chapter09-docker/.gitignore: -------------------------------------------------------------------------------- 1 | ## generic files to ignore 2 | *~ 3 | *.lock 4 | *.DS_Store 5 | *.swp 6 | *.out 7 | 8 | # rails specific 9 | *.sqlite3 10 | config/database.yml 11 | log/* 12 | tmp/* 13 | 14 | # java specific 15 | *.class 16 | 17 | # python specific 18 | *.pyc 19 | 20 | # xcode/iphone specific 21 | build/* 22 | *.pbxuser 23 | *.mode2v3 24 | *.mode1v3 25 | *.perspective 26 | *.perspectivev3 27 | *~.nib 28 | 29 | # akka specific 30 | logs/* 31 | 32 | # sbt specific 33 | target/ 34 | project/boot 35 | lib_managed/* 36 | project/build/target 37 | project/build/lib_managed 38 | project/build/src_managed 39 | project/plugins/lib_managed 40 | project/plugins/target 41 | project/plugins/src_managed 42 | project/plugins/project 43 | 44 | core/lib_managed 45 | core/target 46 | pubsub/lib_managed 47 | pubsub/target 48 | 49 | # eclipse specific 50 | .metadata 51 | jrebel.lic 52 | .settings 53 | .classpath 54 | .project 55 | 56 | .ensime* 57 | *.sublime-* 58 | .cache 59 | 60 | # intellij 61 | *.eml 62 | *.iml 63 | *.ipr 64 | *.iws 65 | .*.sw? 66 | .idea 67 | 68 | # paulp script 69 | /.lib/ 70 | 71 | data/logs/* 72 | -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/.gitignore: -------------------------------------------------------------------------------- 1 | ## generic files to ignore 2 | *~ 3 | *.lock 4 | *.DS_Store 5 | *.swp 6 | *.out 7 | 8 | # rails specific 9 | *.sqlite3 10 | config/database.yml 11 | log/* 12 | tmp/* 13 | 14 | # java specific 15 | *.class 16 | 17 | # python specific 18 | *.pyc 19 | 20 | # xcode/iphone specific 21 | build/* 22 | *.pbxuser 23 | *.mode2v3 24 | *.mode1v3 25 | *.perspective 26 | *.perspectivev3 27 | *~.nib 28 | 29 | # akka specific 30 | logs/* 31 | 32 | # sbt specific 33 | target/ 34 | project/boot 35 | lib_managed/* 36 | project/build/target 37 | project/build/lib_managed 38 | project/build/src_managed 39 | project/plugins/lib_managed 40 | project/plugins/target 41 | project/plugins/src_managed 42 | project/plugins/project 43 | 44 | core/lib_managed 45 | core/target 46 | pubsub/lib_managed 47 | pubsub/target 48 | 49 | # eclipse specific 50 | .metadata 51 | jrebel.lic 52 | .settings 53 | .classpath 54 | .project 55 | 56 | .ensime* 57 | *.sublime-* 58 | .cache 59 | 60 | # intellij 61 | *.eml 62 | *.iml 63 | *.ipr 64 | *.iws 65 | .*.sw? 66 | .idea 67 | 68 | # paulp script 69 | /.lib/ 70 | -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/.gitignore: -------------------------------------------------------------------------------- 1 | ## generic files to ignore 2 | *~ 3 | *.lock 4 | *.DS_Store 5 | *.swp 6 | *.out 7 | 8 | # rails specific 9 | *.sqlite3 10 | config/database.yml 11 | log/* 12 | tmp/* 13 | 14 | # java specific 15 | *.class 16 | 17 | # python specific 18 | *.pyc 19 | 20 | # xcode/iphone specific 21 | build/* 22 | *.pbxuser 23 | *.mode2v3 24 | *.mode1v3 25 | *.perspective 26 | *.perspectivev3 27 | *~.nib 28 | 29 | # akka specific 30 | logs/* 31 | 32 | # sbt specific 33 | target/ 34 | project/boot 35 | lib_managed/* 36 | project/build/target 37 | project/build/lib_managed 38 | project/build/src_managed 39 | project/plugins/lib_managed 40 | project/plugins/target 41 | project/plugins/src_managed 42 | project/plugins/project 43 | 44 | core/lib_managed 45 | core/target 46 | pubsub/lib_managed 47 | pubsub/target 48 | 49 | # eclipse specific 50 | .metadata 51 | jrebel.lic 52 | .settings 53 | .classpath 54 | .project 55 | 56 | .ensime* 57 | *.sublime-* 58 | .cache 59 | 60 | # intellij 61 | *.eml 62 | *.iml 63 | *.ipr 64 | *.iws 65 | .*.sw? 66 | .idea 67 | 68 | # paulp script 69 | /.lib/ 70 | -------------------------------------------------------------------------------- /chapter12/src/main/scala/com/constructiveproof/crawler/CrawlController.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.crawler 2 | 3 | import java.net.URL 4 | import java.nio.charset.StandardCharsets 5 | 6 | import org.scalatra._ 7 | 8 | import scala.concurrent.{ExecutionContext, Future} 9 | import scala.io.Source 10 | 11 | class CrawlController extends CrawlerStack with FutureSupport { 12 | 13 | protected implicit def executor = ExecutionContext.global 14 | 15 | get("/") { 16 | contentType = "text/html" 17 | new AsyncResult { 18 | val is = 19 | Grabber.evaluate(new URL(params("url"))) 20 | } 21 | } 22 | 23 | } 24 | 25 | object Grabber { 26 | def evaluate(url: URL)(implicit ctx: ExecutionContext): Future[String] = { 27 | Future { 28 | val content = Source.fromURL( 29 | url, StandardCharsets.UTF_8.name() 30 | ).mkString 31 | content.contains("Scala") match { 32 | case true => "It's a Scala site, very cool." 33 | case false => "Whoops, you've made some sort " + 34 | "of mistake in your reading choices." 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /chapter13/src/main/scala/com/constructiveproof/hackertracker/DatabaseSetupController.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker 2 | 3 | import com.constructiveproof.hackertracker.auth.OurBasicAuthenticationSupport 4 | import com.constructiveproof.hackertracker.models.Db 5 | import com.constructiveproof.hackertracker.stacks.BrowserStack 6 | 7 | class DatabaseSetupController extends BrowserStack with OurBasicAuthenticationSupport { 8 | 9 | 10 | before() { 11 | contentType = "text/html" 12 | basicAuth 13 | } 14 | 15 | /** 16 | * Create a new database. 17 | */ 18 | get("/create") { 19 | Db.create 20 | flash("notice") = "Database created!" 21 | redirect("/hackers/new") 22 | } 23 | 24 | /** * 25 | * Print the schema definition for our database out to the console. 26 | */ 27 | get("/dump-schema") { 28 | Db.printDdl 29 | "Take a look in your running console and you'll see the data definition list printout." 30 | } 31 | 32 | /** 33 | * Whack the database. 34 | */ 35 | get("/drop") { 36 | Db.drop 37 | "The database has been dropped." 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /comments-collector/src/test/scala/comments/CommentsServletSpec.scala: -------------------------------------------------------------------------------- 1 | package comments 2 | 3 | import org.scalatra.test.specs2._ 4 | 5 | import org.scalatra.swagger._ 6 | import com.mongodb.casbah.Imports._ 7 | 8 | // For more on Specs2, see http://etorreborre.github.com/specs2/guide/org.specs2.guide.QuickStart.html 9 | class CommentsServletSpec extends ScalatraSpec { def is = 10 | "GET / on CommentsServlet" ^ 11 | "should return status 200" ! root200^ 12 | end 13 | 14 | implicit val apiInfo = new ApiInfo("The comments API", 15 | "Docs for the comments API", 16 | "http://www.manning.com/carrero2/", 17 | "Ross", "MIT", "http://scalatra.org") 18 | 19 | implicit val swagger = new Swagger("1.0", "1", apiInfo) 20 | 21 | val mongoClient = MongoClient() 22 | val mongoColl = mongoClient("comments_collector")("comments") 23 | val comments = CommentsRepository(mongoColl) 24 | 25 | addServlet(new CommentsApi(comments), "/api/*") 26 | 27 | def root200 = get("/api/comments") { 28 | status must_== 200 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /chapter06/project/build.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | 4 | import org.scalatra.sbt._ 5 | import com.earldouglas.xwp.JettyPlugin 6 | 7 | object Chapter06Build extends Build { 8 | 9 | val Organization = "org.scalatra" 10 | val Name = "file-upload" 11 | val Version = "0.1.0-SNAPSHOT" 12 | val ScalaVersion = "2.11.6" 13 | val ScalatraVersion = "2.4.0" 14 | 15 | lazy val project = Project ( 16 | Name, 17 | file("."), 18 | settings = Defaults.defaultConfigs ++ ScalatraPlugin.scalatraSettings ++ Seq( 19 | organization := Organization, 20 | name := Name, 21 | version := Version, 22 | scalaVersion := ScalaVersion, 23 | fork in Test := true, 24 | libraryDependencies ++= Seq( 25 | "org.scalatra" %% "scalatra" % ScalatraVersion, 26 | "org.scalatra" %% "scalatra-scalate" % ScalatraVersion, 27 | "org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test", 28 | "ch.qos.logback" % "logback-classic" % "1.0.6" % "runtime", 29 | "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided" 30 | ) 31 | ) 32 | ).enablePlugins(JettyPlugin) 33 | 34 | } 35 | -------------------------------------------------------------------------------- /comments-collector/src/main/scala/comments/frontend.scala: -------------------------------------------------------------------------------- 1 | package comments 2 | 3 | import org.scalatra._ 4 | import org.scalatra.scalate.ScalateSupport 5 | import java.net.URLEncoder 6 | 7 | import scalaz._, Scalaz._ 8 | 9 | class CommentsFrontend(commentsRepo: CommentsRepository) extends ScalatraServlet with ScalateSupport with ScalazSupport { 10 | get("/") { 11 | val urls = commentsRepo.findAll.groupBy(_.url).keys.toSeq.sorted 12 | layoutTemplate("index", "title" -> "Articles with comments", "urls" -> urls) 13 | } 14 | 15 | get("/:url") { 16 | val url = params("url") 17 | val urls = commentsRepo.findAll.groupBy(_.url).keys.toSeq.sorted 18 | layoutTemplate("comments", "urls" -> urls, "url" -> url, "comments" -> commentsRepo.findByUrl(url)) 19 | } 20 | 21 | post("/:url") { 22 | for { 23 | url <- params("url").right 24 | title <- params.get("title") \/> BadRequest("title is required") 25 | body <- params.get("body") \/> BadRequest("body is required") 26 | } yield { 27 | commentsRepo.create(url, title, body) 28 | Found(s"${routeBasePath}/${URLEncoder.encode(url, "utf-8")}") 29 | } 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /chapter03/src/main/scala/org/scalatra/book/chapter03/Artist.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter03 2 | 3 | import scala.xml.Node 4 | import scala.collection.concurrent.TrieMap 5 | 6 | case class Artist(name: String, nationality: String, isActive: Boolean) { 7 | def toXml: Node = 8 | 9 | {name} 10 | {nationality} 11 | {isActive} 12 | 13 | 14 | def toJson: String = ??? // Left as an exercise to the JSON chapter. 15 | } 16 | 17 | object Artist { 18 | // URLs are cleaner without spaces. 19 | def fromParam(name: String) = name.replace('_', ' ') 20 | 21 | private val db = TrieMap.empty[String, Artist] 22 | 23 | def find(name: String): Option[Artist] = db.get(fromParam(name)) 24 | 25 | def fetchAll(): List[Artist] = db.values.toList 26 | 27 | def exists(name: String): Boolean = db.contains(fromParam(name)) 28 | 29 | def save(artist: Artist): Option[Artist] = db.put(artist.name, artist) 30 | 31 | def update(artist: Artist): Option[Artist] = db.put(artist.name, artist) 32 | 33 | def delete(name: String): Option[Artist] = db.remove(fromParam(name)) 34 | } -------------------------------------------------------------------------------- /chapter07-twirl/project/build.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | 4 | import org.scalatra.sbt.ScalatraPlugin 5 | import com.earldouglas.xwp.JettyPlugin 6 | import play.twirl.sbt.SbtTwirl 7 | 8 | object Chapter07TwirlBuild extends Build { 9 | val Organization = "org.scalatra" 10 | val Name = "views-twirl" 11 | val Version = "0.1.0-SNAPSHOT" 12 | val ScalaVersion = "2.11.6" 13 | val ScalatraVersion = "2.4.0" 14 | 15 | lazy val project = Project ( 16 | Name, 17 | file("."), 18 | settings = Seq( 19 | organization := Organization, 20 | name := Name, 21 | version := Version, 22 | scalaVersion := ScalaVersion, 23 | fork in Test := true, 24 | resolvers += Classpaths.typesafeReleases, 25 | libraryDependencies ++= Seq( 26 | "org.scalatra" %% "scalatra" % ScalatraVersion, 27 | "org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test", 28 | "ch.qos.logback" % "logback-classic" % "1.1.3" % "runtime", 29 | "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided" 30 | ) 31 | ) 32 | ).settings(ScalatraPlugin.scalatraSettings:_*) 33 | .enablePlugins(SbtTwirl, JettyPlugin) 34 | } 35 | -------------------------------------------------------------------------------- /comments-collector/src/main/webapp/WEB-INF/layouts/default.scaml: -------------------------------------------------------------------------------- 1 | - response.setContentType("text/html") 2 | - import java.net.URLEncoder.encode 3 | -@ val title: String 4 | -@ val body: String 5 | -@ val urls: Seq[String] 6 | 7 | !!! 8 | %html 9 | %head 10 | %title= title 11 | %link(rel="stylesheet" href={uri("/css/bootstrap.min.css")}) 12 | %link(rel="stylesheet" href={uri("/css/site.css")}) 13 | %script(src={uri("https://code.jquery.com/jquery-1.10.2.min.js")}) 14 | %script(src={uri("/js/bootstrap.min.js")}) 15 | %body 16 | 17 | .container 18 | .header 19 | %ul.nav.nav-pills.pull-right 20 | %li 21 | %a(href={uri("/")}) Home 22 | %li.dropdown 23 | %a(href="#" class="dropdown-toggle" data-toggle="dropdown") Explore 24 | 25 | %ul.dropdown-menu 26 | - if (urls.size > 0) 27 | - for (url <- urls) 28 | %li 29 | %a(href={uri("/"+encode(url, "utf-8"))})= url 30 | - else 31 | %li.dropdown-header No comments yet 32 | 33 | %h2 comments-collector 34 | 35 | != body 36 | 37 | 38 | -------------------------------------------------------------------------------- /chapter07/project/build.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | 4 | import org.scalatra.sbt.ScalatraPlugin 5 | import com.earldouglas.xwp.JettyPlugin 6 | 7 | object Chapter07Build extends Build { 8 | val Organization = "org.scalatra" 9 | val Name = "views-scalate" 10 | val Version = "0.1.0-SNAPSHOT" 11 | val ScalaVersion = "2.11.6" 12 | val ScalatraVersion = "2.4.0" 13 | 14 | lazy val project = Project ( 15 | Name, 16 | file("."), 17 | settings = Defaults.defaultConfigs ++ ScalatraPlugin.scalatraSettings ++ Seq( 18 | organization := Organization, 19 | name := Name, 20 | version := Version, 21 | scalaVersion := ScalaVersion, 22 | fork in Test := true, 23 | resolvers += Classpaths.typesafeReleases, 24 | libraryDependencies ++= Seq( 25 | "org.scalatra" %% "scalatra" % ScalatraVersion, 26 | "org.scalatra" %% "scalatra-scalate" % ScalatraVersion, 27 | "org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test", 28 | "ch.qos.logback" % "logback-classic" % "1.1.3" % "runtime", 29 | "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided" 30 | ) 31 | ) 32 | ).enablePlugins(JettyPlugin) 33 | } 34 | -------------------------------------------------------------------------------- /chapter13/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import com.constructiveproof.hackertracker._ 2 | import com.constructiveproof.hackertracker.init.DatabaseInit 3 | import javax.servlet.ServletContext 4 | import org.scalatra.LifeCycle 5 | import org.scalatra.swagger.{ApiInfo, Swagger} 6 | 7 | class ScalatraBootstrap extends LifeCycle with DatabaseInit { 8 | 9 | implicit val apiInfo = new ApiInfo( 10 | "The HackerTracker API", 11 | "Docs for the HackerTracker API", 12 | "http://www.constructiveproof.com/hacker-tracker/tos.html", 13 | "apiteam@constructiveproof.com", 14 | "MIT", 15 | "http://opensource.org/licenses/MIT") 16 | 17 | implicit val swagger = new Swagger("1.2", "1.0.0", apiInfo) 18 | 19 | override def init(context: ServletContext) { 20 | configureDb() 21 | context.mount(new ApiController, "/hackers-api", "hackers-api") 22 | context.mount(new HackersSwagger, "/api-docs") 23 | context.mount(new DatabaseSetupController, "/database") 24 | context.mount(new HackersController, "/hackers") 25 | context.mount(new SessionsController, "/sessions") 26 | } 27 | 28 | override def destroy(context: ServletContext) { 29 | closeDbConnection() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /chapter05/src/main/scala/org/scalatra/book/chapter06/json4s_custom_serializers.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter06 2 | 3 | import org.json4s._ 4 | import org.json4s.JsonDSL._ 5 | 6 | sealed trait Fact 7 | case class Energy(value: Int) extends Fact 8 | case class Carbohydrate(value: Double) extends Fact 9 | case class Fat(value: Double) extends Fact 10 | case class Protein(value: Double) extends Fact 11 | 12 | case class NutritionFacts( 13 | energy: Energy, 14 | carbohydrate: Carbohydrate, 15 | fat: Fat, 16 | protein: Protein) 17 | 18 | class NutritionFactsSerializer 19 | extends CustomSerializer[NutritionFacts](implicit formats => ({ 20 | case jv: JValue => 21 | val e = (jv \ "energy").extract[Int] 22 | val c = (jv \ "carbohydrate").extract[Double] 23 | val f = (jv \ "fat").extract[Double] 24 | val p = (jv \ "protein").extract[Double] 25 | 26 | NutritionFacts( 27 | Energy(e), Carbohydrate(c), Fat(f), Protein(p)) 28 | }, { 29 | case facts: NutritionFacts => 30 | ("energy" -> facts.energy.value) ~ 31 | ("carbohydrate" -> facts.carbohydrate.value) ~ 32 | ("fat" -> facts.fat.value) ~ 33 | ("protein" -> facts.protein.value) 34 | })) 35 | 36 | -------------------------------------------------------------------------------- /chapter02/src/main/webapp/WEB-INF/templates/layouts/default.ssp: -------------------------------------------------------------------------------- 1 | <%@ val body:String %> 2 | 3 | 4 | Scalatra CMS 5 | 6 | 7 | 11 | 12 | 13 | 31 |
32 | <%= unescape(body) %> 33 |
34 | 35 | -------------------------------------------------------------------------------- /chapter13/src/main/scala/com/constructiveproof/hackertracker/init/DatabaseInit.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.init 2 | 3 | import com.mchange.v2.c3p0.ComboPooledDataSource 4 | import org.squeryl.adapters.{H2Adapter, MySQLAdapter} 5 | import org.squeryl.Session 6 | import org.squeryl.SessionFactory 7 | import org.slf4j.LoggerFactory 8 | 9 | trait DatabaseInit { 10 | val logger = LoggerFactory.getLogger(getClass) 11 | 12 | val databaseUsername = "root" 13 | val databasePassword = "" 14 | val databaseConnection = "jdbc:h2:mem:hackerTracker" 15 | 16 | var cpds = new ComboPooledDataSource 17 | 18 | def configureDb() { 19 | cpds.setDriverClass("org.h2.Driver") 20 | cpds.setJdbcUrl(databaseConnection) 21 | cpds.setUser(databaseUsername) 22 | cpds.setPassword(databasePassword) 23 | 24 | cpds.setMinPoolSize(1) 25 | cpds.setAcquireIncrement(1) 26 | cpds.setMaxPoolSize(50) 27 | 28 | SessionFactory.concreteFactory = Some(() => connection) 29 | 30 | def connection = { 31 | logger.info("Creating connection with c3po connection pool") 32 | Session.create(cpds.getConnection, new H2Adapter) 33 | } 34 | } 35 | 36 | def closeDbConnection() { 37 | logger.info("Closing c3po connection pool") 38 | cpds.close() 39 | } 40 | } -------------------------------------------------------------------------------- /chapter03/project/build.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | 4 | import org.scalatra.sbt.ScalatraPlugin 5 | 6 | import com.earldouglas.xwp.JettyPlugin 7 | 8 | object Chapter03Build extends Build { 9 | val Organization = "org.scalatra" 10 | val Name = "record-store" 11 | val Version = "0.1.0-SNAPSHOT" 12 | val ScalaVersion = "2.11.6" 13 | val ScalatraVersion = "2.4.0" 14 | 15 | lazy val project = Project ( 16 | Name, 17 | file("."), 18 | settings = Defaults.defaultConfigs ++ ScalatraPlugin.scalatraSettings ++ Seq( 19 | organization := Organization, 20 | name := Name, 21 | version := Version, 22 | scalaVersion := ScalaVersion, 23 | fork in Test := true, 24 | resolvers += Classpaths.typesafeReleases, 25 | resolvers += "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases", 26 | libraryDependencies ++= Seq( 27 | "org.scalatra" %% "scalatra" % ScalatraVersion, 28 | "org.scalatra" %% "scalatra-scalate" % ScalatraVersion, 29 | "org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test", 30 | "ch.qos.logback" % "logback-classic" % "1.1.3" % "runtime", 31 | "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided" 32 | ) 33 | ) 34 | ).enablePlugins(JettyPlugin) 35 | } 36 | -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/src/main/scala/com/constructiveproof/hackertracker/init/DatabaseInit.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.init 2 | 3 | import java.io.File 4 | 5 | import com.mchange.v2.c3p0.ComboPooledDataSource 6 | import org.apache.commons.io.FileUtils 7 | import org.slf4j.LoggerFactory 8 | import org.squeryl.adapters.H2Adapter 9 | import org.squeryl.{Session, SessionFactory} 10 | 11 | trait DatabaseInit { 12 | val logger = LoggerFactory.getLogger(getClass) 13 | 14 | val dbDir = "/tmp/db" 15 | val dbPath = dbDir + "/hackertracker.db" 16 | val databaseConnection = "jdbc:h2:file:" + dbPath 17 | 18 | var cpds = new ComboPooledDataSource 19 | 20 | def configureDb() { 21 | cpds.setDriverClass("org.h2.Driver") 22 | cpds.setJdbcUrl(databaseConnection) 23 | 24 | cpds.setMinPoolSize(1) 25 | cpds.setAcquireIncrement(1) 26 | cpds.setMaxPoolSize(50) 27 | 28 | SessionFactory.concreteFactory = Some(() => connection) 29 | 30 | def connection = { 31 | logger.info("Creating connection with c3po connection pool") 32 | Session.create(cpds.getConnection, new H2Adapter) 33 | } 34 | } 35 | 36 | def closeDbConnection() { 37 | logger.info("Closing c3po connection pool") 38 | cpds.close() 39 | } 40 | 41 | def wipeDb = { 42 | FileUtils.deleteDirectory(new File(dbDir)) 43 | } 44 | } -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/scala/com/constructiveproof/hackertracker/init/DatabaseInit.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.init 2 | 3 | import java.io.File 4 | 5 | import com.mchange.v2.c3p0.ComboPooledDataSource 6 | import org.apache.commons.io.FileUtils 7 | import org.slf4j.LoggerFactory 8 | import org.squeryl.{Session, SessionFactory} 9 | import org.squeryl.adapters.H2Adapter 10 | 11 | trait DatabaseInit { 12 | val logger = LoggerFactory.getLogger(getClass) 13 | 14 | val dbDir = "/tmp/db" 15 | val dbPath = dbDir + "/hackertracker.db" 16 | val databaseConnection = "jdbc:h2:file:" + dbPath 17 | 18 | var cpds = new ComboPooledDataSource 19 | 20 | def configureDb() { 21 | cpds.setDriverClass("org.h2.Driver") 22 | cpds.setJdbcUrl(databaseConnection) 23 | 24 | cpds.setMinPoolSize(1) 25 | cpds.setAcquireIncrement(1) 26 | cpds.setMaxPoolSize(50) 27 | 28 | SessionFactory.concreteFactory = Some(() => connection) 29 | 30 | def connection = { 31 | logger.info("Creating connection with c3po connection pool") 32 | Session.create(cpds.getConnection, new H2Adapter) 33 | } 34 | } 35 | 36 | def closeDbConnection() { 37 | logger.info("Closing c3po connection pool") 38 | cpds.close() 39 | } 40 | 41 | def wipeDb = { 42 | FileUtils.deleteDirectory(new File(dbDir)) 43 | } 44 | } -------------------------------------------------------------------------------- /chapter13/src/main/scala/com/constructiveproof/hackertracker/auth/AuthenticationSupport.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.auth 2 | 3 | import com.constructiveproof.hackertracker.auth.strategies.{RememberMeStrategy, UserPasswordStrategy} 4 | import com.constructiveproof.hackertracker.models.User 5 | import org.scalatra.ScalatraBase 6 | import org.scalatra.auth.{ScentryConfig, ScentrySupport} 7 | import org.slf4j.LoggerFactory 8 | 9 | trait AuthenticationSupport extends ScalatraBase with ScentrySupport[User] { 10 | self: ScalatraBase => 11 | 12 | val logger = LoggerFactory.getLogger(getClass) 13 | 14 | 15 | protected val scentryConfig = (new ScentryConfig {}).asInstanceOf[ScentryConfiguration] 16 | 17 | protected def fromSession = { case id: String => User(id) } 18 | protected def toSession = { case usr: User => usr.id } 19 | 20 | protected def requireLogin() = { 21 | if(!isAuthenticated) { 22 | redirect("/sessions/new") 23 | } 24 | } 25 | 26 | 27 | override protected def configureScentry = { 28 | scentry.unauthenticated { 29 | scentry.strategies("UserPassword").unauthenticated() 30 | } 31 | } 32 | 33 | override protected def registerAuthStrategies = { 34 | scentry.register("UserPassword", app => new UserPasswordStrategy(app)) 35 | scentry.register("RememberMe", app => new RememberMeStrategy(app)) 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /chapter08/project/build.scala: -------------------------------------------------------------------------------- 1 | import org.scalatra.sbt.ScalatraPlugin 2 | import com.earldouglas.xwp.JettyPlugin 3 | import sbt._ 4 | import Keys._ 5 | 6 | 7 | object Chapter08Build extends Build { 8 | val Organization = "org.scalatra" 9 | val Name = "testing" 10 | val Version = "0.1.0-SNAPSHOT" 11 | val ScalaVersion = "2.11.6" 12 | val ScalatraVersion = "2.4.0" 13 | 14 | lazy val project = Project ( 15 | Name, 16 | file("."), 17 | settings = Defaults.defaultConfigs ++ ScalatraPlugin.scalatraSettings ++ Seq( 18 | organization := Organization, 19 | name := Name, 20 | version := Version, 21 | scalaVersion := ScalaVersion, 22 | fork in Test := true, 23 | resolvers += Classpaths.typesafeReleases, 24 | resolvers += "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases", 25 | libraryDependencies ++= Seq( 26 | "org.scalatra" %% "scalatra" % ScalatraVersion, 27 | "org.scalatra" %% "scalatra-json" % ScalatraVersion, 28 | "org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test", 29 | "org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test", 30 | "org.json4s" %% "json4s-jackson" % "3.3.0", 31 | "ch.qos.logback" % "logback-classic" % "1.1.3" % "runtime", 32 | "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided" 33 | ) 34 | ) 35 | ).enablePlugins(JettyPlugin) 36 | } 37 | -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/scala/com/constructiveproof/hackertracker/SessionsController.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker 2 | 3 | import com.constructiveproof.hackertracker.models.{Hacker} 4 | import com.constructiveproof.hackertracker.auth.AuthenticationSupport 5 | 6 | 7 | class SessionsController extends HackerTrackerStack with AuthenticationSupport { 8 | 9 | before("/new") { 10 | logger.info("SessionsController: checking whether to run RememberMeStrategy: " + !isAuthenticated) 11 | 12 | if(!isAuthenticated) { 13 | scentry.authenticate("RememberMe") 14 | } 15 | } 16 | 17 | get("/new") { 18 | if (isAuthenticated) redirect("/hackers") 19 | 20 | contentType="text/html" 21 | ssp("/sessions/new", "allHackers" -> Hacker.all, "authenticated" -> isAuthenticated) 22 | } 23 | 24 | post("/") { 25 | scentry.authenticate() 26 | 27 | if (isAuthenticated) { 28 | redirect("/hackers") 29 | }else{ 30 | redirect("/sessions/new") 31 | } 32 | } 33 | 34 | /** 35 | * Any action that has side-effects on the server should not be a GET (a DELETE would 36 | * be preferable here), but I'm going to make this a GET in order to avoid starting a discussion 37 | * of unobtrusive JavaScript and the creation of DELETE links at this point. 38 | */ 39 | get("/destroy") { 40 | scentry.logout() 41 | redirect("/hackers") 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /chapter10/src/test/scala/org/scalatra/book/chapter10/AppSpec.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter10 2 | 3 | import slick.driver.H2Driver.api._ 4 | import org.scalatest.{BeforeAndAfter, FunSuite} 5 | import org.scalatra.test.scalatest.ScalatraSuite 6 | 7 | import scala.concurrent.Await 8 | import scala.concurrent.duration.Duration 9 | 10 | class AppSpec extends FunSuite with ScalatraSuite with BeforeAndAfter { 11 | 12 | val jdbcUrl = "jdbc:h2:mem:chapter10;DB_CLOSE_DELAY=-1" 13 | val jdbcDriverClass = "org.h2.Driver" 14 | val db = Database.forURL(jdbcUrl, driver = jdbcDriverClass) 15 | val res = db.run(DbSetup.createDatabase) 16 | 17 | Await.result(res, Duration(2, "seconds")) 18 | 19 | addServlet(new Chapter10App(db), "/*") 20 | 21 | test("get all areas") { 22 | get("/areas") { 23 | status should equal(200) 24 | } 25 | } 26 | 27 | test("get one area") { 28 | get("/areas/2") { 29 | status should equal(200) 30 | } 31 | } 32 | 33 | test("modify a route") { 34 | put("/routes/1?routeName=foo&description=bar") { 35 | status should equal(200) 36 | } 37 | 38 | put("/routes/100?routeName=foo&description=bar") { 39 | status should equal(404) 40 | } 41 | } 42 | 43 | test("delete a route") { 44 | delete("/routes/1") { 45 | status should equal(200) 46 | } 47 | 48 | delete("/routes/1") { 49 | status should equal(404) 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /chapter13/src/main/scala/com/constructiveproof/hackertracker/SessionsController.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker 2 | 3 | import com.constructiveproof.hackertracker.auth.AuthenticationSupport 4 | import com.constructiveproof.hackertracker.models.Hacker 5 | import com.constructiveproof.hackertracker.stacks.BrowserStack 6 | 7 | class SessionsController extends BrowserStack with AuthenticationSupport { 8 | 9 | before("/new") { 10 | logger.info("SessionsController: checking whether to run RememberMeStrategy: " + !isAuthenticated) 11 | 12 | if(!isAuthenticated) { 13 | scentry.authenticate("RememberMe") 14 | } 15 | } 16 | 17 | get("/new") { 18 | if (isAuthenticated) redirect("/hackers") 19 | 20 | contentType="text/html" 21 | ssp("/sessions/new", "allHackers" -> Hacker.all, "authenticated" -> isAuthenticated) 22 | } 23 | 24 | post("/") { 25 | scentry.authenticate() 26 | 27 | if (isAuthenticated) { 28 | redirect("/hackers") 29 | }else{ 30 | redirect("/sessions/new") 31 | } 32 | } 33 | 34 | /** 35 | * Any action that has side-effects on the server should not be a GET (a DELETE would 36 | * be preferable here), but I'm going to make this a GET in order to avoid starting a discussion 37 | * of unobtrusive JavaScript and the creation of DELETE links at this point. 38 | */ 39 | get("/destroy") { 40 | scentry.logout() 41 | redirect("/hackers") 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/scala/com/constructiveproof/hackertracker/auth/AuthenticationSupport.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.auth 2 | 3 | import org.scalatra.ScalatraBase 4 | import org.scalatra.auth.{ScentryConfig, ScentrySupport} 5 | import com.constructiveproof.hackertracker.auth.strategies.UserPasswordStrategy 6 | import com.constructiveproof.hackertracker.models.User 7 | import com.constructiveproof.hackertracker.auth.strategies.RememberMeStrategy 8 | import org.slf4j.LoggerFactory 9 | 10 | trait AuthenticationSupport extends ScalatraBase with ScentrySupport[User] { 11 | self: ScalatraBase => 12 | 13 | val logger = LoggerFactory.getLogger(getClass) 14 | 15 | 16 | protected val scentryConfig = (new ScentryConfig {}).asInstanceOf[ScentryConfiguration] 17 | 18 | protected def fromSession = { case id: String => User(id) } 19 | protected def toSession = { case usr: User => usr.id } 20 | 21 | protected def requireLogin() = { 22 | if(!isAuthenticated) { 23 | redirect("/sessions/new") 24 | } 25 | } 26 | 27 | 28 | override protected def configureScentry = { 29 | scentry.unauthenticated { 30 | scentry.strategies("UserPassword").unauthenticated() 31 | } 32 | } 33 | 34 | override protected def registerAuthStrategies = { 35 | scentry.register("UserPassword", app => new UserPasswordStrategy(app)) 36 | scentry.register("RememberMe", app => new RememberMeStrategy(app)) 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /chapter13/src/main/scala/com/constructiveproof/hackertracker/auth/OurBasicAuthenticationSupport.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.auth 2 | 3 | import org.scalatra.auth.{ScentryConfig, ScentrySupport} 4 | import org.scalatra.auth.strategy.BasicAuthSupport 5 | import org.scalatra.ScalatraBase 6 | import com.constructiveproof.hackertracker.auth.strategies.OurBasicAuthStrategy 7 | import com.constructiveproof.hackertracker.models.User 8 | 9 | /** 10 | * Mix this trait into any controller and it'll require HTTP Basic Authentication using the OurBasicAuthStrategy. 11 | * 12 | * To protect individual routes, just add "basicAuth" at the top of the action, or use a before() filter to protect the whole controller. 13 | */ 14 | trait OurBasicAuthenticationSupport extends ScentrySupport[User] with BasicAuthSupport[User] { 15 | self: ScalatraBase => 16 | 17 | val realm = "Scalatra Basic Auth Example" 18 | 19 | protected def fromSession = { case id: String => User(id) } 20 | protected def toSession = { case usr: User => usr.id } 21 | 22 | protected val scentryConfig = (new ScentryConfig {}).asInstanceOf[ScentryConfiguration] 23 | 24 | 25 | override protected def configureScentry = { 26 | scentry.unauthenticated { 27 | scentry.strategies("Basic").unauthenticated() 28 | } 29 | } 30 | 31 | override protected def registerAuthStrategies = { 32 | scentry.register("Basic", app => new OurBasicAuthStrategy(app, realm)) 33 | } 34 | 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /chapter05/project/build.scala: -------------------------------------------------------------------------------- 1 | import org.scalatra.sbt.ScalatraPlugin 2 | import com.earldouglas.xwp.JettyPlugin 3 | import sbt._ 4 | import Keys._ 5 | 6 | object Chapter05Build extends Build { 7 | 8 | val Organization = "org.scalatra" 9 | val Name = "foods" 10 | val Version = "0.1.0-SNAPSHOT" 11 | val ScalaVersion = "2.11.6" 12 | val ScalatraVersion = "2.4.0" 13 | 14 | val mySettings = Defaults.defaultConfigs ++ 15 | ScalatraPlugin.scalatraWithDist ++ 16 | Seq( 17 | organization := Organization, 18 | name := Name, 19 | version := Version, 20 | scalaVersion := ScalaVersion, 21 | fork in Test := true, 22 | resolvers += Classpaths.typesafeReleases, 23 | resolvers += "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases", 24 | libraryDependencies ++= Seq( 25 | "org.scalatra" %% "scalatra" % ScalatraVersion, 26 | "org.scalatra" %% "scalatra-scalate" % ScalatraVersion, 27 | "org.scalatra" %% "scalatra-json" % ScalatraVersion, 28 | "org.json4s" %% "json4s-jackson" % "3.3.0", 29 | "org.scalaz" %% "scalaz-core" % "7.1.2", 30 | "org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test", 31 | "ch.qos.logback" % "logback-classic" % "1.1.3" % "runtime", 32 | "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided" 33 | ) 34 | ) 35 | 36 | lazy val project = Project(Name, file(".")) 37 | .enablePlugins(JettyPlugin) 38 | .settings(mySettings:_*) 39 | 40 | } 41 | -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/scala/com/constructiveproof/hackertracker/auth/OurBasicAuthenticationSupport.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.auth 2 | 3 | import org.scalatra.auth.{ScentryConfig, ScentrySupport} 4 | import org.scalatra.auth.strategy.BasicAuthSupport 5 | import org.scalatra.ScalatraBase 6 | import com.constructiveproof.hackertracker.auth.strategies.OurBasicAuthStrategy 7 | import com.constructiveproof.hackertracker.models.User 8 | 9 | /** 10 | * Mix this trait into any controller and it'll require HTTP Basic Authentication using the OurBasicAuthStrategy. 11 | * 12 | * To protect individual routes, just add "basicAuth" at the top of the action, or use a before() filter to protect the whole controller. 13 | */ 14 | trait OurBasicAuthenticationSupport extends ScentrySupport[User] with BasicAuthSupport[User] { 15 | self: ScalatraBase => 16 | 17 | val realm = "Scalatra Basic Auth Example" 18 | 19 | protected def fromSession = { case id: String => User(id) } 20 | protected def toSession = { case usr: User => usr.id } 21 | 22 | protected val scentryConfig = (new ScentryConfig {}).asInstanceOf[ScentryConfiguration] 23 | 24 | 25 | override protected def configureScentry = { 26 | scentry.unauthenticated { 27 | scentry.strategies("Basic").unauthenticated() 28 | } 29 | } 30 | 31 | override protected def registerAuthStrategies = { 32 | scentry.register("Basic", app => new OurBasicAuthStrategy(app, realm)) 33 | } 34 | 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/src/main/scala/com/constructiveproof/hackertracker/HackersController.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker 2 | 3 | import com.constructiveproof.hackertracker.models.Hacker 4 | import org.scalatra._ 5 | 6 | class HackersController extends HackerTrackerStack { 7 | 8 | before() { 9 | contentType = "text/html" 10 | } 11 | 12 | /** 13 | * Show all hackers. 14 | */ 15 | get("/") { 16 | ssp("/hackers/index", "allHackers" -> Hacker.all) 17 | } 18 | 19 | /** 20 | * Show a specific hacker. 21 | */ 22 | get("/:id") { 23 | val id = params.getAs[Int]("id").getOrElse(0) 24 | val hacker = Hacker.get(id) 25 | ssp("/hackers/show", "hacker" -> hacker, "allHackers" -> Hacker.all) 26 | } 27 | 28 | /** 29 | * Display a form for creating a new hacker. 30 | */ 31 | get("/new") { 32 | ssp("/hackers/new", "allHackers" -> Hacker.all) 33 | } 34 | 35 | 36 | /** 37 | * Create a new hacker in the database. 38 | */ 39 | post("/") { 40 | val firstName = params("firstname") 41 | val lastName = params("lastname") 42 | val motto = params("motto") 43 | val birthYear = params.getAs[Int]("birthyear").getOrElse( 44 | halt(BadRequest("Please provide a year of birth."))) 45 | 46 | val hacker = new Hacker(0, firstName, lastName, motto, birthYear) 47 | 48 | if(Hacker.create(hacker)) { 49 | flash("notice") = "Hacker successfully persisted." 50 | redirect("/hackers/" + hacker.id) 51 | } 52 | } 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /comments-collector/src/main/scala/ScalatraBootstrap.scala: -------------------------------------------------------------------------------- 1 | import comments._ 2 | 3 | import org.scalatra._ 4 | import org.scalatra.servlet._ 5 | import org.scalatra.swagger._ 6 | 7 | import com.mongodb.casbah.Imports._ 8 | 9 | import javax.servlet.ServletContext 10 | import javax.servlet.http.HttpServlet 11 | 12 | class ScalatraBootstrap extends LifeCycle { 13 | 14 | // create an implicit instance of ApiInfo which publishes additional information 15 | implicit val apiInfo = new ApiInfo("The comments API", 16 | "Docs for the comments API", 17 | "http://www.manning.com/carrero2/", 18 | "Ross", "MIT", "http://scalatra.org") 19 | 20 | // create an implicit instance of Swagger which is passed to both servlets 21 | implicit val swagger = new Swagger("1.0", "1", apiInfo) 22 | 23 | // create a mongodb client and collection 24 | val mongoClient = MongoClient() 25 | val mongoColl = mongoClient("comments_collector")("comments") 26 | 27 | override def init(context: ServletContext) { 28 | 29 | // create a comments repository using the mongo collection 30 | val comments = CommentsRepository(mongoColl) 31 | 32 | // mount the api + swagger docs 33 | context.mount(new CommentsApi(comments), "/api", "api") 34 | context.mount(new CommentsApiDoc(), "/api-docs") 35 | 36 | // mount the html frontend 37 | context.mount(new CommentsFrontend(comments), "/") 38 | 39 | } 40 | 41 | override def destroy(context: ServletContext) { 42 | 43 | // shutdown the mongo client 44 | mongoClient.close 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /chapter10/project/build.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | import org.scalatra.sbt._ 4 | import com.earldouglas.xwp.JettyPlugin 5 | 6 | object Chapter10Build extends Build { 7 | 8 | val Organization = "org.scalatra" 9 | val Name = "climbing-routes" 10 | val Version = "0.1.0-SNAPSHOT" 11 | val ScalaVersion = "2.11.7" 12 | val ScalatraVersion = "2.4.0" 13 | 14 | val mySettings = Defaults.defaultConfigs ++ 15 | ScalatraPlugin.scalatraSettings ++ 16 | Seq( 17 | organization := Organization, 18 | name := Name, 19 | version := Version, 20 | scalaVersion := ScalaVersion, 21 | fork in Test := true, 22 | resolvers += Classpaths.typesafeReleases, 23 | resolvers += "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases", 24 | libraryDependencies ++= Seq( 25 | "org.scalatra" %% "scalatra" % ScalatraVersion, 26 | "org.scalatra" %% "scalatra-scalate" % ScalatraVersion, 27 | "org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test", 28 | "org.scalaz" %% "scalaz-core" % "7.1.2", 29 | "ch.qos.logback" % "logback-classic" % "1.1.3" % "runtime", 30 | "com.typesafe.slick" %% "slick" % "3.0.0", 31 | "com.h2database" % "h2" % "1.4.187", 32 | "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided", 33 | "org.scala-lang" % "scala-compiler" % ScalaVersion 34 | ) 35 | ) 36 | 37 | lazy val project = Project("climbing-routes", file(".")) 38 | .settings(mySettings:_*) 39 | .enablePlugins(JettyPlugin) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /chapter09-sbtweb/src/test/scala/org/scalatra/book/chapter09/Chapter09Spec.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | import org.eclipse.jetty.servlet.ServletContextHandler 4 | import org.scalatra.test.specs2._ 5 | 6 | class Chapter09Spec extends MutableScalatraSpec { 7 | 8 | val conf = AppConfig.load 9 | sys.props(org.scalatra.EnvironmentKey) = AppEnvironment.asString(conf.env) 10 | 11 | // use target/web/stage as resourceBase 12 | override lazy val servletContextHandler = { 13 | val handler = new ServletContextHandler(ServletContextHandler.SESSIONS) 14 | handler.setContextPath(contextPath) 15 | handler.setResourceBase("target/web/stage") 16 | handler 17 | } 18 | 19 | addServlet(new Chapter09(conf), "/*") 20 | 21 | "/ should should execute an action" in { 22 | get("/") { 23 | status must_== 200 24 | } 25 | } 26 | 27 | "/shorten-url should should execute an action" in { 28 | get("/shorten-url") { 29 | status must_== 200 30 | } 31 | } 32 | 33 | "/static.txt should return static file" in { 34 | get("/static.txt") { 35 | status must_== 200 36 | body must_== "this is static text!" 37 | } 38 | } 39 | 40 | "/hello-scalate should render a template" in { 41 | get("/hello-scalate") { 42 | status must_== 200 43 | body must_== 44 | """

Hello, Scalate!

45 | |""".stripMargin 46 | } 47 | } 48 | 49 | "/css/main.css should return a CSS file" in { 50 | get("/css/main.css") { 51 | status must_== 200 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /chapter13/src/main/webapp/js/foundation/foundation.alerts.js: -------------------------------------------------------------------------------- 1 | /*jslint unparam: true, browser: true, indent: 2 */ 2 | 3 | ;(function ($, window, document, undefined) { 4 | 'use strict'; 5 | 6 | Foundation.libs.alerts = { 7 | name : 'alerts', 8 | 9 | version : '4.3.2', 10 | 11 | settings : { 12 | animation: 'fadeOut', 13 | speed: 300, // fade out speed 14 | callback: function (){} 15 | }, 16 | 17 | init : function (scope, method, options) { 18 | this.scope = scope || this.scope; 19 | Foundation.inherit(this, 'data_options'); 20 | 21 | if (typeof method === 'object') { 22 | $.extend(true, this.settings, method); 23 | } 24 | 25 | if (typeof method !== 'string') { 26 | if (!this.settings.init) { this.events(); } 27 | 28 | return this.settings.init; 29 | } else { 30 | return this[method].call(this, options); 31 | } 32 | }, 33 | 34 | events : function () { 35 | var self = this; 36 | 37 | $(this.scope).on('click.fndtn.alerts', '[data-alert] a.close', function (e) { 38 | var alertBox = $(this).closest("[data-alert]"), 39 | settings = $.extend({}, self.settings, self.data_options(alertBox)); 40 | 41 | e.preventDefault(); 42 | alertBox[settings.animation](settings.speed, function () { 43 | $(this).remove(); 44 | settings.callback(); 45 | }); 46 | }); 47 | 48 | this.settings.init = true; 49 | }, 50 | 51 | off : function () { 52 | $(this.scope).off('.fndtn.alerts'); 53 | }, 54 | 55 | reflow : function () {} 56 | }; 57 | }(Foundation.zj, this, this.document)); 58 | -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/src/main/scala/com/constructiveproof/hackertracker/models/Models.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.models 2 | 3 | import org.squeryl.PrimitiveTypeMode._ 4 | import org.squeryl.Schema 5 | import org.squeryl.KeyedEntity 6 | import org.squeryl.PersistenceStatus 7 | 8 | /** 9 | * A hacker in the tracker. 10 | */ 11 | case class Hacker(val id: Long, val firstName: String, val lastName: String, val motto: String, val birthYear: Int) extends SquerylRecord { 12 | def this() = this(0, "Foo", "McBar", "It's better to ask forgiveness than permission", 1950) 13 | } 14 | 15 | /** 16 | * The BlogDb object acts as a cross between a Dao and a Schema definition file. 17 | */ 18 | object Db extends Schema { 19 | 20 | def init = { 21 | inTransaction { 22 | Db.create 23 | } 24 | } 25 | 26 | val hackers = table[Hacker]("hackers") 27 | on(hackers)(a => declare( 28 | a.id is(autoIncremented))) 29 | } 30 | 31 | object Hacker { 32 | def create(hacker:Hacker):Boolean = { 33 | inTransaction { 34 | val result = Db.hackers.insert(hacker) 35 | if(result.isPersisted) { 36 | true 37 | } else { 38 | false 39 | } 40 | } 41 | } 42 | 43 | def all = { 44 | from(Db.hackers)(select(_)) 45 | } 46 | 47 | def get(id: Long) = { 48 | Db.hackers.where(h => h.id === id).single 49 | } 50 | } 51 | 52 | /** 53 | * This trait is just a way to aggregate our model style across multiple 54 | * models so that we have a single point of change if we want to add 55 | * anything to our model behaviour 56 | */ 57 | trait SquerylRecord extends KeyedEntity[Long] with PersistenceStatus { 58 | 59 | } -------------------------------------------------------------------------------- /chapter11/1-hacker-tracker-unprotected/src/main/webapp/js/foundation/foundation.alerts.js: -------------------------------------------------------------------------------- 1 | /*jslint unparam: true, browser: true, indent: 2 */ 2 | 3 | ;(function ($, window, document, undefined) { 4 | 'use strict'; 5 | 6 | Foundation.libs.alerts = { 7 | name : 'alerts', 8 | 9 | version : '4.3.2', 10 | 11 | settings : { 12 | animation: 'fadeOut', 13 | speed: 300, // fade out speed 14 | callback: function (){} 15 | }, 16 | 17 | init : function (scope, method, options) { 18 | this.scope = scope || this.scope; 19 | Foundation.inherit(this, 'data_options'); 20 | 21 | if (typeof method === 'object') { 22 | $.extend(true, this.settings, method); 23 | } 24 | 25 | if (typeof method !== 'string') { 26 | if (!this.settings.init) { this.events(); } 27 | 28 | return this.settings.init; 29 | } else { 30 | return this[method].call(this, options); 31 | } 32 | }, 33 | 34 | events : function () { 35 | var self = this; 36 | 37 | $(this.scope).on('click.fndtn.alerts', '[data-alert] a.close', function (e) { 38 | var alertBox = $(this).closest("[data-alert]"), 39 | settings = $.extend({}, self.settings, self.data_options(alertBox)); 40 | 41 | e.preventDefault(); 42 | alertBox[settings.animation](settings.speed, function () { 43 | $(this).remove(); 44 | settings.callback(); 45 | }); 46 | }); 47 | 48 | this.settings.init = true; 49 | }, 50 | 51 | off : function () { 52 | $(this.scope).off('.fndtn.alerts'); 53 | }, 54 | 55 | reflow : function () {} 56 | }; 57 | }(Foundation.zj, this, this.document)); 58 | -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/webapp/js/foundation/foundation.alerts.js: -------------------------------------------------------------------------------- 1 | /*jslint unparam: true, browser: true, indent: 2 */ 2 | 3 | ;(function ($, window, document, undefined) { 4 | 'use strict'; 5 | 6 | Foundation.libs.alerts = { 7 | name : 'alerts', 8 | 9 | version : '4.3.2', 10 | 11 | settings : { 12 | animation: 'fadeOut', 13 | speed: 300, // fade out speed 14 | callback: function (){} 15 | }, 16 | 17 | init : function (scope, method, options) { 18 | this.scope = scope || this.scope; 19 | Foundation.inherit(this, 'data_options'); 20 | 21 | if (typeof method === 'object') { 22 | $.extend(true, this.settings, method); 23 | } 24 | 25 | if (typeof method !== 'string') { 26 | if (!this.settings.init) { this.events(); } 27 | 28 | return this.settings.init; 29 | } else { 30 | return this[method].call(this, options); 31 | } 32 | }, 33 | 34 | events : function () { 35 | var self = this; 36 | 37 | $(this.scope).on('click.fndtn.alerts', '[data-alert] a.close', function (e) { 38 | var alertBox = $(this).closest("[data-alert]"), 39 | settings = $.extend({}, self.settings, self.data_options(alertBox)); 40 | 41 | e.preventDefault(); 42 | alertBox[settings.animation](settings.speed, function () { 43 | $(this).remove(); 44 | settings.callback(); 45 | }); 46 | }); 47 | 48 | this.settings.init = true; 49 | }, 50 | 51 | off : function () { 52 | $(this.scope).off('.fndtn.alerts'); 53 | }, 54 | 55 | reflow : function () {} 56 | }; 57 | }(Foundation.zj, this, this.document)); 58 | -------------------------------------------------------------------------------- /chapter09/src/main/scala/org/scalatra/book/chapter09/AppConfig.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | 4 | import com.typesafe.config.ConfigFactory 5 | 6 | case class AppConfig( 7 | webBase: String, 8 | env: AppEnvironment, 9 | mailConfig: MailConfig) { 10 | 11 | def isProduction = env == Production 12 | def isDevelopment = env == Development 13 | } 14 | 15 | case class MailConfig( 16 | user: String, 17 | password: String, 18 | host: String, 19 | sender: String) 20 | 21 | sealed trait AppEnvironment 22 | case object Development extends AppEnvironment 23 | case object Staging extends AppEnvironment 24 | case object Test extends AppEnvironment 25 | case object Production extends AppEnvironment 26 | 27 | object AppEnvironment { 28 | def fromString(s: String): AppEnvironment = { 29 | s match { 30 | case "development" => Development 31 | case "staging" => Staging 32 | case "test" => Test 33 | case "production" => Production 34 | } 35 | } 36 | 37 | def asString(s: AppEnvironment): String = { 38 | s match { 39 | case Development => "development" 40 | case Staging => "staging" 41 | case Test => "test" 42 | case Production => "production" 43 | } 44 | } 45 | } 46 | 47 | object AppConfig { 48 | def load: AppConfig = { 49 | val cfg = ConfigFactory.load 50 | 51 | val webBase = cfg.getString("webBase") 52 | val env = AppEnvironment.fromString(cfg.getString("environment")) 53 | val mailConfig = MailConfig( 54 | cfg.getString("email.user"), 55 | cfg.getString("email.password"), 56 | cfg.getString("email.host"), 57 | cfg.getString("email.sender")) 58 | 59 | AppConfig(webBase, env, mailConfig) 60 | } 61 | } -------------------------------------------------------------------------------- /chapter09-sbt/src/main/scala/org/scalatra/book/chapter09/AppConfig.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | 4 | import com.typesafe.config.ConfigFactory 5 | 6 | case class AppConfig( 7 | webBase: String, 8 | env: AppEnvironment, 9 | mailConfig: MailConfig) { 10 | 11 | def isProduction = env == Production 12 | def isDevelopment = env == Development 13 | } 14 | 15 | case class MailConfig( 16 | user: String, 17 | password: String, 18 | host: String, 19 | sender: String) 20 | 21 | sealed trait AppEnvironment 22 | case object Development extends AppEnvironment 23 | case object Staging extends AppEnvironment 24 | case object Test extends AppEnvironment 25 | case object Production extends AppEnvironment 26 | 27 | object AppEnvironment { 28 | def fromString(s: String): AppEnvironment = { 29 | s match { 30 | case "development" => Development 31 | case "staging" => Staging 32 | case "test" => Test 33 | case "production" => Production 34 | } 35 | } 36 | 37 | def asString(s: AppEnvironment): String = { 38 | s match { 39 | case Development => "development" 40 | case Staging => "staging" 41 | case Test => "test" 42 | case Production => "production" 43 | } 44 | } 45 | } 46 | 47 | object AppConfig { 48 | def load: AppConfig = { 49 | val cfg = ConfigFactory.load 50 | 51 | val webBase = cfg.getString("webBase") 52 | val env = AppEnvironment.fromString(cfg.getString("environment")) 53 | val mailConfig = MailConfig( 54 | cfg.getString("email.user"), 55 | cfg.getString("email.password"), 56 | cfg.getString("email.host"), 57 | cfg.getString("email.sender")) 58 | 59 | AppConfig(webBase, env, mailConfig) 60 | } 61 | } -------------------------------------------------------------------------------- /chapter09-sbtweb/src/main/scala/org/scalatra/book/chapter09/AppConfig.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter09 2 | 3 | import com.typesafe.config.ConfigFactory 4 | 5 | case class AppConfig( 6 | webBase: String, 7 | env: AppEnvironment, 8 | mailConfig: MailConfig) { 9 | 10 | def isProduction = env == Production 11 | def isDevelopment = env == Development 12 | } 13 | 14 | case class MailConfig( 15 | user: String, 16 | password: String, 17 | host: String, 18 | sender: String) 19 | 20 | sealed trait AppEnvironment 21 | case object Development extends AppEnvironment 22 | case object Staging extends AppEnvironment 23 | case object Test extends AppEnvironment 24 | case object Production extends AppEnvironment 25 | 26 | object AppEnvironment { 27 | def fromString(s: String): AppEnvironment = { 28 | s match { 29 | case "development" => Development 30 | case "staging" => Staging 31 | case "test" => Test 32 | case "production" => Production 33 | } 34 | } 35 | 36 | def asString(s: AppEnvironment): String = { 37 | s match { 38 | case Development => "development" 39 | case Staging => "staging" 40 | case Test => "test" 41 | case Production => "production" 42 | } 43 | } 44 | } 45 | 46 | object AppConfig { 47 | def load: AppConfig = { 48 | val cfg = ConfigFactory.load 49 | 50 | val webBase = cfg.getString("webBase") 51 | val env = AppEnvironment.fromString(cfg.getString("environment")) 52 | val mailConfig = MailConfig( 53 | cfg.getString("email.user"), 54 | cfg.getString("email.password"), 55 | cfg.getString("email.host"), 56 | cfg.getString("email.sender")) 57 | 58 | AppConfig(webBase, env, mailConfig) 59 | } 60 | } -------------------------------------------------------------------------------- /chapter06/src/main/scala/org/scalatra/book/chapter06/store.scala: -------------------------------------------------------------------------------- 1 | package org.scalatra.book.chapter06 2 | 3 | import java.io.{File, FileOutputStream, InputStream, OutputStream} 4 | import java.util.concurrent.atomic.AtomicLong 5 | 6 | case class Document(id: Long, name: String, contentType: Option[String], description: String) 7 | 8 | // simple document store 9 | // - an index of documents exists in-memory 10 | // - the documents are saved as files on the filesystem 11 | case class DocumentStore(base: String) { 12 | 13 | private val fileNameIndex = collection.concurrent.TrieMap[Long, Document]() 14 | 15 | private val idCounter = new AtomicLong(0) 16 | 17 | // adds a new document 18 | def add(name: String, in: InputStream, contentType: Option[String], description: String): Long = { 19 | val id = idCounter.getAndIncrement 20 | val out = new FileOutputStream(getFile(id)) 21 | // Files.copy(in, out) 22 | copyStream(in, out) 23 | fileNameIndex(id) = Document(id, name, contentType, description) 24 | id 25 | } 26 | 27 | // a sequence of all documents 28 | def list: Seq[Document] = { 29 | fileNameIndex.values.toSeq 30 | } 31 | 32 | // a document for a given id 33 | def getDocument(id: Long): Option[Document] = { 34 | fileNameIndex.get(id) 35 | } 36 | 37 | // a file for a given id 38 | def getFile(id: Long): File = new File(f"$base/$id") 39 | 40 | // writes an input stream to an output stream 41 | private def copyStream(input: InputStream, output: OutputStream) { 42 | val buffer = Array.ofDim[Byte](1024) 43 | var bytesRead: Int = 0 44 | while (bytesRead != -1) { 45 | bytesRead = input.read(buffer) 46 | if (bytesRead > 0) output.write(buffer, 0, bytesRead) 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /chapter13/src/main/scala/com/constructiveproof/hackertracker/HackersController.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker 2 | 3 | import com.constructiveproof.hackertracker.auth.AuthenticationSupport 4 | import com.constructiveproof.hackertracker.models.Hacker 5 | import com.constructiveproof.hackertracker.stacks.BrowserStack 6 | import org.scalatra._ 7 | 8 | class HackersController extends BrowserStack with AuthenticationSupport { 9 | 10 | before() { 11 | contentType = "text/html" 12 | } 13 | 14 | /** 15 | * Show all hackers. 16 | */ 17 | get("/") { 18 | ssp("/hackers/index", "allHackers" -> Hacker.all, "authenticated" -> isAuthenticated) 19 | } 20 | 21 | /** 22 | * Show a specific hacker. 23 | */ 24 | get("/:id") { 25 | val id = params.getAs[Int]("id").getOrElse(0) 26 | val hacker = Hacker.get(id) 27 | ssp("/hackers/show", "hacker" -> hacker, "allHackers" -> Hacker.all, "authenticated" -> isAuthenticated) 28 | } 29 | 30 | /** 31 | * Display a form for creating a new hacker. 32 | */ 33 | get("/new") { 34 | requireLogin 35 | ssp("/hackers/new", "allHackers" -> Hacker.all, "authenticated" -> isAuthenticated) 36 | } 37 | 38 | 39 | /** 40 | * Create a new hacker in the database. 41 | */ 42 | post("/") { 43 | requireLogin 44 | val firstName = params("firstname") 45 | val lastName = params("lastname") 46 | val motto = params("motto") 47 | val birthYear = params.getAs[Int]("birthyear").getOrElse( 48 | halt(BadRequest("Please provide a year of birth."))) 49 | 50 | val hacker = new Hacker(0, firstName, lastName, motto, birthYear) 51 | 52 | if(Hacker.create(hacker)) { 53 | flash("notice") = "Hacker successfully persisted." 54 | redirect("/hackers/" + hacker.id) 55 | } 56 | } 57 | 58 | } 59 | 60 | -------------------------------------------------------------------------------- /chapter11/2-hacker-tracker-protected/src/main/scala/com/constructiveproof/hackertracker/models/Models.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.models 2 | 3 | import org.squeryl.PrimitiveTypeMode._ 4 | import org.squeryl.Schema 5 | import org.squeryl.KeyedEntity 6 | import org.squeryl.PersistenceStatus 7 | 8 | 9 | case class User(val id:String) { 10 | def forgetMe() = { 11 | println("Destroying token in datastore") 12 | } 13 | } 14 | 15 | /** 16 | * A hacker in the tracker. 17 | */ 18 | case class Hacker(val id: Long, val firstName: String, val lastName: String, val motto: String, val birthYear: Int) extends SquerylRecord { 19 | def this() = this(0, "Foo", "McBar", "It's better to ask forgiveness than permission", 1950) 20 | } 21 | 22 | /** 23 | * The BlogDb object acts as a cross between a Dao and a Schema definition file. 24 | */ 25 | object Db extends Schema { 26 | 27 | def init = { 28 | inTransaction { 29 | Db.create 30 | } 31 | } 32 | 33 | val hackers = table[Hacker]("hackers") 34 | on(hackers)(a => declare( 35 | a.id is(autoIncremented))) 36 | } 37 | 38 | object Hacker { 39 | def create(hacker:Hacker):Boolean = { 40 | inTransaction { 41 | val result = Db.hackers.insert(hacker) 42 | if(result.isPersisted) { 43 | true 44 | } else { 45 | false 46 | } 47 | } 48 | } 49 | 50 | def all = { 51 | from(Db.hackers)(select(_)) 52 | } 53 | 54 | def get(id: Long) = { 55 | Db.hackers.where(h => h.id === id).single 56 | } 57 | } 58 | 59 | /** 60 | * This trait is just a way to aggregate our model style across multiple 61 | * models so that we have a single point of change if we want to add 62 | * anything to our model behaviour 63 | */ 64 | trait SquerylRecord extends KeyedEntity[Long] with PersistenceStatus { 65 | 66 | } -------------------------------------------------------------------------------- /chapter13/src/main/scala/com/constructiveproof/hackertracker/models/Models.scala: -------------------------------------------------------------------------------- 1 | package com.constructiveproof.hackertracker.models 2 | 3 | import org.squeryl.{KeyedEntity, PersistenceStatus, Schema} 4 | import org.squeryl.PrimitiveTypeMode._ 5 | 6 | case class User(val id: String) { 7 | def forgetMe() = { 8 | println("Destroying token in datastore") 9 | } 10 | } 11 | 12 | /** 13 | * A hacker in the tracker. 14 | */ 15 | case class Hacker(val id: Long, val firstName: String, val lastName: String, val motto: String, val birthYear: Int) extends SquerylRecord { 16 | def this() = this(0, "Foo", "McBar", "It's better to ask forgiveness than permission", 1950) 17 | } 18 | 19 | 20 | /** 21 | * The BlogDb object acts as a cross between a Dao and a Schema definition file. 22 | */ 23 | object Db extends Schema { 24 | 25 | def init = { 26 | inTransaction { 27 | Db.create 28 | } 29 | } 30 | 31 | val hackers = table[Hacker]("hackers") 32 | on(hackers)(a => declare( 33 | a.id is (autoIncremented))) 34 | } 35 | 36 | object Hacker { 37 | def create(hacker: Hacker): Boolean = { 38 | inTransaction { 39 | val result = Db.hackers.insert(hacker) 40 | if (result.isPersisted) { 41 | true 42 | } else { 43 | false 44 | } 45 | } 46 | } 47 | 48 | def all = { 49 | from(Db.hackers)(select(_)) 50 | } 51 | 52 | def get(id: Long) = { 53 | Db.hackers.where(h => h.id === id).single 54 | } 55 | 56 | def destroy(id: Long) = { 57 | Db.hackers.delete(id) 58 | } 59 | } 60 | 61 | /** 62 | * This trait is just a way to aggregate our model style across multiple 63 | * models so that we have a single point of change if we want to add 64 | * anything to our model behaviour 65 | */ 66 | trait SquerylRecord extends KeyedEntity[Long] with PersistenceStatus { 67 | 68 | } --------------------------------------------------------------------------------