Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
coopernurse committed Sep 6, 2011
0 parents commit 51e9e3b
Show file tree
Hide file tree
Showing 11 changed files with 218 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitignore
@@ -0,0 +1,8 @@
pom.xml
*jar
/lib/
/classes/
.lein-failures
.lein-deps-sum
*.iml
.idea
26 changes: 26 additions & 0 deletions README.md
@@ -0,0 +1,26 @@
# bookfriend

Source code for the webapp: http://bookfriend.me/

A matchmaking site for Kindle and Nook owners to allow them to share books with
each other.

The site uses:

* Google App Engine (with appengine-magic)
* Noir

## Dev Notes

To test URL fetch when in REPL:

(require '[appengine-magic.local-env-helpers :as ae-helpers])
(ae-helpers/appengine-init (java.io.File. ".") 8090)



## License

Copyright (C) 2011 James Cooper

Distributed under the Eclipse Public License, the same as Clojure.
12 changes: 12 additions & 0 deletions project.clj
@@ -0,0 +1,12 @@
(defproject bookfriend "1.0.0-SNAPSHOT"
:description "bookfriend.me - Kindle and Nook book sharing"
:dependencies [[org.clojure/clojure "1.2.1"]
[org.clojure/clojure-contrib "1.2.0"]
[clj-appengine-oauth "0.1.0"]
[am.ik/clj-aws-ecs "0.1.0"]
[noir "1.1.1-SNAPSHOT"] ]
:dev-dependencies [[appengine-magic "0.4.4"]])

(use 'am.ik.clj-aws-ecs)
(def requester (make-requester "ecs.amazonaws.com" "0E0H5H2MA5VSC80AWH82" "JJqDI8/sz19oE1VkVZ+XOy97ql42Tfo//g+RtkNe"))
(item-search-map requester "Books" "Clojure" { "ResponseGroup" "Medium" "BrowseNode" "1286228011" })
8 changes: 8 additions & 0 deletions src/bookfriend/app_servlet.clj
@@ -0,0 +1,8 @@
(ns bookfriend.app_servlet
(:gen-class :extends javax.servlet.http.HttpServlet)
(:use bookfriend.core)
(:use [appengine-magic.servlet :only [make-servlet-service-method]]))


(defn -service [this request response]
((make-servlet-service-method bookfriend-app) this request response))
16 changes: 16 additions & 0 deletions src/bookfriend/collections.clj
@@ -0,0 +1,16 @@
(ns bookfriend.collections)

(defn create-map-exclude-nil [xs]
(into {} (remove (comp nil? second) xs)))

(defn map-difference [m1 m2]
(let [ks1 (set (keys m1))
ks2 (set (keys m2))
ks1-ks2 (clojure.set/difference ks1 ks2)
ks2-ks1 (clojure.set/difference ks2 ks1)
ks1*ks2 (clojure.set/intersection ks1 ks2)]
(merge (select-keys m1 ks1-ks2)
(select-keys m2 ks2-ks1)
(select-keys m1
(remove (fn [k] (= (m1 k) (m2 k)))
ks1*ks2)))))
32 changes: 32 additions & 0 deletions src/bookfriend/linkshare.clj
@@ -0,0 +1,32 @@
(ns bookfriend.linkshare
(:use clojure.contrib.zip-filter.xml)
(:use bookfriend.xml)
(:use bookfriend.url)
(:use bookfriend.collections)
(:require [appengine-magic.services.url-fetch :as fetch]))

(defmacro dbg [x] `(let [x# ~x] (println '~x "=" x#) x#))

(defn parse-product-item [item]
(element-to-map (:content (first item))))

(defn parse-product-search-result [res]
(let [xz (zip-xml-str res)]
{
:TotalMatches (first (xml-> xz :TotalMatches text))
:TotalPages (first (xml-> xz :TotalPages text))
:items (map parse-product-item (xml-> xz :item))
}))

(defn product-search
[token keyword & [{:keys [cat max-results pagenumber mid sort sort-type]}]]
(let [
query-map (create-map-exclude-nil [
[:token token]
[:keyword keyword]
[:cat cat]
[:MaxResults max-results]
[:pagenumber pagenumber]
[:mid mid]])
url (build-url "http://productsearch.linksynergy.com/productsearch" (dbg query-map))]
(parse-product-search-result (String. (:content (fetch/fetch url))))))
18 changes: 18 additions & 0 deletions src/bookfriend/url.clj
@@ -0,0 +1,18 @@
(ns bookfriend.url
(:use [ring.util.codec :only [url-encode]]))

;; From:
;; http://stackoverflow.com/questions/3644125/clojure-building-of-url-from-constituent-parts
(defn make-query-string [m & [encoding]]
(let [s #(if (instance? clojure.lang.Named %) (name %) %)
enc (or encoding "ISO-8859-1")]
(->> (for [[k v] m]
(str (url-encode (s k) enc)
"="
(url-encode (str v) enc)))
(interpose "&")
(apply str))))

(defn build-url [url-base query-map & [encoding]]
(str url-base "?" (make-query-string query-map encoding)))

16 changes: 16 additions & 0 deletions src/bookfriend/xml.clj
@@ -0,0 +1,16 @@
(ns bookfriend.xml
(:require [clojure.xml :as xml]
[clojure.zip :as zip]))

(declare element-to-map)

(defn zip-xml-str [s]
(zip/xml-zip (xml/parse (new org.xml.sax.InputSource (new java.io.StringReader s)))))

(defn element-value [e]
(if (> (count e) 1) (element-to-map e) (first e)))

(defn element-to-map [e]
(apply array-map
(apply concat
(map (fn [x] (list (:tag x) (element-value (:content x)))) e))))
53 changes: 53 additions & 0 deletions test/bookfriend/test/linkshare.clj
@@ -0,0 +1,53 @@
(ns bookfriend.test.linkshare
(:use [bookfriend.linkshare])
(:use [clojure.test]))

(def result-xml "<result>
<TotalMatches>29</TotalMatches>
<TotalPages>1</TotalPages>
<PageNumber>1</PageNumber>
<item>
<mid>36889</mid>
<merchantname>Barnes&amp;Noble.com</merchantname>
<linkid>9780203451588</linkid>
<createdon>2011-08-04/18:08:12</createdon>
<sku>9780203451588</sku>
<productname>Political Corruption</productname>
<category>
<primary>eBooks</primary>
<secondary>True Crime</secondary>
</category>
<price currency=\"USD\">52.95</price>
<upccode>123abc</upccode>
<description>
<short>Robert Harris,NOOKbook (eBook)</short>
<long/>
</description>
<keywords/>
<linkurl>http://click.linksynergy.com/fs-bin/click?id=2Pjrw9gq7Wo&amp;offerid=229293.9780203451588&amp;type=15&amp;subid=0</linkurl>
<imageurl>http://images.barnesandnoble.com/pimages/gresources/ImageNA_product.gif</imageurl>
</item></result>")

(def expected-map {
:mid "36889"
:merchantname "Barnes&Noble.com"
:createdon "2011-08-04/18:08:12"
:linkid "9780203451588"
:sku "9780203451588"
:productname "Political Corruption"
:category { :primary "eBooks" :secondary "True Crime" }
:price "52.95"
:upccode "123abc"
:description { :short "Robert Harris,NOOKbook (eBook)"
:long nil }
:keywords nil
:linkurl "http://click.linksynergy.com/fs-bin/click?id=2Pjrw9gq7Wo&offerid=229293.9780203451588&type=15&subid=0"
:imageurl "http://images.barnesandnoble.com/pimages/gresources/ImageNA_product.gif"
})

(deftest test-productsearch-xml-parsing
(let [res (parse-product-search-result result-xml)]
(is (= "29" (:TotalMatches res)))
(is (= "1" (:TotalPages res)))
(is (= expected-map (first (:items res))))))

10 changes: 10 additions & 0 deletions war/WEB-INF/appengine-web.xml
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><appengine-web-app xmlns="http://appengine.google.com/ns/1.0">

<application>bookfriend</application>
<version>1</version>
<static-files/>
<resource-files/>

<threadsafe>true</threadsafe>

</appengine-web-app>
19 changes: 19 additions & 0 deletions war/WEB-INF/web.xml
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?><web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.5" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<display-name>bookfriend</display-name>

<servlet>
<servlet-name>app</servlet-name>
<servlet-class>bookfriend.app_servlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>

</web-app>

0 comments on commit 51e9e3b

Please sign in to comment.