version manager; new API signatures; new noteID format
This commit is contained in:
+12
-15
@@ -11,7 +11,7 @@
|
|||||||
[hiccup.util :as util]
|
[hiccup.util :as util]
|
||||||
[notehub.storage :as storage]))
|
[notehub.storage :as storage]))
|
||||||
|
|
||||||
(def version "1.1")
|
(def version "1.2")
|
||||||
|
|
||||||
(def domain
|
(def domain
|
||||||
(get-setting
|
(get-setting
|
||||||
@@ -33,8 +33,8 @@
|
|||||||
; Concatenates all fields to a string
|
; Concatenates all fields to a string
|
||||||
(defn build-key
|
(defn build-key
|
||||||
"Returns a storage-key for the given note coordinates"
|
"Returns a storage-key for the given note coordinates"
|
||||||
[[year month day] title]
|
[year month day title]
|
||||||
(print-str year month day title))
|
(apply str (interpose "/" [year month day title])))
|
||||||
|
|
||||||
(defn derive-title [md-text]
|
(defn derive-title [md-text]
|
||||||
(sreplace (first (split-lines md-text))
|
(sreplace (first (split-lines md-text))
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
(map #(+ (second %) (.get (Calendar/getInstance) (first %)))
|
(map #(+ (second %) (.get (Calendar/getInstance) (first %)))
|
||||||
{Calendar/YEAR 0, Calendar/MONTH 1, Calendar/DAY_OF_MONTH 0}))
|
{Calendar/YEAR 0, Calendar/MONTH 1, Calendar/DAY_OF_MONTH 0}))
|
||||||
|
|
||||||
(defn- create-response
|
(defn create-response
|
||||||
([success] {:success success})
|
([success] {:success success})
|
||||||
([success message & params]
|
([success message & params]
|
||||||
(assoc (create-response success) :message (apply format message params))))
|
(assoc (create-response success) :message (apply format message params))))
|
||||||
@@ -54,12 +54,12 @@
|
|||||||
(defn- get-path [token & [description]]
|
(defn- get-path [token & [description]]
|
||||||
(if (= :url description)
|
(if (= :url description)
|
||||||
(str domain "/" token)
|
(str domain "/" token)
|
||||||
(let [[year month day title] (split token #" ")]
|
(let [[year month day title] (split token #"/")]
|
||||||
(if description
|
(if description
|
||||||
(str domain "/" (storage/create-short-url token {:year year :month month :day day :title title}))
|
(str domain "/" (storage/create-short-url token {:year year :month month :day day :title title}))
|
||||||
(str domain (url year month day title))))))
|
(str domain (url year month day title))))))
|
||||||
|
|
||||||
(defn get-note [noteID]
|
(defn get-note [{:keys [noteID]}]
|
||||||
(if (storage/note-exists? noteID)
|
(if (storage/note-exists? noteID)
|
||||||
(let [note (storage/get-note noteID)]
|
(let [note (storage/get-note noteID)]
|
||||||
{:note note
|
{:note note
|
||||||
@@ -72,8 +72,7 @@
|
|||||||
(create-response false "noteID '%s' unknown" noteID)))
|
(create-response false "noteID '%s' unknown" noteID)))
|
||||||
|
|
||||||
(defn post-note
|
(defn post-note
|
||||||
([note pid signature] (post-note note pid signature {}))
|
[{:keys [note pid signature password] :as params}]
|
||||||
([note pid signature opts]
|
|
||||||
;(log "post-note: %s" {:pid pid :signature signature :password password :note note})
|
;(log "post-note: %s" {:pid pid :signature signature :password password :note note})
|
||||||
(let [errors (filter identity
|
(let [errors (filter identity
|
||||||
[(when-not (storage/valid-publisher? pid) "pid invalid")
|
[(when-not (storage/valid-publisher? pid) "pid invalid")
|
||||||
@@ -82,17 +81,15 @@
|
|||||||
(when (blank? note) "note is empty")])]
|
(when (blank? note) "note is empty")])]
|
||||||
(if (empty? errors)
|
(if (empty? errors)
|
||||||
(let [[year month day] (map str (get-date))
|
(let [[year month day] (map str (get-date))
|
||||||
password (opts :password)
|
params (dissoc params :note :pid :signature :password)
|
||||||
params (opts :params {})
|
|
||||||
raw-title (filter #(or (= \- %) (Character/isLetterOrDigit %))
|
raw-title (filter #(or (= \- %) (Character/isLetterOrDigit %))
|
||||||
(-> note derive-title trim (sreplace " " "-") lower-case))
|
(-> note derive-title trim (sreplace " " "-") lower-case))
|
||||||
max-length (get-setting :max-title-length #(Integer/parseInt %) 80)
|
max-length (get-setting :max-title-length #(Integer/parseInt %) 80)
|
||||||
proposed-title (apply str (take max-length raw-title))
|
proposed-title (apply str (take max-length raw-title))
|
||||||
date [year month day]
|
title (first (drop-while #(storage/note-exists? (build-key year month day %))
|
||||||
title (first (drop-while #(storage/note-exists? (build-key date %))
|
|
||||||
(cons proposed-title
|
(cons proposed-title
|
||||||
(map #(str proposed-title "-" (+ 2 %)) (range)))))
|
(map #(str proposed-title "-" (+ 2 %)) (range)))))
|
||||||
noteID (build-key date title)
|
noteID (build-key year month day title)
|
||||||
new-params (assoc params :year year :month month :day day :title title)
|
new-params (assoc params :year year :month month :day day :title title)
|
||||||
short-url (get-path (storage/create-short-url noteID new-params) :url)
|
short-url (get-path (storage/create-short-url noteID new-params) :url)
|
||||||
long-url (get-path noteID)]
|
long-url (get-path noteID)]
|
||||||
@@ -102,10 +99,10 @@
|
|||||||
:longURL (if (empty? params) long-url (str (util/url long-url params)))
|
:longURL (if (empty? params) long-url (str (util/url long-url params)))
|
||||||
:shortURL short-url
|
:shortURL short-url
|
||||||
:status (create-response true)}))
|
:status (create-response true)}))
|
||||||
{:status (create-response false (first errors))}))))
|
{:status (create-response false (first errors))})))
|
||||||
|
|
||||||
|
|
||||||
(defn update-note [noteID note pid signature password]
|
(defn update-note [{:keys [noteID note pid signature password]}]
|
||||||
;(log "update-note: %s" {:pid pid :noteID noteID :signature signature :password password :note note})
|
;(log "update-note: %s" {:pid pid :noteID noteID :signature signature :password password :note note})
|
||||||
(let [errors (filter identity
|
(let [errors (filter identity
|
||||||
[(when-not (storage/valid-publisher? pid) "pid invalid")
|
[(when-not (storage/valid-publisher? pid) "pid invalid")
|
||||||
|
|||||||
+30
-25
@@ -90,25 +90,27 @@
|
|||||||
{:headers {"Content-Type" ctype}
|
{:headers {"Content-Type" ctype}
|
||||||
:body content})
|
:body content})
|
||||||
|
|
||||||
|
(defn version-manager [f params]
|
||||||
|
(generate-string
|
||||||
|
(if-let [version (:version params)]
|
||||||
|
(f (if (and (:noteID params) (< (Float/parseFloat version) 1.3))
|
||||||
|
(assoc params :noteID (sreplace (params :noteID) #" " "/"))
|
||||||
|
params))
|
||||||
|
(api/create-response false "API version expected"))))
|
||||||
|
|
||||||
(defroutes api-routes
|
(defroutes api-routes
|
||||||
|
|
||||||
(GET "/" [] (layout (get-message :api-title)
|
(GET "/" [] (layout (get-message :api-title)
|
||||||
(md-node :article (slurp "API.md"))))
|
(md-node :article (slurp "API.md"))))
|
||||||
|
|
||||||
(GET "/note" [version noteID]
|
(GET "/note" {params :params}
|
||||||
(generate-string (api/get-note noteID)))
|
(version-manager api/get-note params))
|
||||||
|
|
||||||
(POST "/note" {params :params}
|
(POST "/note" {params :params}
|
||||||
(generate-string
|
(version-manager api/post-note params))
|
||||||
(api/post-note
|
|
||||||
(:note params)
|
|
||||||
(:pid params)
|
|
||||||
(:signature params)
|
|
||||||
{:params (dissoc params :version :note :pid :signature :password)
|
|
||||||
:password (:password params)})))
|
|
||||||
|
|
||||||
(PUT "/note" [version noteID note pid signature password]
|
(PUT "/note" {params :params}
|
||||||
(generate-string (api/update-note noteID note pid signature password))))
|
(version-manager api/update-note params)))
|
||||||
|
|
||||||
(defroutes app-routes
|
(defroutes app-routes
|
||||||
(context "/api" [] api-routes)
|
(context "/api" [] api-routes)
|
||||||
@@ -127,11 +129,11 @@
|
|||||||
(md-node :div.centered.helvetica (get-message :footer))))
|
(md-node :div.centered.helvetica (get-message :footer))))
|
||||||
|
|
||||||
(GET "/:year/:month/:day/:title/export" [year month day title]
|
(GET "/:year/:month/:day/:title/export" [year month day title]
|
||||||
(when-let [md-text (:note (api/get-note (api/build-key [year month day] title)))]
|
(when-let [md-text (:note (api/get-note {:noteID (api/build-key year month day title)}))]
|
||||||
(return-content-type "text/plain; charset=utf-8" md-text)))
|
(return-content-type "text/plain; charset=utf-8" md-text)))
|
||||||
|
|
||||||
(GET "/:year/:month/:day/:title/stats" [year month day title]
|
(GET "/:year/:month/:day/:title/stats" [year month day title]
|
||||||
(when-let [stats (:statistics (api/get-note (api/build-key [year month day] title)))]
|
(when-let [stats (:statistics (api/get-note {:noteID (api/build-key year month day title)}))]
|
||||||
(layout (get-message :statistics)
|
(layout (get-message :statistics)
|
||||||
[:table#stats.helvetica.central-element
|
[:table#stats.helvetica.central-element
|
||||||
(map
|
(map
|
||||||
@@ -140,10 +142,10 @@
|
|||||||
[:published :edited :publisher :views])])))
|
[:published :edited :publisher :views])])))
|
||||||
|
|
||||||
(GET "/:year/:month/:day/:title/edit" [year month day title]
|
(GET "/:year/:month/:day/:title/edit" [year month day title]
|
||||||
(let [noteID (api/build-key [year month day] title)]
|
(let [noteID (api/build-key year month day title)]
|
||||||
(input-form "/update-note" :update
|
(input-form "/update-note" :update
|
||||||
(html (hidden-field :noteID noteID))
|
(html (hidden-field :noteID noteID))
|
||||||
(:note (api/get-note noteID)) :enter-passwd)))
|
(:note (api/get-note {:noteID noteID})) :enter-passwd)))
|
||||||
|
|
||||||
(GET "/new" []
|
(GET "/new" []
|
||||||
(input-form "/post-note" :publish
|
(input-form "/post-note" :publish
|
||||||
@@ -154,9 +156,9 @@
|
|||||||
(GET "/:year/:month/:day/:title" [year month day title :as params]
|
(GET "/:year/:month/:day/:title" [year month day title :as params]
|
||||||
(let [params (assoc (:query-params params)
|
(let [params (assoc (:query-params params)
|
||||||
:year year :month month :day day :title title)
|
:year year :month month :day day :title title)
|
||||||
noteID (api/build-key [year month day] title)]
|
noteID (api/build-key year month day title)]
|
||||||
(when (storage/note-exists? noteID)
|
(when (storage/note-exists? noteID)
|
||||||
(let [note (api/get-note noteID)
|
(let [note (api/get-note {:noteID noteID})
|
||||||
sanitized-note (sanitize (:note note))]
|
sanitized-note (sanitize (:note note))]
|
||||||
(layout (:title note)
|
(layout (:title note)
|
||||||
(md-node :article.bottom-space sanitized-note)
|
(md-node :article.bottom-space sanitized-note)
|
||||||
@@ -180,25 +182,28 @@
|
|||||||
(redirect long-url))))
|
(redirect long-url))))
|
||||||
|
|
||||||
|
|
||||||
(POST "/post-note" [session note signature password version]
|
(POST "/post-note" [session note signature password]
|
||||||
(if (= signature (storage/sign session note))
|
(if (= signature (storage/sign session note))
|
||||||
(let [pid "NoteHub"
|
(let [pid "NoteHub"
|
||||||
psk (storage/get-psk pid)]
|
psk (storage/get-psk pid)
|
||||||
|
params {:session session :note note :signature signature
|
||||||
|
:password password :pid pid}]
|
||||||
(if (storage/valid-publisher? pid)
|
(if (storage/valid-publisher? pid)
|
||||||
(let [resp (api/post-note note pid (storage/sign pid psk note) {:password password})]
|
(let [resp (api/post-note (assoc params :signature (storage/sign pid psk note)))]
|
||||||
(if (get-in resp [:status :success])
|
(if (get-in resp [:status :success])
|
||||||
(redirect (:longURL resp))
|
(redirect (:longURL resp))
|
||||||
(response 400)))
|
(response 400)))
|
||||||
(response 500)))
|
(response 500)))
|
||||||
(response 400)))
|
(response 400)))
|
||||||
|
|
||||||
(POST "/update-note" [noteID note password version]
|
(POST "/update-note" [noteID note password]
|
||||||
(let [pid "NoteHub"
|
(let [pid "NoteHub"
|
||||||
psk (storage/get-psk pid)]
|
psk (storage/get-psk pid)
|
||||||
|
params {:noteID noteID :note note :password password :pid pid}]
|
||||||
(if (storage/valid-publisher? pid)
|
(if (storage/valid-publisher? pid)
|
||||||
(let [resp (api/update-note noteID note pid
|
(let [resp (api/update-note (assoc params
|
||||||
(storage/sign pid psk noteID note password)
|
:signature
|
||||||
password)]
|
(storage/sign pid psk noteID note password)))]
|
||||||
(if (get-in resp [:status :success])
|
(if (get-in resp [:status :success])
|
||||||
(redirect (:longURL resp))
|
(redirect (:longURL resp))
|
||||||
(response 403)))
|
(response 403)))
|
||||||
|
|||||||
+28
-24
@@ -9,7 +9,8 @@
|
|||||||
(def note "hello world!\nThis is a _test_ note!")
|
(def note "hello world!\nThis is a _test_ note!")
|
||||||
(def pid "somePlugin")
|
(def pid "somePlugin")
|
||||||
(def pid2 "somePlugin2")
|
(def pid2 "somePlugin2")
|
||||||
(def note-title (str (apply print-str (get-date)) " hello-world"))
|
(def note-title (let [[y m d] (get-date)]
|
||||||
|
(apply str (interpose "/" [y m d "hello-world"]))))
|
||||||
(def note-url (str (apply str domain "/" (interpose "/" (get-date))) "/hello-world"))
|
(def note-url (str (apply str domain "/" (interpose "/" (get-date))) "/hello-world"))
|
||||||
(defn substring? [a b] (not (= nil (re-matches (re-pattern (str "(?s).*" a ".*")) b))))
|
(defn substring? [a b] (not (= nil (re-matches (re-pattern (str "(?s).*" a ".*")) b))))
|
||||||
|
|
||||||
@@ -45,10 +46,10 @@
|
|||||||
(isnt (storage/valid-publisher? "any_PID"))
|
(isnt (storage/valid-publisher? "any_PID"))
|
||||||
(isnt (storage/valid-publisher? pid2))))
|
(isnt (storage/valid-publisher? pid2))))
|
||||||
(testing "note publishing & retrieval"
|
(testing "note publishing & retrieval"
|
||||||
(isnt (:success (:status (get-note "some note id"))))
|
(isnt (:success (:status (get-note {:noteID "some note id"}))))
|
||||||
(is (= "note is empty" (:message (:status (post-note "" pid (storage/sign pid psk ""))))))
|
(is (= "note is empty" (:message (:status (post-note {:note "" :pid pid :signature (storage/sign pid psk "")})))))
|
||||||
(let [post-response (post-note note pid (storage/sign pid psk note))
|
(let [post-response (post-note {:note note :pid pid :signature (storage/sign pid psk note)})
|
||||||
get-response (get-note (:noteID post-response))]
|
get-response (get-note post-response)]
|
||||||
(is (:success (:status post-response)))
|
(is (:success (:status post-response)))
|
||||||
(is (:success (:status get-response)))
|
(is (:success (:status get-response)))
|
||||||
(is (= note (:note get-response)))
|
(is (= note (:note get-response)))
|
||||||
@@ -65,44 +66,47 @@
|
|||||||
(is (= (:title get-response) (derive-title note)))
|
(is (= (:title get-response) (derive-title note)))
|
||||||
(is (= "1" (get-in get-response [:statistics :views])))
|
(is (= "1" (get-in get-response [:statistics :views])))
|
||||||
(isnt (get-in get-response [:statistics :edited]))
|
(isnt (get-in get-response [:statistics :edited]))
|
||||||
(is (= "3" (get-in (get-note (:noteID post-response)) [:statistics :views])))))
|
(is (= "3" (get-in (get-note post-response) [:statistics :views])))))
|
||||||
(testing "creation with wrong signature"
|
(testing "creation with wrong signature"
|
||||||
(let [response (post-note note pid (storage/sign pid2 psk note))]
|
(let [response (post-note {:note note :pid pid :signature (storage/sign pid2 psk note)})]
|
||||||
(isnt (:success (:status response)))
|
(isnt (:success (:status response)))
|
||||||
(is (= "signature invalid" (:message (:status response)))))
|
(is (= "signature invalid" (:message (:status response)))))
|
||||||
(let [response (post-note note pid (storage/sign pid2 psk "any note"))]
|
(let [response (post-note {:note note :pid pid :signature (storage/sign pid2 psk "any note")})]
|
||||||
(isnt (:success (:status response)))
|
(isnt (:success (:status response)))
|
||||||
(is (= "signature invalid" (:message (:status response)))))
|
(is (= "signature invalid" (:message (:status response)))))
|
||||||
(isnt (:success (:status (post-note note pid (storage/sign pid "random_psk" note)))))
|
(isnt (:success (:status (post-note {:note note :pid pid :signature (storage/sign pid "random_psk" note)}))))
|
||||||
(is (:success (:status (post-note note pid (storage/sign pid psk note)))))
|
(is (:success (:status (post-note {:note note :pid pid :signature (storage/sign pid psk note)}))))
|
||||||
(let [randomPID "randomPID"
|
(let [randomPID "randomPID"
|
||||||
psk2 (storage/register-publisher randomPID)
|
psk2 (storage/register-publisher randomPID)
|
||||||
_ (storage/revoke-publisher randomPID)
|
_ (storage/revoke-publisher randomPID)
|
||||||
response (post-note note randomPID (storage/sign randomPID psk2 note))]
|
response (post-note {:note note :pid randomPID :signature (storage/sign randomPID psk2 note)})]
|
||||||
(isnt (:success (:status response)))
|
(isnt (:success (:status response)))
|
||||||
(is (= (:message (:status response)) "pid invalid"))))
|
(is (= (:message (:status response)) "pid invalid"))))
|
||||||
(testing "note update"
|
(testing "note update"
|
||||||
(let [post-response (post-note note pid (storage/sign pid psk note) {:password "passwd"})
|
(let [post-response (post-note {:note note :pid pid :signature (storage/sign pid psk note) :password "passwd"})
|
||||||
note-id (:noteID post-response)
|
note-id (:noteID post-response)
|
||||||
new-note "a new note!"]
|
new-note "a new note!"]
|
||||||
(is (:success (:status post-response)))
|
(is (:success (:status post-response)))
|
||||||
(is (:success (:status (get-note note-id))))
|
(is (:success (:status (get-note {:noteID note-id}))))
|
||||||
(is (= note (:note (get-note note-id))))
|
(is (= note (:note (get-note {:noteID note-id}))))
|
||||||
(let [update-response (update-note note-id new-note pid (storage/sign pid psk new-note) "passwd")]
|
(let [update-response (update-note {:noteID note-id :note new-note :pid pid
|
||||||
|
:signature (storage/sign pid psk new-note) :password "passwd"})]
|
||||||
(isnt (:success (:status update-response)))
|
(isnt (:success (:status update-response)))
|
||||||
(is (= "signature invalid" (:message (:status update-response)))))
|
(is (= "signature invalid" (:message (:status update-response)))))
|
||||||
(is (= note (:note (get-note note-id))))
|
(is (= note (:note (get-note {:noteID note-id}))))
|
||||||
(let [update-response (update-note note-id new-note pid
|
(let [update-response (update-note {:noteID note-id :note new-note :pid pid
|
||||||
(storage/sign pid psk note-id new-note "passwd") "passwd")]
|
:signature (storage/sign pid psk note-id new-note "passwd")
|
||||||
|
:password "passwd"})]
|
||||||
(is (= { :success true } (:status update-response)))
|
(is (= { :success true } (:status update-response)))
|
||||||
(isnt (= nil (get-in (get-note note-id) [:statistics :edited])))
|
(isnt (= nil (get-in (get-note {:noteID note-id}) [:statistics :edited])))
|
||||||
(is (= new-note (:note (get-note note-id)))))
|
(is (= new-note (:note (get-note {:noteID note-id})))))
|
||||||
(let [update-response (update-note note-id "aaa" pid
|
(let [update-response (update-note {:noteID note-id :note "aaa" :pid pid
|
||||||
(storage/sign pid psk note-id "aaa" "pass") "pass")]
|
:signature (storage/sign pid psk note-id "aaa" "pass")
|
||||||
|
:password "pass"})]
|
||||||
(isnt (:success (:status update-response)))
|
(isnt (:success (:status update-response)))
|
||||||
(is (= "password invalid" (:message (:status update-response)))))
|
(is (= "password invalid" (:message (:status update-response)))))
|
||||||
(is (= new-note (:note (get-note note-id))))
|
(is (= new-note (:note (get-note {:noteID note-id}))))
|
||||||
(is (= new-note (:note (get-note note-id))))))))
|
(is (= new-note (:note (get-note {:noteID note-id}))))))))
|
||||||
|
|
||||||
(deftest api-note-creation
|
(deftest api-note-creation
|
||||||
(testing "Note creation"
|
(testing "Note creation"
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
(ns notehub.test.handler
|
(ns notehub.test.handler
|
||||||
(:use clojure.test
|
(:use clojure.test
|
||||||
[notehub.api :only [build-key get-date url]]
|
[notehub.api :only [get-date url]]
|
||||||
notehub.storage
|
notehub.storage
|
||||||
ring.mock.request
|
ring.mock.request
|
||||||
notehub.test.api
|
notehub.test.api
|
||||||
notehub.handler))
|
notehub.handler))
|
||||||
|
|
||||||
|
(defn build-key [[y m d] t] (notehub.api/build-key y m d t))
|
||||||
(def date [2012 6 3])
|
(def date [2012 6 3])
|
||||||
(def test-title "some-title")
|
(def test-title "some-title")
|
||||||
(def test-note "# This is a test note.\nHello _world_. Motörhead, тест.")
|
(def test-note "# This is a test note.\nHello _world_. Motörhead, тест.")
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
(ns notehub.test.storage
|
(ns notehub.test.storage
|
||||||
(:use [notehub.storage]
|
(:use [notehub.storage]
|
||||||
[notehub.api :only [build-key]]
|
|
||||||
[clojure.test])
|
[clojure.test])
|
||||||
(:require [taoensso.carmine :as car :refer (wcar)]))
|
(:require [taoensso.carmine :as car :refer (wcar)]))
|
||||||
|
|
||||||
|
(defn build-key [[y m d] t] (notehub.api/build-key y m d t))
|
||||||
(def date [2012 06 03])
|
(def date [2012 06 03])
|
||||||
(def test-title "Some title.")
|
(def test-title "Some title.")
|
||||||
(def test-note "This is a test note.")
|
(def test-note "This is a test note.")
|
||||||
|
|||||||
Reference in New Issue
Block a user