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