diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..9a89985 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8570daf --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +logs +project/project +project/target +target +tmp +.history +dist +/.idea +/*.iml +/out +/.idea_modules +/.classpath +/.project +/RUNNING_PID +/.settings diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4baedcb --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +This software is licensed under the Apache 2 license, quoted below. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with +the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000..ad73c38 --- /dev/null +++ b/README @@ -0,0 +1,4 @@ +This is your new Play application +================================= + +This file will be packaged with your application, when using `activator dist`. diff --git a/activator b/activator new file mode 100755 index 0000000..b3d7f8c --- /dev/null +++ b/activator @@ -0,0 +1,342 @@ +#!/bin/bash + +### ------------------------------- ### +### Helper methods for BASH scripts ### +### ------------------------------- ### + +realpath () { +( + TARGET_FILE="$1" + + cd $(dirname "$TARGET_FILE") + TARGET_FILE=$(basename "$TARGET_FILE") + + COUNT=0 + while [ -L "$TARGET_FILE" -a $COUNT -lt 100 ] + do + TARGET_FILE=$(readlink "$TARGET_FILE") + cd $(dirname "$TARGET_FILE") + TARGET_FILE=$(basename "$TARGET_FILE") + COUNT=$(($COUNT + 1)) + done + + if [ "$TARGET_FILE" == "." -o "$TARGET_FILE" == ".." ]; then + cd "$TARGET_FILE" + TARGET_FILEPATH= + else + TARGET_FILEPATH=/$TARGET_FILE + fi + + # make sure we grab the actual windows path, instead of cygwin's path. + if ! is_cygwin; then + echo "$(pwd -P)/$TARGET_FILE" + else + echo $(cygwinpath "$(pwd -P)/$TARGET_FILE") + fi +) +} + +# TODO - Do we need to detect msys? + +# Uses uname to detect if we're in the odd cygwin environment. +is_cygwin() { + local os=$(uname -s) + case "$os" in + CYGWIN*) return 0 ;; + *) return 1 ;; + esac +} + +# This can fix cygwin style /cygdrive paths so we get the +# windows style paths. +cygwinpath() { + local file="$1" + if is_cygwin; then + echo $(cygpath -w $file) + else + echo $file + fi +} + +# Make something URI friendly +make_url() { + url="$1" + local nospaces=${url// /%20} + if is_cygwin; then + echo "/${nospaces//\\//}" + else + echo "$nospaces" + fi +} + +# Detect if we should use JAVA_HOME or just try PATH. +get_java_cmd() { + if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then + echo "$JAVA_HOME/bin/java" + else + echo "java" + fi +} + +echoerr () { + echo 1>&2 "$@" +} +vlog () { + [[ $verbose || $debug ]] && echoerr "$@" +} +dlog () { + [[ $debug ]] && echoerr "$@" +} +execRunner () { + # print the arguments one to a line, quoting any containing spaces + [[ $verbose || $debug ]] && echo "# Executing command line:" && { + for arg; do + if printf "%s\n" "$arg" | grep -q ' '; then + printf "\"%s\"\n" "$arg" + else + printf "%s\n" "$arg" + fi + done + echo "" + } + + exec "$@" +} +addJava () { + dlog "[addJava] arg = '$1'" + java_args=( "${java_args[@]}" "$1" ) +} +addApp () { + dlog "[addApp] arg = '$1'" + sbt_commands=( "${app_commands[@]}" "$1" ) +} +addResidual () { + dlog "[residual] arg = '$1'" + residual_args=( "${residual_args[@]}" "$1" ) +} +addDebugger () { + addJava "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1" +} +addConfigOpts () { + dlog "[addConfigOpts] arg = '$*'" + for item in $* + do + addJava "$item" + done +} +# a ham-fisted attempt to move some memory settings in concert +# so they need not be messed around with individually. +get_mem_opts () { + local mem=${1:-1024} + local meta=$(( $mem / 4 )) + (( $meta > 256 )) || meta=256 + (( $meta < 1024 )) || meta=1024 + + # default is to set memory options but this can be overridden by code section below + memopts="-Xms${mem}m -Xmx${mem}m" + if [[ "${java_version}" > "1.8" ]]; then + extmemopts="-XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=${meta}m" + else + extmemopts="-XX:PermSize=64m -XX:MaxPermSize=${meta}m" + fi + + if [[ "${java_opts}" == *-Xmx* ]] || [[ "${java_opts}" == *-Xms* ]] || [[ "${java_opts}" == *-XX:MaxPermSize* ]] || [[ "${java_opts}" == *-XX:ReservedCodeCacheSize* ]] || [[ "${java_opts}" == *-XX:MaxMetaspaceSize* ]]; then + # if we detect any of these settings in ${java_opts} we need to NOT output our settings. + # The reason is the Xms/Xmx, if they don't line up, cause errors. + memopts="" + extmemopts="" + fi + + echo "${memopts} ${extmemopts}" +} +require_arg () { + local type="$1" + local opt="$2" + local arg="$3" + if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then + die "$opt requires <$type> argument" + fi +} +require_arg () { + local type="$1" + local opt="$2" + local arg="$3" + if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then + die "$opt requires <$type> argument" + fi +} +is_function_defined() { + declare -f "$1" > /dev/null +} + +# If we're *not* running in a terminal, and we don't have any arguments, then we need to add the 'ui' parameter +detect_terminal_for_ui() { + [[ ! -t 0 ]] && [[ "${#residual_args}" == "0" ]] && { + addResidual "ui" + } + # SPECIAL TEST FOR MAC + [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]] && [[ "${#residual_args}" == "0" ]] && { + echo "Detected MAC OSX launched script...." + echo "Swapping to UI" + addResidual "ui" + } +} + +# Processes incoming arguments and places them in appropriate global variables. called by the run method. +process_args () { + while [[ $# -gt 0 ]]; do + case "$1" in + -h|-help) usage; exit 1 ;; + -v|-verbose) verbose=1 && shift ;; + -d|-debug) debug=1 && shift ;; + -mem) require_arg integer "$1" "$2" && app_mem="$2" && shift 2 ;; + -jvm-debug) + if echo "$2" | grep -E ^[0-9]+$ > /dev/null; then + addDebugger "$2" && shift + else + addDebugger 9999 + fi + shift ;; + -java-home) require_arg path "$1" "$2" && java_cmd="$2/bin/java" && shift 2 ;; + -D*) addJava "$1" && shift ;; + -J*) addJava "${1:2}" && shift ;; + *) addResidual "$1" && shift ;; + esac + done + + is_function_defined process_my_args && { + myargs=("${residual_args[@]}") + residual_args=() + process_my_args "${myargs[@]}" + } +} + +# Actually runs the script. +run() { + # TODO - check for sane environment + + # process the combined args, then reset "$@" to the residuals + process_args "$@" + detect_terminal_for_ui + set -- "${residual_args[@]}" + argumentCount=$# + + #check for jline terminal fixes on cygwin + if is_cygwin; then + stty -icanon min 1 -echo > /dev/null 2>&1 + addJava "-Djline.terminal=jline.UnixTerminal" + addJava "-Dsbt.cygwin=true" + fi + + # run sbt + execRunner "$java_cmd" \ + "-Dactivator.home=$(make_url "$activator_home")" \ + $(get_mem_opts $app_mem) \ + ${java_opts[@]} \ + ${java_args[@]} \ + -jar "$app_launcher" \ + "${app_commands[@]}" \ + "${residual_args[@]}" + + local exit_code=$? + if is_cygwin; then + stty icanon echo > /dev/null 2>&1 + fi + exit $exit_code +} + +# Loads a configuration file full of default command line options for this script. +loadConfigFile() { + cat "$1" | sed '/^\#/d' +} + +### ------------------------------- ### +### Start of customized settings ### +### ------------------------------- ### +usage() { + cat < [options] + + Command: + ui Start the Activator UI + new [name] [template-id] Create a new project with [name] using template [template-id] + list-templates Print all available template names + -h | -help Print this message + + Options: + -v | -verbose Make this runner chattier + -d | -debug Set sbt log level to debug + -mem Set memory options (default: $sbt_mem, which is $(get_mem_opts $sbt_mem)) + -jvm-debug Turn on JVM debugging, open at the given port. + + # java version (default: java from PATH, currently $(java -version 2>&1 | grep version)) + -java-home Alternate JAVA_HOME + + # jvm options and output control + -Dkey=val Pass -Dkey=val directly to the java runtime + -J-X Pass option -X directly to the java runtime + (-J is stripped) + + # environment variables (read from context) + JAVA_OPTS Environment variable, if unset uses "" + SBT_OPTS Environment variable, if unset uses "" + ACTIVATOR_OPTS Environment variable, if unset uses "" + +In the case of duplicated or conflicting options, the order above +shows precedence: environment variables lowest, command line options highest. +EOM +} + +### ------------------------------- ### +### Main script ### +### ------------------------------- ### + +declare -a residual_args +declare -a java_args +declare -a app_commands +declare -r real_script_path="$(realpath "$0")" +declare -r activator_home="$(realpath "$(dirname "$real_script_path")")" +declare -r app_version="1.2.10" + +declare -r app_launcher="${activator_home}/activator-launch-${app_version}.jar" +declare -r script_name=activator +declare -r java_cmd=$(get_java_cmd) +declare -r java_opts=( "${ACTIVATOR_OPTS[@]}" "${SBT_OPTS[@]}" "${JAVA_OPTS[@]}" "${java_opts[@]}" ) +userhome="$HOME" +if is_cygwin; then + # cygwin sets home to something f-d up, set to real windows homedir + userhome="$USERPROFILE" +fi +declare -r activator_user_home_dir="${userhome}/.activator" +declare -r java_opts_config_home="${activator_user_home_dir}/activatorconfig.txt" +declare -r java_opts_config_version="${activator_user_home_dir}/${app_version}/activatorconfig.txt" + +# Now check to see if it's a good enough version +declare -r java_version=$("$java_cmd" -version 2>&1 | awk -F '"' '/version/ {print $2}') +if [[ "$java_version" == "" ]]; then + echo + echo No java installations was detected. + echo Please go to http://www.java.com/getjava/ and download + echo + exit 1 +elif [[ ! "$java_version" > "1.6" ]]; then + echo + echo The java installation you have is not up to date + echo Activator requires at least version 1.6+, you have + echo version $java_version + echo + echo Please go to http://www.java.com/getjava/ and download + echo a valid Java Runtime and install before running Activator. + echo + exit 1 +fi + +# if configuration files exist, prepend their contents to the java args so it can be processed by this runner +# a "versioned" config trumps one on the top level +if [[ -f "$java_opts_config_version" ]]; then + addConfigOpts $(loadConfigFile "$java_opts_config_version") +elif [[ -f "$java_opts_config_home" ]]; then + addConfigOpts $(loadConfigFile "$java_opts_config_home") +fi + +run "$@" diff --git a/activator-launch-1.2.10.jar b/activator-launch-1.2.10.jar new file mode 100644 index 0000000..0e17413 Binary files /dev/null and b/activator-launch-1.2.10.jar differ diff --git a/app/controllers/Application.scala b/app/controllers/Application.scala new file mode 100644 index 0000000..ff4a1fe --- /dev/null +++ b/app/controllers/Application.scala @@ -0,0 +1,126 @@ +package controllers + +import models.Beer + +import play.api._ +import play.api.mvc._ +import play.api.libs.json._ +import play.api.libs.functional.syntax._ + +import java.io._ + +object Application extends Controller { + + implicit val beerWrites = new Writes[Beer] { + def writes(beer: Beer) = Json.obj( + "name" -> beer.name, + "beer_type" -> beer.beer_type, + "brewery" -> beer.brewery, + "abv" -> beer.abv, + "country" -> beer.country, + "todo" -> beer.todo + ) + } + + implicit val beerReads: Reads[Beer] = ( + (JsPath \ "name").read[String] and + (JsPath \ "beer_type").readNullable[String] and + (JsPath \ "brewery").readNullable[String] and + (JsPath \ "abv").readNullable[Double] and + (JsPath \ "country").readNullable[String] and + (JsPath \ "todo").readNullable[Boolean] + )(Beer.apply _) + + val jsonList = Json.parse(scala.io.Source.fromFile("beers.json").mkString) + + jsonList.validate[List[Beer]] match { + case s: JsSuccess[List[Beer]] => { + Beer.list = s.get + } + case e: JsError => { + Ok("Error") + } + } + + def index = Action { + Ok(views.html.index(Beer.list)) + } + + def listBeers(index: Integer) = Action { + if(index > 0) { + if(Beer.list.lift(index) != None) { + val json = Json.toJson(Beer.list.lift(index).get) + Ok(json) + } else { + Ok(Json.obj("success" -> "false", "error" -> "Index of ".concat(index.toString).concat(" is empty"))) + } + } else { + val json = Json.toJson(Beer.list) + Ok(json) + } + } + + def saveBeer = Action(BodyParsers.parse.json) { request => + val beerResult = Json.fromJson[Beer](request.body) + beerResult.fold( + errors => { + BadRequest(Json.obj("success" ->"false", "message" -> "Invalid json")) + }, + beer => { + Beer.save(beer) + printToFile(new File("beers.json")) { p => + p.print(Json.toJson(Beer.list)); + } + Ok(Json.obj("success" ->"true", "beer" -> beer)) + } + ) + } + + def updateBeer(index: Integer) = Action(BodyParsers.parse.json) { request => + if(Beer.list.lift(index) != None) { + val newValues = Beer.list.lift(index).get + val updateBeerResult = Json.fromJson(request.body) + updateBeerResult.fold( + errors => { + BadRequest(Json.obj("success" ->"false", "error" -> "Invalid json")) + }, + beer => { + val updateBeer = Beer( + if (beer.name != None) beer.name else newValues.name, + if (beer.beer_type != None) beer.beer_type else newValues.beer_type, + if (beer.brewery != None) beer.brewery else newValues.brewery, + if (beer.abv != None) beer.abv else newValues.abv, + if (beer.country != None) beer.country else newValues.country, + if (beer.todo != None) beer.todo else newValues.todo + ) + val updatedList = Beer.list.updated(index, updateBeer) + Beer.list = updatedList + printToFile(new File("beers.json")) { p => + p.print(Json.toJson(Beer.list)); + } + Ok(Json.obj("success" ->"true", "updated" -> updateBeer)) + } + ) + } else { + Ok(Json.obj("success" -> "false", "error" -> "Index of ".concat(index.toString).concat(" is empty"))) + } + } + + def deleteBeer(index: Integer) = Action { + if(Beer.list.lift(index) != None) { + Beer.list = Beer.list.drop(index+1) + printToFile(new File("beers.json")) { p => + p.print(Json.toJson(Beer.list)); + } + Ok(Json.obj("success" -> "true", "beerList" -> Beer.list)) + } else { + Ok(Json.obj("success" -> "false", "error" -> "Index of ".concat(index.toString).concat(" is empty"))) + } + } + + def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit) { + val p = new java.io.PrintWriter(f) + try { op(p) } finally { p.close() } + } + +} \ No newline at end of file diff --git a/app/models/Beer.scala b/app/models/Beer.scala new file mode 100644 index 0000000..1ad80e2 --- /dev/null +++ b/app/models/Beer.scala @@ -0,0 +1,13 @@ +package models + +case class Beer(name: String, beer_type: Option[String] = None, brewery: Option[String] = None, abv: Option[Double] = None, country: Option[String] = None, todo: Option[Boolean] = None) + +object Beer { + var list: List[Beer] = { + List() + } + + def save(beer: Beer) = { + list = list ::: List(beer) + } +} \ No newline at end of file diff --git a/app/views/index.scala.html b/app/views/index.scala.html new file mode 100644 index 0000000..7df717e --- /dev/null +++ b/app/views/index.scala.html @@ -0,0 +1,9 @@ +@(items: List[Beer]) + +@main("Welcome to Play") { +
    + @for(item <- items) { +
  • @items.lift(1)
  • + } +
+} diff --git a/app/views/main.scala.html b/app/views/main.scala.html new file mode 100644 index 0000000..5025aa5 --- /dev/null +++ b/app/views/main.scala.html @@ -0,0 +1,15 @@ +@(title: String)(content: Html) + + + + + + @title + + + + + + @content + + diff --git a/beers.json b/beers.json new file mode 100644 index 0000000..dc587db --- /dev/null +++ b/beers.json @@ -0,0 +1 @@ +[{"name":"Kala Lageria","beer_type":null,"brewery":null,"abv":null,"country":null,"todo":null},{"name":"Kala Lager","beer_type":null,"brewery":null,"abv":null,"country":null,"todo":null}] \ No newline at end of file diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..0840395 --- /dev/null +++ b/build.sbt @@ -0,0 +1,14 @@ +name := """beer-tracker""" + +version := "1.0-SNAPSHOT" + +lazy val root = (project in file(".")).enablePlugins(PlayScala) + +scalaVersion := "2.11.1" + +libraryDependencies ++= Seq( + jdbc, + anorm, + cache, + ws +) diff --git a/conf/application.conf b/conf/application.conf new file mode 100644 index 0000000..7d05fe7 --- /dev/null +++ b/conf/application.conf @@ -0,0 +1,61 @@ +# This is the main configuration file for the application. +# ~~~~~ + +# Secret key +# ~~~~~ +# The secret key is used to secure cryptographics functions. +# +# This must be changed for production, but we recommend not changing it in this file. +# +# See http://www.playframework.com/documentation/latest/ApplicationSecret for more details. +application.secret="C21?@I4aMEWAU7lT254Nwkv606Wq4G9cluKIgeAJ@LXMGyt7R_EE5>2D_qewHJrh" + +# The application languages +# ~~~~~ +application.langs="en" + +# Global object class +# ~~~~~ +# Define the Global object class for this application. +# Default to Global in the root package. +# application.global=Global + +# Router +# ~~~~~ +# Define the Router object to use for this application. +# This router will be looked up first when the application is starting up, +# so make sure this is the entry point. +# Furthermore, it's assumed your route file is named properly. +# So for an application router like `my.application.Router`, +# you may need to define a router file `conf/my.application.routes`. +# Default to Routes in the root package (and conf/routes) +# application.router=my.application.Routes + +# Database configuration +# ~~~~~ +# You can declare as many datasources as you want. +# By convention, the default datasource is named `default` +# +# db.default.driver=org.h2.Driver +# db.default.url="jdbc:h2:mem:play" +# db.default.user=sa +# db.default.password="" + +# Evolutions +# ~~~~~ +# You can disable evolutions if needed +# evolutionplugin=disabled + +# Logger +# ~~~~~ +# You can also configure logback (http://logback.qos.ch/), +# by providing an application-logger.xml file in the conf directory. + +# Root logger: +logger.root=ERROR + +# Logger used by the framework: +logger.play=INFO + +# Logger provided to your application: +logger.application=DEBUG \ No newline at end of file diff --git a/conf/routes b/conf/routes new file mode 100644 index 0000000..e1a1b0a --- /dev/null +++ b/conf/routes @@ -0,0 +1,16 @@ +# Routes +# This file defines all application routes (Higher priority routes first) +# ~~~~ + +# Home page +GET / controllers.Application.index + +# Map static resources from the /public folder to the /assets URL path +GET /assets/*file controllers.Assets.at(path="/public", file) + +# REST API Methods +GET /beers controllers.Application.listBeers(index: Integer = -1) +GET /beers/:index controllers.Application.listBeers(index: Integer) +POST /beers controllers.Application.saveBeer +PUT /beers/:index controllers.Application.updateBeer(index: Integer) +DELETE /beers/:index controllers.Application.deleteBeer(index: Integer) \ No newline at end of file diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..2299cd3 --- /dev/null +++ b/project/build.properties @@ -0,0 +1,4 @@ +#Activator-generated Properties +#Tue Mar 03 21:13:52 EET 2015 +template.uuid=88679796-ef1c-40cd-82c2-d3114cfa2881 +sbt.version=0.13.5 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..fcc6df7 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,18 @@ +resolvers += "Typesafe repository" at "https://repo.typesafe.com/typesafe/releases/" + +// The Play plugin +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.8") + +// web plugins + +addSbtPlugin("com.typesafe.sbt" % "sbt-coffeescript" % "1.0.0") + +addSbtPlugin("com.typesafe.sbt" % "sbt-less" % "1.0.0") + +addSbtPlugin("com.typesafe.sbt" % "sbt-jshint" % "1.0.1") + +addSbtPlugin("com.typesafe.sbt" % "sbt-rjs" % "1.0.1") + +addSbtPlugin("com.typesafe.sbt" % "sbt-digest" % "1.0.0") + +addSbtPlugin("com.typesafe.sbt" % "sbt-mocha" % "1.0.0") diff --git a/public/images/favicon.png b/public/images/favicon.png new file mode 100644 index 0000000..c7d92d2 Binary files /dev/null and b/public/images/favicon.png differ diff --git a/public/javascripts/hello.js b/public/javascripts/hello.js new file mode 100644 index 0000000..209fbee --- /dev/null +++ b/public/javascripts/hello.js @@ -0,0 +1,3 @@ +if (window.console) { + console.log("Welcome to your Play application's JavaScript!"); +} \ No newline at end of file diff --git a/public/stylesheets/main.css b/public/stylesheets/main.css new file mode 100644 index 0000000..e69de29 diff --git a/test/ApplicationSpec.scala b/test/ApplicationSpec.scala new file mode 100644 index 0000000..6e20bd5 --- /dev/null +++ b/test/ApplicationSpec.scala @@ -0,0 +1,30 @@ +import org.specs2.mutable._ +import org.specs2.runner._ +import org.junit.runner._ + +import play.api.test._ +import play.api.test.Helpers._ + +/** + * Add your spec here. + * You can mock out a whole application including requests, plugins etc. + * For more information, consult the wiki. + */ +@RunWith(classOf[JUnitRunner]) +class ApplicationSpec extends Specification { + + "Application" should { + + "send 404 on a bad request" in new WithApplication{ + route(FakeRequest(GET, "/boum")) must beNone + } + + "render the index page" in new WithApplication{ + val home = route(FakeRequest(GET, "/")).get + + status(home) must equalTo(OK) + contentType(home) must beSome.which(_ == "text/html") + contentAsString(home) must contain ("Your new application is ready.") + } + } +} diff --git a/test/IntegrationSpec.scala b/test/IntegrationSpec.scala new file mode 100644 index 0000000..652edde --- /dev/null +++ b/test/IntegrationSpec.scala @@ -0,0 +1,24 @@ +import org.specs2.mutable._ +import org.specs2.runner._ +import org.junit.runner._ + +import play.api.test._ +import play.api.test.Helpers._ + +/** + * add your integration spec here. + * An integration test will fire up a whole play application in a real (or headless) browser + */ +@RunWith(classOf[JUnitRunner]) +class IntegrationSpec extends Specification { + + "Application" should { + + "work from within a browser" in new WithBrowser { + + browser.goTo("http://localhost:" + port) + + browser.pageSource must contain("Your new application is ready.") + } + } +}