├── Procfile ├── .gitignore ├── profiles.clj ├── src └── address_book │ └── core │ ├── models │ ├── query_defs.clj │ └── address_book_queries.sql │ ├── handler.clj │ ├── routes │ └── address_book_routes.clj │ └── views │ └── address_book_layout.clj ├── provision_example_database.sql ├── README.md ├── project.clj ├── test └── address_book │ └── core │ └── address_book_tests.clj └── resources └── public └── css └── address-book.css /Procfile: -------------------------------------------------------------------------------- 1 | web: lein with-profile production trampoline ring server 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | -------------------------------------------------------------------------------- /profiles.clj: -------------------------------------------------------------------------------- 1 | {:dev-env-vars {:env {:database-url "postgres://address_book_user:password1@127.0.0.1:5432/address_book"}} 2 | 3 | :test-env-vars {:env {:database-url "postgres://address_book_user:password1@127.0.0.1:5432/address_book_test"}}} 4 | -------------------------------------------------------------------------------- /src/address_book/core/models/query_defs.clj: -------------------------------------------------------------------------------- 1 | (ns address-book.core.models.query-defs 2 | (:require [environ.core :refer [env]] 3 | [yesql.core :refer [defqueries]])) 4 | 5 | (defqueries "address_book/core/models/address_book_queries.sql" {:connection (env :database-url)}) 6 | -------------------------------------------------------------------------------- /provision_example_database.sql: -------------------------------------------------------------------------------- 1 | CREATE ROLE address_book_user LOGIN; 2 | ALTER ROLE address_book_user WITH PASSWORD 'password1'; 3 | CREATE DATABASE address_book; 4 | CREATE DATABASE address_book_test; 5 | GRANT ALL PRIVILEGES ON DATABASE address_book TO address_book_user; 6 | GRANT ALL PRIVILEGES ON DATABASE address_book_test TO address_book_user; 7 | -------------------------------------------------------------------------------- /src/address_book/core/handler.clj: -------------------------------------------------------------------------------- 1 | (ns address-book.core.handler 2 | (:require [compojure.core :refer :all] 3 | [compojure.route :as route] 4 | [ring.middleware.defaults :refer [wrap-defaults site-defaults]] 5 | [address-book.core.routes.address-book-routes :refer [address-book-routes]] 6 | [address-book.core.models.query-defs :as query])) 7 | 8 | (defn init [] 9 | (query/create-contacts-table-if-not-exists!)) 10 | 11 | (defroutes app-routes 12 | (route/not-found "Not Found")) 13 | 14 | (def app 15 | (-> (routes address-book-routes app-routes) 16 | (wrap-defaults (assoc-in site-defaults [:security :anti-forgery] false)))) 17 | -------------------------------------------------------------------------------- /src/address_book/core/models/address_book_queries.sql: -------------------------------------------------------------------------------- 1 | -- name: all-contacts 2 | -- Selects all contacts 3 | SELECT id 4 | ,name 5 | ,phone 6 | ,email 7 | FROM contacts; 8 | 9 | -- name: insert-contact -f provision_example_database.sql 23 | 24 | ## Running the application locally 25 | 26 | To start a web server for the application, run: 27 | 28 | lein ring server 29 | 30 | ## Running the tests 31 | 32 | lein with-profile test midje 33 | 34 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject address-book "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :min-lein-version "2.5.0" 5 | 6 | :ring {:handler address-book.core.handler/app 7 | :init address-book.core.handler/init} 8 | 9 | :dependencies [[org.clojure/clojure "1.6.0"] 10 | [compojure "1.3.1"] 11 | [ring/ring-defaults "0.1.3"] 12 | [hiccup "1.0.5"] 13 | [org.clojure/java.jdbc "0.3.6"] 14 | [postgresql/postgresql "9.3-1102.jdbc41"] 15 | [yesql "0.5.0-rc1"] 16 | [environ "1.0.0"]] 17 | 18 | :plugins [[lein-ring "0.9.1"] 19 | [lein-environ "1.0.0"]] 20 | 21 | :profiles {:test-local {:dependencies [[midje "1.6.3"] 22 | [javax.servlet/servlet-api "2.5"] 23 | [ring-mock "0.1.5"]] 24 | 25 | :plugins [[lein-midje "3.1.3"]]} 26 | 27 | ;; Set these in ./profiles.clj 28 | :test-env-vars {} 29 | :dev-env-vars {} 30 | 31 | :test [:test-local :test-env-vars] 32 | :dev [:dev-env-vars] 33 | 34 | :production {:ring {:open-browser? false 35 | :stacktraces? false 36 | :auto-reload? false}}}) 37 | -------------------------------------------------------------------------------- /test/address_book/core/address_book_tests.clj: -------------------------------------------------------------------------------- 1 | (ns address-book.core.address-book-tests 2 | (:use midje.sweet) 3 | (:require [clojure.test :refer :all] 4 | [ring.mock.request :as mock] 5 | [address-book.core.handler :refer :all] 6 | [address-book.core.models.query-defs :as query])) 7 | 8 | (facts "Example GET and POST tests" 9 | (with-state-changes [(before :facts (query/create-contacts-table-if-not-exists!)) 10 | (after :facts (query/drop-contacts-table!))] 11 | 12 | (fact "Test GET request to / route returns expected contacts" 13 | (query/insert-contact 200 17 | (:body response) => (contains "
JT
") 18 | (:body response) => (contains "
Utah
"))) 19 | 20 | (fact "Test POST to /post creates a new contact" 21 | (count (query/all-contacts)) => 0 22 | (let [response (app (mock/request :post "/post" {:name "Some Guy" :phone "(123)" :email "a@a.cim"}))] 23 | (:status response) => 302 24 | (count (query/all-contacts)) => 1)) 25 | 26 | (fact "Test UPDATE a post request to /edit/ updates desired contact information" 27 | (query/insert-contact 302 30 | (count (query/all-contacts)) => 1 31 | (first (query/all-contacts)) => {:id 1 :name "Jrock" :phone "(999) 888-7777" :email "jrock@test.com"})) 32 | 33 | (fact "Test DELETED a post to /delete/ deletes desired contact from database" 34 | (query/insert-contact 1 36 | (let [response (app (mock/request :post "/delete/1" {:id 1}))] 37 | (count (query/all-contacts)) => 0)))) 38 | -------------------------------------------------------------------------------- /src/address_book/core/routes/address_book_routes.clj: -------------------------------------------------------------------------------- 1 | (ns address-book.core.routes.address-book-routes 2 | (:require [ring.util.response :as response] 3 | [compojure.core :refer :all] 4 | [address-book.core.views.address-book-layout :refer [common-layout 5 | read-contact 6 | add-contact-form 7 | edit-contact]] 8 | [address-book.core.models.query-defs :as query])) 9 | 10 | (defn display-contact [contact contact-id] 11 | (if (not= (and contact-id (Integer. contact-id)) (:id contact)) 12 | (read-contact contact) 13 | (edit-contact contact))) 14 | 15 | (defn post-route [request] 16 | (let [name (get-in request [:params :name]) 17 | phone (get-in request [:params :phone]) 18 | email (get-in request [:params :email])] 19 | (query/insert-contact