diff --git a/src-cljs/main.cljs b/src-cljs/main.cljs index 449c06c..ea188da 100644 --- a/src-cljs/main.cljs +++ b/src-cljs/main.cljs @@ -32,13 +32,11 @@ (.click ($ :#preview-button) (fn [e] (xhr [:post "/preview"] - {:session-key (val $session-key) - :draft (val $draft)} + {:draft (val $draft)} (fn [json-map] (let [m (js->clj (JSON/parse json-map))] (do (inner $preview (m "preview")) - (val $session-key (m "session-key")) (show $preview-start-line) (scroll-to $preview-start-line))))))) diff --git a/src/NoteHub/storage.clj b/src/NoteHub/storage.clj index b4b0aca..52981b1 100644 --- a/src/NoteHub/storage.clj +++ b/src/NoteHub/storage.clj @@ -1,5 +1,6 @@ (ns NoteHub.storage (:use [NoteHub.settings] + [noir.util.crypt :only [encrypt]] [noir.options :only [dev-mode?]]) (:require [clj-redis.client :as redis])) @@ -12,11 +13,26 @@ ; DB hierarchy levels (def note "note") (def views "views") +(def sessions "sessions") ; Concatenates all fields to a string (defn- build-key [[year month day] title] (print-str year month day title)) +(defn create-session + "Creates a random session token" + [] + (let [token (encrypt (str (rand-int Integer/MAX_VALUE)))] + (do (redis/sadd db sessions token) token))) + +(defn invalidate-session + "Invalidates given session" + [token] + ; Jedis is buggy & returns an NPE for token == nil + (when token + (let [was-valid (redis/sismember db sessions token)] + (do (redis/srem db sessions token) was-valid)))) + (defn set-note "Creates a note with the given title and text in the given date namespace" [date title text] diff --git a/src/NoteHub/views/pages.clj b/src/NoteHub/views/pages.clj index 29f0323..75fa0a0 100644 --- a/src/NoteHub/views/pages.clj +++ b/src/NoteHub/views/pages.clj @@ -11,10 +11,8 @@ [ring.util.codec :only [url-encode]] [hiccup.core] [hiccup.util :only [escape-html]] - [noir.session :only [flash-put! flash-get]] [noir.response :only [redirect status]] [noir.core :only [defpage render]] - [noir.util.crypt :only [encrypt]] [cheshire.core] [noir.statuses]) (:import @@ -29,12 +27,6 @@ (defn md-to-html [md-text] (.markdownToHtml md-processor md-text)) -; Creates a random session token -(defn- get-flash-key [] - (let [k (encrypt (str (rand-int Integer/MAX_VALUE)))] - (do (flash-put! k true) - (print-str k)))) - ; Sets a custom message for each needed HTTP status. ; The message to be assigned is extracted with a dynamically generated key (doseq [code [400 404 500]] @@ -54,16 +46,20 @@ (layout params title [:article (md-to-html md-text)])) (status 404 (get-page 404)))) +(defn get-date + "Returns today's date" + [] + (map #(+ (second %) (.get (Calendar/getInstance) (first %))) + {Calendar/YEAR 0, Calendar/MONTH 1, Calendar/DAY_OF_MONTH 0})) + ; Routes ; ====== ; This function answers to an AJAX request: it gets a session key and a markdown text. ; It returns the html code of the provided markdown and a new session key. (defpage [:post "/preview"] {:keys [session-key draft]} - (when (flash-get session-key) - (generate-string - {:session-key (get-flash-key) - :preview (md-to-html draft)}))) + (generate-string + {:preview (md-to-html draft)})) ; Landing Page (defpage "/" {} @@ -90,7 +86,7 @@ (layout {:js true} (get-message :new-note) [:div.central-element (form-to [:post "/post-note"] - (hidden-field :session-key (get-flash-key)) + (hidden-field :session-key (create-session)) (hidden-field {:id :session-value} :session-value) (text-area {:class :max-width} :draft (get-message :loading)) [:div#buttons.hidden @@ -118,19 +114,19 @@ (let [views (get-note-views [year month day] title)] (if views (layout (get-message :statistics) - [:table.helvetica-neue.central-element - [:tr - [:td (get-message :published)] - [:td (interpose "-" [year month day])]] - [:tr - [:td (get-message :article-views)] - [:td views]]]) + [:table.helvetica-neue.central-element + [:tr + [:td (get-message :published)] + [:td (interpose "-" [year month day])]] + [:tr + [:td (get-message :article-views)] + [:td views]]]) (response 404)))) ; New Note Posting — the most "complex" function in the entire app ;) (defpage [:post "/post-note"] {:keys [draft session-key session-value]} ; first we collect all info needed to evaluate the validity of the note creation request - (let [valid-session (flash-get session-key) ; was the note posted from a newly generated form? + (let [valid-session (invalidate-session session-key) ; was the note posted from a newly generated form? valid-draft (not (ccs/blank? draft)) ; has the note a meaningful content? ; is the hash code correct? valid-hash (try @@ -142,8 +138,7 @@ ; if yes, we compute the current date, extract a title string from the text, ; which will be a part of the url and look whether this title is free today; ; if not, append "-n", where "n" is the next free number - (let [[year month day] (map #(+ (second %) (.get (Calendar/getInstance) (first %))) - {Calendar/YEAR 0, Calendar/MONTH 1, Calendar/DAY_OF_MONTH 0}) + (let [[year month day] (get-date) untrimmed-line (filter #(or (= \- %) (Character/isLetterOrDigit %)) (-> draft ccs/split-lines first (sreplace " " "-") lower-case)) trim (fn [s] (apply str (drop-while #(= \- %) s))) diff --git a/test/NoteHub/test/storage.clj b/test/NoteHub/test/storage.clj index ab71dfb..653957e 100644 --- a/test/NoteHub/test/storage.clj +++ b/test/NoteHub/test/storage.clj @@ -5,23 +5,33 @@ (def test-title "Some title.") (def test-note "This is a test note.") -(testing "Storage" - (testing "of correct note creation" - (is (= (do - (set-note date test-title test-note) - (get-note date test-title)) - test-note)) - (is (= "1" (get-note-views date test-title))) - (is (= (do - (get-note date test-title) - (get-note-views date test-title)) - "2"))) - (testing "of the note access" - (is (not= (get-note date test-title) "any text"))) - (testing "of note existence" - (is (note-exists? date test-title)) - (is (not (do - (delete-note date test-title) - (note-exists? date test-title)))) - (is (not (note-exists? [2013 06 03] test-title))) - (is (not (note-exists? date "some title"))))) +(deftest storage + (testing "Storage" + (testing "of correct note creation" + (is (= (do + (set-note date test-title test-note) + (get-note date test-title)) + test-note)) + (is (= "1" (get-note-views date test-title))) + (is (= (do + (get-note date test-title) + (get-note-views date test-title)) + "2"))) + (testing "of the note access" + (is (not= (get-note date test-title) "any text"))) + (testing "session management" + (let [s1 (create-session) + s2 (create-session) + s3 (create-session)] + (is (invalidate-session s1)) + (is (not (invalidate-session (str s1 s2)))) + (is (invalidate-session s2)) + (is (not (invalidate-session "wrongtoken"))) + (is (invalidate-session s3)))) + (testing "of note existence" + (is (note-exists? date test-title)) + (is (not (do + (delete-note date test-title) + (note-exists? date test-title)))) + (is (not (note-exists? [2013 06 03] test-title))) + (is (not (note-exists? date "some title")))))) diff --git a/test/NoteHub/test/views/pages.clj b/test/NoteHub/test/views/pages.clj index 8658528..620fb1f 100644 --- a/test/NoteHub/test/views/pages.clj +++ b/test/NoteHub/test/views/pages.clj @@ -1,13 +1,15 @@ (ns NoteHub.test.views.pages + (:require [NoteHub.crossover.lib :as lib]) (:use [NoteHub.views.pages] [noir.util.test] + [clojure.contrib.string :only [substring?]] [NoteHub.views.common :only [url]] [NoteHub.storage] [clojure.test])) (def date [2012 6 3]) (def test-title "some-title") -(def test-note "# This is a test note.\nHello _world_.") +(def test-note "# This is a test note.\nHello _world_. Motörhead, тест.") (defn create-testnote-fixture [f] (set-note date test-title test-note) @@ -29,22 +31,43 @@ (md-to-html "#_hellö_ __world__\ntest `code`"))))) (deftest export-test (testing "Markdown export" - (has-body (send-request (url 2012 6 3 "some-title" "export")) test-note))) + (is (has-body (send-request (url 2012 6 3 "some-title" "export")) test-note)))) + +(deftest note-creation + (let [session-key (create-session) + date (get-date) + ; TODO: replace note generation by a function from pages.clj + title "this-is-a-test-note" + [year month day] date] + (testing "Note creation" + (is (has-status + (send-request + [:post "/post-note"] + {:session-key session-key + :draft test-note + :session-value (str (lib/hash #(.codePointAt % 0) + (str test-note session-key)))}) 302)) + (is (note-exists? date title)) + (is (substring? "Hello world" + ((send-request (url year month day title)) :body))) + (is (do + (delete-note date title) + (not (note-exists? date title))))))) (deftest requests (testing "HTTP Status" (testing "of a wrong access" - (has-status (send-request "/wrong-page") 404) - (has-status (send-request (url 2012 6 3 "lol" "stat")) 404) - (has-status (send-request (url 2012 6 3 "lol" "export")) 404) - (has-status (send-request (url 2012 6 3 "lol")) 404) - (has-status (send-request (url 2012 6 4 "wrong-title")) 404)) + (is (has-status (send-request "/wrong-page") 404)) + (is (has-status (send-request (url 2012 6 3 "lol" "stat")) 404)) + (is (has-status (send-request (url 2012 6 3 "lol" "export")) 404)) + (is (has-status (send-request (url 2012 6 3 "lol")) 404)) + (is (has-status (send-request (url 2012 6 4 "wrong-title")) 404))) (testing "of corrupt note-post" - (has-status (send-request [:post "/post-note"]) 400)) + (is (has-status (send-request [:post "/post-note"]) 400))) (testing "valid accesses" - (has-status (send-request "/new") 200) - (has-status (send-request (url 2012 6 3 "some-title")) 200) - (has-status (send-request (url 2012 6 3 "some-title" "export")) 200) - (has-status (send-request (url 2012 6 3 "some-title" "stat")) 200) - (has-status (send-request (url 2012 6 3 "some-title")) 200) - (has-status (send-request "/") 200)))) + (is (has-status (send-request "/new") 200)) + (is (has-status (send-request (url 2012 6 3 "some-title")) 200)) + (is (has-status (send-request (url 2012 6 3 "some-title" "export")) 200)) + (is (has-status (send-request (url 2012 6 3 "some-title" "stat")) 200)) + (is (has-status (send-request (url 2012 6 3 "some-title")) 200)) + (is (has-status (send-request "/") 200)))))