...
 
Commits (4)
......@@ -19,7 +19,8 @@
[failjure "1.3.0"]
[http-kit "2.2.0"]
[clj-time "0.15.0"]
[cider/cider-nrepl "0.18.0"]]
[cider/cider-nrepl "0.18.0"]
[honeysql "0.9.4"]]
:profiles {:dev { :dependencies [[venantius/pyro "0.1.2"]]
:injections [(require '[pyro.printer :as printer])
(printer/swap-stacktrace-engine!)] }
......
......@@ -26,6 +26,7 @@
(-> (make-handler ["/" {"status" status-handler
"login" {:get login/get-login
:post login/post-login}
"notes" {:get (wrap-auth proj/get-all-notes)}
["organisations/" :organisation-slug "/projects/" :project-slug "/members"]
{:get (wrap-auth proj/get-list-members)
:post (wrap-auth proj/post-create-member)}
......@@ -62,8 +63,6 @@
(defn stop-server []
(when-not (nil? @server)
;; graceful shutdown: wait 100ms for existing requests to be finished
;; :timeout is optional, when no timeout, stop immediately
(@server :timeout 100)
(reset! server nil)))
......
......@@ -5,7 +5,7 @@
;;;
(ns nnts.db.core
(:require [clojure.java.jdbc :as jdbc]))
(:require [clojure.java.jdbc :as jdbc]))
(defn retrieve-all-rows
......@@ -48,5 +48,6 @@
(defn run-custom-query
"Run the honeysql query"
[conn query]
(jdbc/query conn query))
......@@ -5,9 +5,12 @@
(ns nnts.db.project
(:require [nnts.db.core :as db]
[nnts.db.helper :as db-helper]
[nnts.helpers.core :as helper]
[failjure.core :as f]
[clj-time.core :as time]
[clj-time.coerce :as coerce]))
[clj-time.coerce :as coerce]
[honeysql.core :as sql]
[honeysql.helpers :as h]))
(defn create-new-project
......@@ -77,7 +80,7 @@
"Retrieve the project member by project id and user id."
[conn project-id user-id]
(let [member (db/retrieve-all-rows-where conn "project_members" "project_id" project-id "user_id" user-id)]
(if-not member
(if (empty? member)
(f/fail (db-helper/construct-db-error :project-member-not-found))
(first member))))
......@@ -123,3 +126,22 @@
(db/update-row-by-id conn "project_notes" id note))
(catch Exception e
(f/fail (db-helper/construct-db-error :edit-note-failed)))))
(def all-notes-query-map
(-> (h/select :project_notes.* :users.first_name :users.last_name)
(h/from :project_notes)
(h/left-join :users [:= :project_notes.user_id :users.id])
(h/where [:and
[:= :project_notes.project_id (sql/param :project-id)]
[:= :project_notes.note_date (sql/param :note-date)]])))
(defn get-all-notes-by-note-date-and-project-id
[conn note-date project-id]
(try
(let [note-sql-date (coerce/to-sql-date note-date)
project-id-uuid (helper/string->uuid project-id)
notes-query (sql/format all-notes-query-map {:project-id project-id-uuid :note-date note-sql-date})]
(db/run-custom-query conn notes-query))
(catch Exception e
(f/fail (db-helper/construct-db-error :get-all-notes-for-project-by-date-failed)))))
(ns nnts.handlers.helper
(:require [ring.util.response :as res]))
(:require [ring.util.response :as res]
[clj-time.format :as f]))
(defn create-response
......@@ -11,17 +12,8 @@
(assoc-in response [:body :description] (first human-readabla-message))
response)))
(defn string->uuid
[uuid-str]
(java.util.UUID/fromString uuid-str))
(defn construct-handler-error
[error human-readable-message]
{:type :handler-error
:error error
:human-readable-message human-readable-message})
......@@ -7,6 +7,7 @@
[nnts.models.organisation :as org]
[nnts.models.user :as user]
[nnts.handlers.helper :as helper]
[nnts.helpers.core :as ch]
[nnts.models.project :as proj]
[clj-time.format :as format]))
......@@ -14,15 +15,15 @@
(defn get-http-status-code-for-error-kind
[kind]
(case kind
:project-member-already-exists 409
:project-already-exists 409
:project-member-already-exists 409
:project-already-exists 409
:slug-does-not-match-any-organisation 400
:invalid-project-slug 400
:validation-failed-user-not-found 400
:user-not-owner-of-organisation 400
:project-data-invalid 400
:failed-to-create-note-project-member-does-not-exist 400
:failed-to-create-note 500
:invalid-project-slug 400
:validation-failed-user-not-found 400
:user-not-owner-of-organisation 400
:project-data-invalid 400
:project-member-does-not-exist 400
:failed-to-create-note 500
500))
......@@ -67,7 +68,7 @@
"Retrieve the list of all users for the (organisation, project) combination."
[{:keys [route-params authenticated-user-id] :as req}]
(let [organisation-slug (:organisation-slug route-params)
project-slug (:project-slug route-params)]
project-slug (:project-slug route-params)]
(f/attempt-all [valid-organisation-id (org/validate-organisation-by-slug
organisation-slug
authenticated-user-id)
......@@ -91,7 +92,7 @@
"Helper function to create the project_member."
[project-id user-id]
{:project_id project-id
:user_id user-id})
:user_id user-id})
;;
......@@ -104,9 +105,9 @@
add user as a new member of the project."
[{:keys [form-params route-params authenticated-user-id] :as req}]
(let [organisation-slug (:organisation-slug route-params)
project-slug (:project-slug route-params)
user-id (-> (get form-params "user-id")
helper/string->uuid)]
project-slug (:project-slug route-params)
user-id (-> (get form-params "user-id")
ch/string->uuid)]
(f/attempt-all [valid-user-id (user/validate-user-id user-id)
valid-organisation-id (org/validate-organisation-by-slug
organisation-slug
......@@ -130,7 +131,7 @@
"Retrieve all the notes for the (organisation, project) combination"
[{:keys [form-params route-params authenticated-user-id] :as req}]
(let [organisation-slug (:organisation-slug route-params)
project-slug (:project-slug route-params)]
project-slug (:project-slug route-params)]
(f/attempt-all [valid-organisation-id (org/validate-organisation-by-slug
organisation-slug
authenticated-user-id)
......@@ -153,11 +154,11 @@
(defn- note
[date title body project-id user-id]
{:note_date (format/parse custom-date-formatter date)
:title title
:body body
{:note_date (format/parse custom-date-formatter date)
:title title
:body body
:project_id project-id
:user_id user-id})
:user_id user-id})
(defn- create-error-response
......@@ -225,3 +226,23 @@
(f/fail (f/message e))))
(f/when-failed [{:keys [message] :as e}]
(create-error-response message)))))
(defn validate-note-date
[date]
(s/valid? :nnts.models.project/date date))
(defn get-all-notes
"Retrieve all notes of all users for the given project."
[{:keys [query-params authenticated-user-id] :as req}]
(let [{:strs [note-date project-id]} query-params]
(if-not (validate-note-date note-date)
(helper/create-response :invalid-note-date 400)
(f/attempt-all [_ (proj/get-project-owner-or-member
(ch/string->uuid project-id)
authenticated-user-id)
notes (proj/get-all-notes-for-project-by-note-date note-date
project-id)]
(-> (helper/create-response "Ok" 200)
(assoc-in [:body :notes] notes))
(f/when-failed [{:keys [message] :as e}]
(create-error-response message))))))
(ns nnts.helpers.core)
(defn string->uuid
[uuid-str]
(java.util.UUID/fromString uuid-str))
(ns nnts.helpers.core)
(defn string->uuid
[uuid-str]
(println "HERE")
(java.util.UUID/fromString uuid-str))
......@@ -19,6 +19,10 @@
;; create spec for project structure
(s/def ::project (s/keys :req-un [::name ::description ::slug ::organisation_id]))
;; spec for date
(s/def ::date #(re-find #"\d{4}\-\d{2}\-\d{2}" %))
(defn create-project
[project]
......@@ -36,10 +40,6 @@
(:organisation_id project)))
(helper/construct-unknown-error))))))
;;
;; project members
;;
(defn validate-project-by-slug
[project-slug organisation-id]
(f/attempt-all [project (db-project/get-project-by-slug-and-organisation-id (db-helper/get-conn)
......@@ -55,6 +55,10 @@
organisation-id))
(helper/construct-unknown-error))))))
(defn validate-project-by-id
"Returns organisation id if project is valid."
[project-id])
(defn add-member-to-project
[project-member]
......@@ -85,7 +89,6 @@
(f/fail (case (:error message)
(helper/construct-unknown-error))))))
(defn get-project-owner-or-member
[project-id user-id]
(f/attempt-all [owner (db-project/get-owner-of-project (db-helper/get-conn) project-id)]
......@@ -101,7 +104,7 @@
(db-helper/get-conn) project-id user-id)]
entry
(f/fail (helper/construct-model-error
:failed-to-create-note-project-member-does-not-exist
:project-member-does-not-exist
(format "User %s is not the owner or a member of project %s"
user-id
project-id))))
......@@ -128,3 +131,13 @@
:failed-to-edit-note
(format "Failed to edit note %s"
note)))))))
(defn get-all-notes-for-project-by-note-date
[note-date project-id]
(f/attempt-all [notes (db-project/get-all-notes-by-note-date-and-project-id (db-helper/get-conn)
note-date
project-id)]
notes
(f/when-failed [{:keys [message] :as e}]
(f/fail (case (:error message)
(helper/construct-unknown-error))))))
......@@ -127,3 +127,33 @@
(doall (map #(sut/create-new-note db-helpers/*txn* %) project-notes))
(let [notes (sut/get-all-notes-by-project-id db-helpers/*txn* (:id project))]
(is (= 10 (count notes))))))))
(deftest test-view-all-notes-by-date-valid
(testing "Viewing all the notes of a project by note date should succeed."
(let [owner (factory/get-new-user)
organisation (assoc (factory/get-new-organisation) :owner_id (:id owner))
project (assoc (factory/get-new-project) :organisation_id (:id organisation))]
(db-user/create-or-update-user db-helpers/*txn* owner)
(db-org/create-new-organisation db-helpers/*txn* organisation)
(sut/create-new-project db-helpers/*txn* project)
(let [note (factory/get-new-project-note (:id project) (:id owner))
project-notes (replicate 10 note)]
(doall (map #(sut/create-new-note db-helpers/*txn* %) project-notes))
(let [notes (sut/get-all-notes-by-note-date-and-project-id db-helpers/*txn* (:note_date note) (str (:id project)))]
(is (= 10 (count notes))))))))
(deftest test-view-all-notes-by-date-invalid
(testing "Viewing all the notes of a project by invalid note date should fail."
(let [owner (factory/get-new-user)
organisation (assoc (factory/get-new-organisation) :owner_id (:id owner))
project (assoc (factory/get-new-project) :organisation_id (:id organisation))]
(db-user/create-or-update-user db-helpers/*txn* owner)
(db-org/create-new-organisation db-helpers/*txn* organisation)
(sut/create-new-project db-helpers/*txn* project)
(let [note (factory/get-new-project-note (:id project) (:id owner))
project-notes (replicate 10 note)]
(doall (map #(sut/create-new-note db-helpers/*txn* %) project-notes))
(let [notes (sut/get-all-notes-by-note-date-and-project-id db-helpers/*txn* "0000-00-00" (str (:id project)))]
(is (= 0 (count notes))))))))
......@@ -12,6 +12,7 @@
[clojure.walk :refer (stringify-keys)]
[ring.adapter.jetty :as jetty]
[clj-http.client :as client]
[clj-time.format :as fmt]
[failjure.core :as f]
[nnts.models.helper :as helper]
[nnts.db.helper :as db-helper]))
......@@ -46,6 +47,12 @@
:authenticated-user-id (factory/get-new-uuid)
:route-params {:organisation-slug "valid-org-slug" :project-slug "valid-project-slug"}})
(defn- get-new-request-for-view-notes
[note]
{:query-params {"note-date" (:note_date note)
"project-id" (:project_id note)}
:authenticated-user-id (factory/get-new-uuid)})
(def project-atom (atom []))
......@@ -98,7 +105,7 @@
proj/edit-note (fn [id note]
(when-not (= :clojure.spec.alpha/invalid note)
(reset! project-note-atom [note])))]
(reset! project-note-atom [note])))]
(test-fn))))
......@@ -153,46 +160,46 @@
(deftest test-create-note
(testing "Creating a note for a valid (organisation, project, user) should succeed."
(let [req (get-new-request-for-project-note (factory/get-new-project-note))]
(let [res (sut/post-create-note req)]
(is (= 201 (:status res)))
(is (= :project-note-created (get-in res [:body :status])))
(is (= 1 (count @project-note-atom)))))))
(testing "Creating a note for a valid (organisation, project, user) should succeed."
(let [req (get-new-request-for-project-note (factory/get-new-project-note))]
(let [res (sut/post-create-note req)]
(is (= 201 (:status res)))
(is (= :project-note-created (get-in res [:body :status])))
(is (= 1 (count @project-note-atom)))))))
(deftest test-create-note-invalid-org
(testing "Creating a note for an invalid organisation should fail."
(let [note (factory/get-new-project-note)
req (assoc-in (get-new-request-for-project-note note) [:route-params :organisation-slug] "invalid-org-slug")]
(let [res (sut/post-create-note req)]
(is (= 500 (:status res)))
(is (= :invalid-org-slug (get-in res [:body :status])))
(is (= 0 (count @project-note-atom)))))))
(testing "Creating a note for an invalid organisation should fail."
(let [note (factory/get-new-project-note)
req (assoc-in (get-new-request-for-project-note note) [:route-params :organisation-slug] "invalid-org-slug")]
(let [res (sut/post-create-note req)]
(is (= 500 (:status res)))
(is (= :invalid-org-slug (get-in res [:body :status])))
(is (= 0 (count @project-note-atom)))))))
(deftest test-create-note-invalid-project
(testing "Creating a note for an invalid project should fail."
(let [note (factory/get-new-project-note)
req (assoc-in (get-new-request-for-project-note note) [:route-params :project-slug] invalid-uuid)]
(let [res (sut/post-create-note req)]
(is (= 400 (:status res)))
(is (= :invalid-project-slug (get-in res [:body :status])))
(is (= 0 (count @project-note-atom)))))))
(testing "Creating a note for an invalid project should fail."
(let [note (factory/get-new-project-note)
req (assoc-in (get-new-request-for-project-note note) [:route-params :project-slug] invalid-uuid)]
(let [res (sut/post-create-note req)]
(is (= 400 (:status res)))
(is (= :invalid-project-slug (get-in res [:body :status])))
(is (= 0 (count @project-note-atom)))))))
(deftest test-create-note-duplicate
(testing "Creating multiple notes with the same title and body should succeed."
(let [note (factory/get-new-project-note)
req (get-new-request-for-project-note note)]
(let [res (sut/post-create-note req)]
(is (= 201 (:status res)))
(is (= :project-note-created (get-in res [:body :status])))
(is (= 1 (count @project-note-atom))))
(let [res (sut/post-create-note req)]
(is (= 201 (:status res)))
(is (= :project-note-created (get-in res [:body :status])))
(is (= 2 (count @project-note-atom)))))))
(testing "Creating multiple notes with the same title and body should succeed."
(let [note (factory/get-new-project-note)
req (get-new-request-for-project-note note)]
(let [res (sut/post-create-note req)]
(is (= 201 (:status res)))
(is (= :project-note-created (get-in res [:body :status])))
(is (= 1 (count @project-note-atom))))
(let [res (sut/post-create-note req)]
(is (= 201 (:status res)))
(is (= :project-note-created (get-in res [:body :status])))
(is (= 2 (count @project-note-atom)))))))
(deftest test-edit-note
......@@ -214,13 +221,13 @@
(deftest test-edit-note-invalid-org
(testing "Editing a note for an invalid organisation should fail."
(let [note (factory/get-new-project-note)
req (assoc-in (get-new-request-for-project-note note) [:route-params :organisation-slug] "invalid-org-slug")]
(let [res (sut/put-edit-note req)]
(is (= 500 (:status res)))
(is (= :invalid-org-slug (get-in res [:body :status])))
(is (= 0 (count @project-note-atom)))))))
(testing "Editing a note for an invalid organisation should fail."
(let [note (factory/get-new-project-note)
req (assoc-in (get-new-request-for-project-note note) [:route-params :organisation-slug] "invalid-org-slug")]
(let [res (sut/put-edit-note req)]
(is (= 500 (:status res)))
(is (= :invalid-org-slug (get-in res [:body :status])))
(is (= 0 (count @project-note-atom)))))))
(deftest test-edit-note-invalid-project
......@@ -231,3 +238,24 @@
(is (= 400 (:status res)))
(is (= :invalid-project-slug (get-in res [:body :status])))
(is (= 0 (count @project-note-atom)))))))
(deftest test-view-all-notes-authorized
(testing "viewing all notes for a project for an authorized user should succeed."
(with-redefs [proj/get-project-owner-or-member (fn [project-id user-id]
(not= user-id invalid-uuid))
proj/get-all-notes-for-project-by-note-date (fn [note-date project-id]
(filter #(and (= project-id (str (:project_id %)))
(= (fmt/parse (fmt/formatters :date) note-date)
(:note_date %)))
@project-note-atom))]
(let [project-id (factory/get-new-uuid)
note (-> (factory/get-new-project-note project-id)
(update-in [:project_id] str))
notes-req (replicate 10 (get-new-request-for-project-note note))]
(doseq [note-req notes-req]
(sut/post-create-note note-req))
(let [note-project-id (str (:project_id (first @project-note-atom)))
get-note-req (get-new-request-for-view-notes (assoc note :project_id note-project-id))
res (sut/get-all-notes get-note-req)]
(is (= 10 (count (get-in res [:body :notes]))))
(is (= 200 (:status res))))))))
......@@ -20,6 +20,8 @@
helpers/rollback-fixture)
(def invalid-date "0000-00-00")
(deftest test-create-project
(testing "Should create a project for valid data."
(let [test-project (factory/get-new-project)
......@@ -38,10 +40,6 @@
(let [new-proj (sut/create-project test-project)]
(is (= (count @project-atom) 1))))))))
;;
;; project members
;;
(deftest test-add-project-member
(testing "Should add a valid user to a valid project."
(let [project-member (factory/get-new-project-member)
......@@ -103,10 +101,6 @@
(f/when-failed [{:keys [message] :as e}]
(is (= (:error message) :duplicate-project-member))))))))
;;
;; project notes
;;
(deftest test-create-note
(testing "Should add a valid note to a valid project."
(let [project-note (factory/get-new-project-note)
......@@ -174,3 +168,43 @@
:body "updated body")
_ (sut/edit-note (factory/get-new-uuid) updated-note)]
(is (= updated-note (first @project-note-atom))))))))
(deftest test-view-all-notes-valid
(testing "Should be able to view all notes from project, if valid."
(let [project-note (factory/get-new-project-note)
project-notes (replicate 10 project-note)
project-note-atom (atom [])]
(with-redefs [db-helper/get-conn (fn [] :get-all-notes-by-date)
db-project/create-new-note (fn [conn note]
(is (= conn :get-all-notes-by-date))
(swap! project-note-atom conj note))
db-project/get-all-notes-by-note-date-and-project-id (fn [conn note-date project-id]
(is (= conn :get-all-notes-by-date))
(filter #(and (= project-id (:project_id %))
(= note-date (:note_date %)))
@project-note-atom))]
(doall (map #(sut/create-note %) project-notes))
(let [notes (sut/get-all-notes-for-project-by-note-date (:note_date project-note)
(:project_id project-note))]
(is (= 10 (count notes))))))))
(deftest test-view-all-notes-invalid
(testing "Should not be able to view all notes from project, if invalid."
(let [project-note (factory/get-new-project-note)
project-notes (replicate 10 project-note)
project-note-atom (atom [])]
(with-redefs [db-helper/get-conn (fn [] :get-all-notes-by-date)
db-project/create-new-note (fn [conn note]
(is (= conn :get-all-notes-by-date))
(swap! project-note-atom conj note))
db-project/get-all-notes-by-note-date-and-project-id (fn [conn note-date project-id]
(is (= conn :get-all-notes-by-date))
(filter #(and (= project-id (:project_id %))
(= note-date (:note_date %)))
@project-note-atom))]
(doall (map #(sut/create-note %) project-notes))
(let [notes (sut/get-all-notes-for-project-by-note-date (:note_date invalid-date)
(:project_id project-note))]
(is (= 0 (count notes))))))))