Last active
August 29, 2015 13:56
-
-
Save sakekasi/9337146 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns com.sakekasi.linkshare.core | |
"the core namespace does routing and http request handling" | |
(:require [liberator.core :refer [resource defresource]] | |
[liberator.dev :refer [wrap-trace]] | |
[clojure.data.json :as json] | |
[clojure.java.io :as io] | |
[clojure.string :refer [split]] | |
[ring.middleware.params :refer [wrap-params]] | |
;[ring.middleware.stacktrace :refer [wrap-stacktrace]] | |
[ring.middleware.cors :refer [wrap-cors]] | |
[ring.util.response :refer [not-found]] | |
[ring.util.codec :refer [url-decode]] | |
[compojure.core :refer [defroutes ANY OPTIONS]] | |
[com.sakekasi.linkshare.url :as url] | |
[com.sakekasi.linkshare.db :as db])) | |
; RESTful API: | |
; --------------------- | |
; /lookup?url="" (get) | |
; /link (get) | |
; /link (post) | |
; /link (delete) | |
; /link/:id (get) | |
; /link/:id (put) | |
; /link/:id (delete) | |
; /links (get) | |
; /links (post w/ delete flag) | |
; /links/:lim (get) | |
; /reset (delete) | |
(defn single-arg [f] | |
(fn [_] (f))) | |
(defn non-nil [key val] | |
(single-arg #(if (not (nil? val)) | |
{key val}))) | |
(defn non-empty [key val] | |
(single-arg #(if (not (= val [])) | |
{key val}))) | |
(defn etag [{title :title url :url id :id}] | |
(str (hash title) (hash url) id)) | |
(defn m-etag [links] | |
(hash (apply str (map etag links)))) | |
(defn body-as-string [ctx] | |
(if-let [body (get-in ctx [:request :body])] | |
(condp instance? body | |
java.lang.String body | |
(slurp (io/reader body))))) | |
(defn parse-json [context key] | |
(when (#{:put :post} (get-in context [:request :request-method])) | |
(try | |
(if-let [body (body-as-string context)] | |
(let [data (json/read-str body :key-fn keyword)] | |
[false {key data}]) | |
{:message "No body"}) | |
(catch Exception e | |
(.printStackTrace e) | |
{:message (format "IOException: " (.getMessage e))})))) | |
(defn check-content-type [ctx content-types] | |
(if (#{:put :post} (get-in ctx [:request :request-method])) | |
(let [ctype (first (split (get-in ctx [:request :headers "content-type"]) | |
#";"))] | |
(or | |
(some #{ctype} | |
content-types) | |
[false {:message (str "Unsupported Content-Type " | |
ctype)}])) | |
true)) | |
(defresource link [id] | |
:allowed-methods [:get :put :delete] | |
:available-media-types ["application/json" | |
"text/html"] | |
:known-content-type? #(check-content-type % ["application/json"]) | |
:malformed? #(parse-json % ::json) | |
:exists? (non-nil ::val (db/get-link id)) | |
:handle-ok ::val | |
:can-put-to-missing? nil | |
:put! (comp db/update-link ::json) | |
:delete! (single-arg #(db/remove-link id)) | |
:etag (comp etag ::val)) | |
(defresource latest-link | |
:allowed-methods [:get :post :delete] | |
:available-media-types ["application/json" | |
"text/html"] | |
:known-content-type? #(check-content-type % ["application/json"]) | |
:malformed? #(parse-json % ::json) | |
:exists? (non-nil ::val (db/get-latest-link)) | |
:handle-ok ::val | |
:post! (comp db/put-link ::json) | |
:delete! (fn [ctx] (db/remove-link (get-in ctx [::val :id]))) | |
:etag (comp etag ::val)) | |
(defresource links [lim] | |
:allowed-methods [:get] | |
:available-media-types ["application/json" | |
"text/html"] | |
:exists? (non-empty ::val (db/get-links lim)) | |
:handle-ok (fn [ctx] {:links (::val ctx) | |
:next (:id (last (::val ctx)))}) | |
:etag (comp m-etag ::val)) | |
(defresource latest-links | |
:allowed-methods [:get :post] | |
:available-media-types ["application/json" | |
"text/html"] | |
:known-content-type? #(check-content-type % ["application/json"]) | |
:malformed? #(parse-json % ::json) | |
:exists? (non-empty ::val (db/get-latest-links)) | |
:handle-ok (fn [ctx] {:links (::val ctx) | |
:next (:id (last (::val ctx)))}) | |
:post! (fn [ctx] (if (get-in ctx [::json :delete]) | |
(db/remove-links (get-in ctx [::json :ids])) | |
(db/put-links (get-in ctx [::json :links])))) | |
:etag (comp m-etag ::val)) | |
(defresource lookup-url | |
:allowed-methods [:get] | |
:available-media-types ["text/plain" | |
"text/html"] | |
:malformed? #(nil? (get-in % [:request :params "url"])) | |
:handle-ok (fn [ctx] (url/title (url-decode | |
(get-in ctx [:request :params "url"]))))) | |
(defresource reset | |
:allowed-methods [:delete] | |
:delete! db/reinit) | |
(defresource home-page | |
:allowed-methods [:get] | |
:available-media-types ["text/plain" | |
"text/html"] | |
:handle-ok (str "<pre>Welcome to the linkshare REST API\n" | |
"--------------------------------------\n" | |
"/lookup?url="" (get) make the url unquoted\n" | |
"/link (get)\n" | |
"/link (post)\n" | |
"/link (delete)\n" | |
"/link/:id (get)\n" | |
"/link/:id (put)\n" | |
"/link/:id (delete)\n" | |
"/links (get)\n" | |
"/links (post w/ delete flag)\n" | |
"/links/:lim (get)\n" | |
"/reset (delete)</pre>")) | |
(defroutes linkshare | |
(OPTIONS "*" [] "cross origin requests allowed") | |
(ANY "/" [] home-page) | |
(ANY "/link" [] latest-link) | |
(ANY ["/link/:id", :id #"[0-9]+"] [id] (link id)) | |
(ANY "/links" [] latest-links) | |
(ANY ["/links/:lim", :lim #"[0-9]+"] [lim] (links lim)) | |
(ANY "/lookup" [] lookup-url) | |
(ANY "/reset" [] reset) | |
(ANY "*" [] (not-found "not found"))) | |
(def app | |
(-> linkshare | |
(wrap-params) | |
(wrap-cors :access-control-allow-origin #".*" | |
:access-control-allow-headers ["Origin" "X-Requested-With" | |
"Content-Type" "Accept"]))) | |
(defn init [] | |
(db/init) | |
(println "linkshare is starting")) | |
(defn destroy [] | |
(println "linkshare is shutting down")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns com.sakekasi.linkshare.db | |
"the db namespace is in charge of transactions with our database" | |
(:require [clojure.java.jdbc :as jdbc] | |
[com.sakekasi.linkshare.config :as config])) | |
(def db | |
{:classname "com.mysql.jdbc.Driver" | |
:subprotocol "mysql" | |
:subname config/dbpath | |
:user "linkshare" | |
:password "linkshare"}) | |
;; we may want different users to login, implementing multi user. | |
;; different db tables for different users | |
(def create-table | |
"ddl command to create the appropriate table" | |
(str "CREATE TABLE IF NOT EXISTS links " | |
"(id INTEGER AUTO_INCREMENT PRIMARY KEY, " | |
"title TEXT, " | |
"url VARCHAR(65000) );")) | |
(defn init | |
"creates the appropriate table if it does not exist" | |
[] | |
(println "init") | |
(jdbc/db-do-commands db create-table)) | |
(defn reinit | |
"reinitializes table" | |
[] | |
(println "reinit") | |
(jdbc/db-do-commands db | |
(jdbc/drop-table-ddl :links) | |
create-table)) | |
(defn put-links | |
"inserts multiple * pairs into the database" | |
[links] | |
(println "put-links") | |
(apply (partial jdbc/insert! db | |
:links) | |
links)) | |
(defn put-link | |
"inserts a single * pair into the database" | |
[link] | |
(println "put-link") | |
(jdbc/insert! db :links link)) | |
(defn update-link | |
"updates a single link in database" | |
[link] | |
(println "update-link") | |
(jdbc/update! db :links | |
(reduce (fn [m [k v]] (assoc m k v)) {} | |
(filter (fn [[k v]] (and (not (nil? v)) | |
(not (= k :id)))) | |
link)) | |
["id = ?" (:id link)])) | |
(defn get-link | |
"gets the link with id from the table" | |
[id] | |
(println "get-link") | |
(first (jdbc/query db ["SELECT * FROM links WHERE id=? LIMIT 1" id]))) | |
(defn get-latest-link | |
"gets the latest link from the table" | |
[] | |
(println "get-latest-link") | |
(try | |
(throw (Exception. "get-latest-link")) | |
(catch Exception e | |
(.printStackTrace e))) | |
(first (jdbc/query db ["SELECT * from links ORDER BY id DESC LIMIT 1"]))) | |
(defn get-links | |
"gets page-size links older than lim" | |
[lim] | |
(println "get-links") | |
(doall config/page-size | |
(jdbc/query db | |
["SELECT * from links WHERE id<? ORDER BY id DESC LIMIT ?" | |
lim config/page-size]))) | |
(defn get-latest-links | |
"gets the page-size newest links" | |
[] | |
(println "get-latest-links") | |
(doall | |
(jdbc/query db ["SELECT * from links ORDER BY id DESC LIMIT ?" | |
config/page-size]))) | |
(defn remove-links | |
"removes links with ids from db" | |
[ids] | |
(println "remove-links") | |
(doall (map (partial jdbc/db-do-prepared db "DELETE FROM links WHERE id=?") | |
(map vector ids)))) | |
(defn remove-link | |
"removes link with id from db" | |
[id] | |
(println "remove-link") | |
(remove-links (vector id))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
java.lang.Exception: get-latest-link | |
at com.sakekasi.linkshare.db$get_latest_link$fn__649.invoke(db.clj:71) | |
at com.sakekasi.linkshare.db$get_latest_link.invoke(db.clj:71) | |
at clojure.lang.AFn.applyToHelper(AFn.java:159) | |
at clojure.lang.AFn.applyTo(AFn.java:151) | |
at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3458) | |
at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3457) | |
at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3457) | |
at clojure.lang.Compiler$DefExpr.eval(Compiler.java:408) | |
at clojure.lang.Compiler.eval(Compiler.java:6624) | |
at clojure.lang.Compiler.load(Compiler.java:7064) | |
at clojure.lang.RT.loadResourceScript(RT.java:370) | |
at clojure.lang.RT.loadResourceScript(RT.java:361) | |
at clojure.lang.RT.load(RT.java:440) | |
at clojure.lang.RT.load(RT.java:411) | |
at clojure.core$load$fn__5018.invoke(core.clj:5530) | |
at clojure.core$load.doInvoke(core.clj:5529) | |
at clojure.lang.RestFn.invoke(RestFn.java:408) | |
at clojure.core$load_one.invoke(core.clj:5336) | |
at clojure.core$load_lib$fn__4967.invoke(core.clj:5375) | |
at clojure.core$load_lib.doInvoke(core.clj:5374) | |
at clojure.lang.RestFn.applyTo(RestFn.java:142) | |
at clojure.core$apply.invoke(core.clj:619) | |
at clojure.core$load_libs.doInvoke(core.clj:5413) | |
at clojure.lang.RestFn.applyTo(RestFn.java:137) | |
at clojure.core$apply.invoke(core.clj:619) | |
at clojure.core$require.doInvoke(core.clj:5496) | |
at clojure.lang.RestFn.invoke(RestFn.java:457) | |
at user$eval5.invoke(form-init523998336482540680.clj:1) | |
at clojure.lang.Compiler.eval(Compiler.java:6619) | |
at clojure.lang.Compiler.eval(Compiler.java:6608) | |
at clojure.lang.Compiler.load(Compiler.java:7064) | |
at clojure.lang.Compiler.loadFile(Compiler.java:7020) | |
at clojure.main$load_script.invoke(main.clj:294) | |
at clojure.main$init_opt.invoke(main.clj:299) | |
at clojure.main$initialize.invoke(main.clj:327) | |
at clojure.main$null_opt.invoke(main.clj:362) | |
at clojure.main$main.doInvoke(main.clj:440) | |
at clojure.lang.RestFn.invoke(RestFn.java:421) | |
at clojure.lang.Var.invoke(Var.java:419) | |
at clojure.lang.AFn.applyToHelper(AFn.java:163) | |
at clojure.lang.Var.applyTo(Var.java:532) | |
at clojure.main.main(main.java:37) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment