1. Miki Tebeka
  2. dude-remember

Source

dude-remember / src / duderemember.clj

(ns duderemember
  (:gen-class :extends javax.servlet.http.HttpServlet)
  (:use appengine.datastore
        appengine.users 
        clojure.contrib.prxml
        compojure.core
        hiccup.core
        hiccup.form-helpers
        hiccup.page-helpers
        ring.util.response
        ring.util.servlet)
  (:require [compojure.route :as route]))

(defn digits-only [phone]
  (apply str (re-seq #"[0-9]+" phone)))

(defentity User ()
  ((user-id) ; We call it user-id to match Google user object
   (phone)))

(defentity Message ()
  ((user-id)
   (time)
   (text)
   (url)))

(defn phone->user [phone]
  (let [users (find-users-by-phone phone)]
    (if (zero? (count users))
      nil
      (first users))))

(defn user->phone [user]
  (let [entities (find-users-by-user-id (:user-id user))]
    (if (zero? (count entities))
      nil
      (:phone (first entities)))))

(defn new-user? [user]
  (nil? (user->phone user)))

(defn update-user [u phone]
  (let [phone (digits-only phone)]
    (if (new-user? u)
      (save-entity (user {:user-id (:user-id u) :phone phone}))
      (let [entity (first (find-users-by-user-id (:user-id u)))]
        (update-entity entity {:phone phone})
        (save-entity entity)))))

(defn now []
  (System/currentTimeMillis))

(defn add-sms [user text]
  (let [entity (message {:user-id (:user-id user) :text text :time (now)})]
    (save-entity entity)))

(defn add-voice [user url]
  (let [entity (message {:user-id (:user-id user) :url url :time (now)})]
    (save-entity entity)))

(defn user-messages [user]
  (select "message"
    where (= :user-id (:user-id user))
    order-by (:time :desc)))

(def *title* "Dude! ... Remember!")

(defn page [& content]
  (html
    [:head
     (include-css "/static/dude.css")
     [:title *title*]]
    [:body 
     [:div {:class "header"} "TBD"]
     [:h1 [:a {:href "/"} [:img {:src "/static/sms.png"}]] *title*]
     content]))

(defn format-time [t]
  (let [date (new java.util.Date)]
    (.setTime date t)
    (str date)))

(defn render-messages [messages]
  (if (zero? (count messages))
    "No messages for you!"
    (for [message messages]
      [:div {:class "message"}
       "Time: " (format-time (:time message)) [:br]
       "Text:" [:br]
       (:text message)])))

(defn login-page []
  (page
    [:a {:href (login-url "/")} "Please login"]))

(defn user-page [user]
  (let [messages (user-messages user)]
    (page
      [:h2 "Messages for " (:nickname user) " (" (user->phone user) ")"]
      (render-messages messages))))

(defn eval-page [expr]
  (page
    (str (load-string (str "(in-ns 'duderemember)" expr)))
    (form-to [:post]
      [:br]
      (text-field :expr)
      (submit-button "Eval"))))

(defn phone-page []
  (page
    "Please update your phone number" [:br]
    (form-to [:post]
       (text-field :phone)
       (submit-button "Update"))))

(defn twilio-response [& params]
  (with-out-str (prxml [:Response params])))

(defn twilio-sms [params]
  (let [phone (params "From")
        text (params "Body")
        user (phone->user phone)]
    (if user
      (do (add-sms user text)
        (twilio-response))
      ; Can't find user, SMS back how to register
      (twilio-response [:Sms "Dude, I don't know you! "
                            "Check out http://dude-remember.appspot.com"]))))

(defn twilio-voice [params]
  (let [phone (params "From")
        user (phone->user phone)]
    (if (nil? user)
      (twilio-response [:Say {:voice "woman"} "Dude, I don't know you. Ciao!"]
                      [:Hangup])
      (let [url (params "RecordingUrl")]
        (if (nil? url) ; New call
          (twilio-response [:Say {:voice "woman"} "Talk to me babe!"]
                          [:Record {:maxLength "30"}])
          (do (add-voice user url)
            (twilio-response [:Say {:voice "woman"} "Gotcha! Later dude."]
                            [:Hangup])))))))

(defn welcome-page []
  (let [user (current-user)]
    (if (nil? user)
      (login-page)
      (if (new-user? user)
        (redirect "/phone")
        (user-page user)))))

(defroutes duderemeber-routes
  (GET "/" [] (welcome-page))

  (GET "/phone" [] (phone-page))
  (POST "/phone" {params :params}
        (let [phone (params "phone")]
          (update-user (current-user) phone)
          (redirect "/")))

  (POST "/twilio/sms" {params :params} (twilio-sms params))
  (POST "/twilio/voice" {params :params} (twilio-voice params))

; Uncomment these when debugging, *don't* let them slip to production           
;  (GET "/_eval" [] (eval-page "0"))
;  (POST "/_eval" {params :params} (eval-page (params "expr")))

  (route/not-found "Dude! I can't find it."))

(defservice duderemeber-routes)