Switched to duct
This commit is contained in:
parent
172201fab3
commit
20c4ef9315
55 changed files with 174 additions and 1709 deletions
.gitignoreCapstanfileDockerfileProcfileREADME.mdproject.clj
dev
env
dev
prod
test/resources
resources
docs
graphql
html
migrations
pinkblackrose
public
scss
sql
src
clj/pinkblackrose
cljc/pinkblackrose
cljs/pinkblackrose
duct_hierarchy.ednpinkblackrose
test
18
.gitignore
vendored
18
.gitignore
vendored
|
@ -1,18 +1,14 @@
|
|||
/target
|
||||
/lib
|
||||
/logs
|
||||
/classes
|
||||
/checkouts
|
||||
pom.xml
|
||||
dev-config.edn
|
||||
test-config.edn
|
||||
pom.xml.asc
|
||||
*.jar
|
||||
*.class
|
||||
/.lein-*
|
||||
profiles.clj
|
||||
/.env
|
||||
.nrepl-port
|
||||
/.shadow-cljs
|
||||
package.json
|
||||
shadow-cljs.edn
|
||||
/node_modules
|
||||
/log
|
||||
/.nrepl-port
|
||||
/.dir-locals.el
|
||||
/profiles.clj
|
||||
/dev/resources/local.edn
|
||||
/dev/src/local.clj
|
||||
|
|
28
Capstanfile
28
Capstanfile
|
@ -1,28 +0,0 @@
|
|||
|
||||
#
|
||||
# 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 /pinkblackrose/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:
|
||||
/pinkblackrose/app.jar: ./target/uberjar/pinkblackrose.jar
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
FROM openjdk:8-alpine
|
||||
|
||||
COPY target/uberjar/pinkblackrose.jar /pinkblackrose/app.jar
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["java", "-jar", "/pinkblackrose/app.jar"]
|
1
Procfile
1
Procfile
|
@ -1 +0,0 @@
|
|||
web: java -cp target/uberjar/pinkblackrose.jar clojure.main -m pinkblackrose.core
|
79
README.md
79
README.md
|
@ -1,21 +1,82 @@
|
|||
# pinkblackrose
|
||||
|
||||
generated using Luminus version "3.57"
|
||||
FIXME: description
|
||||
|
||||
FIXME
|
||||
## Developing
|
||||
|
||||
## Prerequisites
|
||||
### Setup
|
||||
|
||||
You will need [Leiningen][1] 2.0 or above installed.
|
||||
When you first clone this repository, run:
|
||||
|
||||
[1]: https://github.com/technomancy/leiningen
|
||||
```sh
|
||||
lein duct setup
|
||||
```
|
||||
|
||||
## Running
|
||||
This will create files for local configuration, and prep your system
|
||||
for the project.
|
||||
|
||||
To start a web server for the application, run:
|
||||
### Environment
|
||||
|
||||
lein run
|
||||
To begin developing, start with a REPL.
|
||||
|
||||
## License
|
||||
```sh
|
||||
lein repl
|
||||
```
|
||||
|
||||
Then load the development environment.
|
||||
|
||||
```clojure
|
||||
user=> (dev)
|
||||
:loaded
|
||||
```
|
||||
|
||||
Run `go` to prep and initiate the system.
|
||||
|
||||
```clojure
|
||||
dev=> (go)
|
||||
:duct.server.http.jetty/starting-server {:port 3000}
|
||||
:initiated
|
||||
```
|
||||
|
||||
By default this creates a web server at <http://localhost:3000>.
|
||||
|
||||
When you make changes to your source files, use `reset` to reload any
|
||||
modified files and reset the server. Changes to CSS or ClojureScript
|
||||
files will be hot-loaded into the browser.
|
||||
|
||||
```clojure
|
||||
dev=> (reset)
|
||||
:reloading (...)
|
||||
:resumed
|
||||
```
|
||||
|
||||
If you want to access a ClojureScript REPL, make sure that the site is loaded
|
||||
in a browser and run:
|
||||
|
||||
```clojure
|
||||
dev=> (cljs-repl)
|
||||
Waiting for browser connection... Connected.
|
||||
To quit, type: :cljs/quit
|
||||
nil
|
||||
cljs.user=>
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
Testing is fastest through the REPL, as you avoid environment startup
|
||||
time.
|
||||
|
||||
```clojure
|
||||
dev=> (test)
|
||||
...
|
||||
```
|
||||
|
||||
But you can also run tests through Leiningen.
|
||||
|
||||
```sh
|
||||
lein test
|
||||
```
|
||||
|
||||
## Legal
|
||||
|
||||
Copyright © 2020 FIXME
|
||||
|
|
3
dev/resources/dev.edn
Normal file
3
dev/resources/dev.edn
Normal file
|
@ -0,0 +1,3 @@
|
|||
{:duct.database/sql
|
||||
{:connection-uri "jdbc:postgresql://localhost/postgres"}
|
||||
}
|
31
dev/src/dev.clj
Normal file
31
dev/src/dev.clj
Normal file
|
@ -0,0 +1,31 @@
|
|||
(ns dev
|
||||
(:refer-clojure :exclude [test])
|
||||
(:require [clojure.repl :refer :all]
|
||||
[fipp.edn :refer [pprint]]
|
||||
[clojure.tools.namespace.repl :refer [refresh]]
|
||||
[clojure.java.io :as io]
|
||||
[duct.core :as duct]
|
||||
[duct.core.repl :as duct-repl]
|
||||
[duct.repl.figwheel :refer [cljs-repl]]
|
||||
[eftest.runner :as eftest]
|
||||
[integrant.core :as ig]
|
||||
[integrant.repl :refer [clear halt go init prep reset]]
|
||||
[integrant.repl.state :refer [config system]]))
|
||||
|
||||
(duct/load-hierarchy)
|
||||
|
||||
(defn read-config []
|
||||
(duct/read-config (io/resource "pinkblackrose/config.edn")))
|
||||
|
||||
(defn test []
|
||||
(eftest/run-tests (eftest/find-tests "test")))
|
||||
|
||||
(def profiles
|
||||
[:duct.profile/dev :duct.profile/local])
|
||||
|
||||
(clojure.tools.namespace.repl/set-refresh-dirs "dev/src" "src" "test")
|
||||
|
||||
(when (io/resource "local.clj")
|
||||
(load "local"))
|
||||
|
||||
(integrant.repl/set-prep! #(duct/prep-config (read-config) profiles))
|
8
dev/src/user.clj
Normal file
8
dev/src/user.clj
Normal file
|
@ -0,0 +1,8 @@
|
|||
(ns user)
|
||||
|
||||
(defn dev
|
||||
"Load and switch to the 'dev' namespace."
|
||||
[]
|
||||
(require 'dev)
|
||||
(in-ns 'dev)
|
||||
:loaded)
|
11
env/dev/clj/pinkblackrose/dev_middleware.clj
vendored
11
env/dev/clj/pinkblackrose/dev_middleware.clj
vendored
|
@ -1,11 +0,0 @@
|
|||
(ns pinkblackrose.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 ['pinkblackrose]})))
|
15
env/dev/clj/pinkblackrose/env.clj
vendored
15
env/dev/clj/pinkblackrose/env.clj
vendored
|
@ -1,15 +0,0 @@
|
|||
(ns pinkblackrose.env
|
||||
(:require
|
||||
[selmer.parser :as parser]
|
||||
[clojure.tools.logging :as log]
|
||||
[pinkblackrose.dev-middleware :refer [wrap-dev]]))
|
||||
|
||||
(def defaults
|
||||
{:init
|
||||
(fn []
|
||||
(parser/cache-off!)
|
||||
(log/info "\n-=[pinkblackrose started successfully using the development profile]=-"))
|
||||
:stop
|
||||
(fn []
|
||||
(log/info "\n-=[pinkblackrose has shut down successfully]=-"))
|
||||
:middleware wrap-dev})
|
63
env/dev/clj/user.clj
vendored
63
env/dev/clj/user.clj
vendored
|
@ -1,63 +0,0 @@
|
|||
(ns user
|
||||
"Userspace functions you can run by default in your local REPL."
|
||||
(:require
|
||||
[pinkblackrose.config :refer [env]]
|
||||
[clojure.pprint]
|
||||
[clojure.spec.alpha :as s]
|
||||
[expound.alpha :as expound]
|
||||
[mount.core :as mount]
|
||||
[pinkblackrose.core :refer [start-app]]
|
||||
[pinkblackrose.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 #'pinkblackrose.core/repl-server))
|
||||
|
||||
(defn stop
|
||||
"Stops application."
|
||||
[]
|
||||
(mount/stop-except #'pinkblackrose.core/repl-server))
|
||||
|
||||
(defn restart
|
||||
"Restarts application."
|
||||
[]
|
||||
(stop)
|
||||
(start))
|
||||
|
||||
(defn restart-db
|
||||
"Restarts database."
|
||||
[]
|
||||
(mount/stop #'pinkblackrose.db.core/*db*)
|
||||
(mount/start #'pinkblackrose.db.core/*db*)
|
||||
(binding [*ns* 'pinkblackrose.db.core]
|
||||
(conman/bind-connection pinkblackrose.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/pinkblackrose/app.cljs
vendored
19
env/dev/cljs/pinkblackrose/app.cljs
vendored
|
@ -1,19 +0,0 @@
|
|||
(ns ^:dev/once pinkblackrose.app
|
||||
(:require
|
||||
[pinkblackrose.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!)
|
35
env/dev/resources/logback.xml
vendored
35
env/dev/resources/logback.xml
vendored
|
@ -1,35 +0,0 @@
|
|||
<?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/pinkblackrose.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>log/pinkblackrose.%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="io.netty" level="warn" />
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
</configuration>
|
11
env/prod/clj/pinkblackrose/env.clj
vendored
11
env/prod/clj/pinkblackrose/env.clj
vendored
|
@ -1,11 +0,0 @@
|
|||
(ns pinkblackrose.env
|
||||
(:require [clojure.tools.logging :as log]))
|
||||
|
||||
(def defaults
|
||||
{:init
|
||||
(fn []
|
||||
(log/info "\n-=[pinkblackrose started successfully]=-"))
|
||||
:stop
|
||||
(fn []
|
||||
(log/info "\n-=[pinkblackrose has shut down successfully]=-"))
|
||||
:middleware identity})
|
7
env/prod/cljs/pinkblackrose/app.cljs
vendored
7
env/prod/cljs/pinkblackrose/app.cljs
vendored
|
@ -1,7 +0,0 @@
|
|||
(ns pinkblackrose.app
|
||||
(:require [pinkblackrose.core :as core]))
|
||||
|
||||
;;ignore println statements in prod
|
||||
(set! *print-fn* (fn [& _]))
|
||||
|
||||
(core/init!)
|
2
env/prod/resources/config.edn
vendored
2
env/prod/resources/config.edn
vendored
|
@ -1,2 +0,0 @@
|
|||
{:prod true
|
||||
:port 3000}
|
26
env/prod/resources/logback.xml
vendored
26
env/prod/resources/logback.xml
vendored
|
@ -1,26 +0,0 @@
|
|||
<?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/pinkblackrose.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>log/pinkblackrose.%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="io.netty" level="warn" />
|
||||
<root level="INFO">
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
</configuration>
|
1
env/test/resources/config.edn
vendored
1
env/test/resources/config.edn
vendored
|
@ -1 +0,0 @@
|
|||
{}
|
35
env/test/resources/logback.xml
vendored
35
env/test/resources/logback.xml
vendored
|
@ -1,35 +0,0 @@
|
|||
<?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/pinkblackrose.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>log/pinkblackrose.%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="io.netty" level="warn" />
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
</configuration>
|
148
project.clj
148
project.clj
|
@ -1,132 +1,28 @@
|
|||
(defproject pinkblackrose "0.1.0-SNAPSHOT"
|
||||
|
||||
:description "FIXME: write description"
|
||||
:url "http://example.com/FIXME"
|
||||
|
||||
:dependencies [[buddy/buddy-auth "2.2.0"]
|
||||
[buddy/buddy-core "1.6.0"]
|
||||
[buddy/buddy-hashers "1.4.0"]
|
||||
[buddy/buddy-sign "3.1.0"]
|
||||
[ch.qos.logback/logback-classic "1.2.3"]
|
||||
[cheshire "5.9.0"]
|
||||
[cljs-ajax "0.8.0"]
|
||||
[clojure.java-time "0.3.2"]
|
||||
[com.cognitect/transit-clj "0.8.319"]
|
||||
[com.google.javascript/closure-compiler-unshaded "v20190618" :scope "provided"]
|
||||
[com.walmartlabs/lacinia "0.32.0"]
|
||||
[conman "0.8.4"]
|
||||
[cprop "0.1.15"]
|
||||
[day8.re-frame/http-fx "0.1.6"]
|
||||
[expound "0.8.3"]
|
||||
[funcool/struct "1.4.0"]
|
||||
[luminus-aleph "0.1.6"]
|
||||
[luminus-migrations "0.6.6"]
|
||||
[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/clojurescript "1.10.597" :scope "provided"]
|
||||
[org.clojure/core.async "0.4.500"]
|
||||
[org.clojure/data.json "0.2.6"]
|
||||
[org.clojure/google-closure-library "0.0-20190213-2033d5d9" :scope "provided"]
|
||||
[org.clojure/tools.cli "0.4.2"]
|
||||
[org.clojure/tools.logging "0.5.0"]
|
||||
[org.postgresql/postgresql "42.2.9"]
|
||||
[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"]
|
||||
[selmer "1.12.18"]
|
||||
[thheller/shadow-cljs "2.8.69" :scope "provided"]]
|
||||
|
||||
: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 pinkblackrose.core
|
||||
|
||||
:plugins [[lein-shadow "0.1.7"]
|
||||
[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 "target/cljsbuild"]
|
||||
:shadow-cljs
|
||||
{:nrepl {:port 7002}
|
||||
:builds
|
||||
{:app
|
||||
{:target :browser
|
||||
:output-dir "target/cljsbuild/public/js"
|
||||
:asset-path "/js"
|
||||
:modules {:app {:entries [pinkblackrose.app]}}
|
||||
:devtools
|
||||
{:watch-dir "resources/public" :preloads [re-frisk.preload]}
|
||||
:dev
|
||||
{:closure-defines {"re_frame.trace.trace_enabled_QMARK_" true}}}
|
||||
:test
|
||||
{:target :node-test
|
||||
:output-to "target/test/test.js"
|
||||
:autorun true}}}
|
||||
|
||||
:npm-deps [[shadow-cljs "2.8.69"]
|
||||
[create-react-class "15.6.3"]
|
||||
[react "16.8.6"]
|
||||
[react-dom "16.8.6"]]
|
||||
|
||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[duct/core "0.7.0"]
|
||||
[duct/module.cljs "0.4.1"]
|
||||
[duct/module.logging "0.4.0"]
|
||||
[duct/module.sql "0.5.0"]
|
||||
[duct/module.web "0.7.0"]
|
||||
[org.postgresql/postgresql "42.2.5"]]
|
||||
:plugins [[duct/lein-duct "0.12.1"]]
|
||||
:main ^:skip-aot pinkblackrose.main
|
||||
:resource-paths ["resources" "target/resources"]
|
||||
:prep-tasks ["javac" "compile" ["run" ":duct/compiler"]]
|
||||
:middleware [lein-duct.plugin/middleware]
|
||||
:profiles
|
||||
{:uberjar {:omit-source true
|
||||
:prep-tasks ["compile" ["shadow" "release" "app"]]
|
||||
|
||||
:aot :all
|
||||
:uberjar-name "pinkblackrose.jar"
|
||||
:source-paths ["env/prod/clj" "env/prod/cljs"]
|
||||
: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.11"]
|
||||
[cider/piggieback "0.4.2"]
|
||||
[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"]]
|
||||
|
||||
|
||||
:source-paths ["env/dev/clj" "env/dev/cljs" "test/cljs"]
|
||||
:resource-paths ["env/dev/resources"]
|
||||
:repl-options {:init-ns user
|
||||
:timeout 120000}
|
||||
:injections [(require 'pjstadig.humane-test-output)
|
||||
(pjstadig.humane-test-output/activate!)]}
|
||||
:project/test {:jvm-opts ["-Dconf=test-config.edn"]
|
||||
:resource-paths ["env/test/resources"]
|
||||
|
||||
|
||||
}
|
||||
{:dev [:project/dev :profiles/dev]
|
||||
:repl {:prep-tasks ^:replace ["javac" "compile"]
|
||||
:dependencies [[cider/piggieback "0.4.0"]]
|
||||
:repl-options {:init-ns user, :nrepl-middleware [cider.piggieback/wrap-cljs-repl]}}
|
||||
:uberjar {:aot :all}
|
||||
:profiles/dev {}
|
||||
:profiles/test {}})
|
||||
:project/dev {:source-paths ["dev/src"]
|
||||
:resource-paths ["dev/resources"]
|
||||
:dependencies [[integrant/repl "0.3.1"]
|
||||
[eftest "0.5.7"]
|
||||
[kerodon "0.9.0"]]}})
|
||||
|
|
|
@ -1,145 +0,0 @@
|
|||
<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 `pinkblackrose.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 `pinkblackrose.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/pinkblackrose/core.cljs` file.
|
||||
|
||||
|
||||
|
||||
#### Organizing the routes
|
||||
|
||||
The routes are aggregated and wrapped with middleware in the `pinkblackrose.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 `pinkblackrose.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 `pinkblackrose.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 `pinkblackrose.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>
|
||||
|
||||
<div class="bs-callout bs-callout-danger">
|
||||
|
||||
#### SassC libsass command-line compiler is required
|
||||
|
||||
You must have the SassC command-line compiler installed to use this feature.
|
||||
|
||||
Please follow the instructions at: <a href="http://github.com/sass/sassc">http://github.com/sass/sassc</a>
|
||||
to install the compiler for your platform.
|
||||
|
||||
#### Usage
|
||||
Compile your files once:
|
||||
```
|
||||
$ lein sassc once
|
||||
```
|
||||
|
||||
To delete all the files generated by lein-sassc:
|
||||
```
|
||||
$ lein sassc clean
|
||||
```
|
||||
|
||||
To recompile when any changes are made:
|
||||
|
||||
```
|
||||
$ lein auto sassc once
|
||||
```
|
||||
|
||||
#### Hooks
|
||||
The following hooks are supported by lein-sassc:
|
||||
```
|
||||
$ lein compile
|
||||
$ lein clean
|
||||
```
|
||||
|
||||
Because lein-sassc requires a binary to compile Sass, it often won't work on platforms like Heroku which compile the application on their servers. To get around this limitation, commit the generated CSS files and remove
|
||||
|
||||
```
|
||||
:hooks [leiningen.sassc]
|
||||
```
|
||||
|
||||
from project.clj.
|
||||
|
||||
</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.
|
|
@ -1,25 +0,0 @@
|
|||
{:enums
|
||||
{:episode
|
||||
{:description "The episodes of the original Star Wars trilogy."
|
||||
:values [:NEWHOPE :EMPIRE :JEDI]}}
|
||||
|
||||
:objects
|
||||
{:droid
|
||||
{:fields {:primary_functions {:type (list String)}
|
||||
:id {:type Int}
|
||||
:name {:type String}
|
||||
:appears_in {:type (list :episode)}}}
|
||||
|
||||
:human
|
||||
{:fields {:id {:type Int}
|
||||
:name {:type String}
|
||||
:home_planet {:type String}
|
||||
:appears_in {:type (list :episode)}}}}
|
||||
|
||||
:queries
|
||||
{:hero {:type (non-null :human)
|
||||
:args {:id {:type String :default-value "2001"}}
|
||||
:resolve :get-hero}
|
||||
:droid {:type :droid
|
||||
:args {:name {:type String}}
|
||||
:resolve :get-droid}}}
|
|
@ -1,49 +0,0 @@
|
|||
<!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>
|
|
@ -1,120 +0,0 @@
|
|||
<!--
|
||||
Copyright 2017 WalmartLabs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.5/graphiql.css" />
|
||||
<script src="https://cdn.jsdelivr.net/fetch/0.9.0/fetch.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/react/0.14.7/react.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/react/0.14.7/react-dom.min.js"></script>
|
||||
<script src="https://unpkg.com/subscriptions-transport-ws@0.8.2/browser/client.js"></script>
|
||||
<script src="https://unpkg.com/graphiql-subscriptions-fetcher@0.0.2/browser/client.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.5/graphiql.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
Loading...
|
||||
<script>
|
||||
/**
|
||||
* This GraphiQL example illustrates how to use some of GraphiQL's props
|
||||
* in order to enable reading and updating the URL parameters, making
|
||||
* link sharing of queries a little bit easier.
|
||||
*
|
||||
* This is only one example of this kind of feature, GraphiQL exposes
|
||||
* various React params to enable interesting integrations.
|
||||
*/
|
||||
// Parse the search string to get url parameters.
|
||||
var search = window.location.search;
|
||||
var parameters = {};
|
||||
search.substr(1).split('&').forEach(function (entry) {
|
||||
var eq = entry.indexOf('=');
|
||||
if (eq >= 0) {
|
||||
parameters[decodeURIComponent(entry.slice(0, eq))] =
|
||||
decodeURIComponent(entry.slice(eq + 1));
|
||||
}
|
||||
});
|
||||
// if variables was provided, try to format it.
|
||||
if (parameters.variables) {
|
||||
try {
|
||||
parameters.variables =
|
||||
JSON.stringify(JSON.parse(parameters.variables), null, 2);
|
||||
} catch (e) {
|
||||
// Do nothing, we want to display the invalid JSON as a string, rather
|
||||
// than present an error.
|
||||
}
|
||||
}
|
||||
// When the query and variables string is edited, update the URL bar so
|
||||
// that it can be easily shared
|
||||
function onEditQuery(newQuery) {
|
||||
parameters.query = newQuery;
|
||||
updateURL();
|
||||
}
|
||||
function onEditVariables(newVariables) {
|
||||
parameters.variables = newVariables;
|
||||
updateURL();
|
||||
}
|
||||
function computeParams() {
|
||||
return '?' + Object.keys(parameters).map(function (key) {
|
||||
return encodeURIComponent(key) + '=' +
|
||||
encodeURIComponent(parameters[key]);
|
||||
}).join('&');
|
||||
}
|
||||
function updateURL() {
|
||||
history.replaceState(null, null, computeParams());
|
||||
}
|
||||
// Defines a GraphQL fetcher using the fetch API.
|
||||
function graphQLFetcher(graphQLParams) {
|
||||
console.log(graphQLParams);
|
||||
return fetch(window.location.origin + '/api/graphql' + computeParams(), {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/graphql',
|
||||
'apikey': 'graphiql'
|
||||
},
|
||||
body: graphQLParams.query
|
||||
}).then(function (response) {
|
||||
return response.text();
|
||||
}).then(function (responseBody) {
|
||||
console.log(responseBody);
|
||||
try {
|
||||
return JSON.parse(responseBody);
|
||||
} catch (error) {
|
||||
return responseBody;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Render <GraphiQL /> into the body.
|
||||
ReactDOM.render(
|
||||
React.createElement(GraphiQL, {
|
||||
fetcher: graphQLFetcher,
|
||||
query: parameters.query,
|
||||
variables: parameters.variables,
|
||||
onEditQuery: onEditQuery,
|
||||
onEditVariables: onEditVariables
|
||||
}),
|
||||
document.body
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,41 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Welcome to pinkblackrose</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="app">
|
||||
<section class="section">
|
||||
<div class="container is-fluid">
|
||||
<div class="content">
|
||||
<h4 class="title">Welcome to pinkblackrose</h4>
|
||||
<p>If you're seeing this message, that means you haven't yet compiled your ClojureScript!</p>
|
||||
<p>Please run <code>lein shadow watch app</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>
|
|
@ -1 +0,0 @@
|
|||
DROP TABLE users;
|
|
@ -1,9 +0,0 @@
|
|||
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));
|
18
resources/pinkblackrose/config.edn
Normal file
18
resources/pinkblackrose/config.edn
Normal file
|
@ -0,0 +1,18 @@
|
|||
{:duct.profile/base
|
||||
{:duct.core/project-ns pinkblackrose
|
||||
|
||||
:duct.router/cascading []}
|
||||
|
||||
:duct.profile/dev #duct/include "dev"
|
||||
:duct.profile/local #duct/include "local"
|
||||
:duct.profile/prod {}
|
||||
|
||||
:duct.module/logging {}
|
||||
:duct.module.web/site
|
||||
{}
|
||||
:duct.module/sql
|
||||
{}
|
||||
:duct.module.web/api
|
||||
{}
|
||||
:duct.module/cljs
|
||||
{:main pinkblackrose.client}}
|
|
@ -1,35 +0,0 @@
|
|||
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.
Before Width: 16px | Height: 16px | Size: 1.1 KiB |
Binary file not shown.
Before ![]() (image error) Size: 21 KiB |
|
@ -1,5 +0,0 @@
|
|||
html, body {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
height: 100%;
|
||||
padding-top: 40px;
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
-- :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
|
|
@ -1,13 +0,0 @@
|
|||
(ns pinkblackrose.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)]))
|
|
@ -1,74 +0,0 @@
|
|||
(ns pinkblackrose.core
|
||||
(:require
|
||||
[pinkblackrose.handler :as handler]
|
||||
[pinkblackrose.nrepl :as nrepl]
|
||||
[luminus.http-server :as http]
|
||||
[luminus-migrations.core :as migrations]
|
||||
[pinkblackrose.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 :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]
|
||||
(mount/start #'pinkblackrose.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)))
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
(ns pinkblackrose.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]
|
||||
[pinkblackrose.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)))
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
(ns pinkblackrose.handler
|
||||
(:require
|
||||
[pinkblackrose.middleware :as middleware]
|
||||
[pinkblackrose.layout :refer [error-page]]
|
||||
[pinkblackrose.routes.home :refer [home-routes]]
|
||||
[pinkblackrose.routes.services :refer [service-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]]
|
||||
[pinkblackrose.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)
|
||||
(service-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))
|
|
@ -1,39 +0,0 @@
|
|||
(ns pinkblackrose.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)})
|
|
@ -1,87 +0,0 @@
|
|||
(ns pinkblackrose.middleware
|
||||
(:require
|
||||
[pinkblackrose.env :refer [defaults]]
|
||||
[cheshire.generate :as cheshire]
|
||||
[cognitect.transit :as transit]
|
||||
[clojure.tools.logging :as log]
|
||||
[pinkblackrose.layout :refer [error-page]]
|
||||
[ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
|
||||
[pinkblackrose.middleware.formats :as formats]
|
||||
[muuntaja.middleware :refer [wrap-format wrap-params]]
|
||||
[pinkblackrose.config :refer [env]]
|
||||
[ring-ttl-session.core :refer [ttl-memory-store]]
|
||||
[ring.middleware.defaults :refer [site-defaults wrap-defaults]]
|
||||
[buddy.auth.middleware :refer [wrap-authentication wrap-authorization]]
|
||||
[buddy.auth.accessrules :refer [restrict]]
|
||||
[buddy.auth :refer [authenticated?]]
|
||||
[buddy.auth.backends.token :refer [jwe-backend]]
|
||||
[buddy.sign.jwt :refer [encrypt]]
|
||||
[buddy.core.nonce :refer [random-bytes]][buddy.sign.util :refer [to-timestamp]])
|
||||
|
||||
)
|
||||
|
||||
(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 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}))
|
||||
|
||||
(def secret (random-bytes 32))
|
||||
|
||||
(def token-backend
|
||||
(jwe-backend {:secret secret
|
||||
:options {:alg :a256kw
|
||||
:enc :a128gcm}}))
|
||||
|
||||
(defn token [username]
|
||||
(let [claims {:user (keyword username)
|
||||
:exp (to-timestamp
|
||||
(.getTime
|
||||
(doto (Calendar/getInstance)
|
||||
(.setTime (Date.))
|
||||
(.add Calendar/HOUR_OF_DAY 1))))}]
|
||||
(encrypt claims secret {:alg :a256kw :enc :a128gcm})))
|
||||
|
||||
(defn wrap-auth [handler]
|
||||
(let [backend token-backend]
|
||||
(-> handler
|
||||
(wrap-authentication backend)
|
||||
(wrap-authorization backend))))
|
||||
|
||||
(defn wrap-base [handler]
|
||||
(-> ((:middleware defaults) handler)
|
||||
wrap-auth
|
||||
(wrap-defaults
|
||||
(-> site-defaults
|
||||
(assoc-in [:security :anti-forgery] false)
|
||||
(assoc-in [:session :store] (ttl-memory-store (* 60 30)))))
|
||||
wrap-internal-error))
|
|
@ -1,24 +0,0 @@
|
|||
(ns pinkblackrose.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)})))
|
|
@ -1,15 +0,0 @@
|
|||
(ns pinkblackrose.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)))))
|
|
@ -1,27 +0,0 @@
|
|||
(ns pinkblackrose.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"))
|
|
@ -1,22 +0,0 @@
|
|||
(ns pinkblackrose.routes.home
|
||||
(:require
|
||||
[pinkblackrose.layout :as layout]
|
||||
[pinkblackrose.db.core :as db]
|
||||
[clojure.java.io :as io]
|
||||
[pinkblackrose.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}]
|
||||
["/graphiql" {:get (fn [request] (layout/render request "graphiql.html"))}]
|
||||
["/docs" {:get (fn [_]
|
||||
(-> (response/ok (-> "docs/docs.md" io/resource slurp))
|
||||
(response/header "Content-Type" "text/plain; charset=utf-8")))}]])
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
(ns pinkblackrose.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]
|
||||
[pinkblackrose.routes.services.graphql :as graphql]
|
||||
[pinkblackrose.middleware.formats :as formats]
|
||||
[pinkblackrose.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"}))}]
|
||||
|
||||
["/graphql" {:post (fn [req] (ok (graphql/execute-request (-> req :body slurp))))}]
|
||||
|
||||
["/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))})}}]]])
|
|
@ -1,41 +0,0 @@
|
|||
(ns pinkblackrose.routes.services.graphql
|
||||
(:require
|
||||
[com.walmartlabs.lacinia.util :refer [attach-resolvers]]
|
||||
[com.walmartlabs.lacinia.schema :as schema]
|
||||
[com.walmartlabs.lacinia :as lacinia]
|
||||
[clojure.data.json :as json]
|
||||
[clojure.edn :as edn]
|
||||
[clojure.java.io :as io]
|
||||
[ring.util.http-response :refer :all]
|
||||
[mount.core :refer [defstate]]))
|
||||
|
||||
(defn get-hero [context args value]
|
||||
(let [data [{:id 1000
|
||||
:name "Luke"
|
||||
:home_planet "Tatooine"
|
||||
:appears_in ["NEWHOPE" "EMPIRE" "JEDI"]}
|
||||
{:id 2000
|
||||
:name "Lando Calrissian"
|
||||
:home_planet "Socorro"
|
||||
:appears_in ["EMPIRE" "JEDI"]}]]
|
||||
(first data)))
|
||||
|
||||
(defstate compiled-schema
|
||||
:start
|
||||
(-> "graphql/schema.edn"
|
||||
io/resource
|
||||
slurp
|
||||
edn/read-string
|
||||
(attach-resolvers {:get-hero get-hero
|
||||
:get-droid (constantly {})})
|
||||
schema/compile))
|
||||
|
||||
(defn format-params [query]
|
||||
(let [parsed (json/read-str query)] ;;-> placeholder - need to ensure query meets graphql syntax
|
||||
(str "query { hero(id: \"1000\") { name appears_in }}")))
|
||||
|
||||
(defn execute-request [query]
|
||||
(let [vars nil
|
||||
context nil]
|
||||
(-> (lacinia/execute compiled-schema query vars context)
|
||||
(json/write-str))))
|
|
@ -1,2 +0,0 @@
|
|||
(ns pinkblackrose.validation
|
||||
(:require [struct.core :as st]))
|
|
@ -1,30 +0,0 @@
|
|||
(ns pinkblackrose.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})))
|
|
@ -1,79 +0,0 @@
|
|||
(ns pinkblackrose.core
|
||||
(:require
|
||||
[day8.re-frame.http-fx]
|
||||
[reagent.core :as r]
|
||||
[re-frame.core :as rf]
|
||||
[goog.events :as events]
|
||||
[goog.history.EventType :as HistoryEventType]
|
||||
[markdown.core :refer [md->html]]
|
||||
[pinkblackrose.ajax :as ajax]
|
||||
[pinkblackrose.events]
|
||||
[reitit.core :as reitit]
|
||||
[reitit.frontend.easy :as rfe]
|
||||
[clojure.string :as string])
|
||||
(:import goog.History))
|
||||
|
||||
(defn nav-link [uri title page]
|
||||
[:a.navbar-item
|
||||
{:href uri
|
||||
:class (when (= page @(rf/subscribe [: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}} "pinkblackrose"]
|
||||
[: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" :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 page []
|
||||
(if-let [page @(rf/subscribe [:page])]
|
||||
[:div
|
||||
[navbar]
|
||||
[page]]))
|
||||
|
||||
(defn navigate! [match _]
|
||||
(rf/dispatch [:navigate match]))
|
||||
|
||||
(def router
|
||||
(reitit/router
|
||||
[["/" {:name :home
|
||||
:view #'home-page
|
||||
:controllers [{:start (fn [_] (rf/dispatch [:page/init-home]))}]}]
|
||||
["/about" {:name :about
|
||||
:view #'about-page}]]))
|
||||
|
||||
(defn start-router! []
|
||||
(rfe/start!
|
||||
router
|
||||
navigate!
|
||||
{}))
|
||||
|
||||
;; -------------------------
|
||||
;; Initialize app
|
||||
(defn ^:dev/after-load mount-components []
|
||||
(rf/clear-subscription-cache!)
|
||||
(r/render [#'page] (.getElementById js/document "app")))
|
||||
|
||||
(defn init! []
|
||||
(start-router!)
|
||||
(ajax/load-interceptors!)
|
||||
(mount-components))
|
|
@ -1,78 +0,0 @@
|
|||
(ns pinkblackrose.events
|
||||
(:require
|
||||
[re-frame.core :as rf]
|
||||
[ajax.core :as ajax]
|
||||
[reitit.frontend.easy :as rfe]
|
||||
[reitit.frontend.controllers :as rfc]))
|
||||
|
||||
;;dispatchers
|
||||
|
||||
(rf/reg-event-db
|
||||
:navigate
|
||||
(fn [db [_ match]]
|
||||
(let [old-match (:common/route db)
|
||||
new-match (assoc match :controllers
|
||||
(rfc/apply-controllers (:controllers old-match) match))]
|
||||
(assoc db :route new-match))))
|
||||
|
||||
(rf/reg-fx
|
||||
:navigate-fx!
|
||||
(fn [[k & [params query]]]
|
||||
(rfe/push-state k params query)))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:navigate!
|
||||
(fn [_ [_ url-key params query]]
|
||||
{:navigate-fx! [url-key params query]}))
|
||||
|
||||
(rf/reg-event-db
|
||||
:set-docs
|
||||
(fn [db [_ docs]]
|
||||
(assoc db :docs docs)))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:fetch-docs
|
||||
(fn [_ _]
|
||||
{:http-xhrio {:method :get
|
||||
:uri "/docs"
|
||||
:response-format (ajax/raw-response-format)
|
||||
:on-success [:set-docs]}}))
|
||||
|
||||
(rf/reg-event-db
|
||||
:common/set-error
|
||||
(fn [db [_ error]]
|
||||
(assoc db :common/error error)))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:page/init-home
|
||||
(fn [_ _]
|
||||
{:dispatch [:fetch-docs]}))
|
||||
|
||||
;;subscriptions
|
||||
|
||||
(rf/reg-sub
|
||||
:route
|
||||
(fn [db _]
|
||||
(-> db :route)))
|
||||
|
||||
(rf/reg-sub
|
||||
:page-id
|
||||
:<- [:route]
|
||||
(fn [route _]
|
||||
(-> route :data :name)))
|
||||
|
||||
(rf/reg-sub
|
||||
:page
|
||||
:<- [:route]
|
||||
(fn [route _]
|
||||
(-> route :data :view)))
|
||||
|
||||
(rf/reg-sub
|
||||
:docs
|
||||
(fn [db _]
|
||||
(:docs db)))
|
||||
|
||||
(rf/reg-sub
|
||||
:common/error
|
||||
(fn [db _]
|
||||
(:common/error db)))
|
3
src/pinkblackrose/client.cljs
Normal file
3
src/pinkblackrose/client.cljs
Normal file
|
@ -0,0 +1,3 @@
|
|||
(ns pinkblackrose.client)
|
||||
|
||||
(js/console.log "FIXME")
|
12
src/pinkblackrose/main.clj
Normal file
12
src/pinkblackrose/main.clj
Normal file
|
@ -0,0 +1,12 @@
|
|||
(ns pinkblackrose.main
|
||||
(:gen-class)
|
||||
(:require [duct.core :as duct]))
|
||||
|
||||
(duct/load-hierarchy)
|
||||
|
||||
(defn -main [& args]
|
||||
(let [keys (or (duct/parse-keys args) [:duct/daemon])
|
||||
profiles [:duct.profile/prod]]
|
||||
(-> (duct/resource "pinkblackrose/config.edn")
|
||||
(duct/read-config)
|
||||
(duct/exec-config profiles keys))))
|
|
@ -1,38 +0,0 @@
|
|||
(ns pinkblackrose.test.db.core
|
||||
(:require
|
||||
[pinkblackrose.db.core :refer [*db*] :as db]
|
||||
[java-time.pre-java8]
|
||||
[luminus-migrations.core :as migrations]
|
||||
[clojure.test :refer :all]
|
||||
[clojure.java.jdbc :as jdbc]
|
||||
[pinkblackrose.config :refer [env]]
|
||||
[mount.core :as mount]))
|
||||
|
||||
(use-fixtures
|
||||
:once
|
||||
(fn [f]
|
||||
(mount/start
|
||||
#'pinkblackrose.config/env
|
||||
#'pinkblackrose.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"})))))
|
|
@ -1,52 +0,0 @@
|
|||
(ns pinkblackrose.test.handler
|
||||
(:require
|
||||
[clojure.test :refer :all]
|
||||
[ring.mock.request :refer :all]
|
||||
[pinkblackrose.handler :refer :all]
|
||||
[pinkblackrose.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 #'pinkblackrose.config/env
|
||||
#'pinkblackrose.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)))))))
|
|
@ -1,9 +0,0 @@
|
|||
(ns pinkblackrose.core-test
|
||||
(:require [cljs.test :refer-macros [is are deftest testing use-fixtures]]
|
||||
[pjstadig.humane-test-output]
|
||||
[reagent.core :as reagent :refer [atom]]
|
||||
[pinkblackrose.core :as rc]))
|
||||
|
||||
(deftest test-home
|
||||
(is (= true true)))
|
||||
|
Loading…
Reference in a new issue