Added Luminus app skeleton; added ADL.
This commit is contained in:
		
							parent
							
								
									8a923c1b33
								
							
						
					
					
						commit
						f3c0e728a4
					
				
					 52 changed files with 1761 additions and 0 deletions
				
			
		
							
								
								
									
										28
									
								
								Capstanfile
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Capstanfile
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,28 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Name of the base image. Capstan will download this automatically from
 | 
				
			||||||
 | 
					# Cloudius S3 repository.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#base: cloudius/osv
 | 
				
			||||||
 | 
					base: cloudius/osv-openjdk8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# The command line passed to OSv to start up the application.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					cmdline: /java.so -jar /pastoralist/app.jar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# The command to use to build the application.
 | 
				
			||||||
 | 
					# You can use any build tool/command (make/rake/lein/boot) - this runs locally on your machine
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# For Leiningen, you can use:
 | 
				
			||||||
 | 
					#build: lein uberjar
 | 
				
			||||||
 | 
					# For Boot, you can use:
 | 
				
			||||||
 | 
					#build: boot build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# List of files that are included in the generated image.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					files:
 | 
				
			||||||
 | 
					  /pastoralist/app.jar: ./target/uberjar/pastoralist.jar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										7
									
								
								Dockerfile
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								Dockerfile
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					FROM openjdk:8-alpine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY target/uberjar/pastoralist.jar /pastoralist/app.jar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					EXPOSE 3000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CMD ["java", "-jar", "/pastoralist/app.jar"]
 | 
				
			||||||
							
								
								
									
										1
									
								
								Procfile
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								Procfile
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					web: java -cp target/uberjar/pastoralist.jar clojure.main -m pastoralist.core
 | 
				
			||||||
							
								
								
									
										21
									
								
								README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								README.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					# pastoralist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					generated using Luminus version "3.54"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FIXME
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Prerequisites
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You will need [Leiningen][1] 2.0 or above installed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[1]: https://github.com/technomancy/leiningen
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Running
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To start a web server for the application, run:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    lein run 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright © 2019 FIXME
 | 
				
			||||||
							
								
								
									
										16
									
								
								dev-config.edn
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								dev-config.edn
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					;; WARNING
 | 
				
			||||||
 | 
					;; The dev-config.edn file is used for local environment variables, such as database credentials.
 | 
				
			||||||
 | 
					;; This file is listed in .gitignore and will be excluded from version control by Git.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{:dev true
 | 
				
			||||||
 | 
					 :port 3000
 | 
				
			||||||
 | 
					 ;; when :nrepl-port is set the application starts the nREPL server on load
 | 
				
			||||||
 | 
					 :nrepl-port 7000
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					 ;;Twitter used as an example, replace these URIs with the OAuth provider of your choice
 | 
				
			||||||
 | 
					 :request-token-uri "https://api.twitter.com/oauth/request_token"
 | 
				
			||||||
 | 
					 :access-token-uri "https://api.twitter.com/oauth/access_token"
 | 
				
			||||||
 | 
					 :authorize-uri "https://api.twitter.com/oauth/authenticate"
 | 
				
			||||||
 | 
					 ; set your dev database connection URL here
 | 
				
			||||||
 | 
					 ; :database-url "postgresql://localhost/pastoralist_dev?user=simon&password=db_user_password_here"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										77
									
								
								env/dev/clj/pastoralist/core.clj
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								env/dev/clj/pastoralist/core.clj
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,77 @@
 | 
				
			||||||
 | 
					(ns pastoralist.core
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [pastoralist.handler :as handler]
 | 
				
			||||||
 | 
					    [pastoralist.nrepl :as nrepl]
 | 
				
			||||||
 | 
					    [luminus.http-server :as http]
 | 
				
			||||||
 | 
					    [luminus-migrations.core :as migrations]
 | 
				
			||||||
 | 
					    [pastoralist.config :refer [env]]
 | 
				
			||||||
 | 
					    [clojure.tools.cli :refer [parse-opts]]
 | 
				
			||||||
 | 
					    [clojure.tools.logging :as log]
 | 
				
			||||||
 | 
					    [mount.core :as mount])
 | 
				
			||||||
 | 
					  (:gen-class))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					;; log uncaught exceptions in threads
 | 
				
			||||||
 | 
					(Thread/setDefaultUncaughtExceptionHandler
 | 
				
			||||||
 | 
					  (reify Thread$UncaughtExceptionHandler
 | 
				
			||||||
 | 
					    (uncaughtException [_ thread ex]
 | 
				
			||||||
 | 
					      (log/error {:what :uncaught-exception
 | 
				
			||||||
 | 
					                  :exception ex
 | 
				
			||||||
 | 
					                  :where (str "Uncaught exception on" (.getName thread))}))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(def cli-options
 | 
				
			||||||
 | 
					  [["-p" "--port PORT" "Port number"
 | 
				
			||||||
 | 
					    :parse-fn #(Integer/parseInt %)]])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(mount/defstate ^{:on-reload :noop} http-server
 | 
				
			||||||
 | 
					  :start
 | 
				
			||||||
 | 
					  (http/start
 | 
				
			||||||
 | 
					    (-> env
 | 
				
			||||||
 | 
					        (assoc  :handler (handler/app))
 | 
				
			||||||
 | 
					        (update :io-threads #(or % (* 2 (.availableProcessors (Runtime/getRuntime)))))
 | 
				
			||||||
 | 
					        (update :port #(or (-> env :options :port) %))))
 | 
				
			||||||
 | 
					  :stop
 | 
				
			||||||
 | 
					  (http/stop http-server))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(mount/defstate ^{:on-reload :noop} repl-server
 | 
				
			||||||
 | 
					  :start
 | 
				
			||||||
 | 
					  (when (env :nrepl-port)
 | 
				
			||||||
 | 
					    (nrepl/start {:bind (env :nrepl-bind)
 | 
				
			||||||
 | 
					                  :port (env :nrepl-port)}))
 | 
				
			||||||
 | 
					  :stop
 | 
				
			||||||
 | 
					  (when repl-server
 | 
				
			||||||
 | 
					    (nrepl/stop repl-server)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn init-jndi []
 | 
				
			||||||
 | 
					  (System/setProperty "java.naming.factory.initial"
 | 
				
			||||||
 | 
					                      "org.apache.naming.java.javaURLContextFactory")
 | 
				
			||||||
 | 
					  (System/setProperty "java.naming.factory.url.pkgs"
 | 
				
			||||||
 | 
					                      "org.apache.naming"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn start-app [args]
 | 
				
			||||||
 | 
					  (init-jndi)
 | 
				
			||||||
 | 
					  (doseq [component (-> args
 | 
				
			||||||
 | 
					                        (parse-opts cli-options)
 | 
				
			||||||
 | 
					                        mount/start-with-args
 | 
				
			||||||
 | 
					                        :started)]
 | 
				
			||||||
 | 
					    (log/info component "started"))
 | 
				
			||||||
 | 
					  (.addShutdownHook (Runtime/getRuntime) (Thread. handler/destroy)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn -main [& args]
 | 
				
			||||||
 | 
					  (mount/start #'pastoralist.config/env)
 | 
				
			||||||
 | 
					  (cond
 | 
				
			||||||
 | 
					    (nil? (:database-url env))
 | 
				
			||||||
 | 
					    (do
 | 
				
			||||||
 | 
					      (log/error "Database configuration not found, :database-url environment variable must be set before running")
 | 
				
			||||||
 | 
					      (System/exit 1))
 | 
				
			||||||
 | 
					    (some #{"init"} args)
 | 
				
			||||||
 | 
					    (do
 | 
				
			||||||
 | 
					      (migrations/init (select-keys env [:database-url :init-script]))
 | 
				
			||||||
 | 
					      (System/exit 0))
 | 
				
			||||||
 | 
					    (migrations/migration? args)
 | 
				
			||||||
 | 
					    (do
 | 
				
			||||||
 | 
					      (migrations/migrate args (select-keys env [:database-url]))
 | 
				
			||||||
 | 
					      (System/exit 0))
 | 
				
			||||||
 | 
					    :else
 | 
				
			||||||
 | 
					    (start-app args)))
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
							
								
								
									
										11
									
								
								env/dev/clj/pastoralist/dev_middleware.clj
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								env/dev/clj/pastoralist/dev_middleware.clj
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					(ns pastoralist.dev-middleware
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [ring.middleware.reload :refer [wrap-reload]]
 | 
				
			||||||
 | 
					    [selmer.middleware :refer [wrap-error-page]]
 | 
				
			||||||
 | 
					    [prone.middleware :refer [wrap-exceptions]]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn wrap-dev [handler]
 | 
				
			||||||
 | 
					  (-> handler
 | 
				
			||||||
 | 
					      wrap-reload
 | 
				
			||||||
 | 
					      wrap-error-page
 | 
				
			||||||
 | 
					      (wrap-exceptions {:app-namespaces ['pastoralist]})))
 | 
				
			||||||
							
								
								
									
										15
									
								
								env/dev/clj/pastoralist/env.clj
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								env/dev/clj/pastoralist/env.clj
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,15 @@
 | 
				
			||||||
 | 
					(ns pastoralist.env
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [selmer.parser :as parser]
 | 
				
			||||||
 | 
					    [clojure.tools.logging :as log]
 | 
				
			||||||
 | 
					    [pastoralist.dev-middleware :refer [wrap-dev]]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(def defaults
 | 
				
			||||||
 | 
					  {:init
 | 
				
			||||||
 | 
					   (fn []
 | 
				
			||||||
 | 
					     (parser/cache-off!)
 | 
				
			||||||
 | 
					     (log/info "\n-=[pastoralist started successfully using the development profile]=-"))
 | 
				
			||||||
 | 
					   :stop
 | 
				
			||||||
 | 
					   (fn []
 | 
				
			||||||
 | 
					     (log/info "\n-=[pastoralist has shut down successfully]=-"))
 | 
				
			||||||
 | 
					   :middleware wrap-dev})
 | 
				
			||||||
							
								
								
									
										12
									
								
								env/dev/clj/pastoralist/figwheel.clj
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								env/dev/clj/pastoralist/figwheel.clj
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					(ns pastoralist.figwheel
 | 
				
			||||||
 | 
					  (:require [figwheel-sidecar.repl-api :as ra]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn start-fw []
 | 
				
			||||||
 | 
					  (ra/start-figwheel!))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn stop-fw []
 | 
				
			||||||
 | 
					  (ra/stop-figwheel!))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn cljs []
 | 
				
			||||||
 | 
					  (ra/cljs-repl))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										64
									
								
								env/dev/clj/user.clj
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								env/dev/clj/user.clj
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,64 @@
 | 
				
			||||||
 | 
					(ns user
 | 
				
			||||||
 | 
					  "Userspace functions you can run by default in your local REPL."
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					   [pastoralist.config :refer [env]]
 | 
				
			||||||
 | 
					    [clojure.pprint]
 | 
				
			||||||
 | 
					    [clojure.spec.alpha :as s]
 | 
				
			||||||
 | 
					    [expound.alpha :as expound]
 | 
				
			||||||
 | 
					    [mount.core :as mount]
 | 
				
			||||||
 | 
					    [pastoralist.figwheel :refer [start-fw stop-fw cljs]]
 | 
				
			||||||
 | 
					    [pastoralist.core :refer [start-app]]
 | 
				
			||||||
 | 
					    [pastoralist.db.core]
 | 
				
			||||||
 | 
					    [conman.core :as conman]
 | 
				
			||||||
 | 
					    [luminus-migrations.core :as migrations]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(alter-var-root #'s/*explain-out* (constantly expound/printer))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(add-tap (bound-fn* clojure.pprint/pprint))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn start
 | 
				
			||||||
 | 
					  "Starts application.
 | 
				
			||||||
 | 
					  You'll usually want to run this on startup."
 | 
				
			||||||
 | 
					  []
 | 
				
			||||||
 | 
					  (mount/start-without #'pastoralist.core/repl-server))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn stop
 | 
				
			||||||
 | 
					  "Stops application."
 | 
				
			||||||
 | 
					  []
 | 
				
			||||||
 | 
					  (mount/stop-except #'pastoralist.core/repl-server))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn restart
 | 
				
			||||||
 | 
					  "Restarts application."
 | 
				
			||||||
 | 
					  []
 | 
				
			||||||
 | 
					  (stop)
 | 
				
			||||||
 | 
					  (start))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn restart-db
 | 
				
			||||||
 | 
					  "Restarts database."
 | 
				
			||||||
 | 
					  []
 | 
				
			||||||
 | 
					  (mount/stop #'pastoralist.db.core/*db*)
 | 
				
			||||||
 | 
					  (mount/start #'pastoralist.db.core/*db*)
 | 
				
			||||||
 | 
					  (binding [*ns* 'pastoralist.db.core]
 | 
				
			||||||
 | 
					    (conman/bind-connection pastoralist.db.core/*db* "sql/queries.sql")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn reset-db
 | 
				
			||||||
 | 
					  "Resets database."
 | 
				
			||||||
 | 
					  []
 | 
				
			||||||
 | 
					  (migrations/migrate ["reset"] (select-keys env [:database-url])))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn migrate
 | 
				
			||||||
 | 
					  "Migrates database up for all outstanding migrations."
 | 
				
			||||||
 | 
					  []
 | 
				
			||||||
 | 
					  (migrations/migrate ["migrate"] (select-keys env [:database-url])))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn rollback
 | 
				
			||||||
 | 
					  "Rollback latest database migration."
 | 
				
			||||||
 | 
					  []
 | 
				
			||||||
 | 
					  (migrations/migrate ["rollback"] (select-keys env [:database-url])))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn create-migration
 | 
				
			||||||
 | 
					  "Create a new up and down migration file with a generated timestamp and `name`."
 | 
				
			||||||
 | 
					  [name]
 | 
				
			||||||
 | 
					  (migrations/create name (select-keys env [:database-url])))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										19
									
								
								env/dev/cljs/pastoralist/app.cljs
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								env/dev/cljs/pastoralist/app.cljs
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,19 @@
 | 
				
			||||||
 | 
					(ns^:figwheel-no-load pastoralist.app
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [pastoralist.core :as core]
 | 
				
			||||||
 | 
					    [cljs.spec.alpha :as s]
 | 
				
			||||||
 | 
					    [expound.alpha :as expound]
 | 
				
			||||||
 | 
					    [devtools.core :as devtools]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(extend-protocol IPrintWithWriter
 | 
				
			||||||
 | 
					  js/Symbol
 | 
				
			||||||
 | 
					  (-pr-writer [sym writer _]
 | 
				
			||||||
 | 
					    (-write writer (str "\"" (.toString sym) "\""))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(set! s/*explain-out* expound/printer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(enable-console-print!)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(devtools/install!)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(core/init! true)
 | 
				
			||||||
							
								
								
									
										1
									
								
								env/dev/resources/config.edn
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								env/dev/resources/config.edn
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					{}
 | 
				
			||||||
							
								
								
									
										35
									
								
								env/dev/resources/logback.xml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								env/dev/resources/logback.xml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,35 @@
 | 
				
			||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<configuration scan="true" scanPeriod="10 seconds">
 | 
				
			||||||
 | 
					    <statusListener class="ch.qos.logback.core.status.NopStatusListener" />
 | 
				
			||||||
 | 
					    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
 | 
				
			||||||
 | 
					        <!-- encoders are assigned the type
 | 
				
			||||||
 | 
					             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
 | 
				
			||||||
 | 
					        <encoder>
 | 
				
			||||||
 | 
					            <charset>UTF-8</charset>
 | 
				
			||||||
 | 
					            <pattern>%date{ISO8601} [%thread] %-5level %logger{36} - %msg %n</pattern>
 | 
				
			||||||
 | 
					        </encoder>
 | 
				
			||||||
 | 
					    </appender>
 | 
				
			||||||
 | 
					    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
 | 
				
			||||||
 | 
					        <file>log/pastoralist.log</file>
 | 
				
			||||||
 | 
					        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
 | 
				
			||||||
 | 
					            <fileNamePattern>log/pastoralist.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
 | 
				
			||||||
 | 
					            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
 | 
				
			||||||
 | 
					                <maxFileSize>100MB</maxFileSize>
 | 
				
			||||||
 | 
					            </timeBasedFileNamingAndTriggeringPolicy>
 | 
				
			||||||
 | 
					            <!-- keep 30 days of history -->
 | 
				
			||||||
 | 
					            <maxHistory>30</maxHistory>
 | 
				
			||||||
 | 
					        </rollingPolicy>
 | 
				
			||||||
 | 
					        <encoder>
 | 
				
			||||||
 | 
					            <charset>UTF-8</charset>
 | 
				
			||||||
 | 
					            <pattern>%date{ISO8601} [%thread] %-5level %logger{36} - %msg %n</pattern>
 | 
				
			||||||
 | 
					        </encoder>
 | 
				
			||||||
 | 
					    </appender>
 | 
				
			||||||
 | 
					    <logger name="org.apache.http" level="warn" />
 | 
				
			||||||
 | 
					    <logger name="org.xnio.nio" level="warn" />
 | 
				
			||||||
 | 
					    <logger name="com.zaxxer.hikari" level="warn" />
 | 
				
			||||||
 | 
					    <logger name="org.eclipse.jetty" level="warn" />
 | 
				
			||||||
 | 
					    <root level="DEBUG">
 | 
				
			||||||
 | 
					        <appender-ref ref="STDOUT" />
 | 
				
			||||||
 | 
					        <appender-ref ref="FILE" />
 | 
				
			||||||
 | 
					    </root>
 | 
				
			||||||
 | 
					</configuration>
 | 
				
			||||||
							
								
								
									
										11
									
								
								env/prod/clj/pastoralist/env.clj
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								env/prod/clj/pastoralist/env.clj
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					(ns pastoralist.env
 | 
				
			||||||
 | 
					  (:require [clojure.tools.logging :as log]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(def defaults
 | 
				
			||||||
 | 
					  {:init
 | 
				
			||||||
 | 
					   (fn []
 | 
				
			||||||
 | 
					     (log/info "\n-=[pastoralist started successfully]=-"))
 | 
				
			||||||
 | 
					   :stop
 | 
				
			||||||
 | 
					   (fn []
 | 
				
			||||||
 | 
					     (log/info "\n-=[pastoralist has shut down successfully]=-"))
 | 
				
			||||||
 | 
					   :middleware identity})
 | 
				
			||||||
							
								
								
									
										7
									
								
								env/prod/cljs/pastoralist/app.cljs
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								env/prod/cljs/pastoralist/app.cljs
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					(ns pastoralist.app
 | 
				
			||||||
 | 
					  (:require [pastoralist.core :as core]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					;;ignore println statements in prod
 | 
				
			||||||
 | 
					(set! *print-fn* (fn [& _]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(core/init! false)
 | 
				
			||||||
							
								
								
									
										2
									
								
								env/prod/resources/config.edn
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								env/prod/resources/config.edn
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					{:prod true
 | 
				
			||||||
 | 
					 :port 3000}
 | 
				
			||||||
							
								
								
									
										26
									
								
								env/prod/resources/logback.xml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								env/prod/resources/logback.xml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,26 @@
 | 
				
			||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<configuration>
 | 
				
			||||||
 | 
					    <statusListener class="ch.qos.logback.core.status.NopStatusListener" />
 | 
				
			||||||
 | 
					    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
 | 
				
			||||||
 | 
					        <file>log/pastoralist.log</file>
 | 
				
			||||||
 | 
					        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
 | 
				
			||||||
 | 
					            <fileNamePattern>log/pastoralist.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
 | 
				
			||||||
 | 
					            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
 | 
				
			||||||
 | 
					                <maxFileSize>100MB</maxFileSize>
 | 
				
			||||||
 | 
					            </timeBasedFileNamingAndTriggeringPolicy>
 | 
				
			||||||
 | 
					            <!-- keep 30 days of history -->
 | 
				
			||||||
 | 
					            <maxHistory>30</maxHistory>
 | 
				
			||||||
 | 
					        </rollingPolicy>
 | 
				
			||||||
 | 
					        <encoder>
 | 
				
			||||||
 | 
					            <charset>UTF-8</charset>
 | 
				
			||||||
 | 
					            <pattern>%date{ISO8601} [%thread] %-5level %logger{36} - %msg %n</pattern>
 | 
				
			||||||
 | 
					        </encoder>
 | 
				
			||||||
 | 
					    </appender>
 | 
				
			||||||
 | 
					    <logger name="org.apache.http" level="warn" />
 | 
				
			||||||
 | 
					    <logger name="org.xnio.nio" level="warn" />
 | 
				
			||||||
 | 
					    <logger name="com.zaxxer.hikari" level="warn" />
 | 
				
			||||||
 | 
					    <logger name="org.eclipse.jetty" level="warn" />
 | 
				
			||||||
 | 
					    <root level="INFO">
 | 
				
			||||||
 | 
					        <appender-ref ref="FILE" />
 | 
				
			||||||
 | 
					    </root>
 | 
				
			||||||
 | 
					</configuration>
 | 
				
			||||||
							
								
								
									
										1
									
								
								env/test/resources/config.edn
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								env/test/resources/config.edn
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					{}
 | 
				
			||||||
							
								
								
									
										35
									
								
								env/test/resources/logback.xml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								env/test/resources/logback.xml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,35 @@
 | 
				
			||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<configuration scan="true" scanPeriod="10 seconds">
 | 
				
			||||||
 | 
					    <statusListener class="ch.qos.logback.core.status.NopStatusListener" />
 | 
				
			||||||
 | 
					    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
 | 
				
			||||||
 | 
					        <!-- encoders are assigned the type
 | 
				
			||||||
 | 
					             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
 | 
				
			||||||
 | 
					        <encoder>
 | 
				
			||||||
 | 
					            <charset>UTF-8</charset>
 | 
				
			||||||
 | 
					            <pattern>%date{ISO8601} [%thread] %-5level %logger{36} - %msg %n</pattern>
 | 
				
			||||||
 | 
					        </encoder>
 | 
				
			||||||
 | 
					    </appender>
 | 
				
			||||||
 | 
					    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
 | 
				
			||||||
 | 
					        <file>log/pastoralist.log</file>
 | 
				
			||||||
 | 
					        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
 | 
				
			||||||
 | 
					            <fileNamePattern>log/pastoralist.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
 | 
				
			||||||
 | 
					            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
 | 
				
			||||||
 | 
					                <maxFileSize>100MB</maxFileSize>
 | 
				
			||||||
 | 
					            </timeBasedFileNamingAndTriggeringPolicy>
 | 
				
			||||||
 | 
					            <!-- keep 30 days of history -->
 | 
				
			||||||
 | 
					            <maxHistory>30</maxHistory>
 | 
				
			||||||
 | 
					        </rollingPolicy>
 | 
				
			||||||
 | 
					        <encoder>
 | 
				
			||||||
 | 
					            <charset>UTF-8</charset>
 | 
				
			||||||
 | 
					            <pattern>%date{ISO8601} [%thread] %-5level %logger{36} - %msg %n</pattern>
 | 
				
			||||||
 | 
					        </encoder>
 | 
				
			||||||
 | 
					    </appender>
 | 
				
			||||||
 | 
					    <logger name="org.apache.http" level="warn" />
 | 
				
			||||||
 | 
					    <logger name="org.xnio.nio" level="warn" />
 | 
				
			||||||
 | 
					    <logger name="com.zaxxer.hikari" level="warn" />
 | 
				
			||||||
 | 
					    <logger name="org.eclipse.jetty" level="warn" />
 | 
				
			||||||
 | 
					    <root level="DEBUG">
 | 
				
			||||||
 | 
					        <appender-ref ref="STDOUT" />
 | 
				
			||||||
 | 
					        <appender-ref ref="FILE" />
 | 
				
			||||||
 | 
					    </root>
 | 
				
			||||||
 | 
					</configuration>
 | 
				
			||||||
							
								
								
									
										197
									
								
								pastoralist.adl.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								pastoralist.adl.xml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,197 @@
 | 
				
			||||||
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
 | 
					<!-- DOCTYPE application PUBLIC "-//JOURNEYMAN//DTD ADL 1.4.1//EN"
 | 
				
			||||||
 | 
					"http://www.journeyman.cc/adl/stable/adl/schemas/adl-1.4.1.dtd" -->
 | 
				
			||||||
 | 
					<application xmlns="http://bowyer.journeyman.cc/adl/1.4.1/"
 | 
				
			||||||
 | 
					xmlns:html="http://www.w3.org/1999/xhtml"
 | 
				
			||||||
 | 
					xmlns:adl="http://bowyer.journeyman.cc/adl/1.4.1/" name="pastoralist"
 | 
				
			||||||
 | 
					version="0.0.1">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <documentation>A web-app intended to be used by pastoralists in managing
 | 
				
			||||||
 | 
					    pastures, grazing, and animals.</documentation>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <entity name="holding" magnitude="7" volatility="1">
 | 
				
			||||||
 | 
					    <documentation>All holdings used to the system</documentation>
 | 
				
			||||||
 | 
					    <key>
 | 
				
			||||||
 | 
					      <documentation>Although in the UK and, probably, in many other
 | 
				
			||||||
 | 
					        countries, holdings have a unique, government issued, holding
 | 
				
			||||||
 | 
					        number which could rationally be used as a key, internationally
 | 
				
			||||||
 | 
					        we cannot rely on this.
 | 
				
			||||||
 | 
					      </documentation>
 | 
				
			||||||
 | 
					      <property required="true" type="integer" name="id"
 | 
				
			||||||
 | 
					                column="id" immutable="true" distinct="system">
 | 
				
			||||||
 | 
					        <generator action="native"/>
 | 
				
			||||||
 | 
					      </property>
 | 
				
			||||||
 | 
					    </key>
 | 
				
			||||||
 | 
					    <property size="64" type="string" name="holding-identifier" distinct="all">
 | 
				
			||||||
 | 
					      <prompt prompt="Holding Number" locale="en_GB.UTF-8" immutable="true"/>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					    <property size="128" type="string" name="name" distinct="user"/>
 | 
				
			||||||
 | 
					    <property type="list" name="pastures" entity="pasture"/>
 | 
				
			||||||
 | 
					    <property type="list" name="animals" entity="animal"/>
 | 
				
			||||||
 | 
					    <property type="link" name="users" entity="user">
 | 
				
			||||||
 | 
					      <documentation>A user is a person authorised to see data on a holding.
 | 
				
			||||||
 | 
					      Some users (e.g. vets, farm secretaries) may be authorised to see data
 | 
				
			||||||
 | 
					      on many holdings.</documentation>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					    <property name="is-active" type="boolean" default="true">
 | 
				
			||||||
 | 
					      <documentation>Records are not normally deleted from this system because
 | 
				
			||||||
 | 
					      of historical data integrity; we may want to trace the history of an
 | 
				
			||||||
 | 
					      animal across holdings even after a holding has ceased to be active
 | 
				
			||||||
 | 
					      in the system.</documentation>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					  </entity>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <entity name="pasture" magnitude="8" volatility="2">
 | 
				
			||||||
 | 
					    <documentation>A pasture within a holding. TODO: how should common pastures,
 | 
				
			||||||
 | 
					      or pastures otherwise shared between holdings, be handled?</documentation>
 | 
				
			||||||
 | 
					    <key>
 | 
				
			||||||
 | 
					      <property required="true" type="integer" name="id"
 | 
				
			||||||
 | 
					                column="id" immutable="true" distinct="system">
 | 
				
			||||||
 | 
					        <generator action="native"/>
 | 
				
			||||||
 | 
					      </property>
 | 
				
			||||||
 | 
					    </key>
 | 
				
			||||||
 | 
					    <property size="128" type="string" name="name" distinct="user"/>
 | 
				
			||||||
 | 
					    <property type="real" name="area" distinct="user">
 | 
				
			||||||
 | 
					      <prompt prompt="Area in Ha" locale="en_GB.UTF-8"/>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					    <property name="holding" type="entity" entity="holding" required="true"/>
 | 
				
			||||||
 | 
					    <property name="geometry" type="entity" entity="polygon" required="true">
 | 
				
			||||||
 | 
					      <documentation>
 | 
				
			||||||
 | 
					        Polygon is a first-class datatype in PostGIS, but ADL does not yet
 | 
				
			||||||
 | 
					        support this. Solutions:
 | 
				
			||||||
 | 
					        1. hack ADL to support native polygons;
 | 
				
			||||||
 | 
					        2. implement our own polygon datatype;
 | 
				
			||||||
 | 
					        3. work-around in override code.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Generally, when pasture boundaries are changed, a pasture record
 | 
				
			||||||
 | 
					        should not be edited; rather it should be marked inactive, and new
 | 
				
			||||||
 | 
					        pasture record(s) created.
 | 
				
			||||||
 | 
					      </documentation>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					    <property name="subdivision-of" type="entity" entity="pasture"/>
 | 
				
			||||||
 | 
					    <property name="is-active" type="boolean" default="true">
 | 
				
			||||||
 | 
					      <documentation>Records are not normally deleted from this system because
 | 
				
			||||||
 | 
					        of historical data integrity; we may want to trace the history of an
 | 
				
			||||||
 | 
					        pasture even after pasture boundaries have been changed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Indeed, if we treat subdivisions as pastures (which would be quite sensible),
 | 
				
			||||||
 | 
					        active pastures in the system will change quite frequently, especially if
 | 
				
			||||||
 | 
					        strip-grazing is used.
 | 
				
			||||||
 | 
					      </documentation>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					    <property name="history" type="link" entity="event"/>
 | 
				
			||||||
 | 
					  </entity>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <entity name="animal" magnitude="9" volatility="5">
 | 
				
			||||||
 | 
					    <documentation>An animal on a holding (or which has been on a holding; the
 | 
				
			||||||
 | 
					    historical record is not wiped when an animal dies, is killed, or is sold)
 | 
				
			||||||
 | 
					    </documentation>
 | 
				
			||||||
 | 
					    <key>
 | 
				
			||||||
 | 
					      <documentation>Although in the UK and, probably, in many other
 | 
				
			||||||
 | 
					        countries, animals have a unique, government registered, identifying
 | 
				
			||||||
 | 
					        number which could rationally be used as a key, internationally
 | 
				
			||||||
 | 
					        we cannot rely on this.
 | 
				
			||||||
 | 
					      </documentation>
 | 
				
			||||||
 | 
					      <property required="true" type="integer" name="id"
 | 
				
			||||||
 | 
					                column="id" immutable="true" distinct="system">
 | 
				
			||||||
 | 
					        <generator action="native"/>
 | 
				
			||||||
 | 
					      </property>
 | 
				
			||||||
 | 
					    </key>
 | 
				
			||||||
 | 
					    <property name="holding" type="entity" entity="holding">
 | 
				
			||||||
 | 
					    <documentation>The holding the animal is currently on, if any. Not 'required',
 | 
				
			||||||
 | 
					      since the animal may be dead, or simply moved off to a holding which is
 | 
				
			||||||
 | 
					      not part of the system.</documentation>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					    <property name="dam" type="entity" entity="animal"/>
 | 
				
			||||||
 | 
					    <property name="sire" type="entity" entity="animal"/>
 | 
				
			||||||
 | 
					    <property name="born" type="date" immutable="true" required="true"/>
 | 
				
			||||||
 | 
					    <property name="animal-identifier" type="string" size="64" distinct="user" required="true">
 | 
				
			||||||
 | 
					      <prompt prompt="Ear-tag Number" locale="en_GB.UTF-8"/>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					    <property name="gender" type="entity" entity="gender" required="true"/>
 | 
				
			||||||
 | 
					    <property name="species" type="entity" entity="species" required="true"/>
 | 
				
			||||||
 | 
					    <property name="breed" type="entity" entity="breed"/>
 | 
				
			||||||
 | 
					    <property name="is-pedigree" type="boolean" default="false"/>
 | 
				
			||||||
 | 
					    <property name="pasture" type="entity" entity="pasture"/>
 | 
				
			||||||
 | 
					    <property name="history" type="link" entity="event"/>
 | 
				
			||||||
 | 
					  </entity>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <entity name="gender" magnitude="1" volatility="10">
 | 
				
			||||||
 | 
					    <key>
 | 
				
			||||||
 | 
					      <property name="gender" type="string" size="8" distinct="all" immutable="true"/>
 | 
				
			||||||
 | 
					    </key>
 | 
				
			||||||
 | 
					  </entity>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <entity name="species" magnitude="2" volatility="5">
 | 
				
			||||||
 | 
					    <key>
 | 
				
			||||||
 | 
					      <property name="species" type="string" size="32" distinct="all" immutable="true"/>
 | 
				
			||||||
 | 
					    </key>
 | 
				
			||||||
 | 
					  </entity>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <entity name="breed" magnitude="1" volatility="10">
 | 
				
			||||||
 | 
					    <key>
 | 
				
			||||||
 | 
					      <property name="breed" type="string" size="32" distinct="all" immutable="true"/>
 | 
				
			||||||
 | 
					    </key>
 | 
				
			||||||
 | 
					    <property name="species" type="entity" entity="species"/>
 | 
				
			||||||
 | 
					  </entity>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <entity name="event" magnitude="9" volatility="2">
 | 
				
			||||||
 | 
					    <key>
 | 
				
			||||||
 | 
					      <property required="true" type="integer" name="id"
 | 
				
			||||||
 | 
					                column="id" immutable="true" distinct="system">
 | 
				
			||||||
 | 
					        <generator action="native"/>
 | 
				
			||||||
 | 
					      </property>
 | 
				
			||||||
 | 
					    </key>
 | 
				
			||||||
 | 
					    <property name="type" type="entity" entity="event-type"></property>
 | 
				
			||||||
 | 
					    <property name="actor" type="entity" entity="user" required="true" immutable="true">
 | 
				
			||||||
 | 
					      <documentation>The user who recorded this event.</documentation>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					    <promperty name="date" type="timestamp" required="true" default="now()"/>
 | 
				
			||||||
 | 
					    <property name="pastures" type="link" entity="pasture">
 | 
				
			||||||
 | 
					      <documentation>Pastures affected by this event.</documentation>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					    <property name="animals" type="link" entity="animal">
 | 
				
			||||||
 | 
					      <documentation>Animals affected by this event.</documentation>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					    <property name="summary" type="string" size="80"/>
 | 
				
			||||||
 | 
					    <property name="notes" type="text"/>
 | 
				
			||||||
 | 
					  </entity>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <entity name="user" magnitude="6" volatility="3">
 | 
				
			||||||
 | 
					    <key>
 | 
				
			||||||
 | 
					      <property required="true" type="string" name="username"
 | 
				
			||||||
 | 
					                column="username" immutable="true" distinct="all"/>
 | 
				
			||||||
 | 
					    </key>
 | 
				
			||||||
 | 
					        <property required="true" type="string" name="fullname"
 | 
				
			||||||
 | 
					    column="fullname" size="64" distinct="user">
 | 
				
			||||||
 | 
					      <prompt prompt="fullname" locale="en_GB.UTF-8"/>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					    <property name="avatar" type="image" size="256">
 | 
				
			||||||
 | 
					      <documentation>An image of the canvasser, so that other members of their
 | 
				
			||||||
 | 
					        team can recognise them.</documentation>
 | 
				
			||||||
 | 
					      <prompt prompt="Avatar" locale="en_GB.UTF-8"/>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					    <property name="bio" type="text">
 | 
				
			||||||
 | 
					      <documentation>Information the canvasser supplies about themselves; an introduction.</documentation>
 | 
				
			||||||
 | 
					      <prompt prompt="Bio" locale="en_GB.UTF-8"/>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					    <property type="string" name="phone" column="phone" size="16"
 | 
				
			||||||
 | 
					    distinct="user">
 | 
				
			||||||
 | 
					      <documentation>Does a canvasser record need a phone field? There is a phone
 | 
				
			||||||
 | 
					        field on the elector record. I suppose, for under-16 canvassers, there may
 | 
				
			||||||
 | 
					        be no elector record.</documentation>
 | 
				
			||||||
 | 
					      <prompt prompt="phone" locale="en_GB.UTF-8"/>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					    <property type="string" name="email" column="email" size="128"
 | 
				
			||||||
 | 
					    distinct="user">
 | 
				
			||||||
 | 
					      <prompt prompt="email" locale="en_GB.UTF-8"/>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					    <property required="true" type="entity" name="authority_id"
 | 
				
			||||||
 | 
					    entity="authorities" farkey="id">
 | 
				
			||||||
 | 
					      <prompt prompt="authority_id" locale="en_GB.UTF-8"/>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					    <property type="boolean" name="authorised" column="authorised">
 | 
				
			||||||
 | 
					      <prompt prompt="authorised" locale="en_GB.UTF-8"/>
 | 
				
			||||||
 | 
					    </property>
 | 
				
			||||||
 | 
					  </entity>
 | 
				
			||||||
 | 
					</application>
 | 
				
			||||||
							
								
								
									
										149
									
								
								project.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								project.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,149 @@
 | 
				
			||||||
 | 
					(defproject pastoralist "0.1.0-SNAPSHOT"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  :description "FIXME: write description"
 | 
				
			||||||
 | 
					  :url "http://example.com/FIXME"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  :dependencies [[adl-support "0.1.6"]
 | 
				
			||||||
 | 
					                 [ch.qos.logback/logback-classic "1.2.3"]
 | 
				
			||||||
 | 
					                 [cheshire "5.9.0"]
 | 
				
			||||||
 | 
					                 [clj-oauth "1.5.5"]
 | 
				
			||||||
 | 
					                 [cljs-ajax "0.8.0"]
 | 
				
			||||||
 | 
					                 [clojure.java-time "0.3.2"]
 | 
				
			||||||
 | 
					                 [com.cognitect/transit-clj "0.8.319"]
 | 
				
			||||||
 | 
					                 [conman "0.8.4"]
 | 
				
			||||||
 | 
					                 [cprop "0.1.14"]
 | 
				
			||||||
 | 
					                 [day8.re-frame/http-fx "0.1.6"]
 | 
				
			||||||
 | 
					                 [expound "0.8.1"]
 | 
				
			||||||
 | 
					                 [funcool/struct "1.4.0"]
 | 
				
			||||||
 | 
					                 [kee-frame "0.3.3" :exclusions [metosin/reitit-core org.clojure/core.async]]
 | 
				
			||||||
 | 
					                 [luminus-migrations "0.6.6"]
 | 
				
			||||||
 | 
					                 [luminus-transit "0.1.2"]
 | 
				
			||||||
 | 
					                 [luminus/ring-ttl-session "0.3.3"]
 | 
				
			||||||
 | 
					                 [markdown-clj "1.10.0"]
 | 
				
			||||||
 | 
					                 [metosin/muuntaja "0.6.6"]
 | 
				
			||||||
 | 
					                 [metosin/reitit "0.3.10"]
 | 
				
			||||||
 | 
					                 [metosin/ring-http-response "0.9.1"]
 | 
				
			||||||
 | 
					                 [mount "0.1.16"]
 | 
				
			||||||
 | 
					                 [nrepl "0.6.0"]
 | 
				
			||||||
 | 
					                 [org.clojure/clojure "1.10.1"]
 | 
				
			||||||
 | 
					                 [org.clojure/clojurescript "1.10.597" :scope "provided"]
 | 
				
			||||||
 | 
					                 [org.clojure/tools.cli "0.4.2"]
 | 
				
			||||||
 | 
					                 [org.clojure/tools.logging "0.5.0"]
 | 
				
			||||||
 | 
					                 [org.postgresql/postgresql "42.2.8"]
 | 
				
			||||||
 | 
					                 [org.webjars.npm/bulma "0.8.0"]
 | 
				
			||||||
 | 
					                 [org.webjars.npm/material-icons "0.3.1"]
 | 
				
			||||||
 | 
					                 [org.webjars/webjars-locator "0.38"]
 | 
				
			||||||
 | 
					                 [re-frame "0.10.9"]
 | 
				
			||||||
 | 
					                 [reagent "0.9.0-rc3"]
 | 
				
			||||||
 | 
					                 [ring-webjars "0.2.0"]
 | 
				
			||||||
 | 
					                 [ring/ring-core "1.8.0"]
 | 
				
			||||||
 | 
					                 [ring/ring-defaults "0.3.2"]
 | 
				
			||||||
 | 
					                 [ring/ring-servlet "1.7.1"]
 | 
				
			||||||
 | 
					                 [selmer "1.12.17"]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  :min-lein-version "2.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  :source-paths ["src/clj" "src/cljs" "src/cljc"]
 | 
				
			||||||
 | 
					  :test-paths ["test/clj"]
 | 
				
			||||||
 | 
					  :resource-paths ["resources" "target/cljsbuild"]
 | 
				
			||||||
 | 
					  :target-path "target/%s/"
 | 
				
			||||||
 | 
					  :main ^:skip-aot pastoralist.core
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  :plugins [[lein-adl "0.1.8-SNAPSHOT"]
 | 
				
			||||||
 | 
					            [lein-cljsbuild "1.1.7"]
 | 
				
			||||||
 | 
					            [lein-kibit "0.1.2"]
 | 
				
			||||||
 | 
					            [lein-uberwar "0.2.0"]]
 | 
				
			||||||
 | 
					  :uberwar
 | 
				
			||||||
 | 
					  {:handler pastoralist.handler/app
 | 
				
			||||||
 | 
					   :init pastoralist.handler/init
 | 
				
			||||||
 | 
					   :destroy pastoralist.handler/destroy
 | 
				
			||||||
 | 
					   :name "pastoralist.war"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  :clean-targets ^{:protect false}
 | 
				
			||||||
 | 
					  [:target-path [:cljsbuild :builds :app :compiler :output-dir] [:cljsbuild :builds :app :compiler :output-to]]
 | 
				
			||||||
 | 
					  :figwheel
 | 
				
			||||||
 | 
					  {:http-server-root "public"
 | 
				
			||||||
 | 
					   :server-logfile "log/figwheel-logfile.log"
 | 
				
			||||||
 | 
					   :nrepl-port 7002
 | 
				
			||||||
 | 
					   :css-dirs ["resources/public/css"]
 | 
				
			||||||
 | 
					   :nrepl-middleware [cider.piggieback/wrap-cljs-repl]}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  :profiles
 | 
				
			||||||
 | 
					  {:uberjar {:omit-source true
 | 
				
			||||||
 | 
					             :prep-tasks ["compile" ["cljsbuild" "once" "min"]]
 | 
				
			||||||
 | 
					             :cljsbuild{:builds
 | 
				
			||||||
 | 
					              {:min
 | 
				
			||||||
 | 
					               {:source-paths ["src/cljc" "src/cljs" "env/prod/cljs"]
 | 
				
			||||||
 | 
					                :compiler
 | 
				
			||||||
 | 
					                {:output-dir "target/cljsbuild/public/js"
 | 
				
			||||||
 | 
					                 :output-to "target/cljsbuild/public/js/app.js"
 | 
				
			||||||
 | 
					                 :source-map "target/cljsbuild/public/js/app.js.map"
 | 
				
			||||||
 | 
					                 :optimizations :advanced
 | 
				
			||||||
 | 
					                 :pretty-print false
 | 
				
			||||||
 | 
					                 :infer-externs true
 | 
				
			||||||
 | 
					                 :closure-warnings
 | 
				
			||||||
 | 
					                 {:externs-validation :off :non-standard-jsdoc :off}
 | 
				
			||||||
 | 
					                 :externs ["react/externs/react.js"]}}}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					             :aot :all
 | 
				
			||||||
 | 
					             :uberjar-name "pastoralist.jar"
 | 
				
			||||||
 | 
					             :source-paths ["env/prod/clj"]
 | 
				
			||||||
 | 
					             :resource-paths ["env/prod/resources"]}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   :dev           [:project/dev :profiles/dev]
 | 
				
			||||||
 | 
					   :test          [:project/dev :project/test :profiles/test]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   :project/dev  {:jvm-opts ["-Dconf=dev-config.edn"]
 | 
				
			||||||
 | 
					                  :dependencies [[binaryage/devtools "0.9.10"]
 | 
				
			||||||
 | 
					                                 [cider/piggieback "0.4.2"]
 | 
				
			||||||
 | 
					                                 [directory-naming/naming-java "0.8"]
 | 
				
			||||||
 | 
					                                 [doo "0.1.11"]
 | 
				
			||||||
 | 
					                                 [figwheel-sidecar "0.5.19"]
 | 
				
			||||||
 | 
					                                 [luminus-jetty "0.1.7"]
 | 
				
			||||||
 | 
					                                 [pjstadig/humane-test-output "0.10.0"]
 | 
				
			||||||
 | 
					                                 [prone "2019-07-08"]
 | 
				
			||||||
 | 
					                                 [re-frisk "0.5.4.1"]
 | 
				
			||||||
 | 
					                                 [ring/ring-devel "1.8.0"]
 | 
				
			||||||
 | 
					                                 [ring/ring-mock "0.4.0"]]
 | 
				
			||||||
 | 
					                  :plugins      [[com.jakemccrary/lein-test-refresh "0.24.1"]
 | 
				
			||||||
 | 
					                                 [jonase/eastwood "0.3.5"]
 | 
				
			||||||
 | 
					                                 [lein-doo "0.1.11"]
 | 
				
			||||||
 | 
					                                 [lein-figwheel "0.5.19"]]
 | 
				
			||||||
 | 
					                  :cljsbuild{:builds
 | 
				
			||||||
 | 
					                   {:app
 | 
				
			||||||
 | 
					                    {:source-paths ["src/cljs" "src/cljc" "env/dev/cljs"]
 | 
				
			||||||
 | 
					                     :figwheel {:on-jsload "pastoralist.core/mount-components"}
 | 
				
			||||||
 | 
					                     :compiler
 | 
				
			||||||
 | 
					                     {:output-dir "target/cljsbuild/public/js/out"
 | 
				
			||||||
 | 
					                      :closure-defines {"re_frame.trace.trace_enabled_QMARK_" true}
 | 
				
			||||||
 | 
					                      :optimizations :none
 | 
				
			||||||
 | 
					                      :preloads [re-frisk.preload]
 | 
				
			||||||
 | 
					                      :output-to "target/cljsbuild/public/js/app.js"
 | 
				
			||||||
 | 
					                      :asset-path "/js/out"
 | 
				
			||||||
 | 
					                      :source-map true
 | 
				
			||||||
 | 
					                      :main "pastoralist.app"
 | 
				
			||||||
 | 
					                      :pretty-print true}}}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                  :doo {:build "test"}
 | 
				
			||||||
 | 
					                  :source-paths ["env/dev/clj"]
 | 
				
			||||||
 | 
					                  :resource-paths ["env/dev/resources"]
 | 
				
			||||||
 | 
					                  :repl-options {:init-ns user}
 | 
				
			||||||
 | 
					                  :injections [(require 'pjstadig.humane-test-output)
 | 
				
			||||||
 | 
					                               (pjstadig.humane-test-output/activate!)]}
 | 
				
			||||||
 | 
					   :project/test {:jvm-opts ["-Dconf=test-config.edn"]
 | 
				
			||||||
 | 
					                  :resource-paths ["env/test/resources"]
 | 
				
			||||||
 | 
					                  :cljsbuild
 | 
				
			||||||
 | 
					                  {:builds
 | 
				
			||||||
 | 
					                   {:test
 | 
				
			||||||
 | 
					                    {:source-paths ["src/cljc" "src/cljs" "test/cljs"]
 | 
				
			||||||
 | 
					                     :compiler
 | 
				
			||||||
 | 
					                     {:output-to "target/test.js"
 | 
				
			||||||
 | 
					                      :main "pastoralist.doo-runner"
 | 
				
			||||||
 | 
					                      :optimizations :whitespace
 | 
				
			||||||
 | 
					                      :pretty-print true}}}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					   :profiles/dev {}
 | 
				
			||||||
 | 
					   :profiles/test {}})
 | 
				
			||||||
							
								
								
									
										103
									
								
								resources/docs/docs.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								resources/docs/docs.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,103 @@
 | 
				
			||||||
 | 
					<h2 class="alert alert-success">Congratulations, your <a class="alert-link" href="http://luminusweb.net">Luminus</a> site is ready!</h2>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This page will help guide you through the first steps of building your site.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Why are you seeing this page?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The `home-routes` handler in the `pastoralist.routes.home` namespace
 | 
				
			||||||
 | 
					defines the route that invokes the `home-page` function whenever an HTTP
 | 
				
			||||||
 | 
					request is made to the `/` URI using the `GET` method.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					(defn home-routes []
 | 
				
			||||||
 | 
					  [""
 | 
				
			||||||
 | 
					   {:middleware [middleware/wrap-csrf
 | 
				
			||||||
 | 
					                 middleware/wrap-formats]}
 | 
				
			||||||
 | 
					   ["/" {:get home-page}]
 | 
				
			||||||
 | 
					   ["/docs" {:get (fn [_]
 | 
				
			||||||
 | 
					                    (-> (response/ok (-> "docs/docs.md" io/resource slurp))
 | 
				
			||||||
 | 
					                        (response/header "Content-Type" "text/plain; charset=utf-8")))}]])
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The `home-page` function will in turn call the `pastoralist.layout/render` function
 | 
				
			||||||
 | 
					to render the HTML content:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					(defn home-page [_]
 | 
				
			||||||
 | 
					  (layout/render "home.html"))
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The page contains a link to the compiled ClojureScript found in the `target/cljsbuild/public` folder:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					{% script "/js/app.js" %}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The rest of this page is rendered by ClojureScript found in the `src/cljs/pastoralist/core.cljs` file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Organizing the routes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The routes are aggregated and wrapped with middleware in the `pastoralist.handler` namespace:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					(mount/defstate app
 | 
				
			||||||
 | 
					  :start
 | 
				
			||||||
 | 
					  (middleware/wrap-base
 | 
				
			||||||
 | 
					    (ring/ring-handler
 | 
				
			||||||
 | 
					      (ring/router
 | 
				
			||||||
 | 
					        [(home-routes)])
 | 
				
			||||||
 | 
					      (ring/routes
 | 
				
			||||||
 | 
					        (ring/create-resource-handler
 | 
				
			||||||
 | 
					          {:path "/"})
 | 
				
			||||||
 | 
					        (wrap-content-type
 | 
				
			||||||
 | 
					          (wrap-webjars (constantly nil)))
 | 
				
			||||||
 | 
					        (ring/create-default-handler
 | 
				
			||||||
 | 
					          {:not-found
 | 
				
			||||||
 | 
					           (constantly (error-page {:status 404, :title "404 - Page not found"}))
 | 
				
			||||||
 | 
					           :method-not-allowed
 | 
				
			||||||
 | 
					           (constantly (error-page {:status 405, :title "405 - Not allowed"}))
 | 
				
			||||||
 | 
					           :not-acceptable
 | 
				
			||||||
 | 
					           (constantly (error-page {:status 406, :title "406 - Not acceptable"}))})))))
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The `app` definition groups all the routes in the application into a single handler.
 | 
				
			||||||
 | 
					A default route group is added to handle the `404`, `405`, and `406` errors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<a class="btn btn-primary" href="https://metosin.github.io/reitit/basics">learn more about routing »</a>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Managing your middleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Request middleware functions are located under the `pastoralist.middleware` namespace.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This namespace is reserved for any custom middleware for the application. Some default middleware is
 | 
				
			||||||
 | 
					already defined here. The middleware is assembled in the `wrap-base` function.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Middleware used for development is placed in the `pastoralist.dev-middleware` namespace found in
 | 
				
			||||||
 | 
					the `env/dev/clj/` source path.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<a class="btn btn-primary" href="http://www.luminusweb.net/docs/middleware.md">learn more about middleware »</a>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="bs-callout bs-callout-danger">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Database configuration is required
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you haven't already, then please follow the steps below to configure your database connection and run the necessary migrations.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Create the database for your application.
 | 
				
			||||||
 | 
					* Update the connection URL in the `dev-config.edn` and `test-config.edn` files with your database name and login credentials.
 | 
				
			||||||
 | 
					* Run `lein run migrate` in the root of the project to create the tables.
 | 
				
			||||||
 | 
					* Let `mount` know to start the database connection by `require`-ing `pastoralist.db.core` in some other namespace.
 | 
				
			||||||
 | 
					* Restart the application.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<a class="btn btn-primary" href="http://www.luminusweb.net/docs/database.md">learn more about database access »</a>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Need some help?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Visit the [official documentation](http://www.luminusweb.net/docs) for examples
 | 
				
			||||||
 | 
					on how to accomplish common tasks with Luminus. The `#luminus` channel on the [Clojurians Slack](http://clojurians.net/) and [Google Group](https://groups.google.com/forum/#!forum/luminusweb) are both great places to seek help and discuss projects with other users.
 | 
				
			||||||
							
								
								
									
										49
									
								
								resources/html/error.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								resources/html/error.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,49 @@
 | 
				
			||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <title>Something Bad Happened</title>
 | 
				
			||||||
 | 
					    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
 | 
					    {% style "/assets/bulma/css/bulma.min.css" %}
 | 
				
			||||||
 | 
					    <style type="text/css">
 | 
				
			||||||
 | 
					        html {
 | 
				
			||||||
 | 
					            height: 100%;
 | 
				
			||||||
 | 
					            min-height: 100%;
 | 
				
			||||||
 | 
					            min-width: 100%;
 | 
				
			||||||
 | 
					            overflow: hidden;
 | 
				
			||||||
 | 
					            width: 100%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        html body {
 | 
				
			||||||
 | 
					            height: 100%;
 | 
				
			||||||
 | 
					            margin: 0;
 | 
				
			||||||
 | 
					            padding: 0;
 | 
				
			||||||
 | 
					            width: 100%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        html .container-fluid {
 | 
				
			||||||
 | 
					            display: table;
 | 
				
			||||||
 | 
					            height: 100%;
 | 
				
			||||||
 | 
					            padding: 0;
 | 
				
			||||||
 | 
					            width: 100%;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        html .row-fluid {
 | 
				
			||||||
 | 
					            display: table-cell;
 | 
				
			||||||
 | 
					            height: 100%;
 | 
				
			||||||
 | 
					            vertical-align: middle;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					<div class="container is-fluid">
 | 
				
			||||||
 | 
					    <div class="has-text-centered">
 | 
				
			||||||
 | 
					        <h1><span class="is-size-4 has-text-danger">Error: {{status}}</span></h1>
 | 
				
			||||||
 | 
					        <hr>
 | 
				
			||||||
 | 
					        {% if title %}
 | 
				
			||||||
 | 
					        <h2 class="without-margin">{{title}}</h2>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					        {% if message %}
 | 
				
			||||||
 | 
					        <h4 class="text-danger">{{message}}</h4>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										41
									
								
								resources/html/home.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								resources/html/home.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,41 @@
 | 
				
			||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					  <head>
 | 
				
			||||||
 | 
					      <meta charset="UTF-8"/>
 | 
				
			||||||
 | 
					      <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
				
			||||||
 | 
					      <title>Welcome to pastoralist</title>
 | 
				
			||||||
 | 
					  </head>
 | 
				
			||||||
 | 
					  <body>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    <div id="app">
 | 
				
			||||||
 | 
					      <section class="section">
 | 
				
			||||||
 | 
					        <div class="container is-fluid">
 | 
				
			||||||
 | 
					          <div class="content">
 | 
				
			||||||
 | 
					            <h4 class="title">Welcome to pastoralist</h4>
 | 
				
			||||||
 | 
					            <p>If you're seeing this message, that means you haven't yet compiled your ClojureScript!</p>
 | 
				
			||||||
 | 
					            <p>Please run <code>lein figwheel</code> to start the ClojureScript compiler and reload the page.</p>
 | 
				
			||||||
 | 
					            <h4>For better ClojureScript development experience in Chrome follow these steps:</h4>
 | 
				
			||||||
 | 
					            <ul>
 | 
				
			||||||
 | 
					              <li>Open DevTools
 | 
				
			||||||
 | 
					              <li>Go to Settings ("three dots" icon in the upper right corner of DevTools > Menu > Settings F1 > General > Console)
 | 
				
			||||||
 | 
					              <li>Check-in "Enable custom formatters"
 | 
				
			||||||
 | 
					              <li>Close DevTools
 | 
				
			||||||
 | 
					              <li>Open DevTools
 | 
				
			||||||
 | 
					            </ul>
 | 
				
			||||||
 | 
					            <p>See <a href="http://www.luminusweb.net/docs/clojurescript.md">ClojureScript</a> documentation for further details.</p>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </section>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!-- scripts and styles -->
 | 
				
			||||||
 | 
					    {% style "/assets/bulma/css/bulma.min.css" %}
 | 
				
			||||||
 | 
					    {% style "/assets/material-icons/css/material-icons.min.css" %}
 | 
				
			||||||
 | 
					    {% style "/css/screen.css" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <script type="text/javascript">
 | 
				
			||||||
 | 
					        var csrfToken = "{{csrf-token}}";
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					    {% script "/js/app.js" %}
 | 
				
			||||||
 | 
					  </body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					DROP TABLE users;
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					CREATE TABLE users
 | 
				
			||||||
 | 
					(id VARCHAR(20) PRIMARY KEY,
 | 
				
			||||||
 | 
					 first_name VARCHAR(30),
 | 
				
			||||||
 | 
					 last_name VARCHAR(30),
 | 
				
			||||||
 | 
					 email VARCHAR(30),
 | 
				
			||||||
 | 
					 admin BOOLEAN,
 | 
				
			||||||
 | 
					 last_login TIMESTAMP,
 | 
				
			||||||
 | 
					 is_active BOOLEAN,
 | 
				
			||||||
 | 
					 pass VARCHAR(300));
 | 
				
			||||||
							
								
								
									
										35
									
								
								resources/public/css/screen.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								resources/public/css/screen.css
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,35 @@
 | 
				
			||||||
 | 
					html,
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
						font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					@font-face {
 | 
				
			||||||
 | 
					  font-family: 'Material Icons';
 | 
				
			||||||
 | 
					  font-style: normal;
 | 
				
			||||||
 | 
					  font-weight: 400;
 | 
				
			||||||
 | 
					  src: url(/assets/material-icons/iconfont/MaterialIcons-Regular.eot); /* For IE6-8 */
 | 
				
			||||||
 | 
					  src: local('Material Icons'),
 | 
				
			||||||
 | 
					       local('MaterialIcons-Regular'),
 | 
				
			||||||
 | 
					       url(/assets/material-icons/iconfont/MaterialIcons-Regular.woff2) format('woff2'),
 | 
				
			||||||
 | 
					       url(/assets/material-icons/iconfont/MaterialIcons-Regular.woff) format('woff'),
 | 
				
			||||||
 | 
					       url(/assets/material-icons/iconfont/MaterialIcons-Regular.ttf) format('truetype');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.material-icons {
 | 
				
			||||||
 | 
					  font-family: 'Material Icons';
 | 
				
			||||||
 | 
					  font-weight: normal;
 | 
				
			||||||
 | 
					  font-style: normal;
 | 
				
			||||||
 | 
					  font-size: 24px;  /* Preferred icon size */
 | 
				
			||||||
 | 
					  display: inline-block;
 | 
				
			||||||
 | 
					  line-height: 1;
 | 
				
			||||||
 | 
					  text-transform: none;
 | 
				
			||||||
 | 
					  letter-spacing: normal;
 | 
				
			||||||
 | 
					  word-wrap: normal;
 | 
				
			||||||
 | 
					  white-space: nowrap;
 | 
				
			||||||
 | 
					  direction: ltr;
 | 
				
			||||||
 | 
					  /* Support for all WebKit browsers. */
 | 
				
			||||||
 | 
					  -webkit-font-smoothing: antialiased;
 | 
				
			||||||
 | 
					  /* Support for Safari and Chrome. */
 | 
				
			||||||
 | 
					  text-rendering: optimizeLegibility;
 | 
				
			||||||
 | 
					  /* Support for Firefox. */
 | 
				
			||||||
 | 
					  -moz-osx-font-smoothing: grayscale;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								resources/public/favicon.ico
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								resources/public/favicon.ico
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								resources/public/img/warning_clojure.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								resources/public/img/warning_clojure.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 21 KiB  | 
							
								
								
									
										21
									
								
								resources/sql/queries.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								resources/sql/queries.sql
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					-- :name create-user! :! :n
 | 
				
			||||||
 | 
					-- :doc creates a new user record
 | 
				
			||||||
 | 
					INSERT INTO users
 | 
				
			||||||
 | 
					(id, first_name, last_name, email, pass)
 | 
				
			||||||
 | 
					VALUES (:id, :first_name, :last_name, :email, :pass)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- :name update-user! :! :n
 | 
				
			||||||
 | 
					-- :doc updates an existing user record
 | 
				
			||||||
 | 
					UPDATE users
 | 
				
			||||||
 | 
					SET first_name = :first_name, last_name = :last_name, email = :email
 | 
				
			||||||
 | 
					WHERE id = :id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- :name get-user :? :1
 | 
				
			||||||
 | 
					-- :doc retrieves a user record given the id
 | 
				
			||||||
 | 
					SELECT * FROM users
 | 
				
			||||||
 | 
					WHERE id = :id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- :name delete-user! :! :n
 | 
				
			||||||
 | 
					-- :doc deletes a user record given the id
 | 
				
			||||||
 | 
					DELETE FROM users
 | 
				
			||||||
 | 
					WHERE id = :id
 | 
				
			||||||
							
								
								
									
										13
									
								
								src/clj/pastoralist/config.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/clj/pastoralist/config.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					(ns pastoralist.config
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [cprop.core :refer [load-config]]
 | 
				
			||||||
 | 
					    [cprop.source :as source]
 | 
				
			||||||
 | 
					    [mount.core :refer [args defstate]]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defstate env
 | 
				
			||||||
 | 
					  :start
 | 
				
			||||||
 | 
					  (load-config
 | 
				
			||||||
 | 
					    :merge
 | 
				
			||||||
 | 
					    [(args)
 | 
				
			||||||
 | 
					     (source/from-system-props)
 | 
				
			||||||
 | 
					     (source/from-env)]))
 | 
				
			||||||
							
								
								
									
										86
									
								
								src/clj/pastoralist/db/core.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/clj/pastoralist/db/core.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,86 @@
 | 
				
			||||||
 | 
					(ns pastoralist.db.core
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [cheshire.core :refer [generate-string parse-string]]
 | 
				
			||||||
 | 
					    [clojure.java.jdbc :as jdbc]
 | 
				
			||||||
 | 
					    [clojure.tools.logging :as log]
 | 
				
			||||||
 | 
					    [conman.core :as conman]
 | 
				
			||||||
 | 
					    [java-time :as jt]
 | 
				
			||||||
 | 
					    [java-time.pre-java8]
 | 
				
			||||||
 | 
					    [pastoralist.config :refer [env]]
 | 
				
			||||||
 | 
					    [mount.core :refer [defstate]])
 | 
				
			||||||
 | 
					  (:import org.postgresql.util.PGobject
 | 
				
			||||||
 | 
					           java.sql.Array
 | 
				
			||||||
 | 
					           clojure.lang.IPersistentMap
 | 
				
			||||||
 | 
					           clojure.lang.IPersistentVector
 | 
				
			||||||
 | 
					           [java.sql
 | 
				
			||||||
 | 
					            BatchUpdateException
 | 
				
			||||||
 | 
					            PreparedStatement]))
 | 
				
			||||||
 | 
					(defstate ^:dynamic *db*
 | 
				
			||||||
 | 
					  :start (if-let [jdbc-url (env :database-url)]
 | 
				
			||||||
 | 
					           (conman/connect! {:jdbc-url jdbc-url})
 | 
				
			||||||
 | 
					           (do
 | 
				
			||||||
 | 
					             (log/warn "database connection URL was not found, please set :database-url in your config, e.g: dev-config.edn")
 | 
				
			||||||
 | 
					             *db*))
 | 
				
			||||||
 | 
					  :stop (conman/disconnect! *db*))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(conman/bind-connection *db* "sql/queries.sql")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(extend-protocol jdbc/IResultSetReadColumn
 | 
				
			||||||
 | 
					    java.sql.Timestamp
 | 
				
			||||||
 | 
					  (result-set-read-column [v _2 _3]
 | 
				
			||||||
 | 
					    (.toLocalDateTime v))
 | 
				
			||||||
 | 
					  java.sql.Date
 | 
				
			||||||
 | 
					  (result-set-read-column [v _2 _3]
 | 
				
			||||||
 | 
					    (.toLocalDate v))
 | 
				
			||||||
 | 
					  java.sql.Time
 | 
				
			||||||
 | 
					  (result-set-read-column [v _2 _3]
 | 
				
			||||||
 | 
					    (.toLocalTime v))
 | 
				
			||||||
 | 
					  Array
 | 
				
			||||||
 | 
					  (result-set-read-column [v _ _] (vec (.getArray v)))
 | 
				
			||||||
 | 
					  PGobject
 | 
				
			||||||
 | 
					  (result-set-read-column [pgobj _metadata _index]
 | 
				
			||||||
 | 
					    (let [type  (.getType pgobj)
 | 
				
			||||||
 | 
					          value (.getValue pgobj)]
 | 
				
			||||||
 | 
					      (case type
 | 
				
			||||||
 | 
					        "json" (parse-string value true)
 | 
				
			||||||
 | 
					        "jsonb" (parse-string value true)
 | 
				
			||||||
 | 
					        "citext" (str value)
 | 
				
			||||||
 | 
					        value))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn to-pg-json [value]
 | 
				
			||||||
 | 
					  (doto (PGobject.)
 | 
				
			||||||
 | 
					    (.setType "jsonb")
 | 
				
			||||||
 | 
					    (.setValue (generate-string value))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(extend-type clojure.lang.IPersistentVector
 | 
				
			||||||
 | 
					  jdbc/ISQLParameter
 | 
				
			||||||
 | 
					  (set-parameter [v ^java.sql.PreparedStatement stmt ^long idx]
 | 
				
			||||||
 | 
					    (let [conn      (.getConnection stmt)
 | 
				
			||||||
 | 
					          meta      (.getParameterMetaData stmt)
 | 
				
			||||||
 | 
					          type-name (.getParameterTypeName meta idx)]
 | 
				
			||||||
 | 
					      (if-let [elem-type (when (= (first type-name) \_) (apply str (rest type-name)))]
 | 
				
			||||||
 | 
					        (.setObject stmt idx (.createArrayOf conn elem-type (to-array v)))
 | 
				
			||||||
 | 
					        (.setObject stmt idx (to-pg-json v))))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(extend-protocol jdbc/ISQLValue
 | 
				
			||||||
 | 
					    java.util.Date
 | 
				
			||||||
 | 
					  (sql-value [v]
 | 
				
			||||||
 | 
					    (java.sql.Timestamp. (.getTime v)))
 | 
				
			||||||
 | 
					  java.time.LocalTime
 | 
				
			||||||
 | 
					  (sql-value [v]
 | 
				
			||||||
 | 
					    (jt/sql-time v))
 | 
				
			||||||
 | 
					  java.time.LocalDate
 | 
				
			||||||
 | 
					  (sql-value [v]
 | 
				
			||||||
 | 
					    (jt/sql-date v))
 | 
				
			||||||
 | 
					  java.time.LocalDateTime
 | 
				
			||||||
 | 
					  (sql-value [v]
 | 
				
			||||||
 | 
					    (jt/sql-timestamp v))
 | 
				
			||||||
 | 
					  java.time.ZonedDateTime
 | 
				
			||||||
 | 
					  (sql-value [v]
 | 
				
			||||||
 | 
					    (jt/sql-timestamp v))
 | 
				
			||||||
 | 
					  IPersistentMap
 | 
				
			||||||
 | 
					  (sql-value [value] (to-pg-json value))
 | 
				
			||||||
 | 
					  IPersistentVector
 | 
				
			||||||
 | 
					  (sql-value [value] (to-pg-json value)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										64
									
								
								src/clj/pastoralist/handler.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/clj/pastoralist/handler.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,64 @@
 | 
				
			||||||
 | 
					(ns pastoralist.handler
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [pastoralist.middleware :as middleware]
 | 
				
			||||||
 | 
					    [pastoralist.layout :refer [error-page]]
 | 
				
			||||||
 | 
					    [pastoralist.routes.home :refer [home-routes]]
 | 
				
			||||||
 | 
					    [pastoralist.routes.services :refer [service-routes]]
 | 
				
			||||||
 | 
					    [pastoralist.routes.oauth :refer [oauth-routes]]
 | 
				
			||||||
 | 
					    [reitit.swagger-ui :as swagger-ui]
 | 
				
			||||||
 | 
					    [reitit.ring :as ring]
 | 
				
			||||||
 | 
					    [ring.middleware.content-type :refer [wrap-content-type]]
 | 
				
			||||||
 | 
					    [ring.middleware.webjars :refer [wrap-webjars]]
 | 
				
			||||||
 | 
					    [pastoralist.env :refer [defaults]]
 | 
				
			||||||
 | 
					    [mount.core :as mount]
 | 
				
			||||||
 | 
					    [clojure.tools.logging :as log]
 | 
				
			||||||
 | 
					    [pastoralist.config :refer [env]]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(mount/defstate init-app
 | 
				
			||||||
 | 
					  :start ((or (:init defaults) (fn [])))
 | 
				
			||||||
 | 
					  :stop  ((or (:stop defaults) (fn []))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn init
 | 
				
			||||||
 | 
					  "init will be called once when
 | 
				
			||||||
 | 
					   app is deployed as a servlet on
 | 
				
			||||||
 | 
					   an app server such as Tomcat
 | 
				
			||||||
 | 
					   put any initialization code here"
 | 
				
			||||||
 | 
					  []
 | 
				
			||||||
 | 
					  (doseq [component (:started (mount/start))]
 | 
				
			||||||
 | 
					    (log/info component "started")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn destroy
 | 
				
			||||||
 | 
					  "destroy will be called when your application
 | 
				
			||||||
 | 
					   shuts down, put any clean up code here"
 | 
				
			||||||
 | 
					  []
 | 
				
			||||||
 | 
					  (doseq [component (:stopped (mount/stop))]
 | 
				
			||||||
 | 
					    (log/info component "stopped"))
 | 
				
			||||||
 | 
					  (shutdown-agents)
 | 
				
			||||||
 | 
					  (log/info "pastoralist has shut down!"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(mount/defstate app-routes
 | 
				
			||||||
 | 
					  :start
 | 
				
			||||||
 | 
					  (ring/ring-handler
 | 
				
			||||||
 | 
					    (ring/router
 | 
				
			||||||
 | 
					      [(home-routes)
 | 
				
			||||||
 | 
					       (service-routes)
 | 
				
			||||||
 | 
					       (oauth-routes)])
 | 
				
			||||||
 | 
					    (ring/routes
 | 
				
			||||||
 | 
					      (swagger-ui/create-swagger-ui-handler
 | 
				
			||||||
 | 
					        {:path   "/swagger-ui"
 | 
				
			||||||
 | 
					         :url    "/api/swagger.json"
 | 
				
			||||||
 | 
					         :config {:validator-url nil}})
 | 
				
			||||||
 | 
					      (ring/create-resource-handler
 | 
				
			||||||
 | 
					        {:path "/"})
 | 
				
			||||||
 | 
					      (wrap-content-type
 | 
				
			||||||
 | 
					        (wrap-webjars (constantly nil)))
 | 
				
			||||||
 | 
					      (ring/create-default-handler
 | 
				
			||||||
 | 
					        {:not-found
 | 
				
			||||||
 | 
					         (constantly (error-page {:status 404, :title "404 - Page not found"}))
 | 
				
			||||||
 | 
					         :method-not-allowed
 | 
				
			||||||
 | 
					         (constantly (error-page {:status 405, :title "405 - Not allowed"}))
 | 
				
			||||||
 | 
					         :not-acceptable
 | 
				
			||||||
 | 
					         (constantly (error-page {:status 406, :title "406 - Not acceptable"}))}))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn app []
 | 
				
			||||||
 | 
					  (middleware/wrap-base #'app-routes))
 | 
				
			||||||
							
								
								
									
										39
									
								
								src/clj/pastoralist/layout.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/clj/pastoralist/layout.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					(ns pastoralist.layout
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [clojure.java.io]
 | 
				
			||||||
 | 
					    [selmer.parser :as parser]
 | 
				
			||||||
 | 
					    [selmer.filters :as filters]
 | 
				
			||||||
 | 
					    [markdown.core :refer [md-to-html-string]]
 | 
				
			||||||
 | 
					    [ring.util.http-response :refer [content-type ok]]
 | 
				
			||||||
 | 
					    [ring.util.anti-forgery :refer [anti-forgery-field]]
 | 
				
			||||||
 | 
					    [ring.middleware.anti-forgery :refer [*anti-forgery-token*]]
 | 
				
			||||||
 | 
					    [ring.util.response]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(parser/set-resource-path!  (clojure.java.io/resource "html"))
 | 
				
			||||||
 | 
					(parser/add-tag! :csrf-field (fn [_ _] (anti-forgery-field)))
 | 
				
			||||||
 | 
					(filters/add-filter! :markdown (fn [content] [:safe (md-to-html-string content)]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn render
 | 
				
			||||||
 | 
					  "renders the HTML template located relative to resources/html"
 | 
				
			||||||
 | 
					  [request template & [params]]
 | 
				
			||||||
 | 
					  (content-type
 | 
				
			||||||
 | 
					    (ok
 | 
				
			||||||
 | 
					      (parser/render-file
 | 
				
			||||||
 | 
					        template
 | 
				
			||||||
 | 
					        (assoc params
 | 
				
			||||||
 | 
					          :page template
 | 
				
			||||||
 | 
					          :csrf-token *anti-forgery-token*)))
 | 
				
			||||||
 | 
					    "text/html; charset=utf-8"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn error-page
 | 
				
			||||||
 | 
					  "error-details should be a map containing the following keys:
 | 
				
			||||||
 | 
					   :status - error status
 | 
				
			||||||
 | 
					   :title - error title (optional)
 | 
				
			||||||
 | 
					   :message - detailed error message (optional)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   returns a response map with the error page as the body
 | 
				
			||||||
 | 
					   and the status specified by the status key"
 | 
				
			||||||
 | 
					  [error-details]
 | 
				
			||||||
 | 
					  {:status  (:status error-details)
 | 
				
			||||||
 | 
					   :headers {"Content-Type" "text/html; charset=utf-8"}
 | 
				
			||||||
 | 
					   :body    (parser/render-file "error.html" error-details)})
 | 
				
			||||||
							
								
								
									
										49
									
								
								src/clj/pastoralist/middleware.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/clj/pastoralist/middleware.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,49 @@
 | 
				
			||||||
 | 
					(ns pastoralist.middleware
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [pastoralist.env :refer [defaults]]
 | 
				
			||||||
 | 
					    [cheshire.generate :as cheshire]
 | 
				
			||||||
 | 
					    [cognitect.transit :as transit]
 | 
				
			||||||
 | 
					    [clojure.tools.logging :as log]
 | 
				
			||||||
 | 
					    [pastoralist.layout :refer [error-page]]
 | 
				
			||||||
 | 
					    [ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
 | 
				
			||||||
 | 
					    [pastoralist.middleware.formats :as formats]
 | 
				
			||||||
 | 
					    [muuntaja.middleware :refer [wrap-format wrap-params]]
 | 
				
			||||||
 | 
					    [pastoralist.config :refer [env]]
 | 
				
			||||||
 | 
					    [ring-ttl-session.core :refer [ttl-memory-store]]
 | 
				
			||||||
 | 
					    [ring.middleware.defaults :refer [site-defaults wrap-defaults]])
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					           )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn wrap-internal-error [handler]
 | 
				
			||||||
 | 
					  (fn [req]
 | 
				
			||||||
 | 
					    (try
 | 
				
			||||||
 | 
					      (handler req)
 | 
				
			||||||
 | 
					      (catch Throwable t
 | 
				
			||||||
 | 
					        (log/error t (.getMessage t))
 | 
				
			||||||
 | 
					        (error-page {:status 500
 | 
				
			||||||
 | 
					                     :title "Something very bad has happened!"
 | 
				
			||||||
 | 
					                     :message "We've dispatched a team of highly trained gnomes to take care of the problem."})))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn wrap-csrf [handler]
 | 
				
			||||||
 | 
					  (wrap-anti-forgery
 | 
				
			||||||
 | 
					    handler
 | 
				
			||||||
 | 
					    {:error-response
 | 
				
			||||||
 | 
					     (error-page
 | 
				
			||||||
 | 
					       {:status 403
 | 
				
			||||||
 | 
					        :title "Invalid anti-forgery token"})}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn wrap-formats [handler]
 | 
				
			||||||
 | 
					  (let [wrapped (-> handler wrap-params (wrap-format formats/instance))]
 | 
				
			||||||
 | 
					    (fn [request]
 | 
				
			||||||
 | 
					      ;; disable wrap-formats for websockets
 | 
				
			||||||
 | 
					      ;; since they're not compatible with this middleware
 | 
				
			||||||
 | 
					      ((if (:websocket? request) handler wrapped) request))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn wrap-base [handler]
 | 
				
			||||||
 | 
					  (-> ((:middleware defaults) handler)
 | 
				
			||||||
 | 
					      (wrap-defaults
 | 
				
			||||||
 | 
					        (-> site-defaults
 | 
				
			||||||
 | 
					            (assoc-in [:security :anti-forgery] false)
 | 
				
			||||||
 | 
					            (assoc-in  [:session :store] (ttl-memory-store (* 60 30)))))
 | 
				
			||||||
 | 
					      wrap-internal-error))
 | 
				
			||||||
							
								
								
									
										24
									
								
								src/clj/pastoralist/middleware/exception.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/clj/pastoralist/middleware/exception.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,24 @@
 | 
				
			||||||
 | 
					(ns pastoralist.middleware.exception
 | 
				
			||||||
 | 
					  (:require [clojure.tools.logging :as log]
 | 
				
			||||||
 | 
					            [expound.alpha :as expound]
 | 
				
			||||||
 | 
					            [reitit.coercion :as coercion]
 | 
				
			||||||
 | 
					            [reitit.ring.middleware.exception :as exception]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn coercion-error-handler [status]
 | 
				
			||||||
 | 
					  (let [printer (expound/custom-printer {:print-specs? false})]
 | 
				
			||||||
 | 
					    (fn [exception request]
 | 
				
			||||||
 | 
					      {:status status
 | 
				
			||||||
 | 
					       :headers {"Content-Type" "text/html"}
 | 
				
			||||||
 | 
					       :body (with-out-str (printer (-> exception ex-data :problems)))})))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(def exception-middleware
 | 
				
			||||||
 | 
					  (exception/create-exception-middleware
 | 
				
			||||||
 | 
					   (merge
 | 
				
			||||||
 | 
					    exception/default-handlers
 | 
				
			||||||
 | 
					    {;; log stack-traces for all exceptions
 | 
				
			||||||
 | 
					     ::exception/wrap (fn [handler e request]
 | 
				
			||||||
 | 
					                        (log/error e (.getMessage e))
 | 
				
			||||||
 | 
					                        (handler e request))
 | 
				
			||||||
 | 
					     ;; human-optimized validation messages
 | 
				
			||||||
 | 
					     ::coercion/request-coercion (coercion-error-handler 400)
 | 
				
			||||||
 | 
					     ::coercion/response-coercion (coercion-error-handler 500)})))
 | 
				
			||||||
							
								
								
									
										15
									
								
								src/clj/pastoralist/middleware/formats.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/clj/pastoralist/middleware/formats.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,15 @@
 | 
				
			||||||
 | 
					(ns pastoralist.middleware.formats
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [cognitect.transit :as transit]
 | 
				
			||||||
 | 
					    [luminus-transit.time :as time]
 | 
				
			||||||
 | 
					    [muuntaja.core :as m]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(def instance
 | 
				
			||||||
 | 
					  (m/create
 | 
				
			||||||
 | 
					    (-> m/default-options
 | 
				
			||||||
 | 
					        (update-in
 | 
				
			||||||
 | 
					          [:formats "application/transit+json" :decoder-opts]
 | 
				
			||||||
 | 
					          (partial merge time/time-deserialization-handlers))
 | 
				
			||||||
 | 
					        (update-in
 | 
				
			||||||
 | 
					          [:formats "application/transit+json" :encoder-opts]
 | 
				
			||||||
 | 
					          (partial merge time/time-serialization-handlers)))))
 | 
				
			||||||
							
								
								
									
										27
									
								
								src/clj/pastoralist/nrepl.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/clj/pastoralist/nrepl.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,27 @@
 | 
				
			||||||
 | 
					(ns pastoralist.nrepl
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [nrepl.server :as nrepl]
 | 
				
			||||||
 | 
					    [clojure.tools.logging :as log]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn start
 | 
				
			||||||
 | 
					  "Start a network repl for debugging on specified port followed by
 | 
				
			||||||
 | 
					  an optional parameters map. The :bind, :transport-fn, :handler,
 | 
				
			||||||
 | 
					  :ack-port and :greeting-fn will be forwarded to
 | 
				
			||||||
 | 
					  clojure.tools.nrepl.server/start-server as they are."
 | 
				
			||||||
 | 
					  [{:keys [port bind transport-fn handler ack-port greeting-fn]}]
 | 
				
			||||||
 | 
					  (try
 | 
				
			||||||
 | 
					    (log/info "starting nREPL server on port" port)
 | 
				
			||||||
 | 
					    (nrepl/start-server :port port
 | 
				
			||||||
 | 
					                        :bind bind
 | 
				
			||||||
 | 
					                        :transport-fn transport-fn
 | 
				
			||||||
 | 
					                        :handler handler
 | 
				
			||||||
 | 
					                        :ack-port ack-port
 | 
				
			||||||
 | 
					                        :greeting-fn greeting-fn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (catch Throwable t
 | 
				
			||||||
 | 
					      (log/error t "failed to start nREPL")
 | 
				
			||||||
 | 
					      (throw t))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn stop [server]
 | 
				
			||||||
 | 
					  (nrepl/stop-server server)
 | 
				
			||||||
 | 
					  (log/info "nREPL server stopped"))
 | 
				
			||||||
							
								
								
									
										36
									
								
								src/clj/pastoralist/oauth.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/clj/pastoralist/oauth.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					(ns pastoralist.oauth
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [pastoralist.config :refer [env]]
 | 
				
			||||||
 | 
					    [oauth.client :as oauth]
 | 
				
			||||||
 | 
					    [mount.core :refer [defstate]]
 | 
				
			||||||
 | 
					    [clojure.tools.logging :as log]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defstate consumer
 | 
				
			||||||
 | 
					  :start (oauth/make-consumer
 | 
				
			||||||
 | 
					           (env :oauth-consumer-key)
 | 
				
			||||||
 | 
					           (env :oauth-consumer-secret)
 | 
				
			||||||
 | 
					           (env :request-token-uri)
 | 
				
			||||||
 | 
					           (env :access-token-uri)
 | 
				
			||||||
 | 
					           (env :authorize-uri)
 | 
				
			||||||
 | 
					           :hmac-sha1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn oauth-callback-uri
 | 
				
			||||||
 | 
					  "Generates the oauth request callback URI"
 | 
				
			||||||
 | 
					  [{:keys [headers]}]
 | 
				
			||||||
 | 
					  (str (headers "x-forwarded-proto") "://" (headers "host") "/oauth/oauth-callback"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn fetch-request-token
 | 
				
			||||||
 | 
					  "Fetches a request token."
 | 
				
			||||||
 | 
					  [request]
 | 
				
			||||||
 | 
					  (let [callback-uri (oauth-callback-uri request)]
 | 
				
			||||||
 | 
					    (log/info "Fetching request token using callback-uri" callback-uri)
 | 
				
			||||||
 | 
					    (oauth/request-token consumer (oauth-callback-uri request))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn fetch-access-token
 | 
				
			||||||
 | 
					  [request_token]
 | 
				
			||||||
 | 
					  (oauth/access-token consumer request_token (:oauth_verifier request_token)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn auth-redirect-uri
 | 
				
			||||||
 | 
					  "Gets the URI the user should be redirected to when authenticating."
 | 
				
			||||||
 | 
					  [request-token]
 | 
				
			||||||
 | 
					  (str (oauth/user-approval-uri consumer request-token)))
 | 
				
			||||||
							
								
								
									
										21
									
								
								src/clj/pastoralist/routes/home.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/clj/pastoralist/routes/home.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					(ns pastoralist.routes.home
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					   [pastoralist.layout :as layout]
 | 
				
			||||||
 | 
					   [pastoralist.db.core :as db]
 | 
				
			||||||
 | 
					   [clojure.java.io :as io]
 | 
				
			||||||
 | 
					   [pastoralist.middleware :as middleware]
 | 
				
			||||||
 | 
					   [ring.util.response]
 | 
				
			||||||
 | 
					   [ring.util.http-response :as response]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn home-page [request]
 | 
				
			||||||
 | 
					  (layout/render request "home.html"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn home-routes []
 | 
				
			||||||
 | 
					  [""
 | 
				
			||||||
 | 
					   {:middleware [middleware/wrap-csrf
 | 
				
			||||||
 | 
					                 middleware/wrap-formats]}
 | 
				
			||||||
 | 
					   ["/" {:get home-page}]
 | 
				
			||||||
 | 
					   ["/docs" {:get (fn [_]
 | 
				
			||||||
 | 
					                    (-> (response/ok (-> "docs/docs.md" io/resource slurp))
 | 
				
			||||||
 | 
					                        (response/header "Content-Type" "text/plain; charset=utf-8")))}]])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										33
									
								
								src/clj/pastoralist/routes/oauth.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/clj/pastoralist/routes/oauth.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,33 @@
 | 
				
			||||||
 | 
					(ns pastoralist.routes.oauth
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [ring.util.http-response :refer [ok found]]
 | 
				
			||||||
 | 
					    [clojure.java.io :as io]
 | 
				
			||||||
 | 
					    [pastoralist.oauth :as oauth]
 | 
				
			||||||
 | 
					    [clojure.tools.logging :as log]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn oauth-init
 | 
				
			||||||
 | 
					  "Initiates the Twitter OAuth"
 | 
				
			||||||
 | 
					  [request]
 | 
				
			||||||
 | 
					  (-> (oauth/fetch-request-token request)
 | 
				
			||||||
 | 
					      :oauth_token
 | 
				
			||||||
 | 
					      oauth/auth-redirect-uri
 | 
				
			||||||
 | 
					      found))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn oauth-callback
 | 
				
			||||||
 | 
					  "Handles the callback from Twitter."
 | 
				
			||||||
 | 
					  [{:keys [session params]}]
 | 
				
			||||||
 | 
					  ; oauth request was denied by user
 | 
				
			||||||
 | 
					  (if (:denied params)
 | 
				
			||||||
 | 
					    (-> (found "/")
 | 
				
			||||||
 | 
					        (assoc :flash {:denied true}))
 | 
				
			||||||
 | 
					    ; fetch the request token and do anything else you wanna do if not denied.
 | 
				
			||||||
 | 
					    (let [{:keys [user_id screen_name]} (oauth/fetch-access-token params)]
 | 
				
			||||||
 | 
					      (log/info "successfully authenticated as" user_id screen_name)
 | 
				
			||||||
 | 
					      (-> (found "/")
 | 
				
			||||||
 | 
					          (assoc :session
 | 
				
			||||||
 | 
					            (assoc session :user-id user_id :screen-name screen_name))))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn oauth-routes []
 | 
				
			||||||
 | 
					  ["/oauth"
 | 
				
			||||||
 | 
					   ["/oauth-init" {:get oauth-init}]
 | 
				
			||||||
 | 
					   ["/oauth-callback" {:get oauth-callback}]])
 | 
				
			||||||
							
								
								
									
										91
									
								
								src/clj/pastoralist/routes/services.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/clj/pastoralist/routes/services.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,91 @@
 | 
				
			||||||
 | 
					(ns pastoralist.routes.services
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [reitit.swagger :as swagger]
 | 
				
			||||||
 | 
					    [reitit.swagger-ui :as swagger-ui]
 | 
				
			||||||
 | 
					    [reitit.ring.coercion :as coercion]
 | 
				
			||||||
 | 
					    [reitit.coercion.spec :as spec-coercion]
 | 
				
			||||||
 | 
					    [reitit.ring.middleware.muuntaja :as muuntaja]
 | 
				
			||||||
 | 
					    [reitit.ring.middleware.multipart :as multipart]
 | 
				
			||||||
 | 
					    [reitit.ring.middleware.parameters :as parameters]
 | 
				
			||||||
 | 
					    [pastoralist.middleware.formats :as formats]
 | 
				
			||||||
 | 
					    [pastoralist.middleware.exception :as exception]
 | 
				
			||||||
 | 
					    [ring.util.http-response :refer :all]
 | 
				
			||||||
 | 
					    [clojure.java.io :as io]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn service-routes []
 | 
				
			||||||
 | 
					  ["/api"
 | 
				
			||||||
 | 
					   {:coercion spec-coercion/coercion
 | 
				
			||||||
 | 
					    :muuntaja formats/instance
 | 
				
			||||||
 | 
					    :swagger {:id ::api}
 | 
				
			||||||
 | 
					    :middleware [;; query-params & form-params
 | 
				
			||||||
 | 
					                 parameters/parameters-middleware
 | 
				
			||||||
 | 
					                 ;; content-negotiation
 | 
				
			||||||
 | 
					                 muuntaja/format-negotiate-middleware
 | 
				
			||||||
 | 
					                 ;; encoding response body
 | 
				
			||||||
 | 
					                 muuntaja/format-response-middleware
 | 
				
			||||||
 | 
					                 ;; exception handling
 | 
				
			||||||
 | 
					                 exception/exception-middleware
 | 
				
			||||||
 | 
					                 ;; decoding request body
 | 
				
			||||||
 | 
					                 muuntaja/format-request-middleware
 | 
				
			||||||
 | 
					                 ;; coercing response bodys
 | 
				
			||||||
 | 
					                 coercion/coerce-response-middleware
 | 
				
			||||||
 | 
					                 ;; coercing request parameters
 | 
				
			||||||
 | 
					                 coercion/coerce-request-middleware
 | 
				
			||||||
 | 
					                 ;; multipart
 | 
				
			||||||
 | 
					                 multipart/multipart-middleware]}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   ;; swagger documentation
 | 
				
			||||||
 | 
					   ["" {:no-doc true
 | 
				
			||||||
 | 
					        :swagger {:info {:title "my-api"
 | 
				
			||||||
 | 
					                         :description "https://cljdoc.org/d/metosin/reitit"}}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ["/swagger.json"
 | 
				
			||||||
 | 
					     {:get (swagger/create-swagger-handler)}]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ["/api-docs/*"
 | 
				
			||||||
 | 
					     {:get (swagger-ui/create-swagger-ui-handler
 | 
				
			||||||
 | 
					             {:url "/api/swagger.json"
 | 
				
			||||||
 | 
					              :config {:validator-url nil}})}]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   ["/ping"
 | 
				
			||||||
 | 
					    {:get (constantly (ok {:message "pong"}))}]
 | 
				
			||||||
 | 
					   
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   ["/math"
 | 
				
			||||||
 | 
					    {:swagger {:tags ["math"]}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ["/plus"
 | 
				
			||||||
 | 
					     {:get {:summary "plus with spec query parameters"
 | 
				
			||||||
 | 
					            :parameters {:query {:x int?, :y int?}}
 | 
				
			||||||
 | 
					            :responses {200 {:body {:total pos-int?}}}
 | 
				
			||||||
 | 
					            :handler (fn [{{{:keys [x y]} :query} :parameters}]
 | 
				
			||||||
 | 
					                       {:status 200
 | 
				
			||||||
 | 
					                        :body {:total (+ x y)}})}
 | 
				
			||||||
 | 
					      :post {:summary "plus with spec body parameters"
 | 
				
			||||||
 | 
					             :parameters {:body {:x int?, :y int?}}
 | 
				
			||||||
 | 
					             :responses {200 {:body {:total pos-int?}}}
 | 
				
			||||||
 | 
					             :handler (fn [{{{:keys [x y]} :body} :parameters}]
 | 
				
			||||||
 | 
					                        {:status 200
 | 
				
			||||||
 | 
					                         :body {:total (+ x y)}})}}]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   ["/files"
 | 
				
			||||||
 | 
					    {:swagger {:tags ["files"]}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ["/upload"
 | 
				
			||||||
 | 
					     {:post {:summary "upload a file"
 | 
				
			||||||
 | 
					             :parameters {:multipart {:file multipart/temp-file-part}}
 | 
				
			||||||
 | 
					             :responses {200 {:body {:name string?, :size int?}}}
 | 
				
			||||||
 | 
					             :handler (fn [{{{:keys [file]} :multipart} :parameters}]
 | 
				
			||||||
 | 
					                        {:status 200
 | 
				
			||||||
 | 
					                         :body {:name (:filename file)
 | 
				
			||||||
 | 
					                                :size (:size file)}})}}]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ["/download"
 | 
				
			||||||
 | 
					     {:get {:summary "downloads a file"
 | 
				
			||||||
 | 
					            :swagger {:produces ["image/png"]}
 | 
				
			||||||
 | 
					            :handler (fn [_]
 | 
				
			||||||
 | 
					                       {:status 200
 | 
				
			||||||
 | 
					                        :headers {"Content-Type" "image/png"}
 | 
				
			||||||
 | 
					                        :body (-> "public/img/warning_clojure.png"
 | 
				
			||||||
 | 
					                                  (io/resource)
 | 
				
			||||||
 | 
					                                  (io/input-stream))})}}]]])
 | 
				
			||||||
							
								
								
									
										2
									
								
								src/cljc/pastoralist/validation.cljc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/cljc/pastoralist/validation.cljc
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					(ns pastoralist.validation
 | 
				
			||||||
 | 
					  (:require [struct.core :as st]))
 | 
				
			||||||
							
								
								
									
										30
									
								
								src/cljs/pastoralist/ajax.cljs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/cljs/pastoralist/ajax.cljs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,30 @@
 | 
				
			||||||
 | 
					(ns pastoralist.ajax
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [ajax.core :as ajax]
 | 
				
			||||||
 | 
					    [luminus-transit.time :as time]
 | 
				
			||||||
 | 
					    [cognitect.transit :as transit]
 | 
				
			||||||
 | 
					    [re-frame.core :as rf]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn local-uri? [{:keys [uri]}]
 | 
				
			||||||
 | 
					  (not (re-find #"^\w+?://" uri)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn default-headers [request]
 | 
				
			||||||
 | 
					  (if (local-uri? request)
 | 
				
			||||||
 | 
					    (-> request
 | 
				
			||||||
 | 
					        (update :headers #(merge {"x-csrf-token" js/csrfToken} %)))
 | 
				
			||||||
 | 
					    request))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					;; injects transit serialization config into request options
 | 
				
			||||||
 | 
					(defn as-transit [opts]
 | 
				
			||||||
 | 
					  (merge {:raw             false
 | 
				
			||||||
 | 
					          :format          :transit
 | 
				
			||||||
 | 
					          :response-format :transit
 | 
				
			||||||
 | 
					          :reader          (transit/reader :json time/time-deserialization-handlers)
 | 
				
			||||||
 | 
					          :writer          (transit/writer :json time/time-serialization-handlers)}
 | 
				
			||||||
 | 
					         opts))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn load-interceptors! []
 | 
				
			||||||
 | 
					  (swap! ajax/default-interceptors
 | 
				
			||||||
 | 
					         conj
 | 
				
			||||||
 | 
					         (ajax/to-interceptor {:name "default headers"
 | 
				
			||||||
 | 
					                               :request default-headers})))
 | 
				
			||||||
							
								
								
									
										55
									
								
								src/cljs/pastoralist/core.cljs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/cljs/pastoralist/core.cljs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,55 @@
 | 
				
			||||||
 | 
					(ns pastoralist.core
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [kee-frame.core :as kf]
 | 
				
			||||||
 | 
					    [re-frame.core :as rf]
 | 
				
			||||||
 | 
					    [ajax.core :as http]
 | 
				
			||||||
 | 
					    [pastoralist.ajax :as ajax]
 | 
				
			||||||
 | 
					    [pastoralist.routing :as routing]
 | 
				
			||||||
 | 
					    [pastoralist.view :as view]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(rf/reg-event-fx
 | 
				
			||||||
 | 
					  ::load-about-page
 | 
				
			||||||
 | 
					  (constantly nil))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(kf/reg-controller
 | 
				
			||||||
 | 
					  ::about-controller
 | 
				
			||||||
 | 
					  {:params (constantly true)
 | 
				
			||||||
 | 
					   :start  [::load-about-page]})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(rf/reg-sub
 | 
				
			||||||
 | 
					  :docs
 | 
				
			||||||
 | 
					  (fn [db _]
 | 
				
			||||||
 | 
					    (:docs db)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(kf/reg-chain
 | 
				
			||||||
 | 
					  ::load-home-page
 | 
				
			||||||
 | 
					  (fn [_ _]
 | 
				
			||||||
 | 
					    {:http-xhrio {:method          :get
 | 
				
			||||||
 | 
					                  :uri             "/docs"
 | 
				
			||||||
 | 
					                  :response-format (http/raw-response-format)
 | 
				
			||||||
 | 
					                  :on-failure      [:common/set-error]}})
 | 
				
			||||||
 | 
					  (fn [{:keys [db]} [_ docs]]
 | 
				
			||||||
 | 
					    {:db (assoc db :docs docs)}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(kf/reg-controller
 | 
				
			||||||
 | 
					  ::home-controller
 | 
				
			||||||
 | 
					  {:params (constantly true)
 | 
				
			||||||
 | 
					   :start  [::load-home-page]})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					;; -------------------------
 | 
				
			||||||
 | 
					;; Initialize app
 | 
				
			||||||
 | 
					(defn mount-components
 | 
				
			||||||
 | 
					  ([] (mount-components true))
 | 
				
			||||||
 | 
					  ([debug?]
 | 
				
			||||||
 | 
					    (rf/clear-subscription-cache!)
 | 
				
			||||||
 | 
					    (kf/start! {:debug?         (boolean debug?)
 | 
				
			||||||
 | 
					                :routes         routing/routes
 | 
				
			||||||
 | 
					                :hash-routing?  true
 | 
				
			||||||
 | 
					                :initial-db     {}
 | 
				
			||||||
 | 
					                :root-component [view/root-component]})))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn init! [debug?]
 | 
				
			||||||
 | 
					  (ajax/load-interceptors!)
 | 
				
			||||||
 | 
					  (mount-components debug?))
 | 
				
			||||||
							
								
								
									
										24
									
								
								src/cljs/pastoralist/routing.cljs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/cljs/pastoralist/routing.cljs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,24 @@
 | 
				
			||||||
 | 
					(ns pastoralist.routing
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [re-frame.core :as rf]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(def routes
 | 
				
			||||||
 | 
					  [["/" :home]
 | 
				
			||||||
 | 
					   ["/about" :about]])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(rf/reg-sub
 | 
				
			||||||
 | 
					  :nav/route
 | 
				
			||||||
 | 
					  :<- [:kee-frame/route]
 | 
				
			||||||
 | 
					  identity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(rf/reg-event-fx
 | 
				
			||||||
 | 
					  :nav/route-name
 | 
				
			||||||
 | 
					  (fn [_ [_ route-name]]
 | 
				
			||||||
 | 
					    {:navigate-to [route-name]}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(rf/reg-sub
 | 
				
			||||||
 | 
					  :nav/page
 | 
				
			||||||
 | 
					  :<- [:nav/route]
 | 
				
			||||||
 | 
					  (fn [route _]
 | 
				
			||||||
 | 
					    (-> route :data :name)))
 | 
				
			||||||
							
								
								
									
										45
									
								
								src/cljs/pastoralist/view.cljs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/cljs/pastoralist/view.cljs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,45 @@
 | 
				
			||||||
 | 
					(ns pastoralist.view
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [kee-frame.core :as kf]
 | 
				
			||||||
 | 
					    [markdown.core :refer [md->html]]
 | 
				
			||||||
 | 
					    [reagent.core :as r]
 | 
				
			||||||
 | 
					    [re-frame.core :as rf]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn nav-link [title page]
 | 
				
			||||||
 | 
					  [:a.navbar-item
 | 
				
			||||||
 | 
					   {:href   (kf/path-for [page])
 | 
				
			||||||
 | 
					    :class (when (= page @(rf/subscribe [:nav/page])) "is-active")}
 | 
				
			||||||
 | 
					   title])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn navbar []
 | 
				
			||||||
 | 
					  (r/with-let [expanded? (r/atom false)]
 | 
				
			||||||
 | 
					    [:nav.navbar.is-info>div.container
 | 
				
			||||||
 | 
					     [:div.navbar-brand
 | 
				
			||||||
 | 
					      [:a.navbar-item {:href "/" :style {:font-weight :bold}} "pastoralist"]
 | 
				
			||||||
 | 
					      [:span.navbar-burger.burger
 | 
				
			||||||
 | 
					       {:data-target :nav-menu
 | 
				
			||||||
 | 
					        :on-click #(swap! expanded? not)
 | 
				
			||||||
 | 
					        :class (when @expanded? :is-active)}
 | 
				
			||||||
 | 
					       [:span][:span][:span]]]
 | 
				
			||||||
 | 
					     [:div#nav-menu.navbar-menu
 | 
				
			||||||
 | 
					      {:class (when @expanded? :is-active)}
 | 
				
			||||||
 | 
					      [:div.navbar-start
 | 
				
			||||||
 | 
					       [nav-link "Home" :home]
 | 
				
			||||||
 | 
					       [nav-link "About" :about]]]]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn about-page []
 | 
				
			||||||
 | 
					  [:section.section>div.container>div.content
 | 
				
			||||||
 | 
					   [:img {:src "/img/warning_clojure.png"}]])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn home-page []
 | 
				
			||||||
 | 
					  [:section.section>div.container>div.content
 | 
				
			||||||
 | 
					   (when-let [docs @(rf/subscribe [:docs])]
 | 
				
			||||||
 | 
					     [:div {:dangerouslySetInnerHTML {:__html (md->html docs)}}])])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn root-component []
 | 
				
			||||||
 | 
					  [:div
 | 
				
			||||||
 | 
					   [navbar]
 | 
				
			||||||
 | 
					   [kf/switch-route (fn [route] (get-in route [:data :name]))
 | 
				
			||||||
 | 
					    :home home-page
 | 
				
			||||||
 | 
					    :about about-page
 | 
				
			||||||
 | 
					    nil [:div ""]]])
 | 
				
			||||||
							
								
								
									
										8
									
								
								test-config.edn
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								test-config.edn
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					;; WARNING
 | 
				
			||||||
 | 
					;; The test-config.edn file is used for local environment variables, such as database credentials.
 | 
				
			||||||
 | 
					;; This file is listed in .gitignore and will be excluded from version control by Git.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{:port 3000
 | 
				
			||||||
 | 
					 ; set your test database connection URL here
 | 
				
			||||||
 | 
					 ; :database-url "postgresql://localhost/pastoralist_test?user=simon&password=db_user_password_here"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										38
									
								
								test/clj/pastoralist/test/db/core.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								test/clj/pastoralist/test/db/core.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,38 @@
 | 
				
			||||||
 | 
					(ns pastoralist.test.db.core
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					   [pastoralist.db.core :refer [*db*] :as db]
 | 
				
			||||||
 | 
					   [java-time.pre-java8]
 | 
				
			||||||
 | 
					   [luminus-migrations.core :as migrations]
 | 
				
			||||||
 | 
					   [clojure.test :refer :all]
 | 
				
			||||||
 | 
					   [clojure.java.jdbc :as jdbc]
 | 
				
			||||||
 | 
					   [pastoralist.config :refer [env]]
 | 
				
			||||||
 | 
					   [mount.core :as mount]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(use-fixtures
 | 
				
			||||||
 | 
					  :once
 | 
				
			||||||
 | 
					  (fn [f]
 | 
				
			||||||
 | 
					    (mount/start
 | 
				
			||||||
 | 
					     #'pastoralist.config/env
 | 
				
			||||||
 | 
					     #'pastoralist.db.core/*db*)
 | 
				
			||||||
 | 
					    (migrations/migrate ["migrate"] (select-keys env [:database-url]))
 | 
				
			||||||
 | 
					    (f)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(deftest test-users
 | 
				
			||||||
 | 
					  (jdbc/with-db-transaction [t-conn *db*]
 | 
				
			||||||
 | 
					    (jdbc/db-set-rollback-only! t-conn)
 | 
				
			||||||
 | 
					    (is (= 1 (db/create-user!
 | 
				
			||||||
 | 
					              t-conn
 | 
				
			||||||
 | 
					              {:id         "1"
 | 
				
			||||||
 | 
					               :first_name "Sam"
 | 
				
			||||||
 | 
					               :last_name  "Smith"
 | 
				
			||||||
 | 
					               :email      "sam.smith@example.com"
 | 
				
			||||||
 | 
					               :pass       "pass"})))
 | 
				
			||||||
 | 
					    (is (= {:id         "1"
 | 
				
			||||||
 | 
					            :first_name "Sam"
 | 
				
			||||||
 | 
					            :last_name  "Smith"
 | 
				
			||||||
 | 
					            :email      "sam.smith@example.com"
 | 
				
			||||||
 | 
					            :pass       "pass"
 | 
				
			||||||
 | 
					            :admin      nil
 | 
				
			||||||
 | 
					            :last_login nil
 | 
				
			||||||
 | 
					            :is_active  nil}
 | 
				
			||||||
 | 
					           (db/get-user t-conn {:id "1"})))))
 | 
				
			||||||
							
								
								
									
										52
									
								
								test/clj/pastoralist/test/handler.clj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								test/clj/pastoralist/test/handler.clj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,52 @@
 | 
				
			||||||
 | 
					(ns pastoralist.test.handler
 | 
				
			||||||
 | 
					  (:require
 | 
				
			||||||
 | 
					    [clojure.test :refer :all]
 | 
				
			||||||
 | 
					    [ring.mock.request :refer :all]
 | 
				
			||||||
 | 
					    [pastoralist.handler :refer :all]
 | 
				
			||||||
 | 
					    [pastoralist.middleware.formats :as formats]
 | 
				
			||||||
 | 
					    [muuntaja.core :as m]
 | 
				
			||||||
 | 
					    [mount.core :as mount]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(defn parse-json [body]
 | 
				
			||||||
 | 
					  (m/decode formats/instance "application/json" body))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(use-fixtures
 | 
				
			||||||
 | 
					  :once
 | 
				
			||||||
 | 
					  (fn [f]
 | 
				
			||||||
 | 
					    (mount/start #'pastoralist.config/env
 | 
				
			||||||
 | 
					                 #'pastoralist.handler/app-routes)
 | 
				
			||||||
 | 
					    (f)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(deftest test-app
 | 
				
			||||||
 | 
					  (testing "main route"
 | 
				
			||||||
 | 
					    (let [response ((app) (request :get "/"))]
 | 
				
			||||||
 | 
					      (is (= 200 (:status response)))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  (testing "not-found route"
 | 
				
			||||||
 | 
					    (let [response ((app) (request :get "/invalid"))]
 | 
				
			||||||
 | 
					      (is (= 404 (:status response)))))
 | 
				
			||||||
 | 
					  (testing "services"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (testing "success"
 | 
				
			||||||
 | 
					      (let [response ((app) (-> (request :post "/api/math/plus")
 | 
				
			||||||
 | 
					                                (json-body {:x 10, :y 6})))]
 | 
				
			||||||
 | 
					        (is (= 200 (:status response)))
 | 
				
			||||||
 | 
					        (is (= {:total 16} (m/decode-response-body response)))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (testing "parameter coercion error"
 | 
				
			||||||
 | 
					      (let [response ((app) (-> (request :post "/api/math/plus")
 | 
				
			||||||
 | 
					                                (json-body {:x 10, :y "invalid"})))]
 | 
				
			||||||
 | 
					        (is (= 400 (:status response)))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (testing "response coercion error"
 | 
				
			||||||
 | 
					      (let [response ((app) (-> (request :post "/api/math/plus")
 | 
				
			||||||
 | 
					                                (json-body {:x -10, :y 6})))]
 | 
				
			||||||
 | 
					        (is (= 500 (:status response)))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (testing "content negotiation"
 | 
				
			||||||
 | 
					      (let [response ((app) (-> (request :post "/api/math/plus")
 | 
				
			||||||
 | 
					                                (body (pr-str {:x 10, :y 6}))
 | 
				
			||||||
 | 
					                                (content-type "application/edn")
 | 
				
			||||||
 | 
					                                (header "accept" "application/transit+json")))]
 | 
				
			||||||
 | 
					        (is (= 200 (:status response)))
 | 
				
			||||||
 | 
					        (is (= {:total 16} (m/decode-response-body response)))))))
 | 
				
			||||||
							
								
								
									
										9
									
								
								test/cljs/pastoralist/core_test.cljs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								test/cljs/pastoralist/core_test.cljs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					(ns pastoralist.core-test
 | 
				
			||||||
 | 
					  (:require [cljs.test :refer-macros [is are deftest testing use-fixtures]]
 | 
				
			||||||
 | 
					            [pjstadig.humane-test-output]
 | 
				
			||||||
 | 
					            [reagent.core :as reagent :refer [atom]]
 | 
				
			||||||
 | 
					            [pastoralist.core :as rc]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(deftest test-home
 | 
				
			||||||
 | 
					  (is (= true true)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										6
									
								
								test/cljs/pastoralist/doo_runner.cljs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								test/cljs/pastoralist/doo_runner.cljs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					(ns pastoralist.doo-runner
 | 
				
			||||||
 | 
					  (:require [doo.runner :refer-macros [doo-tests]]
 | 
				
			||||||
 | 
					            [pastoralist.core-test]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(doo-tests 'pastoralist.core-test)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue