Initial creation of Luminus project
This commit is contained in:
parent
572197ac2a
commit
6448bd979a
|
@ -12,3 +12,5 @@ pom.xml.asc
|
|||
.lein-failures
|
||||
.nrepl-port
|
||||
.cpcache/
|
||||
.log
|
||||
log
|
||||
|
|
|
@ -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 /shapey-shifty/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:
|
||||
/shapey-shifty/app.jar: ./target/uberjar/shapey-shifty.jar
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
FROM openjdk:8-alpine
|
||||
|
||||
COPY target/uberjar/shapey-shifty.jar /shapey-shifty/app.jar
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["java", "-jar", "/shapey-shifty/app.jar"]
|
|
@ -0,0 +1 @@
|
|||
web: java -cp target/uberjar/shapey-shifty.jar clojure.main -m shapey-shifty.core
|
23
README.md
23
README.md
|
@ -1,2 +1,21 @@
|
|||
# ShapeyShifty
|
||||
Personal CRM for the IndieWeb
|
||||
# shapey-shifty
|
||||
|
||||
generated using Luminus version "3.55"
|
||||
|
||||
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 © 2020 FIXME
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
;; 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
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
(ns shapey-shifty.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 ['shapey-shifty]})))
|
|
@ -0,0 +1,15 @@
|
|||
(ns shapey-shifty.env
|
||||
(:require
|
||||
[selmer.parser :as parser]
|
||||
[clojure.tools.logging :as log]
|
||||
[shapey-shifty.dev-middleware :refer [wrap-dev]]))
|
||||
|
||||
(def defaults
|
||||
{:init
|
||||
(fn []
|
||||
(parser/cache-off!)
|
||||
(log/info "\n-=[shapey-shifty started successfully using the development profile]=-"))
|
||||
:stop
|
||||
(fn []
|
||||
(log/info "\n-=[shapey-shifty has shut down successfully]=-"))
|
||||
:middleware wrap-dev})
|
|
@ -0,0 +1,32 @@
|
|||
(ns user
|
||||
"Userspace functions you can run by default in your local REPL."
|
||||
(:require
|
||||
[shapey-shifty.config :refer [env]]
|
||||
[clojure.pprint]
|
||||
[clojure.spec.alpha :as s]
|
||||
[expound.alpha :as expound]
|
||||
[mount.core :as mount]
|
||||
[shapey-shifty.core :refer [start-app]]))
|
||||
|
||||
(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 #'shapey-shifty.core/repl-server))
|
||||
|
||||
(defn stop
|
||||
"Stops application."
|
||||
[]
|
||||
(mount/stop-except #'shapey-shifty.core/repl-server))
|
||||
|
||||
(defn restart
|
||||
"Restarts application."
|
||||
[]
|
||||
(stop)
|
||||
(start))
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -0,0 +1,34 @@
|
|||
<?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/shapey-shifty.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>log/shapey-shifty.%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="org.eclipse.jetty" level="warn" />
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
</configuration>
|
|
@ -0,0 +1,11 @@
|
|||
(ns shapey-shifty.env
|
||||
(:require [clojure.tools.logging :as log]))
|
||||
|
||||
(def defaults
|
||||
{:init
|
||||
(fn []
|
||||
(log/info "\n-=[shapey-shifty started successfully]=-"))
|
||||
:stop
|
||||
(fn []
|
||||
(log/info "\n-=[shapey-shifty has shut down successfully]=-"))
|
||||
:middleware identity})
|
|
@ -0,0 +1,2 @@
|
|||
{:prod true
|
||||
:port 3000}
|
|
@ -0,0 +1,25 @@
|
|||
<?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/shapey-shifty.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>log/shapey-shifty.%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="org.eclipse.jetty" level="warn" />
|
||||
<root level="INFO">
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
</configuration>
|
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -0,0 +1,34 @@
|
|||
<?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/shapey-shifty.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>log/shapey-shifty.%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="org.eclipse.jetty" level="warn" />
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
</configuration>
|
|
@ -0,0 +1,68 @@
|
|||
(defproject shapey-shifty "0.1.0-SNAPSHOT"
|
||||
|
||||
:description "FIXME: write description"
|
||||
:url "http://example.com/FIXME"
|
||||
|
||||
:dependencies [[ch.qos.logback/logback-classic "1.2.3"]
|
||||
[cheshire "5.9.0"]
|
||||
[clojure.java-time "0.3.2"]
|
||||
[cprop "0.1.15"]
|
||||
[expound "0.8.3"]
|
||||
[funcool/struct "1.4.0"]
|
||||
[luminus-jetty "0.1.7"]
|
||||
[luminus-transit "0.1.2"]
|
||||
[luminus/ring-ttl-session "0.3.3"]
|
||||
[markdown-clj "1.10.1"]
|
||||
[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/tools.cli "0.4.2"]
|
||||
[org.clojure/tools.logging "0.5.0"]
|
||||
[org.webjars.npm/bulma "0.8.0"]
|
||||
[org.webjars.npm/material-icons "0.3.1"]
|
||||
[org.webjars/webjars-locator "0.38"]
|
||||
[ring-webjars "0.2.0"]
|
||||
[ring/ring-core "1.8.0"]
|
||||
[ring/ring-defaults "0.3.2"]
|
||||
[selmer "1.12.18"]]
|
||||
|
||||
:min-lein-version "2.0.0"
|
||||
|
||||
:source-paths ["src/clj"]
|
||||
:test-paths ["test/clj"]
|
||||
:resource-paths ["resources"]
|
||||
:target-path "target/%s/"
|
||||
:main ^:skip-aot shapey-shifty.core
|
||||
|
||||
:plugins []
|
||||
|
||||
:profiles
|
||||
{:uberjar {:omit-source true
|
||||
:aot :all
|
||||
:uberjar-name "shapey-shifty.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 [[pjstadig/humane-test-output "0.10.0"]
|
||||
[prone "2019-07-08"]
|
||||
[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"]]
|
||||
|
||||
: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"]}
|
||||
:profiles/dev {}
|
||||
:profiles/test {}})
|
|
@ -0,0 +1,100 @@
|
|||
<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 `shapey-shifty.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}]
|
||||
["/about" {:get about-page}]])
|
||||
```
|
||||
|
||||
The `home-routes` are wrapped with two middleware functions. The first enables CSRF protection.
|
||||
The second takes care of serializing and deserializing various encoding formats, such as JSON.
|
||||
|
||||
The `home-page` function will in turn call the `shapey-shifty.layout/render` function
|
||||
to render the HTML content:
|
||||
|
||||
```
|
||||
(defn home-page [_]
|
||||
(layout/render
|
||||
"home.html" {:docs (-> "docs/docs.md" io/resource slurp)}))
|
||||
```
|
||||
|
||||
The `render` function will render the `home.html` template found in the `resources/templates`
|
||||
folder using a parameter map containing the `:docs` key. This key points to the
|
||||
contents of the `resources/docs/docs.md` file containing these instructions.
|
||||
|
||||
|
||||
The HTML templates are written using [Selmer](https://github.com/yogthos/Selmer) templating engine.
|
||||
|
||||
|
||||
```
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{docs|markdown}}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
<a class="btn btn-primary" href="http://www.luminusweb.net/docs/html_templating.md">learn more about HTML templating »</a>
|
||||
|
||||
|
||||
|
||||
#### Organizing the routes
|
||||
|
||||
The routes are aggregated and wrapped with middleware in the `shapey-shifty.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 `shapey-shifty.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 `shapey-shifty.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>
|
||||
|
||||
|
||||
|
||||
|
||||
#### 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.
|
|
@ -0,0 +1,4 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<img src="/img/warning_clojure.png"></img>
|
||||
{% endblock %}
|
|
@ -0,0 +1,58 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Welcome to shapey-shifty</title>
|
||||
|
||||
<!-- styles -->
|
||||
{% style "/assets/bulma/css/bulma.min.css" %}
|
||||
{% style "/assets/material-icons/css/material-icons.min.css" %}
|
||||
|
||||
{% style "/css/screen.css" %}
|
||||
</head>
|
||||
<body>
|
||||
<!-- navbar -->
|
||||
<nav class="navbar is-info">
|
||||
<div class="container">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/" style="font-weight:bold;">shapey-shifty</a>
|
||||
<span class="navbar-burger burger" data-target="nav-menu">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</span>
|
||||
</div>
|
||||
<div id="nav-menu" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
<a href="/" class="navbar-item{% ifequal page "home.html" %} is-active{%endifequal%}">Home</a>
|
||||
<a href="/about" class="navbar-item{% ifequal page "about.html" %} is-active{%endifequal%}">About</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- scripts -->
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var burger = document.querySelector('.burger');
|
||||
var nav = document.querySelector('#'+burger.dataset.target);
|
||||
burger.addEventListener('click', function(){
|
||||
burger.classList.toggle('is-active');
|
||||
nav.classList.toggle('is-active');
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
{% block page-scripts %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
|
@ -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>
|
|
@ -0,0 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class="content">
|
||||
{{docs|markdown}}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -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;
|
||||
}
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
|
@ -0,0 +1,13 @@
|
|||
(ns shapey-shifty.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)]))
|
|
@ -0,0 +1,58 @@
|
|||
(ns shapey-shifty.core
|
||||
(:require
|
||||
[shapey-shifty.handler :as handler]
|
||||
[shapey-shifty.nrepl :as nrepl]
|
||||
[luminus.http-server :as http]
|
||||
[shapey-shifty.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 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]
|
||||
(start-app args))
|
|
@ -0,0 +1,35 @@
|
|||
(ns shapey-shifty.handler
|
||||
(:require
|
||||
[shapey-shifty.middleware :as middleware]
|
||||
[shapey-shifty.layout :refer [error-page]]
|
||||
[shapey-shifty.routes.home :refer [home-routes]]
|
||||
[reitit.ring :as ring]
|
||||
[ring.middleware.content-type :refer [wrap-content-type]]
|
||||
[ring.middleware.webjars :refer [wrap-webjars]]
|
||||
[shapey-shifty.env :refer [defaults]]
|
||||
[mount.core :as mount]))
|
||||
|
||||
(mount/defstate init-app
|
||||
:start ((or (:init defaults) (fn [])))
|
||||
:stop ((or (:stop defaults) (fn []))))
|
||||
|
||||
(mount/defstate app-routes
|
||||
:start
|
||||
(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"}))}))))
|
||||
|
||||
(defn app []
|
||||
(middleware/wrap-base #'app-routes))
|
|
@ -0,0 +1,39 @@
|
|||
(ns shapey-shifty.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)})
|
|
@ -0,0 +1,49 @@
|
|||
(ns shapey-shifty.middleware
|
||||
(:require
|
||||
[shapey-shifty.env :refer [defaults]]
|
||||
[cheshire.generate :as cheshire]
|
||||
[cognitect.transit :as transit]
|
||||
[clojure.tools.logging :as log]
|
||||
[shapey-shifty.layout :refer [error-page]]
|
||||
[ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
|
||||
[shapey-shifty.middleware.formats :as formats]
|
||||
[muuntaja.middleware :refer [wrap-format wrap-params]]
|
||||
[shapey-shifty.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))
|
|
@ -0,0 +1,15 @@
|
|||
(ns shapey-shifty.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)))))
|
|
@ -0,0 +1,27 @@
|
|||
(ns shapey-shifty.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"))
|
|
@ -0,0 +1,21 @@
|
|||
(ns shapey-shifty.routes.home
|
||||
(:require
|
||||
[shapey-shifty.layout :as layout]
|
||||
[clojure.java.io :as io]
|
||||
[shapey-shifty.middleware :as middleware]
|
||||
[ring.util.response]
|
||||
[ring.util.http-response :as response]))
|
||||
|
||||
(defn home-page [request]
|
||||
(layout/render request "home.html" {:docs (-> "docs/docs.md" io/resource slurp)}))
|
||||
|
||||
(defn about-page [request]
|
||||
(layout/render request "about.html"))
|
||||
|
||||
(defn home-routes []
|
||||
[""
|
||||
{:middleware [middleware/wrap-csrf
|
||||
middleware/wrap-formats]}
|
||||
["/" {:get home-page}]
|
||||
["/about" {:get about-page}]])
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
;; 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
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
(ns shapey-shifty.test.handler
|
||||
(:require
|
||||
[clojure.test :refer :all]
|
||||
[ring.mock.request :refer :all]
|
||||
[shapey-shifty.handler :refer :all]
|
||||
[shapey-shifty.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 #'shapey-shifty.config/env
|
||||
#'shapey-shifty.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))))))
|
Loading…
Reference in New Issue