Luminus skeleton + useless github button

This commit is contained in:
kagel 2016-08-21 00:36:09 +03:00
commit c4bab5a6ed
41 changed files with 1067 additions and 0 deletions

17
.gitignore vendored Normal file
View File

@ -0,0 +1,17 @@
/target
/lib
/classes
/checkouts
pom.xml
*.jar
*.class
/.lein-*
profiles.clj
/.env
.nrepl-port
/log
.idea
*.iml
*.ipr
/resources/public/css/screen.css
*.log

28
Capstanfile Normal file
View 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 /commiteth/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:
/commiteth/app.jar: ./target/uberjar/commiteth.jar

8
Dockerfile Normal file
View File

@ -0,0 +1,8 @@
FROM java:8-alpine
MAINTAINER Your Name <you@example.com>
ADD target/uberjar/commiteth.jar /commiteth/app.jar
EXPOSE 3000
CMD ["java", "-jar", "/commiteth/app.jar"]

1
Procfile Normal file
View File

@ -0,0 +1 @@
web: java $JVM_OPTS -cp target/uberjar/commiteth.jar clojure.main -m commiteth.core

24
README.md Normal file
View File

@ -0,0 +1,24 @@
# commiteth
generated using Luminus version "2.9.10.94"
FIXME
## Prerequisites
* You will need [Leiningen][1] 2.0 or above installed.
* [SassC][2] libsass command-line compiler is required
[1]: https://github.com/technomancy/leiningen
[2]: http://github.com/sass/sassc
## Running
lein run
lein figwheel
lein auto sassc once
## License
Copyright © 2016 FIXME

View File

@ -0,0 +1,10 @@
(ns commiteth.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))

14
env/dev/clj/commiteth/env.clj vendored Normal file
View File

@ -0,0 +1,14 @@
(ns commiteth.env
(:require [selmer.parser :as parser]
[clojure.tools.logging :as log]
[commiteth.dev-middleware :refer [wrap-dev]]))
(def defaults
{:init
(fn []
(parser/cache-off!)
(log/info "\n-=[commiteth started successfully using the development profile]=-"))
:stop
(fn []
(log/info "\n-=[commiteth has shut down successfully]=-"))
:middleware wrap-dev})

12
env/dev/clj/commiteth/figwheel.clj vendored Normal file
View File

@ -0,0 +1,12 @@
(ns commiteth.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))

16
env/dev/clj/user.clj vendored Normal file
View File

