├── .gitignore ├── LICENSE ├── Makefile ├── Procfile ├── README.md ├── RELEASE_CHECKLIST.md ├── bin └── rename-project.sh ├── dev ├── client │ └── cljs │ │ └── user.cljs └── server │ └── user.clj ├── docs └── img │ ├── figwheel.png │ └── server.png ├── package.json ├── project.clj ├── resources └── public │ ├── cards.html │ ├── css │ ├── edn.css │ └── test.css │ ├── index-dev.html │ ├── index.html │ └── test.html ├── script └── figwheel.clj ├── specs ├── client │ └── untangled_template │ │ ├── all_tests.cljs │ │ ├── sample_spec.cljs │ │ ├── spec_main.cljs │ │ ├── tests_to_run.cljs │ │ └── ui │ │ └── root_spec.cljs └── server │ └── sample │ └── sample_spec.clj └── src ├── cards └── untangled_template │ ├── cards.cljs │ └── intro.cljs ├── client └── untangled_template │ ├── core.cljs │ ├── main.cljs │ ├── state │ └── mutations.cljs │ └── ui │ ├── components.cljs │ ├── login.cljs │ ├── main.cljs │ ├── new_user.cljs │ └── root.cljs └── server ├── config ├── defaults.edn ├── dev.edn └── prod.edn └── untangled_template ├── api ├── mutations.clj └── read.clj ├── core.clj └── system.clj /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.iml 3 | *.jar 4 | .idea 5 | checkouts 6 | classes 7 | figwheel_server.log 8 | lein-template.iml 9 | node_modules 10 | pom.xml 11 | pom.xml.asc 12 | resources/public/js 13 | target 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 NAVIS 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 5 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 6 | persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 11 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 13 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LEIN_RUN = rlwrap lein run -m clojure.main ./script/figwheel.clj 2 | 3 | # Run the dev and test cljs builds in figwheel 4 | dev: 5 | lein do clean, -U deps ; JVM_OPTS="-server -Ddev -Dtest" ${LEIN_RUN} 6 | 7 | # Run the test cljs builds in figwheel 8 | test: 9 | JVM_OPTS="-server -Dtest" ${LEIN_RUN} 10 | 11 | # Run the cards cljs builds in figwheel 12 | cards: 13 | JVM_OPTS="-server -Dcards" ${LEIN_RUN} 14 | 15 | # Run a REPL capable of running the web server 16 | server: 17 | rlwrap lein run -m clojure.main 18 | 19 | server-tests: 20 | lein test-refresh :run-once 21 | 22 | # Run the command-line (karma-based) automated cljs tests 23 | ci-cljs-tests: 24 | npm install 25 | lein do clean, doo chrome automated-tests once 26 | 27 | # Run all tests (once) from the command line. Useful for CI 28 | ci-tests: ci-cljs-tests server-tests 29 | 30 | clean: 31 | lein clean 32 | 33 | rename: 34 | bin/rename-project.sh 35 | 36 | help: 37 | @ make -rpn | sed -n -e '/^$$/ { n ; /^[^ ]*:/p; }' | sort | egrep --color '^[^ ]*:' 38 | 39 | .PHONY: dev test cards server server-tests ci-cljs-tests ci-tests clean rename help 40 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: java $JVM_OPTS -Dconfig=config/prod.edn -jar target/untangled_template.jar 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Template 2 | 3 | This is a Full Stack template with specs, dev cards, and client code. 4 | It contains a mock login/signup screen, top-level tab routing (once logged in), etc. 5 | 6 | You must run the server (and use it through the server) for login to work, but ANY username/password are accepted. The 7 | server always approves login. 8 | 9 | It is set up to be deployable to Heroku (or anywhere) as a standalone jar. 10 | 11 | ## Contents 12 | 13 | ``` 14 | ├── Makefile Convenience targets 15 | ├── Procfile Sample Heroku deployment file 16 | ├── dev 17 | │   ├── client 18 | │   │   └── cljs 19 | │   │   └── user.cljs REPL helpers and entry point for cljs dev mode 20 | │   └── server 21 | │   └── user.clj REPL functions for starting server and cljs builds 22 | ├── package.json NODE config, used for running CI cljs tests 23 | ├── project.clj 24 | ├── resources 25 | │   └── public 26 | │   ├── cards.html Devcards HTML page 27 | │   ├── css 28 | │   │   ├── edn.css CSS files for rendering specs in browser 29 | │   │   └── test.css 30 | │   ├── index-dev.html Dev mode application home page 31 | │   ├── index.html Production mode application home page 32 | │   └── test.html Tests HTML page 33 | ├── script 34 | │   └── figwheel.clj CLJ script for starting figwheel automatically 35 | ├── specs 36 | │   ├── client 37 | │   │   └── untangled_template 38 | │   │   ├── all_tests.cljs CI file for running all tests 39 | │   │   ├── sample_spec.cljs Sample CLJS specification 40 | │   │   ├── spec_main.cljs File to join all specs into a browser-runnable spec 41 | │   │   ├── tests_to_run.cljs Common file (for CI and Browser) to ensure all tests are loaded 42 | │   │   └── ui 43 | │   │   └── root_spec.cljs Sample Specification 44 | │   ├── config 45 | │   └── server 46 | │   └── sample 47 | │   └── sample_spec.clj Sample Server-side specification 48 | ├── src 49 | │   ├── cards 50 | │   │   └── untangled_template 51 | │   │   ├── cards.cljs Devcards setup 52 | │   │   └── intro.cljs Sample Devcard 53 | │   ├── client 54 | │   │   └── untangled_template 55 | │   │   ├── core.cljs Definition of app. Used by production and dev modes 56 | │   │   ├── main.cljs Production entry point for cljs app 57 | │   │   ├── state 58 | │   │   │   └── mutations.cljs A place to put Om mutations 59 | │   │   └── ui 60 | │   │   ├── components.cljs Sample UI component 61 | │   │   ├── login.cljs UI Login screen. Includes some mutations. 62 | │   │   ├── main.cljs UI Main screen 63 | │   │   ├── new_user.cljs UI New User Screen 64 | │   │   └── root.cljs Root UI with Union query for tab switching. Includes nav mutations. 65 | │   └── server 66 | │   ├── config Server EDN configuration files 67 | │   │   ├── defaults.edn Always applied (but always used as a base for config merge) 68 | │   │   ├── dev.edn Dev-mode config (auto-selected by user.clj setup) 69 | │   │   └── prod.edn Production-mode config. Selected via -Dconfig=config/prod.edn 70 | │   └── untangled_template 71 | │   ├── api 72 | │   │   ├── mutations.clj Server-side Om mutations 73 | │   │   └── read.clj Server-side Om queries 74 | │   ├── core.clj Server-side entry point for production mode 75 | │   └── system.clj Server-side system configuration (shared for dev and production) 76 | ``` 77 | 78 | ## Setting up Run Configurations (IntelliJ) 79 | 80 | Add a figwheel config: 81 | 82 | 83 | 84 | Add a server config: 85 | 86 | 87 | 88 | Then run *both* from IntelliJ. 89 | 90 | ## Using from other editors 91 | 92 | See the Makefile for useful command-line targets, which are useful for 93 | when working from a lower-level system editor. 94 | 95 | The simplest approach is to start a REPL: 96 | 97 | ``` 98 | lein repl 99 | ``` 100 | 101 | *You will need two REPLs*: one for the server, and one for you dev builds of the client. 102 | 103 | There is a pre-supplied function named `start-figwheel` that will start the cljs builds and figwheel hot code push. 104 | 105 | ## Using the server 106 | 107 | In the server REPL, start the server with: 108 | 109 | ``` 110 | (go) 111 | ``` 112 | 113 | To reload the server code: 114 | 115 | ``` 116 | (reset) 117 | ``` 118 | 119 | IF your compile fails, Recompile after failed compile: 120 | 121 | ``` 122 | (refresh) 123 | (go) 124 | ``` 125 | 126 | If you cannot find `refresh`, try: 127 | 128 | ``` 129 | (tools-ns/refresh) 130 | ``` 131 | 132 | ## Using the Full Stack App (dev mode) 133 | 134 | Open a browser on: 135 | 136 | ``` 137 | http://localhost:3000/index-dev.html 138 | ``` 139 | 140 | ## Dev Cards 141 | 142 | Open a browser on: 143 | 144 | ``` 145 | http://localhost:3449/cards.html 146 | ``` 147 | 148 | ## Specs 149 | 150 | Open a browser on: 151 | 152 | ``` 153 | http://localhost:3449/test.html 154 | ``` 155 | 156 | ## Continuous Integration Tests 157 | 158 | The project is set up to be able to run both the UI and Server tests from a 159 | standard *NIX command-line (untested with Windows, but works with OSX and 160 | Linux). 161 | 162 | The UI tests use node, karma, and doo to accomplish tests. 163 | 164 | The Makefile has targets for running the various CI tests modes. You 165 | must install Node and NPM. In OSX, Home Brew can make quick work of that. 166 | 167 | ## Makefile 168 | 169 | There is a GNU `Makefile` in the project that can start various command 170 | line interactions. This file is commented so you can see what targets 171 | are valid. 172 | 173 | Example: Run a REPL that is ready to run the Untangled Server: 174 | 175 | ``` 176 | make server 177 | ``` 178 | 179 | # Deploying 180 | 181 | Build the standalone Jar with: 182 | 183 | ``` 184 | lein uberjar 185 | ``` 186 | 187 | will build `target/untangled_template.jar`. 188 | 189 | The production `prod.edn` file (in src/config) grabs the web PORT from 190 | the environment (as required by Heroku). So, this jar can be run with: 191 | 192 | ``` 193 | export PORT=8080 # the web server port to use 194 | java -Dconfig=config/prod.edn -jar untangled_template.jar 195 | ``` 196 | 197 | The `Procfile` gives the correct information to heroku, so if you've 198 | configured the app (see Heroku docs) you should be able to deploy with 199 | git: 200 | 201 | ``` 202 | git push heroku master 203 | ``` 204 | -------------------------------------------------------------------------------- /RELEASE_CHECKLIST.md: -------------------------------------------------------------------------------- 1 | # RELEASE CHECKLIST 2 | 3 | Before releasing a new version of this template, you must ensure the 4 | following things work as expected: 5 | 6 | - Versions 7 | - All deps are up-to-date 8 | - All tooling (lein plugins, etc) are up-to-date 9 | - There are NO warnings from `lein deps :tree` 10 | - IDE Development for IntelliJ, Emacs, and VIM 11 | - Figwheel 12 | - Server run AND code reload 13 | - Uberjar build: 14 | - Does a production cljs build 15 | - Includes all necessary resources and files 16 | - Is runnable according to the README instructions 17 | - The UI works 18 | - The server can be configured (e.g. PORT) as described in README 19 | - The following figwheel configs work as expected, and hot reload: 20 | - CSS 21 | - Cards 22 | - General development 23 | - Tests 24 | - Tests 25 | - Are runnable from UI (client) 26 | - Work with test-refresh (server) 27 | - Are runnable from CI (server and client) 28 | -------------------------------------------------------------------------------- /bin/rename-project.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ([[ -n "$DEBUG" ]] || [[ -n "$TRACE" ]]) && set -x 3 | set -e 4 | 5 | assert_clean_work_tree () { 6 | if [[ -z "$OVERRIDE" ]] && [[ -n "$(git status -s)" ]]; then 7 | echo "[ERROR]: Uncommited changes!" 8 | git status 9 | exit 1 10 | fi 11 | } 12 | 13 | search_and_replace () { 14 | #LC_ALL=C is important because we have utf-8 symbols in the readme 15 | #and sed will corrupt them & git will explode otherwise 16 | #stackoverflow.com/questions/19242275/re-error-illegal-byte-sequence-on-mac-os-x 17 | #stackoverflow.com/questions/1115854/how-to-resolve-error-bad-index-fatal-index-file-corrupt-when-using-git 18 | export LC_ALL=C 19 | for f in $(grep --exclude="$0" --exclude-dir=".git" -lr "$1" *); do 20 | #using .bak in place extension for portability between sed versions 21 | #is more portable (& easier) than no backup 22 | sed -i.bak "s/$1/$2/g" "$f" 23 | rm ${f}.bak 24 | done 25 | unset LC_ALL 26 | } 27 | 28 | rename_matching_dirs () { 29 | for d in $(find . -type d -name "$1"); do 30 | mv "$d" "$(dirname $d)/$2" 31 | done 32 | } 33 | 34 | main () { 35 | assert_clean_work_tree 36 | read -p "Renaming 'untangled-template' to: " ns 37 | search_and_replace "untangled-template" "$ns" 38 | local fdir="${ns//-/_}" 39 | search_and_replace "untangled_template" "$fdir" 40 | rename_matching_dirs "untangled_template" "$fdir" 41 | } 42 | 43 | main "$@" 44 | -------------------------------------------------------------------------------- /dev/client/cljs/user.cljs: -------------------------------------------------------------------------------- 1 | (ns cljs.user 2 | (:require 3 | [untangled.client.core :as uc] 4 | [om.next :as om] 5 | 6 | [untangled-template.core :as core] 7 | [untangled-template.ui.root :as root] 8 | 9 | [cljs.pprint :refer [pprint]])) 10 | 11 | (enable-console-print!) 12 | 13 | (reset! core/app (uc/mount @core/app root/Root "app")) 14 | 15 | (defn app-state [] @(:reconciler @core/app)) 16 | 17 | (defn log-app-state [& keywords] 18 | (pprint (let [app-state (app-state)] 19 | (if (= 0 (count keywords)) 20 | app-state 21 | (select-keys app-state keywords))))) 22 | 23 | (defn dump-query [comp] 24 | (let [component (om/class->any (:reconciler @core/app) comp)] 25 | (om/full-query component))) 26 | 27 | (defn dump-query-kw [kw] 28 | (let [component (om/ref->any (:reconciler @core/app) kw)] 29 | (om/full-query component))) 30 | 31 | (defn q 32 | "Run the query of the given UI class and return the result as a map of the query that was run and the data that was returned. 33 | NOTE: If the component is mounted in several places on the UI, you may not get the expected result. Be sure to check 34 | the QUERY part of the result to see the query used." 35 | [ui-class] 36 | (let [query (dump-query ui-class) 37 | state (app-state)] 38 | {:QUERY query 39 | :RESULT (om/db->tree query state state)})) 40 | 41 | (defn qkw 42 | "Find a component that uses the given keyword in its query, then run that query against the app database and show 43 | the result. NOTE: If more than one component matches, your results may vary. Be sure to examine the query that 44 | was used." 45 | [query-kw] 46 | (let [query (dump-query-kw query-kw) 47 | state (app-state)] 48 | {:QUERY query 49 | :RESULT (om/db->tree query state state)})) 50 | -------------------------------------------------------------------------------- /dev/server/user.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require 3 | [clojure.pprint :refer [pprint]] 4 | [clojure.stacktrace :refer [print-stack-trace]] 5 | 6 | [clojure.tools.namespace.repl :as tools-ns :refer [set-refresh-dirs]] 7 | [com.stuartsierra.component :as component] 8 | [figwheel-sidecar.system :as fig] 9 | [untangled-template.system :as sys])) 10 | 11 | ;;FIGWHEEL 12 | (def figwheel (atom nil)) 13 | 14 | ; Usable from a REPL to start one-or-more figwheel builds 15 | (defn start-figwheel 16 | "Start Figwheel on the given builds, or defaults to build-ids in `figwheel-config`." 17 | ([] 18 | (let [figwheel-config (fig/fetch-config) 19 | props (System/getProperties) 20 | all-builds (->> figwheel-config :data :all-builds (mapv :id))] 21 | (start-figwheel (keys (select-keys props all-builds))))) 22 | ([build-ids] 23 | (let [figwheel-config (fig/fetch-config) 24 | default-build-ids (-> figwheel-config :data :build-ids) 25 | build-ids (if (empty? build-ids) default-build-ids build-ids) 26 | preferred-config (assoc-in figwheel-config [:data :build-ids] build-ids)] 27 | (reset! figwheel (component/system-map 28 | :css-watcher (fig/css-watcher {:watch-paths ["resources/public/css"]}) 29 | :figwheel-system (fig/figwheel-system preferred-config))) 30 | (println "STARTING FIGWHEEL ON BUILDS: " build-ids) 31 | (swap! figwheel component/start) 32 | (fig/cljs-repl (:figwheel-system @figwheel))))) 33 | 34 | ;; ==================== SERVER ==================== 35 | 36 | (set-refresh-dirs "dev/server" "src/server" "specs/server") 37 | 38 | (defn started? [sys] 39 | (-> sys :config :value)) 40 | 41 | (defonce system (atom nil)) 42 | (def cfg-paths {:dev "config/dev.edn"}) 43 | 44 | (defn refresh [& args] 45 | {:pre [(not @system)]} 46 | (apply tools-ns/refresh args)) 47 | 48 | (defn init [path] 49 | {:pre [(not (started? @system)) 50 | (get cfg-paths path)]} 51 | (when-let [new-system (sys/make-system (get cfg-paths path))] 52 | (reset! system new-system))) 53 | 54 | (defn start [] 55 | {:pre [@system (not (started? @system))]} 56 | (swap! system component/start)) 57 | 58 | (defn stop [] 59 | (when (started? @system) 60 | (swap! system component/stop)) 61 | (reset! system nil)) 62 | 63 | (defn go 64 | ([] (go :dev)) 65 | ([path] {:pre [(not @system) (not (started? @system))]} 66 | (init path) 67 | (start))) 68 | 69 | (defn reset [] 70 | (stop) 71 | (refresh :after 'user/go)) 72 | 73 | (defn engage [path & build-ids] 74 | (stop) (go path) (start-figwheel build-ids)) 75 | -------------------------------------------------------------------------------- /docs/img/figwheel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/untangled-web/untangled-template/af934820cf8f73567dc2f6a87b976b022dedf416/docs/img/figwheel.png -------------------------------------------------------------------------------- /docs/img/server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/untangled-web/untangled-template/af934820cf8f73567dc2f6a87b976b022dedf416/docs/img/server.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "untangled-template-spec", 3 | "version": "1.0.0", 4 | "description": "Testing", 5 | "main": "index.js", 6 | "directories": {}, 7 | "dependencies": { 8 | }, 9 | "devDependencies": { 10 | "karma": "^0.13.19", 11 | "karma-chrome-launcher": "^0.2.2", 12 | "karma-cljs-test": "^0.1.0", 13 | "karma-firefox-launcher": "^0.1.7" 14 | }, 15 | "author": "", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject untangled-template "0.1.0-SNAPSHOT" 2 | :description "A clonable & upstream untangled template" 3 | :license {:name "MIT" :url "https://opensource.org/licenses/MIT"} 4 | 5 | :dependencies [[org.clojure/clojure "1.9.0-alpha14"] 6 | [org.clojure/clojurescript "1.9.494"] 7 | [commons-io "2.5"] 8 | 9 | [navis/untangled-client "0.7.0"] 10 | [untangled/om-css "1.0.0"] 11 | [org.omcljs/om "1.0.0-alpha48"] 12 | 13 | [navis/untangled-spec "0.3.9" :scope "test" :exclusions [io.aviso/pretty]] 14 | [lein-doo "0.1.7" :scope "test"] 15 | [org.clojure/core.async "0.2.395"] 16 | [http-kit "2.2.0"] 17 | [com.taoensso/timbre "4.7.4"] 18 | [navis/untangled-server "0.7.0"]] 19 | 20 | :plugins [[lein-cljsbuild "1.1.5"] 21 | [lein-doo "0.1.7"] 22 | [com.jakemccrary/lein-test-refresh "0.17.0"]] 23 | 24 | :doo {:build "automated-tests" 25 | :paths {:karma "node_modules/karma/bin/karma"}} 26 | 27 | :uberjar-name "untangled_template.jar" 28 | 29 | :test-refresh {:report untangled-spec.reporters.terminal/untangled-report 30 | :with-repl true 31 | :changes-only true} 32 | 33 | :source-paths ["src/server"] 34 | :test-paths ["specs" "specs/server" "specs/config"] 35 | :clean-targets ^{:protect false} ["target" "resources/public/js" "resources/private"] 36 | 37 | :figwheel {:css-dirs ["resources/public/css"]} 38 | 39 | :cljsbuild {:builds [{:id "production" 40 | :source-paths ["src/client"] 41 | :jar true 42 | :compiler {:asset-path "js/prod" 43 | :main untangled-template.main 44 | :optimizations :simple 45 | :output-dir "resources/public/js/prod" 46 | :output-to "resources/public/js/untangled_template.min.js"}} 47 | {:id "dev" 48 | :figwheel true 49 | :source-paths ["src/client" "dev/client"] 50 | :compiler {:asset-path "js/dev" 51 | :external-config 52 | {:devtools/config 53 | ;;github.com/binaryage/cljs-devtools/blob/master/docs/configuration.md 54 | {:print-config-overrides true}} 55 | :main cljs.user 56 | :optimizations :none 57 | :output-dir "resources/public/js/dev" 58 | :output-to "resources/public/js/untangled_template.js" 59 | :preloads [devtools.preload] 60 | :source-map-timestamp true}} 61 | {:id "test" 62 | :source-paths ["specs/client" "src/client"] 63 | :figwheel true 64 | :compiler {:asset-path "js/specs" 65 | :main untangled-template.spec-main 66 | :optimizations :none 67 | :output-dir "resources/public/js/specs" 68 | :output-to "resources/public/js/specs.js" 69 | :preloads [devtools.preload]}} 70 | {:id "automated-tests" 71 | :source-paths ["specs/client" "src/client"] 72 | :compiler {:asset-path "js/ci" 73 | :main untangled-template.all-tests 74 | :optimizations :none 75 | :output-dir "resources/private/js/ci" 76 | :output-to "resources/private/js/unit-tests.js"}} 77 | {:id "cards" 78 | :figwheel {:devcards true} 79 | :source-paths ["src/client" "src/cards"] 80 | :compiler {:asset-path "js/cards" 81 | :main untangled-template.cards 82 | :optimizations :none 83 | :output-dir "resources/public/js/cards" 84 | :output-to "resources/public/js/cards.js" 85 | :preloads [devtools.preload] 86 | :source-map-timestamp true}}]} 87 | 88 | :profiles {:uberjar {:main untangled-template.core 89 | :aot :all 90 | :prep-tasks ["compile" 91 | ["cljsbuild" "once" "production"]]} 92 | :dev {:source-paths ["dev/client" "dev/server" "src/client" "src/server"] 93 | :dependencies [[binaryage/devtools "0.9.1"] 94 | [org.clojure/tools.namespace "0.2.11"] 95 | [com.cemerick/piggieback "0.2.1"] 96 | [figwheel-sidecar "0.5.9" :exclusions [org.clojure/tools.reader]] 97 | [devcards "0.2.2" :exclusions [org.omcljs/om]]] 98 | :repl-options {:init-ns user 99 | :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}}}) 100 | -------------------------------------------------------------------------------- /resources/public/cards.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Template Devcards 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /resources/public/css/edn.css: -------------------------------------------------------------------------------- 1 | .rendered-edn .collection { 2 | display: flex; 3 | display: -webkit-flex; 4 | } 5 | 6 | .rendered-edn .keyval { 7 | display: flex; 8 | display: -webkit-flex; 9 | flex-wrap: wrap; 10 | -webkit-flex-wrap: wrap; 11 | } 12 | 13 | .rendered-edn .keyval > .keyword { 14 | color: #a94442; 15 | } 16 | 17 | .rendered-edn .keyval > *:first-child { 18 | margin: 0px 3px; 19 | flex-shrink: 0; 20 | -webkit-flex-shrink: 0; 21 | } 22 | 23 | .rendered-edn .keyval > *:last-child { 24 | margin: 0px 3px; 25 | } 26 | 27 | .rendered-edn .opener { 28 | color: #999; 29 | margin: 0px 4px; 30 | flex-shrink: 0; 31 | -webkit-flex-shrink: 0; 32 | } 33 | 34 | .rendered-edn .closer { 35 | display: flex; 36 | display: -webkit-flex; 37 | flex-direction: column-reverse; 38 | -webkit-flex-direction: column-reverse; 39 | margin: 0px 3px; 40 | color: #999; 41 | } 42 | 43 | .rendered-edn .string { 44 | color: #428bca; 45 | } 46 | 47 | .rendered-edn .string .opener, 48 | .rendered-edn .string .closer { 49 | display: inline; 50 | margin: 0px; 51 | color: #428bca; 52 | } 53 | -------------------------------------------------------------------------------- /resources/public/css/test.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Lato); 2 | @import url(https://fonts.googleapis.com/css?family=Cutive+Mono); 3 | 4 | .overlay { 5 | background-color: rgb(230, 230, 240); 6 | width: 194px; 7 | } 8 | 9 | .hidden { 10 | display: none; 11 | } 12 | 13 | .test-report { 14 | font-family: 'Lato' sans-serif; 15 | display: block; 16 | font-size: 20pt; 17 | } 18 | 19 | .test-item { 20 | display: block; 21 | font-size: 13pt; 22 | } 23 | 24 | .test-namespace { 25 | margin-top: 20px; 26 | font-weight: 400; 27 | display: block; 28 | font-size: 18pt; 29 | } 30 | 31 | .test-header { 32 | font-weight: 700; 33 | display: block; 34 | font-size: 18pt; 35 | } 36 | 37 | .test-manually { 38 | color: orange; 39 | } 40 | 41 | .filter-controls { 42 | position: fixed; 43 | top: 0px; 44 | right: 0px; 45 | } 46 | 47 | .filter-controls label { 48 | font-size: 10pt; 49 | } 50 | 51 | .filter-controls a { 52 | font-size: 10pt; 53 | padding-left: 5pt; 54 | } 55 | 56 | .selected { 57 | color: green; 58 | text-decoration: underline; 59 | } 60 | 61 | .test-pending { 62 | color: gray; 63 | } 64 | 65 | .test-passed { 66 | color: limegreen; 67 | } 68 | 69 | .test-error { 70 | color: red; 71 | } 72 | 73 | .test-failed { 74 | color: red; 75 | } 76 | 77 | .test-list { 78 | list-style-type: none; 79 | } 80 | 81 | .test-result { 82 | margin: 12px; 83 | font-size: 16px; 84 | } 85 | 86 | .test-result-title { 87 | width: 100px; 88 | font-size: 16px; 89 | } 90 | 91 | .test-count { 92 | margin: 20px 0 20px 20px; 93 | } 94 | 95 | .test-report ul { 96 | padding-left: 10px; 97 | margin-bottom: 10px; 98 | } 99 | 100 | .test-report ul:empty { 101 | display: none; 102 | } 103 | 104 | .test-report h2 { 105 | font-size: 24px; 106 | margin-bottom: 15px; 107 | } 108 | 109 | #test-app { 110 | display: none; 111 | } 112 | -------------------------------------------------------------------------------- /resources/public/index-dev.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Home Page (Dev Mode) 8 | 9 | 10 |
Loading...
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Home Page 8 | 9 | 10 |
Loading...TODO: Put your loading marker for production here. If you're in dev 11 | mode, use index-dev.html instead.
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /resources/public/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /script/figwheel.clj: -------------------------------------------------------------------------------- 1 | (require '[user :refer [start-figwheel]]) 2 | 3 | (start-figwheel) 4 | -------------------------------------------------------------------------------- /specs/client/untangled_template/all_tests.cljs: -------------------------------------------------------------------------------- 1 | (ns untangled-template.all-tests 2 | (:require 3 | untangled-template.tests-to-run 4 | [doo.runner :refer-macros [doo-all-tests]])) 5 | 6 | (doo-all-tests #".*-spec") 7 | -------------------------------------------------------------------------------- /specs/client/untangled_template/sample_spec.cljs: -------------------------------------------------------------------------------- 1 | (ns untangled-template.sample-spec 2 | (:require 3 | [untangled-spec.core :refer-macros [specification behavior assertions]])) 4 | 5 | (specification "a sample spec file" 6 | (behavior "for you to tinker with" 7 | (assertions "Silly test" 8 | 1 => 1))) 9 | -------------------------------------------------------------------------------- /specs/client/untangled_template/spec_main.cljs: -------------------------------------------------------------------------------- 1 | (ns untangled-template.spec-main 2 | (:require-macros 3 | [untangled-spec.reporters.suite :as ts]) 4 | (:require 5 | untangled-spec.reporters.impl.suite 6 | untangled-template.tests-to-run)) 7 | 8 | (enable-console-print!) 9 | 10 | (ts/deftest-all-suite specs #".*-spec") 11 | 12 | (specs) 13 | -------------------------------------------------------------------------------- /specs/client/untangled_template/tests_to_run.cljs: -------------------------------------------------------------------------------- 1 | (ns untangled-template.tests-to-run 2 | (:require 3 | untangled-template.ui.root-spec 4 | untangled-template.sample-spec)) 5 | -------------------------------------------------------------------------------- /specs/client/untangled_template/ui/root_spec.cljs: -------------------------------------------------------------------------------- 1 | (ns untangled-template.ui.root-spec 2 | (:require 3 | [untangled-template.ui.root :as root] 4 | [untangled-spec.core :refer-macros [specification component behavior assertions]])) 5 | 6 | (specification "Root level mutations" 7 | (component "Navigation - nav-to helper function" 8 | (let [state-atom (atom {}) 9 | env {:state state-atom}] 10 | 11 | (root/nav-to env :my-page) 12 | 13 | (assertions 14 | "Sets the current-page ident to have a second element of :page" 15 | (-> state-atom deref :current-page second) => :page 16 | "Sets the current-page ident to have the selected page as the first element" 17 | (-> state-atom deref :current-page first) => :my-page)))) 18 | -------------------------------------------------------------------------------- /specs/server/sample/sample_spec.clj: -------------------------------------------------------------------------------- 1 | (ns sample.sample-spec 2 | (:require 3 | ;[untangled-spec.stub] 4 | [clojure.test :refer [is]] 5 | [untangled-spec.core :refer [specification provided behavior assertions]])) 6 | 7 | (specification "Server Math" 8 | (behavior "addition computes addition correctly" 9 | (assertions 10 | "with positive integers" 11 | (+ 1 5 3) => 9 12 | "with negative integers" 13 | (+ -1 -3 -5) => -9 14 | "with a mix of signed integers" 15 | (+ +5 -3) => 2))) 16 | 17 | -------------------------------------------------------------------------------- /src/cards/untangled_template/cards.cljs: -------------------------------------------------------------------------------- 1 | (ns untangled-template.cards 2 | (:require [untangled-template.intro])) 3 | -------------------------------------------------------------------------------- /src/cards/untangled_template/intro.cljs: -------------------------------------------------------------------------------- 1 | (ns untangled-template.intro 2 | (:require [devcards.core :as rc :refer-macros [defcard]] 3 | [om.next :as om :refer-macros [defui]] 4 | [untangled-template.ui.components :as comp] 5 | [om.dom :as dom])) 6 | 7 | (defcard SVGPlaceholder 8 | "# SVG Placeholder" 9 | (comp/ui-placeholder {:w 200 :h 200})) 10 | -------------------------------------------------------------------------------- /src/client/untangled_template/core.cljs: -------------------------------------------------------------------------------- 1 | (ns untangled-template.core 2 | (:require [om.next :as om] 3 | [untangled.client.core :as uc] 4 | [untangled.client.data-fetch :as f] 5 | untangled-template.state.mutations 6 | [untangled.client.logging :as log])) 7 | 8 | (defn merge-mutations [state k p] 9 | (log/info "Got return value for " k " -> " p) 10 | state) 11 | 12 | (defonce app 13 | (atom (uc/new-untangled-client 14 | :mutation-merge merge-mutations 15 | :started-callback (fn [{:keys [reconciler]}] 16 | (f/load-data reconciler [:logged-in? :current-user] 17 | :post-mutation 'login/login-complete) 18 | ;;TODO: initial load of data 19 | )))) 20 | -------------------------------------------------------------------------------- /src/client/untangled_template/main.cljs: -------------------------------------------------------------------------------- 1 | (ns untangled-template.main 2 | (:require [untangled-template.core :refer [app]] 3 | [untangled.client.core :as core] 4 | [untangled-template.ui.root :as root])) 5 | 6 | (reset! app (core/mount @app root/Root "app")) 7 | -------------------------------------------------------------------------------- /src/client/untangled_template/state/mutations.cljs: -------------------------------------------------------------------------------- 1 | (ns untangled-template.state.mutations 2 | (:require [om.next :as om] 3 | [untangled.client.mutations :refer [mutate post-mutate]] 4 | [untangled.client.impl.data-fetch :as df])) 5 | 6 | (comment 7 | (defmethod mutate 'app/do-thing [{:keys [state ast] :as env} mut-name params] 8 | {:remote true; or modify (env :ast) 9 | :action (fn [] (swap! state assoc :thing params))})) 10 | -------------------------------------------------------------------------------- /src/client/untangled_template/ui/components.cljs: -------------------------------------------------------------------------------- 1 | (ns untangled-template.ui.components 2 | (:require 3 | [om.next :as om :refer-macros [defui]] 4 | [om.dom :as dom])) 5 | 6 | (defui PlaceholderImage 7 | Object 8 | (render [this] 9 | (let [{:keys [w h label]} (om/props this) 10 | label (or label (str w "x" h))] 11 | (dom/svg #js {:width w :height h} 12 | (dom/rect #js {:width w :height h :style #js {:fill "rgb(200,200,200)" 13 | :strokeWidth 2 14 | :stroke "black"}}) 15 | (dom/text #js {:textAnchor "middle" :x (/ w 2) :y (/ h 2)} label))))) 16 | 17 | (def ui-placeholder (om/factory PlaceholderImage)) 18 | -------------------------------------------------------------------------------- /src/client/untangled_template/ui/login.cljs: -------------------------------------------------------------------------------- 1 | (ns untangled-template.ui.login 2 | (:require [om.next :as om :refer-macros [defui]] 3 | [untangled.client.core :as u] 4 | [untangled.client.data-fetch :as f] 5 | [om.dom :as dom] 6 | [om-css.core :as css :refer-macros [localize-classnames]] 7 | [untangled.client.mutations :as m])) 8 | 9 | (defmethod m/mutate 'login/attempt-login [{:keys [state]} k {:keys [uid]}] 10 | {:remote true 11 | :action (fn [] (swap! state assoc 12 | :current-user {:id uid :name "???"} 13 | :server-down false))}) 14 | 15 | (defmethod m/mutate 'login/server-down [{:keys [state]} k p] 16 | {:action (fn [] (swap! state assoc :server-down true))}) 17 | 18 | (defmethod m/mutate 'login/login-complete [{:keys [state]} k p] 19 | {:action (fn [] 20 | (let [{:keys [logged-in? current-user]} @state] 21 | (if logged-in? 22 | (swap! state assoc :current-page [:main :page]) 23 | (swap! state assoc :current-page [:login :page]))))}) 24 | 25 | (defmethod m/mutate 'login/logout [{:keys [state]} k p] 26 | {:remote true 27 | :action (fn [] 28 | (swap! state assoc 29 | :current-user {} 30 | :logged-in? false 31 | :current-page [:login :page]))}) 32 | 33 | (defui ^:once LoginPage 34 | static u/InitialAppState 35 | (initial-state [this params] {:id :login :ui/username "" :ui/password "" :ui/server-down false :ui/error nil}) 36 | static om/IQuery 37 | (query [this] [:id :ui/username :ui/password [:server-down '_] [:ui/loading-data '_]]) 38 | static css/CSS 39 | (css [this] [[(css/local-kw LoginPage :form)]]) 40 | static om/Ident 41 | (ident [this props] [:login :page]) 42 | Object 43 | (render [this] 44 | (localize-classnames LoginPage 45 | (let [{:keys [ui/username ui/password server-down ui/loading-data]} (om/props this)] 46 | (dom/div nil 47 | (dom/div #js {:className "row"} 48 | (dom/div #js {:className "col-xs-4"} "") 49 | (dom/div #js {:class [:form :$col-xs-4]} 50 | (when server-down 51 | (dom/div nil "Unable to contact server. Try again later.")) 52 | (when loading-data 53 | (dom/div nil "Working...")) 54 | (dom/div #js {:className "form-group"} 55 | (dom/label #js {:htmlFor "username"} "Username") 56 | (dom/input #js {:className "form-control" :name "username" :value username 57 | :onChange #(m/set-string! this :ui/username :event %)})) 58 | (dom/div #js {:className "form-group"} 59 | (dom/label #js {:htmlFor "password"} "Password") 60 | (dom/input #js {:name "password" :className "form-control" :type "password" :value password 61 | :onChange #(m/set-string! this :ui/password :event %)})) 62 | (dom/button #js {:onClick #(om/transact! this `[(login/attempt-login {:uid ~(om/tempid) :u ~username :p ~password}) 63 | (tx/fallback {:action login/server-down}) 64 | (untangled/load {:query [:logged-in? :current-user] :post-mutation login/login-complete}) 65 | :ui/react-key 66 | :logged-in? 67 | :current-user 68 | ])} "Login"))) 69 | (dom/div #js {:className "row"} 70 | (dom/div #js {:className "col-xs-4"} "") 71 | (dom/div #js {:className "col-xs-4"} 72 | "Don't have a login yet? " 73 | (dom/a #js {:onClick #(om/transact! this '[(nav/new-user) :ui/react-key])} "Sign up!")))))))) 74 | 75 | (def ui-login (om/factory LoginPage)) 76 | -------------------------------------------------------------------------------- /src/client/untangled_template/ui/main.cljs: -------------------------------------------------------------------------------- 1 | (ns untangled-template.ui.main 2 | (:require [om.next :as om :refer-macros [defui]] 3 | [untangled.client.core :as u] 4 | [om.dom :as dom] 5 | [om-css.core :as css :refer-macros [localize-classnames]] 6 | [untangled.client.mutations :as m])) 7 | 8 | (defui ^:once MainPage 9 | static u/InitialAppState 10 | (initial-state [this params] {:id :main}) 11 | static om/IQuery 12 | (query [this] [:id [:current-user '_]]) 13 | static css/CSS 14 | (css [this] [[(css/local-kw MainPage :x)]]) 15 | static om/Ident 16 | (ident [this props] [:main :page]) 17 | Object 18 | (render [this] 19 | (localize-classnames MainPage 20 | (let [{:keys [current-user]} (om/props this)] 21 | (dom/div #js {:class :form} "MAIN PAGE"))))) 22 | 23 | (def ui-main (om/factory MainPage)) 24 | -------------------------------------------------------------------------------- /src/client/untangled_template/ui/new_user.cljs: -------------------------------------------------------------------------------- 1 | (ns untangled-template.ui.new-user 2 | (:require [om.next :as om :refer-macros [defui]] 3 | [untangled.client.core :as u] 4 | [untangled.client.data-fetch :as f] 5 | [om.dom :as dom] 6 | [om-css.core :as css :refer-macros [localize-classnames]] 7 | [untangled.client.mutations :as m])) 8 | 9 | (defui ^:once NewUser 10 | static u/InitialAppState 11 | (initial-state [this params] {:id :new-user :ui/username "" :ui/password "" :ui/password2 ""}) 12 | static om/IQuery 13 | (query [this] [:id :ui/username :ui/password :ui/password2]) 14 | static om/Ident 15 | (ident [this props] [:new-user :page]) 16 | Object 17 | (render [this] 18 | (localize-classnames NewUser 19 | (let [{:keys [ui/username ui/password ui/password2]} (om/props this)] 20 | (dom/div nil 21 | (dom/div #js {:className "row"} 22 | (dom/div #js {:className "col-xs-4"} "") 23 | (dom/div #js {:class [:form :$col-xs-4]} 24 | (dom/div #js {:className "form-group"} 25 | (dom/label #js {:htmlFor "username"} "Email Address") 26 | (dom/input #js {:className "form-control" :type "email" :name "username" :value username 27 | :onChange #(m/set-string! this :ui/username :event %)})) 28 | (dom/div #js {:className "form-group"} 29 | (dom/label #js {:htmlFor "password"} "Password") 30 | (dom/input #js {:name "password" :className "form-control" 31 | :type "password" :value password 32 | :onChange #(m/set-string! this :ui/password :event %)})) 33 | (dom/div #js {:className "form-group"} 34 | (dom/label #js {:htmlFor "password2"} "Verify your Password") 35 | (dom/input #js {:name "password2" :className "form-control" 36 | :type "password" :value password2 37 | :onChange #(m/set-string! this :ui/password2 :event %)})) 38 | (dom/button #js {:onClick #(om/transact! this `[(new-user/sign-up {:uid ~(om/tempid) :u ~username :p ~password})])} "Sign Up!")))))))) 39 | 40 | (def ui-new-user (om/factory NewUser)) 41 | -------------------------------------------------------------------------------- /src/client/untangled_template/ui/root.cljs: -------------------------------------------------------------------------------- 1 | (ns untangled-template.ui.root 2 | (:require 3 | [untangled.client.mutations :as mut] 4 | [om.dom :as dom] 5 | [untangled-template.ui.login :as l] 6 | [untangled-template.ui.main :as main] 7 | [untangled-template.ui.new-user :as nu] 8 | [om.next :as om :refer-macros [defui]] 9 | [untangled.client.core :as u] 10 | [untangled.client.mutations :as m])) 11 | 12 | (defn nav-to [env page] (swap! (:state env) assoc :current-page [page :page])) 13 | 14 | (defmethod m/mutate 'nav/new-user [env k p] {:action (fn [] (nav-to env :new-user))}) 15 | (defmethod m/mutate 'nav/login [env k p] {:action (fn [] (nav-to env :login))}) 16 | (defmethod m/mutate 'nav/main [env k p] {:action (fn [] (nav-to env :main))}) 17 | 18 | (defui ^:once Loading 19 | static u/InitialAppState 20 | (initial-state [this params] {:id :loading}) 21 | static om/IQuery 22 | (query [this] [:id]) 23 | static om/Ident 24 | (ident [this props] [:loading :page]) 25 | Object 26 | (render [this] 27 | (dom/div nil "Loading..."))) 28 | 29 | (def ui-loading (om/factory Loading)) 30 | 31 | (defui ^:once Pages 32 | static u/InitialAppState 33 | (initial-state [this params] (u/initial-state Loading nil)) 34 | static om/IQuery 35 | (query [this] {:loading (om/get-query Loading) 36 | :new-user (om/get-query nu/NewUser) 37 | :login (om/get-query l/LoginPage) 38 | :main (om/get-query main/MainPage)}) 39 | static om/Ident 40 | (ident [this props] [(:id props) :page]) 41 | Object 42 | (render [this] 43 | (let [{:keys [id login] :as props} (om/props this)] 44 | (case id 45 | :new-user (nu/ui-new-user props) 46 | :loading (ui-loading props) 47 | :login (l/ui-login props) 48 | :main (main/ui-main props) 49 | (dom/div nil "MISSING PAGE"))))) 50 | 51 | (def ui-pages (om/factory Pages)) 52 | 53 | (defn ui-login-stats [loading? user logout-fn] 54 | (dom/p #js {:className "navbar-text navbar-right"} 55 | (when loading? 56 | (dom/span #js {:className "badge"} "...")) 57 | (:name user) 58 | (dom/br nil) 59 | (dom/a #js {:onClick logout-fn} " Log out"))) 60 | 61 | (defn ui-login-button [loading? login-fn] 62 | (dom/p #js {:className "navbar-right"} 63 | (when loading? 64 | (dom/span #js {:className "navbar-text badge"} "...")) 65 | (dom/button #js {:type "button" 66 | :onClick login-fn 67 | :className "btn btn-default navbar-btn "} 68 | "Sign in"))) 69 | 70 | (defn ui-navbar [this] 71 | (let [login #(om/transact! this '[(nav/login)]) 72 | logout #(om/transact! this '[(login/logout)]) 73 | {:keys [ui/loading-data current-user logged-in?]} (om/props this)] 74 | (dom/div #js {:className "navbar navbar-default"} 75 | (dom/div #js {:className "container-fluid"} 76 | (dom/div #js {:className "navbar-header"} 77 | (dom/span #js {:className "navbar-brand"} 78 | (dom/span nil "Template Brand"))) 79 | (dom/div #js {:className "collapse navbar-collapse"} 80 | (when logged-in? 81 | (dom/ul #js {:className "nav navbar-nav"} 82 | ;; More nav links here 83 | (dom/li nil (dom/a #js {:className "active" :onClick #(om/transact! this '[(nav/main)]) :href "#"} "Main")))) 84 | (if logged-in? 85 | (ui-login-stats loading-data current-user logout) 86 | (ui-login-button loading-data login))))))) 87 | 88 | (defui ^:once Root 89 | static om/IQuery 90 | (query [this] [:ui/react-key :logged-in? :current-user :ui/loading-data {:current-page (om/get-query Pages)}]) 91 | static u/InitialAppState 92 | (initial-state [this params] {:logged-in? false :current-user {} :current-page (u/initial-state Pages nil)}) 93 | Object 94 | (render [this] 95 | (let [{:keys [ui/loading-data ui/react-key current-page current-user logged-in?] :or {ui/react-key "ROOT"}} (om/props this) 96 | logout #(om/transact! this '[(login/logout)])] 97 | (dom/div #js {:key react-key} 98 | (ui-navbar this) 99 | (ui-pages (om/computed current-page {:logout logout})))))) 100 | -------------------------------------------------------------------------------- /src/server/config/defaults.edn: -------------------------------------------------------------------------------- 1 | { :port 3000} 2 | -------------------------------------------------------------------------------- /src/server/config/dev.edn: -------------------------------------------------------------------------------- 1 | { :port 3000} 2 | -------------------------------------------------------------------------------- /src/server/config/prod.edn: -------------------------------------------------------------------------------- 1 | ; Config files can read raw strings or EDN from env variables (:env/VAR and :env.edn/VAR) 2 | {:user :env/USER 3 | :port :env.edn/PORT} 4 | -------------------------------------------------------------------------------- /src/server/untangled_template/api/mutations.clj: -------------------------------------------------------------------------------- 1 | (ns untangled-template.api.mutations 2 | (:require 3 | 4 | [om.next.server :as oms] 5 | [taoensso.timbre :as timbre] 6 | [untangled.server.core :as core])) 7 | 8 | (defmulti apimutate oms/dispatch) 9 | 10 | (defonce logged-in? (atom false)) 11 | 12 | (defmethod apimutate 'login/attempt-login [env k {:keys [u p uid]}] 13 | {:action (fn [] 14 | (reset! logged-in? true) 15 | {:uid 42 16 | :tempids {uid 42}})}) 17 | 18 | (defmethod apimutate 'login/logout [{:keys [sample-component] :as env} k {:keys [u p uid]}] 19 | ; Demo that the injected component appears in env 20 | (timbre/info "Sample component data: " (:stuff sample-component)) 21 | {:action (fn [] (reset! logged-in? false))}) 22 | -------------------------------------------------------------------------------- /src/server/untangled_template/api/read.clj: -------------------------------------------------------------------------------- 1 | (ns untangled-template.api.read 2 | (:require 3 | ;[untangled.datomic.protocols :as udb] 4 | [untangled-template.api.mutations :as m] 5 | [taoensso.timbre :as timbre])) 6 | 7 | (timbre/info "Loading API definitions for untangled-template.api.read") 8 | 9 | 10 | (defn api-read [{:keys [query request] :as env} disp-key params] 11 | ;(let [connection (udb/get-connection survey-database)]) 12 | (case disp-key 13 | :logged-in? {:value @m/logged-in?} 14 | :hello-world {:value 42} 15 | :current-user {:value {:id 42 :name "Tony Kay"}} 16 | (throw (ex-info "Invalid request" {:query query :key disp-key})))) 17 | -------------------------------------------------------------------------------- /src/server/untangled_template/core.clj: -------------------------------------------------------------------------------- 1 | (ns untangled-template.core 2 | (:require 3 | [com.stuartsierra.component :as component] 4 | [untangled.server.core :as c] 5 | [untangled.server.impl.components.config :refer [load-config]] 6 | [taoensso.timbre :as timbre] 7 | [untangled-template.system :as sys]) 8 | (:gen-class)) 9 | 10 | (def config-path "/usr/local/etc/untangled_template.edn") 11 | 12 | (defn -main [& args] 13 | (let [system (sys/make-system config-path)] 14 | (component/start system))) 15 | -------------------------------------------------------------------------------- /src/server/untangled_template/system.clj: -------------------------------------------------------------------------------- 1 | (ns untangled-template.system 2 | (:require 3 | [com.stuartsierra.component :as component] 4 | [untangled-template.api.mutations :as m] 5 | [untangled-template.api.read :as r] 6 | [ring.middleware.content-type :refer [wrap-content-type]] 7 | [ring.middleware.gzip :refer [wrap-gzip]] 8 | [ring.middleware.not-modified :refer [wrap-not-modified]] 9 | [ring.middleware.params :refer [wrap-params]] 10 | [ring.middleware.resource :refer [wrap-resource]] 11 | [untangled.server.core :as core] 12 | [untangled.server.impl.middleware :as middleware] 13 | [clojure.java.io :as io] 14 | [taoensso.timbre :as timbre])) 15 | 16 | ; This is both a server module AND hooks into the Om parser for the incoming /api read/mutate requests. The 17 | ; modular server support lets you chain as many of these together as you want, allowing you to define 18 | ; reusable Om server components. 19 | (defrecord ApiHandler [] 20 | core/Module 21 | (system-key [this] ::api) 22 | (components [this] {}) 23 | core/APIHandler 24 | (api-read [this] r/api-read) 25 | (api-mutate [this] m/apimutate)) 26 | 27 | (defn build-api-handler [& [deps]] 28 | "`deps`: Vector of keys passed as arguments to be 29 | included as dependecies in `env`." 30 | (component/using 31 | (map->ApiHandler {}) deps)) 32 | 33 | (defn MIDDLEWARE [handler component] 34 | ((get component :middleware) handler)) 35 | 36 | (defn not-found [req] 37 | {:status 404 38 | :headers {"Content-Type" "text/plain"} 39 | :body "Resource not found."}) 40 | 41 | (defrecord CustomMiddleware [middleware api-handler] 42 | component/Lifecycle 43 | (stop [this] (dissoc this :middleware)) 44 | (start [this] 45 | (assoc this :middleware 46 | (-> not-found 47 | (MIDDLEWARE api-handler) 48 | ;; TRANSIT 49 | middleware/wrap-transit-params 50 | middleware/wrap-transit-response 51 | ;; RESOURCES 52 | (wrap-resource "public") 53 | ;; HELPERS 54 | wrap-content-type 55 | wrap-not-modified 56 | wrap-params 57 | wrap-gzip)))) 58 | 59 | ; IMPORTANT: You want to inject the built-in API handler (which is where modular API handlers get chained) 60 | (defn build-middleware [] 61 | (component/using 62 | (map->CustomMiddleware {}) 63 | {:api-handler ::core/api-handler})) 64 | 65 | (defrecord Sample [stuff] 66 | component/Lifecycle 67 | (start [this] (assoc this :stuff {:data 42})) 68 | (stop [this] this)) 69 | 70 | (defn make-system [config-path] 71 | (core/untangled-system 72 | {:components {:config (core/new-config config-path) ; you MUST use config if you use our server 73 | ::middleware (build-middleware) ; complete Ring stack 74 | :sample-component (map->Sample {}) 75 | :web-server (core/make-web-server ::middleware)} ; must provide the component key to your middleware 76 | ; Modules are composable into the Om API handler (can have their own read/mutate) and 77 | ; are joined together in a chain. Any components you declare as deps will appear in the parser env. 78 | :modules [(build-api-handler [:sample-component])]})) 79 | --------------------------------------------------------------------------------