(ns NoteHub.api (: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 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 ([success] { :success success }) ([success message & params] (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")] (defn get-signature "Returns the MD5 hash for the concatenation of all passed parameters" [& args] (let [input (apply str args)] (do (.reset md5Instance) (.update md5Instance (.getBytes input)) (.toString (new java.math.BigInteger 1 (.digest md5Instance)) 16))))) (defn get-note [noteID] {:note (storage/get-note noteID) :longURL (getURL noteID) :shortURL (getURL noteID :short) :statistics (storage/get-note-statistics noteID) :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))})))