@ -0,0 +1,16 @@
(ns user
(:require [mount.core :as mount]
[commiteth.figwheel :refer [start-fw stop-fw cljs]]
commiteth.core))
(defn start []
(mount/start-without #'commiteth.core/repl-server))
(defn stop []
(mount/stop-except #'commiteth.core/repl-server))
(defn restart []
(stop)
(start))

14
env/dev/cljs/commiteth/dev.cljs vendored Normal file
View File

@ -0,0 +1,14 @@
(ns ^:figwheel-no-load commiteth.app
(:require [commiteth.core :as core]
[devtools.core :as devtools]
[figwheel.client :as figwheel :include-macros true]))
(enable-console-print!)
(figwheel/watch-and-reload
:websocket-url "ws://localhost:3449/figwheel-ws"
:on-jsload core/mount-components)
(devtools/install!)
(core/init!)

6
env/dev/resources/config.edn vendored Normal file
View File

@ -0,0 +1,6 @@
{:dev true
:port 3000
;; when :nrepl-port is set the application starts the nREPL server on load
:nrepl-port 7000
:github-access-token "c9323a357c2beeebe4e8d618e0fda47dc9e15f62"
:github-user "kagel"}

39
env/dev/resources/logback.xml vendored Normal file
View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<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/commiteth.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/commiteth.%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.xnio.nio" level="warn">
<AppenderRef ref="STDOUT"/>
<AppenderRef ref="FILE"/>
</logger>
<logger name="com.zaxxer.hikari" level="warn">
<AppenderRef ref="STDOUT"/>
<AppenderRef ref="FILE"/>
</logger>
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>

11
env/prod/clj/commiteth/env.clj vendored Normal file
View File

@ -0,0 +1,11 @@
(ns commiteth.env
(:require [clojure.tools.logging :as log]))
(def defaults
{:init
(fn []
(log/info "\n-=[commiteth started successfully]=-"))
:stop
(fn []
(log/info "\n-=[commiteth has shut down successfully]=-"))
:middleware identity})

7
env/prod/cljs/commiteth/prod.cljs vendored Normal file
View File

@ -0,0 +1,7 @@
(ns commiteth.app
(:require [commiteth.core :as core]))
;;ignore println statements in prod
(set! *print-fn* (fn [& _]))
(core/init!)

4
env/prod/resources/config.edn vendored Normal file
View File

@ -0,0 +1,4 @@
{:production true
:port 3000
:github-access-token "c9323a357c2beeebe4e8d618e0fda47dc9e15f62"
:github-user "kagel"}

28
env/prod/resources/logback.xml vendored Normal file
View File

@ -0,0 +1,28 @@
<?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/commiteth.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/commiteth.%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.xnio.nio" level="warn">
<AppenderRef ref="FILE"/>
</logger>
<logger name="com.zaxxer.hikari" level="warn">
<AppenderRef ref="FILE"/>
</logger>
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</configuration>

6
env/test/resources/config.edn vendored Normal file
View File

@ -0,0 +1,6 @@
{:test true
:port 3001
;; when :nrepl-port is set the application starts the nREPL server on load
:nrepl-port 7001
:github-access-token "c9323a357c2beeebe4e8d618e0fda47dc9e15f62"
:github-user "kagel"}

143
project.clj Normal file
View File

@ -0,0 +1,143 @@
(defproject commiteth "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:dependencies [[metosin/compojure-api "1.1.6"]
[re-frame "0.8.0"]
[cljs-ajax "0.5.8"]
[secretary "1.2.3"]
[reagent-utils "0.2.0"]
[reagent "0.6.0-rc"]
[org.clojure/clojurescript "1.9.225" :scope "provided"]
[org.clojure/clojure "1.8.0"]
[selmer "1.0.7"]
[markdown-clj "0.9.89"]
[ring-middleware-format "0.7.0"]
[metosin/ring-http-response "0.8.0"]
[bouncer "1.0.0"]
[org.webjars/bootstrap "4.0.0-alpha.3"]
[org.webjars/font-awesome "4.6.3"]
[org.webjars/bootstrap-social "5.0.0"]
[org.webjars.bower/tether "1.3.3"]
[org.clojure/tools.logging "0.3.1"]
[compojure "1.5.1"]
[ring-webjars "0.1.1"]
[ring/ring-defaults "0.2.1"]
[mount "0.1.10"]
[cprop "0.1.9"]
[org.clojure/tools.cli "0.3.5"]
[luminus-nrepl "0.1.4"]
[buddy "1.0.0"]
[luminus-migrations "0.2.6"]
[conman "0.6.0"]
[org.postgresql/postgresql "9.4.1209"]
[org.webjars/webjars-locator-jboss-vfs "0.1.0"]
[luminus-immutant "0.2.2"]
[tentacles "0.5.1"]]
:min-lein-version "2.0.0"
:jvm-opts ["-server" "-Dconf=.lein-env"]
:source-paths ["src/clj" "src/cljc"]
:resource-paths ["resources" "target/cljsbuild"]
:target-path "target/%s/"
:main commiteth.core
:migratus {:store :database :db ~(get (System/getenv) "DATABASE_URL")}
:plugins [[lein-cprop "1.0.1"]
[migratus-lein "0.4.1"]
[lein-cljsbuild "1.1.3"]
[lein-immutant "2.1.0"]
[lein-sassc "0.10.4"]
[lein-auto "0.1.2"]]
:sassc
[{:src "resources/scss/screen.scss"
:output-to "resources/public/css/screen.css"
:style "nested"
:import-path "resources/scss"}]
:auto
{"sassc" {:file-pattern #"\.(scss|sass)$" :paths ["resources/scss"]}}
:hooks [leiningen.sassc]
:clean-targets ^{:protect false}
[:target-path [:cljsbuild :builds :app :compiler :output-dir] [:cljsbuild :builds :app :compiler :output-to]]
:figwheel
{:http-server-root "public"
:nrepl-port 7002
:css-dirs ["resources/public/css"]
:nrepl-middleware [cemerick.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-to "target/cljsbuild/public/js/app.js"
:externs ["react/externs/react.js"]
:optimizations :advanced
:pretty-print false
:closure-warnings
{:externs-validation :off :non-standard-jsdoc :off}}}}}
:aot :all
:uberjar-name "commiteth.jar"
:source-paths ["env/prod/clj"]
:resource-paths ["env/prod/resources"]}
:dev [:project/dev :profiles/dev]
:test [:project/test :profiles/test]
:project/dev {:dependencies [[prone "1.1.1"]
[ring/ring-mock "0.3.0"]
[ring/ring-devel "1.5.0"]
[pjstadig/humane-test-output "0.8.1"]
[doo "0.1.7"]
[binaryage/devtools "0.8.1"]
[figwheel-sidecar "0.5.4-7"]
[com.cemerick/piggieback "0.2.2-SNAPSHOT"]]
:plugins [[com.jakemccrary/lein-test-refresh "0.14.0"]
[lein-doo "0.1.7"]
[lein-figwheel "0.5.4-7"]
[org.clojure/clojurescript "1.9.225"]]
:cljsbuild
{:builds
{:app
{:source-paths ["src/cljs" "src/cljc" "env/dev/cljs"]
:compiler
{:main "commiteth.app"
:asset-path "/js/out"
:output-to "target/cljsbuild/public/js/app.js"
:output-dir "target/cljsbuild/public/js/out"
:source-map true
:optimizations :none
:pretty-print true}}}}
:doo {:build "test"}
:source-paths ["env/dev/clj" "test/clj"]
:resource-paths ["env/dev/resources"]
:repl-options {:init-ns user}
:injections [(require 'pjstadig.humane-test-output)
(pjstadig.humane-test-output/activate!)]}
:project/test {:resource-paths ["env/dev/resources" "env/test/resources"]
:cljsbuild
{:builds
{:test
{:source-paths ["src/cljc" "src/cljs" "test/cljs"]
:compiler
{:output-to "target/test.js"
:main "commiteth.doo-runner"
:optimizations :whitespace
:pretty-print true}}}}
}
:profiles/dev {}
:profiles/test {}})

View File

View File

@ -0,0 +1,5 @@
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
height: 100%;
padding-top: 0px;
}

21
resources/sql/queries.sql Normal file
View 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 update 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 retrieve a user given the id.
SELECT * FROM users
WHERE id = :id
-- :name delete-user! :! :n
-- :doc delete a user given the id
DELETE FROM users
WHERE id = :id

View File

@ -0,0 +1,59 @@
<!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/bootstrap/css/bootstrap.min.css" %}
{% style "/assets/bootstrap/css/bootstrap-theme.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-fluid">
<div class="row-fluid">
<div class="col-lg-12">
<div class="centering text-center">
<div class="text-center">
<h1><span class="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>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Welcome to commiteth</title>
</head>
<body>
<div id="app">
<div class="container-fluid">
</div>
</div>
<!-- scripts and styles -->
{% style "/assets/bootstrap/css/bootstrap.min.css" %}
{% style "/assets/bootstrap-social/bootstrap-social.css" %}
{% style "/assets/font-awesome/css/font-awesome.min.css" %}
{% style "/css/screen.css" %}
<script type="text/javascript">
var context = "{{servlet-context}}";
var csrfToken = "{{csrf-token}}";
</script>
{% script "/js/app.js" %}
</body>
</html>

View File

@ -0,0 +1,10 @@
(ns commiteth.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)]))

