Switched to duct

This commit is contained in:
Carolyn Knight-Serrano 2020-02-18 18:37:43 -08:00
parent 172201fab3
commit 20c4ef9315
Signed by: l4nn-1312
GPG Key ID: 16CA55B816FBAFF4
55 changed files with 174 additions and 1709 deletions

18
.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -1,7 +0,0 @@
FROM openjdk:8-alpine
COPY target/uberjar/pinkblackrose.jar /pinkblackrose/app.jar
EXPOSE 3000
CMD ["java", "-jar", "/pinkblackrose/app.jar"]

View File

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

View File

@ -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
View File

@ -0,0 +1,3 @@
{:duct.database/sql
{:connection-uri "jdbc:postgresql://localhost/postgres"}
}

31
dev/src/dev.clj Normal file
View 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
View File

@ -0,0 +1,8 @@
(ns user)
(defn dev
"Load and switch to the 'dev' namespace."
[]
(require 'dev)
(in-ns 'dev)
:loaded)

View File

@ -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]})))

View File

@ -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
View File

@ -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])))

View File

@ -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!)

View File

@ -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>

View File

@ -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})

View File

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

View File

@ -1,2 +0,0 @@
{:prod true
:port 3000}

View File

@ -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>

View File

@ -1 +0,0 @@
{}

View File

@ -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>

View File

@ -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"]]}})

View File

@ -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.

View File

@ -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}}}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -1 +0,0 @@
DROP TABLE users;

View File

@ -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));

View 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}}

View File

@ -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:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

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

View File

@ -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

View File

@ -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)]))

View File

@ -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)))

View File

@ -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)))

View File

@ -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))

View File

@ -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)})

View File

@ -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))

View File

@ -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)})))

View File

@ -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)))))

View File

@ -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"))

View File

@ -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")))}]])

View File

@ -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))})}}]]])

View File

@ -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))))

View File

@ -1,2 +0,0 @@
(ns pinkblackrose.validation
(:require [struct.core :as st]))

View File

@ -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})))

View File

@ -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))

View File

@ -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)))

View File

@ -0,0 +1,3 @@
(ns pinkblackrose.client)
(js/console.log "FIXME")

View 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))))

View File

@ -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"})))))

View File

@ -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)))))))

View File

@ -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)))