├── .gitignore ├── .midje.clj ├── .travis.yml ├── LICENSE ├── README.md ├── project.clj ├── release.sh └── src └── leiningen └── new ├── mr_clojure.clj └── mr_clojure ├── .gitignore ├── .midje.clj ├── acceptance ├── acceptance.clj ├── all ├── autounit ├── core.cljs ├── dev.cljs ├── favicon.ico ├── integration ├── integration.clj ├── jetty ├── logback.xml ├── page_frame.clj ├── postinstall.sh ├── postremove.sh ├── preinstall.sh ├── preremove.sh ├── prod.cljs ├── project.clj ├── setup.clj ├── site.css ├── start.sh ├── stop.sh ├── test_common.clj ├── test_helper.sh ├── web.clj └── web_unit.clj /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | target/* 3 | lib* 4 | *.orig 5 | *jar 6 | *.iml 7 | .idea 8 | .lein-failures 9 | dc-sdk.log 10 | .classpath 11 | .lein-deps-sum 12 | .lein-plugins/ 13 | .project 14 | .lein-env 15 | pom.xml 16 | pom.xml.asc 17 | -------------------------------------------------------------------------------- /.midje.clj: -------------------------------------------------------------------------------- 1 | (change-defaults :print-level :print-facts) 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | lein: lein2 3 | script: 4 | - lein2 install 5 | - lein2 new mr-clojure test-api --to-dir target/test-api 6 | - lein2 new mr-clojure test-webapp --to-dir target/test-webapp -- --reagent-webapp 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Mix Radio 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mr-Clojure 2 | 3 | MixRadio's clojure skeleton project. 4 | 5 | This generates the base REST service project that we use at MixRadio. In contains various libraries that we find useful and can be considered a condensing of the various pieces of commonality between our many clojure web services. We use this every day at MixRadio to power our music backend. 6 | 7 | This project is the underlying Leiningen template, see [mr-clojure-exploded](http://github.com/mixradio/mr-clojure-exploded) for an example of the resulting service. 8 | 9 | ## Usage 10 | 11 | Generate a new project with: 12 | 13 | `lein new mr-clojure ` 14 | 15 | You now have a web service with a couple of basic "I'm alive" resources and an embedded Jetty server to run it. 16 | 17 | By supplying the `--reagent-webapp` optional argument the template configures the project for clojurescript development with [reagent](https://github.com/reagent-project/reagent) and [figwheel](https://github.com/bhauman/lein-figwheel): 18 | 19 | `lein new mr-clojure -- --reagent-webapp` 20 | 21 | Typing `lein figwheel` inside the project directory will start the web server and give you a running clojurescript REPL. 22 | 23 | ### Testing your new service 24 | 25 | `cd` into your new project directory and run 26 | 27 | `./acceptance wait` 28 | 29 | This starts the service in acceptance test mode, but rather than running the tests and reporting the result, it just starts the service and waits. This allows you to manually call it or to run tests against it from the repl. 30 | 31 | Let's call the healthcheck resource and see if we get a response: 32 | 33 | `curl localhost:8080/healthcheck` 34 | 35 | and we should see the result: 36 | 37 | `{"name":"i","version":"1.0.0-SNAPSHOT","success":true,"dependencies":[]}` 38 | 39 | ## Adding in a new resource 40 | 41 | Let's just add a quick example of a resource that responds to an input parameter. Leave the server running as we're going to do this live. 42 | 43 | Open `src//web.clj` and navigate to the route definitions at the bottom of the file. Add a new route on the resource `/hello` and tell it to call a function called greet and take a parameter of `name`. As a rule of thumb we like to keep our route definitions clean from too much code as it makes it very easy to see at a glace what a service does. The main way to do this to immediately defer to a handling function. 44 | 45 | Here's how it should look: 46 | 47 | ```clj 48 | (defroutes routes 49 | 50 | (GET "/healthcheck" 51 | [] (healthcheck)) 52 | 53 | (GET "/ping" 54 | [] "pong") 55 | 56 | (GET "/hello" 57 | [nickname] (greet nickname)) 58 | 59 | (route/not-found (error-response "Resource not found" 404))) 60 | ``` 61 | 62 | And now let's define our greet function: 63 | 64 | ```clj 65 | (defn greet 66 | "Says hello!" 67 | [nickname] 68 | {:status 200 :body (format "Hello %s!\n" nickname)}) 69 | ``` 70 | 71 | Save the file and give it a test: 72 | 73 | `curl "http://localhost:8080/hello?nickname=world"` 74 | 75 | Should give us the output: 76 | 77 | `Hello world!` 78 | 79 | Success! 80 | 81 | ## Testing 82 | 83 | There are a few different types of test defined in our skeleton project: unit, acceptance and integration. These words mean different things to different people and our definition is no exception so I'll define it here. 84 | * Unit - This one is pretty well defined. However unlike a lot of people we often test our web layer heavily using unit like tests where we don't start the server. There are examples of this in `test/unit/web.clj` 85 | * Acceptance - Our definition here is probably one that suits our service oriented architecture. To us acceptance tests mean starting the service and poking it from another process, but only in isolation. This is when we use [rest driver](http://github.com/whostolebenfrog/rest-cljer) to mock out our calls to other web services. Some teams use this heavily where others lean more towards web unit test. 86 | * Integration - Many people refer to this as an acceptance test. This is where the service is started and called with all its dependencies. 87 | 88 | Each of these can be run in isolation using: 89 | 90 | `lein midje :filter unit` or `./autounit` to watch for changes and automatically run unit tests. 91 | 92 | `./acceptance` or `./acceptance wait` to only start the server and not run the tests 93 | 94 | `./integration` or `./integration wait` to only start the server and not run the tests 95 | 96 | `./all` to run all the tests 97 | 98 | ### Running a single test in isolation 99 | If you add metadata to a specific integration/acceptance test (or tests) and then tell midje to run 100 | just those tests with that metadata. For example, consider the following acceptance test: 101 | 102 | ```clojure 103 | (fact "test something" 104 | :only 105 | (test-stuff)) 106 | ``` 107 | you can now run that test in isolation with `./acceptance only`. 108 | 109 | ## Libraries 110 | 111 | These are some of the libraries that we use: 112 | 113 | * [cheshire](https://github.com/dakrone/cheshire) - excellent and fast JSON parsing and generation 114 | * [clj-http](https://github.com/dakrone/clj-http) - a nice interface for making http calls 115 | * [clj-time](https://github.com/clj-time/clj-time) - date and time library with a great interface 116 | * [clojure](http://clojure.org) - 1.6 117 | * [compojure](https://github.com/weavejester/compojure) - the basis of our web service, used to define resources and middleware 118 | * [environ](https://github.com/weavejester/environ) - reads environment variables and allows development values to be defined in the project.clj 119 | * [jetty](http://www.eclipse.org/jetty/) - a lightweight JVM web server that we embed with our services 120 | * [midje](https://github.com/marick/Midje) - testing and mocking 121 | * [rest-driver](https://github.com/whostolebenfrog/rest-cljer) - http level dependency mocking 122 | 123 | ## Deployment 124 | 125 | `lein release` will update the version number, uberjar and then create an RPM of the service. This RPM can be installed on any compatible server (e.g. Redhat, CentOs, Amazon Linux). You will, however need to ensure that the environment variables defined in the project.clj are available for the RPM to run. 126 | 127 | ## License 128 | 129 | Copyright © 2015 MixRadio 130 | 131 | [mr-clojure is released under the 3-clause license ("New BSD License" or "Modified BSD License")](https://github.com/mixradio/mr-clojure/blob/master/LICENSE) 132 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject mr-clojure/lein-template "1.0.12-SNAPSHOT" 2 | :description "MixRadio clojure skeleton template for Leiningen. Generates a Clojure HTTP REST service in the style of MixRadio" 3 | :url "http://github.com/mixradio/mr-clojure" 4 | :eval-in-leiningen true 5 | :license "https://github.com/mixradio/mr-clojure/blob/master/LICENSE") 6 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Making this script because we can't accept a password during `lein release` and don't want to add keys IDs to project.clj 3 | 4 | lein vcs assert-committed 5 | lein change version leiningen.release/bump-version release 6 | lein vcs commit 7 | git tag `cat project.clj | grep defproject | cut -d" " -f 3 | tr -d "\""` # I'm actually pretty happy with this 8 | lein deploy clojars 9 | lein change version leiningen.release/bump-version 10 | lein vcs commit 11 | lein vcs push 12 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.new.mr-clojure 2 | (:use [leiningen.new.templates :only [renderer name-to-path ->files year]] 3 | [leiningen.core.main :as main]) 4 | (:require [clojure.java.io :as io] 5 | [clojure.string :as str])) 6 | 7 | (def render (renderer "mr-clojure")) 8 | 9 | (defn cap [s] 10 | (str (.toUpperCase (subs s 0 1)) (subs s 1))) 11 | 12 | (def valid-opts 13 | ["--reagent-webapp"]) 14 | 15 | (defn opt->kw 16 | [opt] 17 | (-> opt 18 | (str/replace #"^--" "") 19 | (str "?") 20 | keyword)) 21 | 22 | (defn parse-opts 23 | [opts] 24 | (->> (for [valid-opt valid-opts 25 | :let [valid-opt? (boolean (some #(= valid-opt %) opts))]] 26 | [(opt->kw valid-opt) valid-opt?]) 27 | (into {}))) 28 | 29 | (defn build-data 30 | [name opts] 31 | (let [{:keys [reagent-webapp?] :as parsed-opts} (parse-opts opts) 32 | cljs-app? reagent-webapp?] 33 | (merge {:name name 34 | :upper-name (cap name) 35 | :lower-name (.toLowerCase name) 36 | :sanitized (name-to-path name) 37 | :year (year)} 38 | parsed-opts 39 | {:cljs-app? cljs-app? 40 | :clj-path (if cljs-app? "clj/" "")}))) 41 | 42 | (defn mr-clojure 43 | "Skeleton Clojure project" 44 | [name & opts] 45 | (let [{:keys [cljs-app? clj-path cljs-path] :as data} (build-data name opts)] 46 | (->> (cond-> [["project.clj" (render "project.clj" data)] 47 | ["all" (render "all" data) :executable true] 48 | ["test_helper.sh" (render "test_helper.sh" data)] 49 | ["acceptance" (render "acceptance" data) :executable true] 50 | ["integration" (render "integration" data) :executable true] 51 | [".gitignore" (render ".gitignore" data)] 52 | ["src/{{clj-path}}{{sanitized}}/setup.clj" (render "setup.clj" data)] 53 | ["src/{{clj-path}}{{sanitized}}/web.clj" (render "web.clj" data)] 54 | ["resources/logback.xml" (render "logback.xml" data)] 55 | 56 | ["test/{{sanitized}}/unit/web.clj" (render "web_unit.clj" data)] 57 | ["test/{{sanitized}}/test_common.clj" (render "test_common.clj" data)] 58 | ["test/{{sanitized}}/acceptance.clj" (render "acceptance.clj" data)] 59 | ["test/{{sanitized}}/integration.clj" (render "integration.clj" data)] 60 | [".midje.clj" (render ".midje.clj" data)] 61 | 62 | ["scripts/bin/start.sh" (render "start.sh" data)] 63 | ["scripts/bin/stop.sh" (render "stop.sh" data)] 64 | ["scripts/rpm/postinstall.sh" (render "postinstall.sh" data)] 65 | ["scripts/rpm/postremove.sh" (render "postremove.sh" data)] 66 | ["scripts/rpm/preinstall.sh" (render "preinstall.sh" data)] 67 | ["scripts/rpm/preremove.sh" (render "preremove.sh" data)] 68 | ["scripts/service/{{lower-name}}" (render "jetty" data)]] 69 | 70 | cljs-app? (concat [["env/dev/cljs/{{sanitized}}/dev.cljs" (render "dev.cljs" data)] 71 | ["env/prod/cljs/{{sanitized}}/prod.cljs" (render "prod.cljs" data)] 72 | ["src/clj/{{sanitized}}/page_frame.clj" (render "page_frame.clj" data)] 73 | ["src/cljs/{{sanitized}}/core.cljs" (render "core.cljs" data)] 74 | ["resources/public/css/site.css" (render "site.css" data)] 75 | ["resources/public/img/favicon.ico" (render "favicon.ico")]])) 76 | 77 | (remove nil?) 78 | (apply ->files data)))) 79 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | {{#cljs-app?}} 3 | /resources/public/js 4 | figwheel_server.log 5 | *.min.css 6 | {{/cljs-app?}} 7 | *.swp 8 | target/* 9 | lib* 10 | *.orig 11 | *jar 12 | .idea 13 | .lein-failures 14 | dc-sdk.log 15 | .classpath 16 | .lein-deps-sum 17 | .lein-plugins/ 18 | .project 19 | .lein-env 20 | .nrepl-port 21 | .lein-repl-history 22 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/.midje.clj: -------------------------------------------------------------------------------- 1 | (change-defaults :print-level :print-facts) 2 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/acceptance: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . test_helper.sh 4 | TIMEOUT=30 5 | 6 | if [ "$1" != "wait" ] 7 | then 8 | export FILTER=${1:-acceptance} 9 | run_test "{{name}}.acceptance" $TIMEOUT $FILTER 10 | else 11 | lein run 12 | fi 13 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/acceptance.clj: -------------------------------------------------------------------------------- 1 | (ns {{name}}.acceptance 2 | (:require [{{name}}.test-common :refer :all] 3 | [clj-http.client :as http] 4 | [environ.core :refer [env]] 5 | [midje.sweet :refer :all])) 6 | 7 | (fact-group 8 | :acceptance 9 | 10 | (fact "Ping resource returns 200 HTTP response" 11 | (let [response (http/get (url+ "/ping") {:throw-exceptions false})] 12 | response => (contains {:status 200}))) 13 | 14 | (fact "Healthcheck resource returns 200 HTTP response" 15 | (let [response (http/get (url+ "/healthcheck") {:throw-exceptions false}) 16 | body (json-body response)] 17 | response => (contains {:status 200}) 18 | body => (contains {:name "{{name}}" 19 | :success true 20 | :version truthy})))) 21 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/all: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exit_if_fail () { 4 | if [ $? -ne 0 ] 5 | then 6 | echo -e "\e[31m" 7 | echo "" 8 | echo " FAIL WHALE!" 9 | echo -e "\e[34m" 10 | echo "" 11 | echo "W W W " 12 | echo "W W W W " 13 | echo " '. W " 14 | echo " .-\"\"-._ \ \.--| " 15 | echo " / \"-..__) .-' " 16 | echo "| _ / " 17 | echo "\\'-.__, .__.,' " 18 | echo " \`'----'._\\--' " 19 | echo "" 20 | echo "$1 test failure! Why would you even write something like that?" 21 | echo -e "\e[0m" 22 | exit 1 23 | fi 24 | } 25 | 26 | echo "Running unit tests" 27 | 28 | lein midje :filter unit 29 | exit_if_fail Unit 30 | 31 | echo "Running integration tests" 32 | 33 | ./integration 34 | exit_if_fail Integration 35 | 36 | sleep 1 37 | 38 | echo "Running acceptance tests" 39 | 40 | ./acceptance 41 | exit_if_fail Acceptance 42 | 43 | sleep 1 44 | 45 | exit 0 46 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/autounit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export FILTER=${1:-unit} 3 | 4 | lein midje :autotest {{name}}.unit :filter $FILTER -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/core.cljs: -------------------------------------------------------------------------------- 1 | (ns {{name}}.core 2 | (:require [reagent.core :as reagent :refer [atom]] 3 | [reagent.session :as session] 4 | [secretary.core :as secretary :include-macros true] 5 | [goog.events :as events] 6 | [goog.history.EventType :as EventType]) 7 | (:import goog.History)) 8 | 9 | ;; ------------------------- 10 | ;; Views 11 | 12 | (defn home-page [] 13 | [:div [:h2 "Welcome to {{name}}"] 14 | [:div [:a {:href "#/about"} "go to about page"]]]) 15 | 16 | (defn about-page [] 17 | [:div [:h2 "About {{name}}"] 18 | [:div [:a {:href "#/"} "go to the home page"]]]) 19 | 20 | (defn current-page [] 21 | [:div [(session/get :current-page)]]) 22 | 23 | ;; ------------------------- 24 | ;; Routes 25 | (secretary/set-config! :prefix "#") 26 | 27 | (secretary/defroute "/" [] 28 | (session/put! :current-page #'home-page)) 29 | 30 | (secretary/defroute "/about" [] 31 | (session/put! :current-page #'about-page)) 32 | 33 | ;; ------------------------- 34 | ;; History 35 | ;; must be called after routes have been defined 36 | (defn hook-browser-navigation! [] 37 | (doto (History.) 38 | (events/listen 39 | EventType/NAVIGATE 40 | (fn [event] 41 | (secretary/dispatch! (.-token event)))) 42 | (.setEnabled true))) 43 | 44 | ;; ------------------------- 45 | ;; Initialize app 46 | (defn mount-root [] 47 | (reagent/render [current-page] (.getElementById js/document "app"))) 48 | 49 | (defn init! [] 50 | (hook-browser-navigation!) 51 | (mount-root)) 52 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/dev.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:figwheel-no-load {{name}}.dev 2 | (:require [{{name}}.core :as core] 3 | [figwheel.client :as figwheel :include-macros true])) 4 | 5 | (enable-console-print!) 6 | 7 | (figwheel/watch-and-reload 8 | :websocket-url "ws://localhost:8080/figwheel-ws" 9 | :jsload-callback core/mount-root) 10 | 11 | (core/init!) 12 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whostolebenfrog/mr-clojure/d202d9ed0597114feacc4aad0b628341e5102d7f/src/leiningen/new/mr_clojure/favicon.ico -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/integration: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . test_helper.sh 4 | TIMEOUT=30 5 | 6 | if [ "$1" != "wait" ] 7 | then 8 | export FILTER=${1:-integration} 9 | run_test "{{name}}.integration" $TIMEOUT $FILTER 10 | else 11 | lein run 12 | fi 13 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/integration.clj: -------------------------------------------------------------------------------- 1 | (ns {{name}}.integration 2 | (:require [{{name}}.test-common :refer :all] 3 | [clj-http.client :as http] 4 | [environ.core :refer [env]] 5 | [midje.sweet :refer :all])) 6 | 7 | (fact-group 8 | :integration 9 | 10 | (fact "Ping resource returns 200 HTTP response" 11 | (let [response (http/get (url+ "/ping") {:throw-exceptions false})] 12 | response => (contains {:status 200}))) 13 | 14 | (fact "Healthcheck resource returns 200 HTTP response" 15 | (let [response (http/get (url+ "/healthcheck") {:throw-exceptions false})] 16 | response => (contains {:status 200})))) 17 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/jetty: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # {{upper-name}} Control Script 4 | # 5 | # To use this script run it as root - it will switch to the specified user 6 | # 7 | # Here is a little (and extremely primitive) startup/shutdown script 8 | # for RedHat based systems. It assumes that {{upper-name}} lives in /usr/local/{{lower-name}}, 9 | # it's run by user '{{lower-name}}' and JDK binaries are in /usr/java/default/bin. 10 | # All this can be changed in the script itself. 11 | # 12 | # Either modify this script for your requirements or just ensure that 13 | # the following variables are set correctly before calling the script. 14 | # chkconfig: 345 99 05 15 | # description: Runs {{upper-name}} 16 | # processname: {{lower-name}} 17 | 18 | LOCKFILE=/var/lock/subsys/{{lower-name}} 19 | 20 | #define where {{lower-name}} is 21 | GIVEN_HOME="/usr/local/{{lower-name}}" 22 | 23 | JETTY_HOME=${JETTY_HOME:-"$GIVEN_HOME"} 24 | 25 | #define the user under which {{lower-name}} will run, or use 'RUNASIS' to run as the current user 26 | JETTY_USER=${JETTY_USER:-"{{lower-name}}"} 27 | 28 | #make sure java is in your path 29 | JAVAPTH=${JAVAPTH:-"/usr/java/default/bin"} 30 | 31 | #define the script to use to start {{lower-name}} 32 | JETTYSH=${JETTYSH:-"$JETTY_HOME/bin/start.sh"} 33 | 34 | if [ "$JETTY_USER" = "RUNASIS" ]; then 35 | SUBIT="" 36 | else 37 | SUBIT="su - $JETTY_USER -s /bin/sh -c " 38 | fi 39 | 40 | JETTY_CONSOLE="/dev/null" 41 | 42 | JETTY_CMD_START="cd $JETTY_HOME; $JETTYSH" 43 | JETTY_CMD_STOP=${JETTY_CMD_STOP:-"$JETTY_HOME/bin/stop.sh"} 44 | 45 | if [ -z "`echo $PATH | grep $JAVAPTH`" ]; then 46 | export PATH=$PATH:$JAVAPTH 47 | fi 48 | 49 | if [ ! -d "$JETTY_HOME" ]; then 50 | echo JETTY_HOME does not exist as a valid directory : $JETTY_HOME 51 | exit 1 52 | fi 53 | 54 | case "$1" in 55 | start) 56 | echo Starting {{upper-name}} with JETTY_CMD_START=$JETTY_CMD_START 57 | cd $JETTY_HOME 58 | if [ -z "$SUBIT" ]; then 59 | eval $JETTY_CMD_START 60 | else 61 | $SUBIT "$JETTY_CMD_START" 62 | fi 63 | touch $LOCKFILE 64 | ;; 65 | stop) 66 | echo Stopping {{upper-name}} 67 | if [ -z "$SUBIT" ]; then 68 | $JETTY_CMD_STOP 69 | else 70 | $SUBIT "$JETTY_CMD_STOP" 71 | fi 72 | rm -f $LOCKFILE 73 | ;; 74 | restart) 75 | $0 stop 76 | $0 start 77 | ;; 78 | *) 79 | echo "usage: $0 (start|stop|restart)" 80 | esac 81 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ${LOGGING_FILETHRESHOLD:-info} 14 | 15 | ${LOGGING_PATH:-/tmp}/${SERVICE_NAME:-{{name}}}.log 16 | 17 | ${LOGGING_PATH:-/tmp}/${SERVICE_NAME:-{{name}}}.%d{yyyy-MM-dd}.log.zip 18 | 28 19 | 20 | 21 | %d [%thread] %-5level %X{log-id} %logger{36} - %msg%n%ex 22 | 23 | 24 | 25 | 26 | 27 | ${LOGGING_CONSOLETHRESHOLD:-info} 28 | 29 | 30 | %d [%thread] %-5level %X{log-id} %logger{36} - %msg%n%ex 31 | 32 | 33 | 34 | 35 | 36 | ${LOGGING_STASHTHRESHOLD:-warn} 37 | 38 | 39 | ${LOGGING_PATH:-/tmp}/${SERVICE_NAME:-{{name}}}-stash.log 40 | 41 | ${LOGGING_PATH:-/tmp}/${SERVICE_NAME:-{{name}}}-stash.%d{yyyy-MM-dd}.%i.log.zip 42 | 43 | 50MB 44 | 45 | 1 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/page_frame.clj: -------------------------------------------------------------------------------- 1 | (ns {{name}}.page-frame 2 | (:require [hiccup 3 | [core :refer [html]] 4 | [page :refer [include-css include-js]]])) 5 | 6 | (defn page-frame 7 | [dev-mode?] 8 | (html 9 | [:html 10 | [:head 11 | [:meta {:charset "utf-8"}] 12 | [:meta {:name "viewport" 13 | :content "width=device-width, initial-scale=1"}] 14 | [:link {:rel "icon" 15 | :type "image/png" 16 | :href "img/favicon.ico"}] 17 | (include-css (if dev-mode? "css/site.css" "css/site.min.css"))] 18 | [:body 19 | [:div#app 20 | [:p "Loading... "]] 21 | (include-js "js/app.js")]])) 22 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/postinstall.sh: -------------------------------------------------------------------------------- 1 | /bin/echo "postinstall script started [$1]" 2 | 3 | APP_NAME={{lower-name}} 4 | 5 | if [ "$1" -le 1 ] 6 | then 7 | /sbin/chkconfig --add $APP_NAME 8 | else 9 | /sbin/chkconfig --list $APP_NAME 10 | fi 11 | 12 | ln -s /var/encrypted/logs/$APP_NAME /var/log/$APP_NAME 13 | 14 | chown -R $APP_NAME:$APP_NAME /usr/local/$APP_NAME 15 | 16 | chmod 755 /usr/local/$APP_NAME/bin 17 | 18 | /bin/echo "postinstall script finished" 19 | exit 0 20 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/postremove.sh: -------------------------------------------------------------------------------- 1 | /bin/echo "postremove script started [$1]" 2 | 3 | if [ "$1" = 0 ] 4 | then 5 | /usr/sbin/userdel -r {{lower-name}} 2> /dev/null || : 6 | /bin/rm -rf /usr/local/{{lower-name}} 7 | fi 8 | 9 | /bin/echo "postremove script finished" 10 | exit 0 11 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/preinstall.sh: -------------------------------------------------------------------------------- 1 | /bin/echo "preinstall script started [$1]" 2 | 3 | APP_NAME={{lower-name}} 4 | prefixDir=/usr/local/$APP_NAME 5 | identifier=$APP_NAME.jar 6 | 7 | isJettyRunning=`pgrep java -lf | grep $identifier | cut -d" " -f1 | /usr/bin/wc -l` 8 | if [ $isJettyRunning -eq 0 ] 9 | then 10 | /bin/echo "{{upper-name}} is not running" 11 | else 12 | sleepCounter=0 13 | sleepIncrement=2 14 | waitTimeOut=600 15 | 16 | /bin/echo "Timeout is $waitTimeOut seconds" 17 | /bin/echo "{{upper-name}} is running, stopping service" 18 | /sbin/service $APP_NAME stop & 19 | myPid=$! 20 | 21 | until [ `pgrep java -lf | grep $identifier | cut -d" " -f1 | /usr/bin/wc -l` -eq 0 ] 22 | do 23 | if [ $sleepCounter -ge $waitTimeOut ] 24 | then 25 | /usr/bin/pkill -KILL -f '$identifier' 26 | /bin/echo "Killed {{upper-name}}" 27 | break 28 | fi 29 | sleep $sleepIncrement 30 | sleepCounter=$(($sleepCounter + $sleepIncrement)) 31 | done 32 | 33 | wait $myPid 34 | 35 | /bin/echo "{{upper-name}} down" 36 | fi 37 | 38 | rm -rf $prefixDir 39 | 40 | if [ "$1" -le 1 ] 41 | then 42 | mkdir -p $prefixDir 43 | /usr/sbin/useradd -r -s /sbin/nologin -d $prefixDir -m -c "{{upper-name}} user for the {{upper-name}} service" $APP_NAME 2> /dev/null || : 44 | fi 45 | 46 | /usr/bin/getent passwd $APP_NAME 47 | 48 | /bin/echo "preinstall script finished" 49 | exit 0 50 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/preremove.sh: -------------------------------------------------------------------------------- 1 | /bin/echo "preremove script started [$1]" 2 | 3 | APP_NAME={{lower-name}} 4 | prefixDir=/usr/local/$APP_NAME 5 | identifier=$APP_NAME.jar 6 | 7 | isJettyRunning=`pgrep java -lf | grep $identifier | cut -d" " -f1 | /usr/bin/wc -l` 8 | if [ $isJettyRunning -eq 0 ] 9 | then 10 | /bin/echo "{{upper-name}} is not running" 11 | else 12 | sleepCounter=0 13 | sleepIncrement=2 14 | waitTimeOut=600 15 | /bin/echo "Timeout is $waitTimeOut seconds" 16 | /bin/echo "{{upper-name}} is running, stopping service" 17 | /sbin/service $APP_NAME stop & 18 | myPid=$! 19 | 20 | until [ `pgrep java -lf | grep $identifier | cut -d" " -f1 | /usr/bin/wc -l` -eq 0 ] 21 | do 22 | if [ $sleepCounter -ge $waitTimeOut ] 23 | then 24 | /usr/bin/pkill -KILL -f '$identifier' 25 | /bin/echo "Killed {{upper-name}}" 26 | break 27 | fi 28 | sleep $sleepIncrement 29 | sleepCounter=$(($sleepCounter + $sleepIncrement)) 30 | done 31 | 32 | wait $myPid 33 | 34 | /bin/echo "{{upper-name}} down" 35 | fi 36 | 37 | if [ "$1" = 0 ] 38 | then 39 | /sbin/chkconfig --del $APP_NAME 40 | else 41 | /sbin/chkconfig --list $APP_NAME 42 | fi 43 | 44 | /bin/echo "preremove script finished" 45 | exit 0 46 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/prod.cljs: -------------------------------------------------------------------------------- 1 | (ns {{name}}.prod 2 | (:require [{{name}}.core :as core])) 3 | 4 | ;;ignore println statements in prod 5 | (set! *print-fn* (fn [& _])) 6 | 7 | (core/init!) 8 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/project.clj: -------------------------------------------------------------------------------- 1 | (defproject {{name}} "1.0.0-SNAPSHOT" 2 | :description "{{upper-name}} service" 3 | 4 | :dependencies [[ch.qos.logback/logback-classic "1.1.3"] 5 | [cheshire "5.4.0"] 6 | [clj-http "1.1.2"] 7 | [clj-time "0.9.0"] 8 | [compojure "1.3.4"] 9 | [environ "1.0.0"] 10 | [com.codahale.metrics/metrics-logback "3.0.2"] 11 | [mixradio/graphite-filter "1.0.0"] 12 | [mixradio/instrumented-ring-jetty-adapter "1.0.4" :exclusions [metrics-clojure]] 13 | [mixradio/radix "1.0.13"] 14 | [net.logstash.logback/logstash-logback-encoder "4.3"] 15 | [org.clojure/clojure "1.7.0"] 16 | [org.clojure/tools.logging "0.3.1"] 17 | [ring/ring-json "0.3.1"] 18 | [ring-middleware-format "0.5.0"]{{#cljs-app?}} 19 | 20 | [cljsjs/react "0.13.3-1"] 21 | [hiccup "1.0.5"] 22 | [org.clojure/clojurescript "0.0-3308" :scope "provided"] 23 | [org.clojure/core.async "0.1.346.0-17112a-alpha"] 24 | [prone "0.8.2"] 25 | [reagent "0.5.0"] 26 | [reagent-utils "0.1.5"] 27 | [secretary "1.2.3"]{{/cljs-app?}}] 28 | 29 | :exclusions [commons-logging 30 | log4j 31 | org.clojure/clojure{{#cljs-app?}} 32 | cljsjs/react-with-addons{{/cljs-app?}}] 33 | 34 | :plugins [[lein-environ "1.0.0"] 35 | [lein-release "1.0.5"] 36 | [lein-ring "0.8.12"]{{#cljs-app?}} 37 | [lein-asset-minifier "0.2.2"]{{/cljs-app?}}] 38 | {{#cljs-app?}} 39 | 40 | :source-paths ["src/clj" "src/cljs"] 41 | {{/cljs-app?}} 42 | 43 | :env {:auto-reload "true" 44 | :environment-name "poke" 45 | :graphite-enabled "false" 46 | :graphite-host "" 47 | :graphite-port "2003" 48 | :graphite-post-interval-seconds "60" 49 | :logging-consolethreshold "info" 50 | :logging-filethreshold "info" 51 | :logging-level "info" 52 | :logging-path "/tmp" 53 | :logging-stashthreshold "off" 54 | :production "false" 55 | :requestlog-enabled "false" 56 | :requestlog-retainhours "24" 57 | :restdriver-port "8081" 58 | :service-name "{{name}}" 59 | :service-port "8080" 60 | :service-url "http://localhost:%s" 61 | :shutdown-timeout-millis "5000" 62 | :start-timeout-seconds "120" 63 | :threads "254"} 64 | 65 | :lein-release {:deploy-via :shell 66 | :shell ["lein" "do" "clean," "uberjar," "pom," "rpm"]} 67 | 68 | :ring {:handler {{name}}.web/app 69 | :main {{name}}.setup 70 | :port ~(Integer/valueOf (get (System/getenv) "SERVICE_PORT" "8080")) 71 | :init {{name}}.setup/setup 72 | :browser-uri "/healthcheck" 73 | :nrepl {:start? true}} 74 | 75 | :uberjar-name "{{lower-name}}.jar" 76 | {{#cljs-app?}} 77 | 78 | :cljsbuild {:builds {:app {:source-paths ["src/cljs"] 79 | :compiler {:output-to "resources/public/js/app.js" 80 | :output-dir "resources/public/js/out" 81 | :asset-path "js/out" 82 | :optimizations :none 83 | :pretty-print true}}}} 84 | 85 | :clean-targets ^{:protect false} [:target-path 86 | [:cljsbuild :builds :app :compiler :output-dir] 87 | [:cljsbuild :builds :app :compiler :output-to]] 88 | 89 | :minify-assets {:assets {"resources/public/css/site.min.css" "resources/public/css/site.css"}} 90 | {{/cljs-app?}} 91 | 92 | :profiles {:dev {:dependencies [[com.github.rest-driver/rest-client-driver "1.1.42" 93 | :exclusions [org.slf4j/slf4j-nop 94 | javax.servlet/servlet-api 95 | org.eclipse.jetty.orbit/javax.servlet]] 96 | [junit "4.12"] 97 | [midje "1.6.3"] 98 | [rest-cljer "0.1.20"]{{#cljs-app?}} 99 | [lein-figwheel "0.3.7"] 100 | [org.clojure/tools.nrepl "0.2.10"]{{/cljs-app?}}] 101 | 102 | :plugins [[lein-kibit "0.0.8"] 103 | [lein-midje "3.1.3"] 104 | [lein-rpm "0.0.5"]{{#cljs-app?}} 105 | [lein-figwheel "0.3.7"] 106 | [lein-cljsbuild "1.0.6"]{{/cljs-app?}}]{{#cljs-app?}} 107 | 108 | :env {:dev-mode true} 109 | 110 | :repl-options {:init-ns {{name}}.repl} 111 | 112 | :source-paths ["env/dev/clj"] 113 | 114 | :figwheel {:http-server-root "public" 115 | :server-port 8080 116 | :nrepl-port 7002 117 | :css-dirs ["resources/public/css"] 118 | :ring-handler {{name}}.web/app} 119 | 120 | :cljsbuild {:builds {:app {:source-paths ["env/dev/cljs"] 121 | :compiler {:main "{{name}}.dev" 122 | :source-map true}}}}{{/cljs-app?}}}{{#cljs-app?}} 123 | 124 | :uberjar {:hooks [leiningen.cljsbuild minify-assets.plugin/hooks] 125 | :aot :all 126 | :omit-source true 127 | :cljsbuild {:jar true 128 | :builds {:app 129 | {:source-paths ["env/prod/cljs"] 130 | :compiler 131 | {:optimizations :advanced 132 | :pretty-print false}}}}}{{/cljs-app?}}} 133 | 134 | :rpm {:name "{{lower-name}}" 135 | :summary "RPM for {{upper-name}} service" 136 | :copyright "MixRadio {{year}}" 137 | :preinstall {:scriptFile "scripts/rpm/preinstall.sh"} 138 | :postinstall {:scriptFile "scripts/rpm/postinstall.sh"} 139 | :preremove {:scriptFile "scripts/rpm/preremove.sh"} 140 | :postremove {:scriptFile "scripts/rpm/postremove.sh"} 141 | :requires ["jdk >= 2000:1.7.0_55-fcs"] 142 | :mappings [{:directory "/usr/local/{{lower-name}}" 143 | :filemode "444" 144 | :username "{{lower-name}}" 145 | :groupname "{{lower-name}}" 146 | :sources {:source [{:location "target/{{lower-name}}.jar"}]}} 147 | {:directory "/usr/local/{{lower-name}}/bin" 148 | :filemode "744" 149 | :username "{{lower-name}}" 150 | :groupname "{{lower-name}}" 151 | :sources {:source [{:location "scripts/bin"}]}} 152 | {:directory "/etc/rc.d/init.d" 153 | :filemode "755" 154 | :sources {:source [{:location "scripts/service/{{lower-name}}" 155 | :destination "{{lower-name}}"}]}}]} 156 | 157 | 158 | :aot [{{name}}.setup] 159 | 160 | :main {{name}}.setup) 161 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/setup.clj: -------------------------------------------------------------------------------- 1 | (ns {{name}}.setup 2 | (:require [{{name}}.web :as web] 3 | [clojure.string :as str] 4 | [environ.core :refer [env]] 5 | [metrics.core :refer [default-registry]] 6 | [mixradio.instrumented-jetty :refer [run-jetty]] 7 | [radix.setup :as setup]) 8 | (:import [ch.qos.logback.classic Logger] 9 | [com.codahale.metrics.logback InstrumentedAppender] 10 | [mixradio.metrics GraphiteReporterFilter] 11 | [org.slf4j LoggerFactory]) 12 | (:gen-class)) 13 | 14 | (defonce server 15 | (atom nil)) 16 | 17 | (defn configure-server 18 | [server] 19 | (doto server 20 | (.setStopAtShutdown true) 21 | (.setStopTimeout setup/shutdown-timeout))) 22 | 23 | (defn start-server 24 | [] 25 | (run-jetty #'web/app {:port setup/service-port 26 | :max-threads setup/threads 27 | :join? false 28 | :stacktraces? (not setup/production?) 29 | :auto-reload? (not setup/production?) 30 | :configurator configure-server 31 | :send-server-version false})) 32 | 33 | (defn- configure-graphite-appender 34 | [] 35 | (let [factory (LoggerFactory/getILoggerFactory) 36 | root (.getLogger factory Logger/ROOT_LOGGER_NAME) 37 | appender (InstrumentedAppender. default-registry)] 38 | 39 | (.setContext appender (.getLoggerContext root)) 40 | (.addFilter appender (GraphiteReporterFilter.)) 41 | (.start appender) 42 | (.addAppender root appender))) 43 | 44 | (defn remove-all-metrics 45 | [] 46 | (doseq [metric (.getNames default-registry)] 47 | (.remove default-registry metric))) 48 | 49 | (defn start 50 | [] 51 | (setup/configure-logging) 52 | (configure-graphite-appender) 53 | (setup/start-graphite-reporting {:graphite-prefix (str/join "." [(env :environment-name) (env :service-name) (env :box-id setup/hostname)])}) 54 | (reset! server (start-server))) 55 | 56 | (defn stop 57 | [] 58 | (remove-all-metrics) 59 | (when-let [s @server] 60 | (.stop s) 61 | (reset! server nil))) 62 | 63 | (defn kill-service 64 | [] 65 | (stop) 66 | (shutdown-agents)) 67 | 68 | (defn -main 69 | [& args] 70 | (.addShutdownHook (Runtime/getRuntime) (Thread. kill-service)) 71 | (start)) 72 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Helvetica Neue', Verdana, Helvetica, Arial, sans-serif; 3 | 4 | max-width: 600px; 5 | margin: 0 auto; 6 | padding-top: 72px; 7 | -webkit-font-smoothing: antialiased; 8 | font-size: 1.125em; 9 | color: #333; 10 | line-height: 1.5em; 11 | } 12 | 13 | h1, h2, h3 { 14 | color: #000; 15 | } 16 | h1 { 17 | font-size: 2.5em 18 | } 19 | 20 | h2 { 21 | font-size: 2em 22 | } 23 | 24 | h3 { 25 | font-size: 1.5em 26 | } 27 | 28 | a { 29 | text-decoration: none; 30 | color: #09f; 31 | } 32 | 33 | a:hover { 34 | text-decoration: underline; 35 | } 36 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | APP_NAME={{lower-name}} 4 | 5 | PIDS=$(pgrep java -lf | grep $APP_NAME | cut -d" " -f1); 6 | 7 | if [ -n "$PIDS" ] 8 | then 9 | echo "{{upper-name}} is already running in process $PIDS"; 10 | exit 1 11 | fi 12 | 13 | JETTY_HOME=/usr/local/$APP_NAME 14 | JAR_NAME=$JETTY_HOME/$APP_NAME.jar 15 | 16 | IFS="$(echo -e "\n\r")" 17 | for LINE in `cat /etc/${APP_NAME}.properties` 18 | do 19 | case $LINE in 20 | \#*) ;; 21 | *) 22 | LEFT=`echo $LINE | cut -d"=" -f1` 23 | RIGHT=`echo $LINE | cut -d"=" -f2- | sed -e 's/\\\:/:/g' | sed -e 's/\\\=/=/g' | sed -e 's/\\\ / /g' | sed -e 's/\\\!/!/g' | sed -e 's/\\\\\\\/\\\/g'` 24 | ULEFT=`echo $LEFT | awk '{print toupper($0)}' | sed -e 's/\./_/g'` 25 | export $ULEFT=$RIGHT 26 | esac 27 | done 28 | 29 | IFS="$(echo -e " ")" 30 | 31 | SERVICE_PORT=${SERVICE_PORT:-"8080"} 32 | HEALTHCHECK_PATH=${HEALTHCHECK_PATH:-"/healthcheck"} 33 | START_TIMEOUT_SECONDS=${START_TIMEOUT_SECONDS:-"60"} 34 | LOGGING_PATH=${LOGGING_PATH:-"/var/log/${SERVICE_NAME}"} 35 | LOG_FILE=${LOGGING_PATH}/{{lower-name}}.out 36 | ERR_FILE=${LOGGING_PATH}/{{lower-name}}.err 37 | 38 | mkdir -p /var/encrypted/logs/${APP_NAME} 39 | 40 | nohup java $SERVICE_JVMARGS -jar $JAR_NAME > $LOG_FILE 2> $ERR_FILE < /dev/null & 41 | 42 | statusUrl=http://localhost:$SERVICE_PORT$HEALTHCHECK_PATH 43 | waitTimeout=$START_TIMEOUT_SECONDS 44 | sleepCounter=0 45 | sleepIncrement=2 46 | 47 | echo "Giving {{upper-name}} $waitTimeout seconds to start successfully" 48 | echo "Using $statusUrl to determine service status" 49 | 50 | retVal=0 51 | 52 | until [ `curl --write-out %{http_code} --silent --output /dev/null $statusUrl` -eq 200 ] 53 | do 54 | if [ $sleepCounter -ge $waitTimeout ] 55 | then 56 | echo "{{upper-name}} didn't start within $waitTimeout seconds." 57 | PIDS=$(pgrep java -lf | grep $APP_NAME | cut -d" " -f1); 58 | if [ -n "$PIDS" ] 59 | then 60 | echo "Killing $PIDS"; 61 | echo $PIDS | xargs kill; 62 | else 63 | echo "No running instances found"; 64 | fi 65 | retVal=1 66 | break 67 | fi 68 | sleep $sleepIncrement 69 | sleepCounter=$(($sleepCounter + $sleepIncrement)) 70 | done 71 | 72 | echo ====================================================== 73 | echo Contents of $LOG_FILE 74 | echo ====================================================== 75 | cat $LOG_FILE 76 | echo ====================================================== 1>&2 77 | echo Contents of $ERR_FILE 1>&2 78 | echo ====================================================== 1>&2 79 | cat $ERR_FILE 1>&2 80 | 81 | if [ $retVal -eq 1 ] 82 | then 83 | echo "Starting {{upper-name}} failed" 84 | else 85 | echo "Starting {{upper-name}} succeeded" 86 | fi 87 | 88 | exit $retVal 89 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PIDS=$(pgrep java -lf | grep {{lower-name}} | cut -d" " -f1); 4 | 5 | if [ -n "$PIDS" ] 6 | then 7 | echo "Killing $PIDS"; 8 | echo $PIDS | xargs kill; 9 | else 10 | echo "No running instances found"; 11 | fi 12 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/test_common.clj: -------------------------------------------------------------------------------- 1 | (ns {{name}}.test-common 2 | (:require [cheshire.core :as json] 3 | [clojure.string :as str] 4 | [environ.core :refer [env]])) 5 | 6 | (defn url+ [& suffix] 7 | (apply str (format (env :service-url) (env :service-port)) suffix)) 8 | 9 | (defn json-body 10 | "Reads the body of the request as json and parses it into a map with keywords. 11 | 12 | Fails pre-condition if content type is not application/json." 13 | [resp] 14 | {:pre [(re-matches #"application/(.+\+)?json.*" (get-in resp [:headers "content-type"]))]} 15 | (json/parse-string (:body resp) true)) 16 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/test_helper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TIMEOUT=30 4 | 5 | wait_for_port () { 6 | port=$1 7 | timeout=$2 8 | 9 | for i in `seq 1 $TIMEOUT` 10 | do 11 | if [ `lsof -nt -iTCP:${SERVICE_PORT:="8080"} -sTCP:LISTEN | wc -l` -gt 0 ] 12 | then 13 | return 0 14 | fi 15 | sleep 1 16 | done 17 | 18 | return 1 19 | } 20 | 21 | server_pid= 22 | kill_server () { 23 | if [ $server_pid ]; then echo "killing $server_pid"; kill $server_pid; fi 24 | } 25 | 26 | handle_force_exit () { 27 | echo -e "\nHandling interrupt" 28 | kill_server 29 | exit 1 30 | } 31 | 32 | trap handle_force_exit INT 33 | 34 | run_test () { 35 | type=$1 36 | timeout=$2 37 | 38 | lein trampoline run& 39 | server_pid=$! 40 | port=${SERVICE_PORT:="8080"} 41 | 42 | echo "PID: $server_pid" 43 | echo -e "**********\nGiving lein $timeout seconds to build and start the application...\n**********" 44 | if wait_for_port $port $timeout 45 | then 46 | lein midje $1 47 | at_res=$? 48 | 49 | kill_server 50 | exit $at_res 51 | else 52 | kill_server 53 | echo "{{upper-name}} failed to start, it was not reachable on port $port within $timeout seconds" 54 | exit 1 55 | fi 56 | } 57 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/web.clj: -------------------------------------------------------------------------------- 1 | (ns {{name}}.web 2 | (:require [compojure 3 | [core :refer [defroutes GET]] 4 | [route :as route]] 5 | [environ.core :refer [env]] 6 | [metrics.ring 7 | [expose :refer [expose-metrics-as-json]] 8 | [instrument :refer [instrument]]] 9 | {{#cljs-app?}} 10 | [{{name}}.page-frame :refer [page-frame]] 11 | [prone.middleware :refer [wrap-exceptions]] 12 | {{/cljs-app?}} 13 | [radix 14 | [error :refer [error-response wrap-error-handling]] 15 | [ignore-trailing-slash :refer [wrap-ignore-trailing-slash]] 16 | [reload :refer [wrap-reload]] 17 | [setup :as setup]] 18 | [ring.middleware 19 | [format-params :refer [wrap-json-kw-params]] 20 | [json :refer [wrap-json-response]] 21 | [params :refer [wrap-params]]])) 22 | 23 | (def version 24 | (setup/version "{{name}}")) 25 | 26 | {{#cljs-app?}} 27 | (def dev-mode? 28 | (boolean (env :dev-mode false))) 29 | {{/cljs-app?}} 30 | 31 | (defn healthcheck 32 | [] 33 | (let [body {:name "{{name}}" 34 | :version version 35 | :success true 36 | :dependencies []}] 37 | {:headers {"content-type" "application/json"} 38 | :status (if (:success body) 200 500) 39 | :body body})) 40 | 41 | (defroutes routes 42 | 43 | (GET "/healthcheck" 44 | [] (healthcheck)) 45 | 46 | (GET "/ping" 47 | [] "pong") 48 | 49 | {{#cljs-app?}} 50 | (GET "/" 51 | [] (page-frame dev-mode?)) 52 | 53 | (route/resources "/") 54 | {{/cljs-app?}} 55 | 56 | (route/not-found (error-response "Resource not found" 404))) 57 | 58 | (def app 59 | (-> routes 60 | {{#cljs-app?}} 61 | (cond-> dev-mode? wrap-exceptions) 62 | {{/cljs-app?}} 63 | (wrap-reload) 64 | (instrument) 65 | (wrap-error-handling) 66 | (wrap-ignore-trailing-slash) 67 | (wrap-json-response) 68 | (wrap-json-kw-params) 69 | (wrap-params) 70 | (expose-metrics-as-json))) 71 | -------------------------------------------------------------------------------- /src/leiningen/new/mr_clojure/web_unit.clj: -------------------------------------------------------------------------------- 1 | (ns {{name}}.unit.web 2 | (:require [{{name}}.web :refer :all] 3 | [cheshire.core :as json] 4 | [midje.sweet :refer :all]) 5 | (:import [java.io ByteArrayInputStream InputStream])) 6 | 7 | (defn- json-response? 8 | [res] 9 | (when-let [content-type (get-in res [:headers "Content-Type"])] 10 | (re-find #"^application/(..+)?json.+" content-type))) 11 | 12 | (defn request 13 | "Creates a compojure request map and applies it to our routes. 14 | Accepts method, resource and optionally an extended map" 15 | [method resource & [{:keys [params body content-type headers] 16 | :or {params {} 17 | headers {}}}]] 18 | (let [{:keys [body] :as res} 19 | (app (merge {:request-method method 20 | :uri resource 21 | :params params 22 | :headers headers} 23 | (when body {:body (-> body json/generate-string .getBytes ByteArrayInputStream.)}) 24 | (when content-type {:content-type content-type})))] 25 | (cond-> res 26 | (instance? InputStream body) 27 | (update-in [:body] slurp) 28 | 29 | (json-response? res) 30 | (update-in [:body] #(json/parse-string % true))))) 31 | 32 | (fact-group 33 | :unit 34 | 35 | (fact "Ping returns a pong" 36 | (:body (request :get "/ping")) => "pong") 37 | 38 | (fact "Healthcheck returns OK" 39 | (let [resp (request :get "/healthcheck")] 40 | (:status resp) => 200 41 | (get-in resp [:body :name]) => "{{name}}"))) 42 | --------------------------------------------------------------------------------