View File

@ -0,0 +1,58 @@
(ns commiteth.core
(:require [commiteth.handler :as handler]
[luminus.repl-server :as repl]
[luminus.http-server :as http]
[luminus-migrations.core :as migrations]
[commiteth.config :refer [env]]
[clojure.tools.cli :refer [parse-opts]]
[clojure.tools.logging :as log]
[mount.core :as mount])
(:gen-class))
(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 :port #(or (-> env :options :port) %))))
:stop
(http/stop http-server))
(mount/defstate ^{:on-reload :noop}
repl-server
:start
(when-let [nrepl-port (env :nrepl-port)]
(repl/start {:port nrepl-port}))
:stop
(when repl-server
(repl/stop repl-server)))
(defn stop-app []
(doseq [component (:stopped (mount/stop))]
(log/info component "stopped"))
(shutdown-agents))
(defn start-app [args]
(doseq [component (-> args
(parse-opts cli-options)
mount/start-with-args
:started)]
(log/info component "started"))
(.addShutdownHook (Runtime/getRuntime) (Thread. stop-app)))
(defn -main [& args]
(cond
(some #{"migrate" "rollback"} args)
(do
(mount/start #'commiteth.config/env)
(migrations/migrate args (select-keys env [:database-url]))
(System/exit 0))
:else
(start-app args)))

View File

@ -0,0 +1,71 @@
(ns commiteth.db.core
(:require
[cheshire.core :refer [generate-string parse-string]]
[clojure.java.jdbc :as jdbc]
[conman.core :as conman]
[commiteth.config :refer [env]]
[mount.core :refer [defstate]])
(:import org.postgresql.util.PGobject
java.sql.Array
clojure.lang.IPersistentMap
clojure.lang.IPersistentVector
[java.sql
BatchUpdateException
Date
Timestamp
PreparedStatement]))
(defstate ^:dynamic *db*
:start (conman/connect! {:jdbc-url (env :database-url)})
:stop (conman/disconnect! *db*))
(conman/bind-connection *db* "sql/queries.sql")
(defn to-date [^java.sql.Date sql-date]
(-> sql-date (.getTime) (java.util.Date.)))
(extend-protocol jdbc/IResultSetReadColumn
Date
(result-set-read-column [v _ _] (to-date v))
Timestamp
(result-set-read-column [v _ _] (to-date 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))))
(extend-type java.util.Date
jdbc/ISQLParameter
(set-parameter [v ^PreparedStatement stmt ^long idx]
(.setTimestamp stmt idx (Timestamp. (.getTime v)))))
(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
IPersistentMap
(sql-value [value] (to-pg-json value))
IPersistentVector
(sql-value [value] (to-pg-json value)))

View File

@ -0,0 +1,27 @@
(ns commiteth.handler
(:require [compojure.core :refer [routes wrap-routes]]
[commiteth.layout :refer [error-page]]
[commiteth.routes.home :refer [home-routes]]
[commiteth.routes.services :refer [service-routes]]
[compojure.route :as route]
[commiteth.env :refer [defaults]]
[mount.core :as mount]
[commiteth.middleware :as middleware]))
(mount/defstate init-app
:start ((or (:init defaults) identity))
:stop ((or (:stop defaults) identity)))
(def app-routes
(routes
(-> #'home-routes
(wrap-routes middleware/wrap-csrf)
(wrap-routes middleware/wrap-formats))
#'service-routes
(route/not-found
(:body
(error-page {:status 404
:title "page not found"})))))
(defn app [] (middleware/wrap-base #'app-routes))

View File

@ -0,0 +1,39 @@
(ns commiteth.layout
(:require [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*]]))
(declare ^:dynamic *identity*)
(declare ^:dynamic *app-context*)
(parser/set-resource-path! (clojure.java.io/resource "templates"))
(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/templates"
[template & [params]]
(content-type
(ok
(parser/render-file
template
(assoc params
:page template
:csrf-token *anti-forgery-token*
:servlet-context *app-context*)))
"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)})

View File

@ -0,0 +1,93 @@
(ns commiteth.middleware
(:require [commiteth.env :refer [defaults]]
[clojure.tools.logging :as log]
[commiteth.layout :refer [*app-context* error-page]]
[ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
[ring.middleware.webjars :refer [wrap-webjars]]
[ring.middleware.format :refer [wrap-restful-format]]
[commiteth.config :refer [env]]
[ring.middleware.flash :refer [wrap-flash]]
[immutant.web.middleware :refer [wrap-session]]
[ring.middleware.defaults :refer [site-defaults wrap-defaults]]
[buddy.auth.middleware :refer [wrap-authentication wrap-authorization]]
[buddy.auth.backends.session :refer [session-backend]]
[buddy.auth.accessrules :refer [restrict]]
[buddy.auth :refer [authenticated?]]
[commiteth.layout :refer [*identity*]])
(:import [javax.servlet ServletContext]))
(defn wrap-context [handler]
(fn [request]
(binding [*app-context*
(if-let [context (:servlet-context request)]
;; If we're not inside a servlet environment
;; (for example when using mock requests), then
;; .getContextPath might not exist
(try (.getContextPath ^ServletContext context)
(catch IllegalArgumentException _ context))
;; if the context is not specified in the request
;; we check if one has been specified in the environment
;; instead
(:app-context env))]
(handler request))))
(defn wrap-internal-error [handler]
(fn [req]
(try
(handler req)
(catch Throwable t
(log/error 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 (wrap-restful-format
handler
{:formats [:json-kw :transit-json :transit-msgpack]})]
(fn [request]
;; disable wrap-formats for websockets
;; since they're not compatible with this middleware
((if (:websocket? request) handler wrapped) request))))
(defn on-error [request response]
(error-page
{:status 403
:title (str "Access to " (:uri request) " is not authorized")}))
(defn wrap-restricted [handler]
(restrict handler {:handler authenticated?
:on-error on-error}))
(defn wrap-identity [handler]
(fn [request]
(binding [*identity* (get-in request [:session :identity])]
(handler request))))
(defn wrap-auth [handler]
(let [backend (session-backend)]
(-> handler
wrap-identity
(wrap-authentication backend)
(wrap-authorization backend))))
(defn wrap-base [handler]
(-> ((:middleware defaults) handler)
wrap-auth
wrap-webjars
wrap-flash
(wrap-session {:cookie-attrs {:http-only true}})
(wrap-defaults
(-> site-defaults
(assoc-in [:security :anti-forgery] false)
(dissoc :session)))
wrap-context
wrap-internal-error))

View File

@ -0,0 +1,14 @@
(ns commiteth.routes.home
(:require [commiteth.layout :as layout]
[compojure.core :refer [defroutes GET]]
[ring.util.http-response :as response]
[clojure.java.io :as io]))
(defn home-page []
(layout/render "home.html"))
(defroutes home-routes
(GET "/" [] (home-page))
(GET "/docs" [] (-> (response/ok (-> "docs/docs.md" io/resource slurp))
(response/header "Content-Type" "text/plain; charset=utf-8"))))

View File

@ -0,0 +1,66 @@
(ns commiteth.routes.services
(:require [ring.util.http-response :refer :all]
[compojure.api.sweet :refer :all]
[schema.core :as s]
[compojure.api.meta :refer [restructure-param]]
[buddy.auth.accessrules :refer [restrict]]
[buddy.auth :refer [authenticated?]]))
(defn access-error [_ _]
(unauthorized {:error "unauthorized"}))
(defn wrap-restricted [handler rule]
(restrict handler {:handler rule
:on-error access-error}))
(defmethod restructure-param :auth-rules
[_ rule acc]
(update-in acc [:middleware] conj [wrap-restricted rule]))
(defmethod restructure-param :current-user
[_ binding acc]
(update-in acc [:letks] into [binding `(:identity ~'+compojure-api-request+)]))
(defapi service-routes
{:swagger {:ui "/swagger-ui"
:spec "/swagger.json"
:data {:info {:version "1.0.0"
:title "Sample API"
:description "Sample Services"}}}}
(GET "/authenticated" []
:auth-rules authenticated?
:current-user user
(ok {:user user}))
(context "/api" []
:tags ["thingie"]
(GET "/plus" []
:return Long
:query-params [x :- Long, {y :- Long 1}]
:summary "x+y with query-parameters. y defaults to 1."
(ok (+ x y)))
(POST "/minus" []
:return Long
:body-params [x :- Long, y :- Long]
:summary "x-y with body-parameters."
(ok (- x y)))
(GET "/times/:x/:y" []
:return Long
:path-params [x :- Long, y :- Long]
:summary "x*y with path-parameters"
(ok (* x y)))
(POST "/divide" []
:return Double
:form-params [x :- Long, y :- Long]
:summary "x/y with form-parameters"
(ok (/ x y)))
(GET "/power" []
:return Long
:header-params [x :- Long, y :- Long]
:summary "x^y with header-parameters"
(ok (long (Math/pow x y))))))

View File

@ -0,0 +1,3 @@
(ns commiteth.validation
(:require [bouncer.core :as b]
[bouncer.validators :as v]))

View File

@ -0,0 +1,18 @@
(ns commiteth.ajax
(:require [ajax.core :as ajax]))
(defn default-headers [request]
(-> request
(update :uri #(str js/context %))
(update
:headers
#(merge
%
{"Accept" "application/transit+json"
"x-csrf-token" js/csrfToken}))))
(defn load-interceptors! []
(swap! ajax/default-interceptors
conj
(ajax/to-interceptor {:name "default headers"
:request default-headers})))

View File

@ -0,0 +1,76 @@
(ns commiteth.core
(:require [reagent.core :as r]
[re-frame.core :as rf]
[secretary.core :as secretary]
[goog.events :as events]
[goog.history.EventType :as HistoryEventType]
[markdown.core :refer [md->html]]
[ajax.core :refer [GET POST]]
[commiteth.ajax :refer [load-interceptors!]]
[commiteth.handlers]
[commiteth.subscriptions])
(:import goog.History))
(defn nav-link [uri title page collapsed?]
(let [selected-page (rf/subscribe [:page])] [:li.nav-item
{:class (when (= page @selected-page) "active")}
[:a.nav-link
{:href uri
:on-click #(reset! collapsed? true)} title]]))
(defn navbar []
(r/with-let [collapsed? (r/atom true)]
[:nav.navbar.navbar-light.bg-faded
[:button.navbar-toggler.hidden-sm-up
{:on-click #(swap! collapsed? not)} "☰"]
[:div.collapse.navbar-toggleable-xs
(when-not @collapsed? {:class "in"})
[:a.navbar-brand {:href "#/"} "commiteth"]
[:ul.nav.navbar-nav
[nav-link "#/" "Home" :home collapsed?]]]]))
(defn home-page []
[:div.container
[:div.jumbotron
[:h1 "Welcome to commitETH"]
[:p [:a.btn.btn-block.btn-social.btn-github
{:href "http://github.com"}
[:i.fa.fa-github]
"Sign in with GitHub"]]]])
(def pages
{:home #'home-page})
(defn page []
[:div
[navbar]
[(pages @(rf/subscribe [:page]))]])
;; -------------------------
;; Routes
(secretary/set-config! :prefix "#")
(secretary/defroute "/" []
(rf/dispatch [:set-active-page :home]))
;; -------------------------
;; History
;; must be called after routes have been defined
(defn hook-browser-navigation! []
(doto (History.)
(events/listen
HistoryEventType/NAVIGATE
(fn [event]
(secretary/dispatch! (.-token event))))
(.setEnabled true)))
;; -------------------------
;; Initialize app
(defn mount-components []
(r/render [#'page] (.getElementById js/document "app")))
(defn init! []
(rf/dispatch-sync [:initialize-db])
(load-interceptors!)
(hook-browser-navigation!)
(mount-components))

View File

@ -0,0 +1,4 @@
(ns commiteth.db)
(def default-db
{:page :home})

View File

@ -0,0 +1,13 @@
(ns commiteth.handlers
(:require [commiteth.db :as db]
[re-frame.core :refer [dispatch reg-event-db]]))
(reg-event-db
:initialize-db
(fn [_ _]
db/default-db))
(reg-event-db
:set-active-page
(fn [db [_ page]]
(assoc db :page page)))

View File

@ -0,0 +1,12 @@
(ns commiteth.subscriptions
(:require [re-frame.core :refer [reg-sub]]))
(reg-sub
:page
(fn [db _]
(:page db)))
(reg-sub
:docs
(fn [db _]
(:docs db)))

View File

@ -0,0 +1,36 @@
(ns commiteth.test.db.core
(:require [commiteth.db.core :refer [*db*] :as db]
[luminus-migrations.core :as migrations]
[clojure.test :refer :all]
[clojure.java.jdbc :as jdbc]
[commiteth.config :refer [env]]
[mount.core :as mount]))
(use-fixtures
:once
(fn [f]
(mount/start
#'commiteth.config/env
#'commiteth.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"})))))

View File

@ -0,0 +1,13 @@
(ns commiteth.test.handler
(:require [clojure.test :refer :all]
[ring.mock.request :refer :all]
[commiteth.handler :refer :all]))
(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))))))

View File

@ -0,0 +1,8 @@
(ns commiteth.core-test
(:require [cljs.test :refer-macros [is are deftest testing use-fixtures]]
[reagent.core :as reagent :refer [atom]]
[commiteth.core :as rc]))
(deftest test-home
(is (= true true)))

View File

@ -0,0 +1,6 @@
(ns commiteth.doo-runner
(:require [doo.runner :refer-macros [doo-tests]]
[commiteth.core-test]))
(doo-tests 'commiteth.core-test)