Skip to content

Instantly share code, notes, and snippets.

@sakekasi
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sakekasi/9337146 to your computer and use it in GitHub Desktop.
Save sakekasi/9337146 to your computer and use it in GitHub Desktop.
(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"))
(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)))
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