first api version implemented
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
The NoteHub API can only be used in combination with a __Publisher ID__ (PID) and __Publisher Secret Key__ (PSK), which can be issued [here](http://notehub.org/api/register). The PSK can be revoked at any moment in case of an API abuse.
|
The NoteHub API can only be used in combination with a __Publisher ID__ (PID) and __Publisher Secret Key__ (PSK), which can be requested [here](http://notehub.org/api/register). The PSK can be revoked at any moment in case of an API abuse.
|
||||||
|
|
||||||
A PID is a string chosen by the publisher and cannot be longer than 16 characters (e.g.: __notepadPlugin__). A PSK will be generated by the NoteHub API and can be a string of any length and content.
|
A PID is a string chosen by the publisher and cannot be longer than 16 characters (e.g.: __notepadPlugin__). A PSK will be generated by the NoteHub API and can be a string of any length and content.
|
||||||
|
|
||||||
@@ -26,6 +26,7 @@ Example:
|
|||||||
shortURL: "http://notehub.org/0vrcp",
|
shortURL: "http://notehub.org/0vrcp",
|
||||||
statistics: {
|
statistics: {
|
||||||
published: "2014-1-3",
|
published: "2014-1-3",
|
||||||
|
edited: "2014-1-12",
|
||||||
views: 24
|
views: 24
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
@@ -36,7 +37,7 @@ Example:
|
|||||||
|
|
||||||
Hence, the status of the request can be evaluated by reading of the property `status.success`. The field `status.comment`might contain an error message, a warning or any other comments from the server.
|
Hence, the status of the request can be evaluated by reading of the property `status.success`. The field `status.comment`might contain an error message, a warning or any other comments from the server.
|
||||||
|
|
||||||
The note ID is a string, containing the date of publishing and a few first words of the note (usually the header), e.g.: `2014/1/3/lorem-ipsum`. This ID will be generated by NoteHub automatically.
|
The note ID is a string, containing the date of publishing and a few first words of the note (usually the title), e.g.: `2014 1 3 lorem-ipsum`. This ID will be generated by NoteHub automatically.
|
||||||
|
|
||||||
## Note Creation
|
## Note Creation
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ enter-passwd = Password
|
|||||||
publish = Publish
|
publish = Publish
|
||||||
update = Save
|
update = Save
|
||||||
published = Published
|
published = Published
|
||||||
|
edited = Edited
|
||||||
article-views = Article Views
|
article-views = Article Views
|
||||||
statistics = Statistics
|
statistics = Statistics
|
||||||
stats = statistics
|
stats = statistics
|
||||||
|
|||||||
+88
-9
@@ -1,12 +1,37 @@
|
|||||||
(ns NoteHub.api
|
(ns NoteHub.api
|
||||||
(:require [NoteHub.storage :as persistance]))
|
(:import
|
||||||
|
[java.util Calendar])
|
||||||
|
(:use
|
||||||
|
[NoteHub.settings]
|
||||||
|
[clojure.string :rename {replace sreplace}
|
||||||
|
:only [replace blank? lower-case split-lines]])
|
||||||
|
(:require [NoteHub.storage :as storage]))
|
||||||
|
|
||||||
(def api-version "1.0")
|
(def api-version "1.0")
|
||||||
|
|
||||||
|
(def domain "http://notehub.org/")
|
||||||
|
|
||||||
|
; 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))
|
||||||
|
|
||||||
|
(defn get-date
|
||||||
|
"Returns today's date"
|
||||||
|
[]
|
||||||
|
(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] { :success success })
|
||||||
([success message]
|
([success message & params]
|
||||||
(assoc (create-response success) :message message)))
|
(assoc (create-response success) :message (apply format message params))))
|
||||||
|
|
||||||
|
(defn- getURL [noteID description]
|
||||||
|
(if (description)
|
||||||
|
(str domain (storage/get-short-url noteID))
|
||||||
|
(str domain (sreplace noteID #" " "/"))))
|
||||||
|
|
||||||
(let [md5Instance (java.security.MessageDigest/getInstance "MD5")]
|
(let [md5Instance (java.security.MessageDigest/getInstance "MD5")]
|
||||||
(defn get-signature
|
(defn get-signature
|
||||||
@@ -17,9 +42,63 @@
|
|||||||
(.update md5Instance (.getBytes input))
|
(.update md5Instance (.getBytes input))
|
||||||
(.toString (new java.math.BigInteger 1 (.digest md5Instance)) 16)))))
|
(.toString (new java.math.BigInteger 1 (.digest md5Instance)) 16)))))
|
||||||
|
|
||||||
(defn get-note [noteID])
|
(defn get-note [noteID]
|
||||||
(defn post-note [& args])
|
{:note (storage/get-note noteID)
|
||||||
(defn update-note [& args])
|
:longURL (getURL noteID)
|
||||||
(defn register-publisher [& args])
|
:shortURL (getURL noteID :short)
|
||||||
(defn revoke-publisher [& args])
|
:statistics (storage/get-note-statistics noteID)
|
||||||
(defn valid-publisher? [& args])
|
:status (if (storage/note-exists? noteID)
|
||||||
|
(create-response true)
|
||||||
|
(create-response false "noteID '%s' unknown" noteID))})
|
||||||
|
|
||||||
|
(defn post-note
|
||||||
|
([note pid signature] (post-note note pid signature nil))
|
||||||
|
([note pid signature password]
|
||||||
|
(let [errors (filter identity
|
||||||
|
(lazy-seq
|
||||||
|
[(when-not (storage/valid-publisher? pid) "pid invalid")
|
||||||
|
(when-not (= signature
|
||||||
|
(get-signature pid (storage/get-psk pid) note))
|
||||||
|
"signature invalid")
|
||||||
|
(when (blank? note) "note is empty")]))]
|
||||||
|
(if (empty? errors)
|
||||||
|
(let [[year month day] (get-date)
|
||||||
|
untrimmed-line (filter #(or (= \- %) (Character/isLetterOrDigit %))
|
||||||
|
(-> note split-lines first (sreplace " " "-") lower-case))
|
||||||
|
trim (fn [s] (apply str (drop-while #(= \- %) s)))
|
||||||
|
title-uncut (-> untrimmed-line trim reverse trim reverse)
|
||||||
|
max-length (get-setting :max-title-length #(Integer/parseInt %) 80)
|
||||||
|
proposed-title (apply str (take max-length title-uncut))
|
||||||
|
date [year month day]
|
||||||
|
title (first (drop-while #(storage/note-exists? (build-key date %))
|
||||||
|
(cons proposed-title
|
||||||
|
(map #(str proposed-title "-" (+ 2 %)) (range)))))
|
||||||
|
noteID (build-key date title)]
|
||||||
|
(do
|
||||||
|
(storage/add-note noteID note password)
|
||||||
|
(storage/create-short-url noteID)
|
||||||
|
{
|
||||||
|
:noteID noteID
|
||||||
|
:longURL (getURL noteID)
|
||||||
|
:shortURL (getURL noteID :short)
|
||||||
|
:status (create-response true)
|
||||||
|
}))
|
||||||
|
{:status (create-response false (first errors))}))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn update-note [noteID note pid signature password]
|
||||||
|
(let [errors (filter identity
|
||||||
|
(lazy-seq
|
||||||
|
[(when-not (storage/valid-publisher? pid) "pid invalid")
|
||||||
|
(when-not (= signature
|
||||||
|
(get-signature pid (storage/get-psk pid) noteID note password))
|
||||||
|
"signature invalid")
|
||||||
|
(when (blank? note) "note is empty")
|
||||||
|
(when-not (storage/update-note noteID note password) "password invalid")]))]
|
||||||
|
(if (empty? errors)
|
||||||
|
{
|
||||||
|
:longURL (getURL noteID)
|
||||||
|
:shortURL (getURL noteID :short)
|
||||||
|
:status (create-response true)
|
||||||
|
}
|
||||||
|
{:status (create-response false (first errors))})))
|
||||||
|
|||||||
+42
-11
@@ -11,12 +11,34 @@
|
|||||||
(when-not (dev-mode?)
|
(when-not (dev-mode?)
|
||||||
{:url (get-setting :db-url)})))
|
{:url (get-setting :db-url)})))
|
||||||
|
|
||||||
|
(defn get-current-date []
|
||||||
|
(str (java.util.Date.)))
|
||||||
|
|
||||||
; DB hierarchy levels
|
; DB hierarchy levels
|
||||||
(def note "note")
|
(def note "note")
|
||||||
|
(def published "published")
|
||||||
|
(def edited "edited")
|
||||||
(def views "views")
|
(def views "views")
|
||||||
(def password "password")
|
(def password "password")
|
||||||
(def sessions "sessions")
|
(def sessions "sessions")
|
||||||
(def short-url "short-url")
|
(def short-url "short-url")
|
||||||
|
(def publisher "publisher")
|
||||||
|
|
||||||
|
(defn valid-publisher? [pid]
|
||||||
|
(redis/hexists db publisher pid))
|
||||||
|
|
||||||
|
(defn register-publisher [pid]
|
||||||
|
"Returns nil if given PID exists or a PSK otherwise"
|
||||||
|
(when (not (valid-publisher? pid))
|
||||||
|
(let [psk (encrypt (str (rand-int Integer/MAX_VALUE) pid))
|
||||||
|
_ (redis/hset db publisher pid psk)]
|
||||||
|
psk)))
|
||||||
|
|
||||||
|
(defn revoke-publisher [pid]
|
||||||
|
(redis/hdel db publisher pid))
|
||||||
|
|
||||||
|
(defn get-psk [pid]
|
||||||
|
(redis/hget db publisher pid))
|
||||||
|
|
||||||
(defn create-session
|
(defn create-session
|
||||||
[]
|
[]
|
||||||
@@ -36,6 +58,7 @@
|
|||||||
[noteID text passwd]
|
[noteID text passwd]
|
||||||
(let [stored-password (redis/hget db password noteID)]
|
(let [stored-password (redis/hget db password noteID)]
|
||||||
(when (and stored-password (= passwd stored-password))
|
(when (and stored-password (= passwd stored-password))
|
||||||
|
(redis/hset db edited noteID (get-current-date))
|
||||||
(redis/hset db note noteID text))))
|
(redis/hset db note noteID text))))
|
||||||
|
|
||||||
(defn add-note
|
(defn add-note
|
||||||
@@ -43,6 +66,7 @@
|
|||||||
([noteID text passwd]
|
([noteID text passwd]
|
||||||
(do
|
(do
|
||||||
(redis/hset db note noteID text)
|
(redis/hset db note noteID text)
|
||||||
|
(redis/hset db published noteID (get-current-date))
|
||||||
(when (not (blank? passwd))
|
(when (not (blank? passwd))
|
||||||
(redis/hset db password noteID passwd)))))
|
(redis/hset db password noteID passwd)))))
|
||||||
|
|
||||||
@@ -55,17 +79,21 @@
|
|||||||
text))))
|
text))))
|
||||||
|
|
||||||
(defn get-note-views
|
(defn get-note-views
|
||||||
"Returns the number of views for the specified noteID"
|
|
||||||
[noteID]
|
[noteID]
|
||||||
(redis/hget db views noteID))
|
(redis/hget db views noteID))
|
||||||
|
|
||||||
|
(defn get-note-statistics
|
||||||
|
"Return views, publishing and editing timestamp"
|
||||||
|
[noteID]
|
||||||
|
{ :view (redis/hget db views noteID)
|
||||||
|
:published (redis/hget db published noteID)
|
||||||
|
:edited (redis/hget db edited noteID) })
|
||||||
|
|
||||||
(defn note-exists?
|
(defn note-exists?
|
||||||
"Returns true if the note with the specified noteID"
|
|
||||||
[noteID]
|
[noteID]
|
||||||
(redis/hexists db note noteID))
|
(redis/hexists db note noteID))
|
||||||
|
|
||||||
(defn delete-note
|
(defn delete-note
|
||||||
"Deletes the note with the specified coordinates"
|
|
||||||
[noteID]
|
[noteID]
|
||||||
(doseq [kw [password views note]]
|
(doseq [kw [password views note]]
|
||||||
; TODO: delete short url by looking for the title
|
; TODO: delete short url by looking for the title
|
||||||
@@ -76,6 +104,9 @@
|
|||||||
[url]
|
[url]
|
||||||
(redis/hexists db short-url url))
|
(redis/hexists db short-url url))
|
||||||
|
|
||||||
|
(defn get-short-url [noteID]
|
||||||
|
(redis/hget db short-url noteID))
|
||||||
|
|
||||||
(defn resolve-url
|
(defn resolve-url
|
||||||
"Resolves short url by providing all metadata of the request"
|
"Resolves short url by providing all metadata of the request"
|
||||||
[url]
|
[url]
|
||||||
@@ -92,12 +123,12 @@
|
|||||||
(redis/hdel db short-url value))))
|
(redis/hdel db short-url value))))
|
||||||
|
|
||||||
(defn create-short-url
|
(defn create-short-url
|
||||||
"Creates a short url for the given request metadata or extracts
|
"Creates a short url for the given request metadata or noteID or extracts
|
||||||
one if it was already created"
|
one if it was already created"
|
||||||
[metadata]
|
[arg]
|
||||||
(let [request (str (into (sorted-map) metadata))]
|
(let [key (if (map? arg) (str (into (sorted-map) arg)) arg)]
|
||||||
(if (short-url-exists? request)
|
(if (short-url-exists? key)
|
||||||
(redis/hget db short-url request)
|
(redis/hget db short-url key)
|
||||||
(let [hash-stream (partition 5 (repeatedly #(rand-int 36)))
|
(let [hash-stream (partition 5 (repeatedly #(rand-int 36)))
|
||||||
hash-to-string (fn [hash]
|
hash-to-string (fn [hash]
|
||||||
(apply str
|
(apply str
|
||||||
@@ -108,8 +139,8 @@
|
|||||||
(remove short-url-exists?
|
(remove short-url-exists?
|
||||||
(map hash-to-string hash-stream)))]
|
(map hash-to-string hash-stream)))]
|
||||||
(do
|
(do
|
||||||
; we create two mappings: request params -> short url and back,
|
; we create two mappings: key params -> short url and back,
|
||||||
; s.t. we can later easily check whether a short url already exists
|
; s.t. we can later easily check whether a short url already exists
|
||||||
(redis/hset db short-url url request)
|
(redis/hset db short-url url key)
|
||||||
(redis/hset db short-url request url)
|
(redis/hset db short-url key url)
|
||||||
url)))))
|
url)))))
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
(ns NoteHub.views.pages
|
(ns NoteHub.views.pages
|
||||||
(:require [hiccup.util :as util])
|
(:require [hiccup.util :as util])
|
||||||
(:use
|
(:use
|
||||||
[NoteHub.storage]
|
[NoteHub.storage] ; TODO: delete this
|
||||||
|
[NoteHub.api :only [build-key get-date]]
|
||||||
[NoteHub.settings]
|
[NoteHub.settings]
|
||||||
[NoteHub.views.common]
|
[NoteHub.views.common]
|
||||||
[clojure.string :rename {replace sreplace}
|
[clojure.string :rename {replace sreplace}
|
||||||
@@ -12,15 +13,7 @@
|
|||||||
[hiccup.element]
|
[hiccup.element]
|
||||||
[noir.response :only [redirect status content-type]]
|
[noir.response :only [redirect status content-type]]
|
||||||
[noir.core :only [defpage defpartial]]
|
[noir.core :only [defpage defpartial]]
|
||||||
[noir.statuses])
|
[noir.statuses]))
|
||||||
(:import
|
|
||||||
[java.util Calendar]))
|
|
||||||
|
|
||||||
; 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))
|
|
||||||
|
|
||||||
(defn get-hash
|
(defn get-hash
|
||||||
"A simple hash-function, which computes a hash from the text field
|
"A simple hash-function, which computes a hash from the text field
|
||||||
@@ -66,12 +59,6 @@
|
|||||||
links (interpose separator links)]
|
links (interpose separator links)]
|
||||||
[:div#panel (map identity links)]))))
|
[:div#panel (map identity links)]))))
|
||||||
|
|
||||||
(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
|
; Routes
|
||||||
; ======
|
; ======
|
||||||
|
|
||||||
|
|||||||
+18
-15
@@ -1,5 +1,8 @@
|
|||||||
(ns NoteHub.test.api
|
(ns NoteHub.test.api
|
||||||
(:use [NoteHub.api] [clojure.test]))
|
(:require
|
||||||
|
[NoteHub.storage :as storage])
|
||||||
|
(:use [NoteHub.api]
|
||||||
|
[clojure.test]))
|
||||||
|
|
||||||
(def note "Hello world, this is a test note!")
|
(def note "Hello world, this is a test note!")
|
||||||
(def note2 "Another test note")
|
(def note2 "Another test note")
|
||||||
@@ -9,37 +12,37 @@
|
|||||||
(defmacro isnt [arg] `(is (not ~arg)))
|
(defmacro isnt [arg] `(is (not ~arg)))
|
||||||
|
|
||||||
(defn register-publisher-fixture [f]
|
(defn register-publisher-fixture [f]
|
||||||
(def psk (register-publisher pid))
|
(def psk (storage/register-publisher pid))
|
||||||
(f)
|
(f)
|
||||||
(revoke-publisher pid))
|
(storage/revoke-publisher pid))
|
||||||
|
|
||||||
#_
|
|
||||||
(deftest api
|
(deftest api
|
||||||
(testing "API"
|
(testing "API"
|
||||||
(testing "publisher registration"
|
(testing "publisher registration"
|
||||||
(let [psk2 (register-publisher pid2)]
|
(let [psk2 (storage/register-publisher pid2)]
|
||||||
(is (valid-publisher? pid))
|
(is (storage/valid-publisher? pid))
|
||||||
(is (valid-publisher? pid2))
|
(is (storage/valid-publisher? pid2))
|
||||||
(is (revoke-publisher pid2))
|
(is (storage/revoke-publisher pid2))
|
||||||
(isnt (revoke-publisher "anyPID"))
|
(isnt (storage/revoke-publisher "anyPID"))
|
||||||
(isnt (valid-publisher? "any_PID"))
|
(isnt (storage/valid-publisher? "any_PID"))
|
||||||
(isnt (valid-publisher? pid2))))
|
(isnt (storage/valid-publisher? pid2))))
|
||||||
(testing "note publishing & retrieval"
|
#_ (testing "note publishing & retrieval"
|
||||||
(let [post-response (post-note note pid (get-signature pid psk note))
|
(let [post-response (post-note note pid (get-signature pid psk note))
|
||||||
get-response (get-note (:noteID post-response))]
|
get-response (get-note (:noteID 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)))
|
||||||
|
; TODO: test all response fields!!!!
|
||||||
(is (= (:longURL post-response) (:longURL get-response)))
|
(is (= (:longURL post-response) (:longURL get-response)))
|
||||||
(is (= (:shortURL post-response) (:shortURL get-response))))
|
(is (= (:shortURL post-response) (:shortURL get-response))))
|
||||||
(isnt (:success (:status (post-note note pid (get-signature pid psk note)))))
|
(isnt (:success (:status (post-note note pid (get-signature pid psk note)))))
|
||||||
(isnt (:success (:status (post-note note pid (get-signature pid "random_psk" note)))))
|
(isnt (:success (:status (post-note note pid (get-signature pid "random_psk" note)))))
|
||||||
(is (:success (:status (post-note note pid (get-signature pid psk note)))))
|
(is (:success (:status (post-note note pid (get-signature pid psk note)))))
|
||||||
(let [psk2 (register-publisher "randomPID")]
|
(let [psk2 (storage/register-publisher "randomPID")]
|
||||||
(is (:success (:status (post-note note "randomPID" (get-signature pid psk2 note)))))
|
(is (:success (:status (post-note note "randomPID" (get-signature pid psk2 note)))))
|
||||||
(is (revoke-publisher pid2))
|
(is (storage/revoke-publisher pid2))
|
||||||
(isnt (:success (:status (post-note note "randomPID" (get-signature pid psk2 note)))))))
|
(isnt (:success (:status (post-note note "randomPID" (get-signature pid psk2 note)))))))
|
||||||
(testing "note update"
|
#_ (testing "note update"
|
||||||
(let [post-response (post-note note pid (get-signature pid psk note) "passwd")
|
(let [post-response (post-note note pid (get-signature pid psk note) "passwd")
|
||||||
note-id (:noteID post-response)
|
note-id (:noteID post-response)
|
||||||
get-response (get-note note-id)
|
get-response (get-note note-id)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
(ns NoteHub.test.storage
|
(ns NoteHub.test.storage
|
||||||
(:use [NoteHub.storage]
|
(:use [NoteHub.storage]
|
||||||
|
[NoteHub.api :only [build-key]]
|
||||||
[NoteHub.views.pages]
|
[NoteHub.views.pages]
|
||||||
[clojure.test]))
|
[clojure.test]))
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
(ns NoteHub.test.views.pages
|
(ns NoteHub.test.views.pages
|
||||||
(:use [NoteHub.views.pages]
|
(:use [NoteHub.views.pages]
|
||||||
|
[NoteHub.api :only [build-key get-date]]
|
||||||
[noir.util.test]
|
[noir.util.test]
|
||||||
[NoteHub.views.common :only [url]]
|
[NoteHub.views.common :only [url]]
|
||||||
[NoteHub.storage]
|
[NoteHub.storage]
|
||||||
|
|||||||
Reference in New Issue
Block a user