summaryrefslogtreecommitdiff
path: root/src/site.clj
diff options
context:
space:
mode:
Diffstat (limited to 'src/site.clj')
-rw-r--r--[-rwxr-xr-x]src/site.clj248
1 files changed, 214 insertions, 34 deletions
diff --git a/src/site.clj b/src/site.clj
index 3a59264..fc61686 100755..100644
--- a/src/site.clj
+++ b/src/site.clj
@@ -15,12 +15,14 @@
clojure.contrib.json.write
clojure.contrib.sql
clojure.contrib.str-utils
+ clojure.contrib.def
compojure
email
utils
cookie-login
session-sweeper
- feed))
+ feed
+ tags))
(def *run-flusher* true)
(def *flusher-sleep* (seconds 4))
@@ -127,21 +129,57 @@
(defn add-message [msg room]
(alter (room :messages) (swap cons) msg))
+;; Logging
+
+; is there a better way to do this or am i insane for introducing state? just wanna do
+; (let [log (debug-log)]
+; (log "something")
+; (log "something else")
+; (log)) ; gets log array for output to template
+(defn make-debug-logger
+ ([] (make-debug-logger (new java.util.ArrayList)))
+ ([log]
+ (fn
+ ([] (to-array log))
+ ([s] (make-debug-logger (.add log s))))))
+
+(defn make-time-logger
+ ([] (make-time-logger (new java.util.ArrayList)))
+ ([log]
+ (fn
+ ([] (to-array log));
+ ([f & args]
+ (let [start (.getTime (new Date))
+ ret (apply f args)
+ log-string (str f ": " (- (.getTime (new Date)) start) " msecs" )]
+ (.add log log-string)
+ ret)))))
+
;; Output
(defn strip-empty-vals [m]
(into {} (filter (fn [[k v]] (non-empty-string? v)) m)))
+(declare stringify-and-escape)
+(defn escape-html-deep [o]
+ (if (map? o)
+ (stringify-and-escape o)
+ (if (seq? o)
+ (map escape-html-deep o)
+ (escape-html o))))
+
(defn stringify-and-escape [m]
- (zipmap (map name (keys m)) (map escape-html (vals m))))
+ (zipmap (map str* (keys m)) (map escape-html-deep (vals m))))
(defn process-message-for-json [d]
(assoc d :created_on (.getTime (d :created_on))))
(defn process-message-for-output [d]
- (stringify-and-escape
- (strip-empty-vals
- (assoc d :created_on (.format formatter (d :created_on))))))
+ (escape-html-deep
+ (strip-empty-vals
+ (if (contains? d :created_on)
+ (assoc d :created_on (.format formatter (d :created_on)))
+ d))))
(defn new-messages [room since-ts]
(let [since-date (new Date (long since-ts))]
@@ -163,12 +201,12 @@
(let [m {"users" (prepare-user-list room)
"messages" (map process-message-for-json
(new-messages room since))}
+
topic @(room :topic)]
(if topic
(assoc m "topic" topic)
m)))
-(def *dumps-per-page* 20)
(defn count-messages-by-room [room-id image-only]
(let [query (str "SELECT COUNT(*)
@@ -181,11 +219,11 @@
([room-id image-only] (fetch-messages-by-room room-id image-only 0))
([room-id image-only offset]
(let [query (str "SELECT m.content, m.message_id, m.created_on, u.nick, u.avatar
- FROM messages m, users u
- WHERE room_id = ? AND m.user_id = u.user_id "
- (if image-only "AND m.is_image = true " "")
- "ORDER BY created_on DESC
- LIMIT ? OFFSET ?")]
+ FROM users u, messages m
+ WHERE room_id = ? AND m.user_id = u.user_id "
+ (if image-only "AND m.is_image = true " "")
+ "ORDER BY created_on DESC
+ LIMIT ? OFFSET ?")]
(do-select [query room-id *dumps-per-page* offset]))))
(defn count-messages-by-nick [nick image-only]
@@ -208,6 +246,7 @@
LIMIT ? OFFSET ?")]
(do-select [query nick *dumps-per-page* offset]))))
+
(defn fetch-public-message-by-id [id]
(let [query "SELECT m.content, m.created_on, m.user_id, u.nick, u.avatar
FROM messages m, users u, rooms r
@@ -228,6 +267,26 @@
:topic (ref nil)
})
+;; User-id/nick cache
+;; I keep needing to grab user-id from a nick or nick from a user-id so I thought I'd cache them
+
+(def user-id-cache (ref {}))
+(def *user-id-cache-size* 500)
+
+(defn user-id-from-nick [nick]
+ (let [nick (lower-case nick)
+ found (@user-id-cache nick)]
+ (if found
+ found
+ (let [query (str "SELECT user_id FROM users WHERE lower(nick) = ?")
+ res (first (do-select [query nick]))]
+ (if (nil? res)
+ nil
+ (let [found (res :user_id)]
+ (dosync
+ (if (> (count @user-id-cache) *user-id-cache-size*) (ref-set user-id-cache {}))
+ (alter user-id-cache assoc nick found))
+ found))))))
;; Login code
@@ -244,11 +303,11 @@
(defn session-assoc-from-db
[user-info]
(session-assoc :user_id (user-info :user_id)
- :nick (user-info :nick)
- :email (user-info :email)
- :is_admin (user-info :is_admin)
- :avatar (user-info :avatar)
- :password_login true))
+ :nick (user-info :nick)
+ :email (user-info :email)
+ :is_admin (user-info :is_admin)
+ :avatar (user-info :avatar)
+ :password_login true))
;; Templates
@@ -346,15 +405,17 @@
(defn profile [session profile-nick offset]
(if-let [user-info (fetch-nick profile-nick)]
- (let [nick (session :nick)
+ (let [st (fetch-template "profile" session)
+ nick (session :nick)
+ logger (make-time-logger)
is-home (and nick (= nick profile-nick))
has-avatar (non-empty-string? (user-info :avatar))
offset (maybe-parse-int offset 0)
dump-offset (* offset *dumps-per-page*)
- dumps (fetch-messages-by-nick profile-nick true dump-offset)
- dump-count (count-messages-by-nick profile-nick true)
- st (fetch-template "profile" session)
- dumps (map process-message-for-output dumps)]
+ raw-dumps (logger tags/fetch-dumps-by-nick :nick profile-nick :amount (+ 1 *dumps-per-page*) :offset dump-offset)
+ dumps (map tags/add-favorited-flag (take *dumps-per-page* raw-dumps) (repeat session))
+ dumps (map tags/remove-tags-for-output dumps)
+ dumps (logger doall (map process-message-for-output dumps))]
(do
(.setAttribute st "is_home" is-home)
(doseq [a [:nick :avatar :contact :bio]]
@@ -363,14 +424,14 @@
(if (non-empty-string? v) (escape-html v)))))
(if (> (count dumps) 0)
(.setAttribute st "dumps" dumps))
- (if (< (+ dump-offset *dumps-per-page*) dump-count)
+ (if (> (count raw-dumps) *dumps-per-page*)
(.setAttribute st "next" (inc offset)))
(if (not= offset 0)
(.setAttribute st "prev" (max (dec offset) 0)))
+ (.setAttribute st "debug_log_items" (logger))
(.toString st)))
(resp-error "NO_USER")))
-
(defn update-user-db [user-id attr val]
(with-connection *db*
(update-values "users" ["user_id = ?" user-id] {attr val})))
@@ -545,9 +606,10 @@
(let [now (System/currentTimeMillis)
nick (session :nick)
st (fetch-template template session)
- message-list (map process-message-for-output
- ; TODO: remove db query
- (reverse (fetch-messages-by-room (room :room_id) false)))]
+ ; TODO: remove db query
+ message-list (reverse (tags/fetch-dumps-by-room :room-id (room :room_id) :image-only false))
+ message-list (map tags/add-favorited-flag message-list (repeat session))
+ message-list (to-array (map process-message-for-output message-list))]
(if nick
(dosync
(login-user (user-struct-from-session session) room)))
@@ -655,22 +717,32 @@
;; Chat Log
; TODO: Optimize dump counts
+; timb: ^^ done... i changed it to fetch one more than is shown per page to determine if next page is needed
(defn log [session room offset params]
(let [st (fetch-template "log" session)
+ logger (make-time-logger)
offset (maybe-parse-int offset 0)
- dump-offset (* offset *dumps-per-page*)
- image-only (and (not (room :admin_only))
+ dump-offset (* offset *dumps-per-page*)
+ image-only (and (not (room :admin_only))
(not= (params :show) "all"))
- dumps (map process-message-for-output
- (fetch-messages-by-room (room :room_id) image-only dump-offset))
- dump-count (count-messages-by-room (room :room_id) image-only)]
- (if (< (+ dump-offset *dumps-per-page*) dump-count)
+ raw-dumps (logger tags/fetch-dumps-by-room :room-id (room :room_id)
+ :image-only image-only
+ :amount (+ 1 *dumps-per-page*)
+ :offset dump-offset)
+ dumps (map tags/add-favorited-flag (take *dumps-per-page* raw-dumps) (repeat session))
+;; json-tags (for [dump dumps :when (not (empty? (dump :tags)))]
+;; (json-str {"id" (dump :message_id) "tags" (dump :tags) }))
+ dumps (map tags/remove-tags-for-output dumps)
+ dumps (logger doall (map process-message-for-output dumps))]
+ (if (> (count raw-dumps) *dumps-per-page*)
(.setAttribute st "next" (inc offset)))
(if (not= offset 0)
(.setAttribute st "prev" (max (dec offset) 0)))
(.setAttribute st "dumps" dumps)
+;; (.setAttribute st "json_tags" json-tags)
(.setAttribute st "roomkey" (room :key))
(.setAttribute st "roomname" (room :name))
+ (.setAttribute st "debug_log_items" (logger))
(.toString st)))
(defn validated-log [session room-key offset params]
@@ -678,6 +750,103 @@
(log session (@rooms room-key) offset params)
(resp-error "UNKNOWN_ROOM")))
+;; Tags
+
+(defn undecoded-url-piece [url position]
+ "Get nth thing out of a url path.
+ For example, (undecoded-url-piece 'http://example.com/a/b/c?foo' 2) will return 'c'"
+ (let [path-without-domain (nth (re-find #"//[^/]+/(.+)" url) 1)]
+ (nth (re-split #"/|\?" path-without-domain) position)))
+
+(defn add-tag [user-id message-id tag]
+ (try
+ (do-insert "tags" ["user_id" "message_id" "tag"] [user-id (maybe-parse-int message-id) (normalize-tag-for-db tag)])
+ true
+ ; catch error when inserting duplicate tags
+ (catch Exception e false)))
+
+
+; to do: don't let people set tags on messages they can't access
+(defn validated-add-tag [session params]
+ (if (session :nick)
+ (if (add-tag (session :user_id) (params :message_id) (params :tag))
+ (resp-success "OK")
+ (resp-error "TAG_EXISTS_ALREADY_OR_SOMETHING_ELSE_IS_FUCKED"))
+ (resp-error "NO_USER")))
+
+(defn remove-tag [user-id message-id tag]
+ (let [query "user_id = ? AND message_id = ? AND lower(tag) = ?"]
+ (do-delete "tags" [query user-id (maybe-parse-int message-id) (normalize-tag-for-db (.toLowerCase tag))])
+ (resp-success "OK")))
+
+(defn validated-remove-tag [session params]
+ (if (session :nick)
+ (remove-tag (session :user_id) (params :message_id) (params :tag))
+ (resp-error "NO_USER")))
+
+(defnk tagged-dumps-template [session params tags url page-title :message-user-id false
+ :tag-user-id false
+ :logger (make-time-logger)]
+ (let [st (fetch-template "tagged_dumps" session)
+ offset (maybe-parse-int (params :offset) 0)
+ dump-offset (* offset *dumps-per-page*)
+ raw-dumps (logger tags/fetch-dumps-by-tag :tags tags
+ :image-only false
+ :amount (+ 1 *dumps-per-page*)
+ :offset dump-offset
+ :message-user-id message-user-id
+ :tag-user-id tag-user-id)
+ dumps (map tags/add-favorited-flag (take *dumps-per-page* raw-dumps) (repeat session))
+ dumps (map tags/remove-tags-for-output dumps)
+ dumps (logger doall (map process-message-for-output dumps))]
+ (if (> (count raw-dumps) *dumps-per-page*)
+ (.setAttribute st "next" (inc offset)))
+ (if (not= offset 0)
+ (.setAttribute st "prev" (max (dec offset) 0)))
+ (.setAttribute st "dumps" dumps)
+ (.setAttribute st "page_title" page-title)
+ (.setAttribute st "page_url" url)
+ (.setAttribute st "debug_log_items" (logger))
+ (.toString st)))
+
+;; gotta parse tag intersections myself because +'s get decoded into spaces
+;; there's probably a less hacky way to do this
+(defn tagged-dumps-by-nick [session params url]
+ (let [nick (params :nick)
+ tags (map url-decode (re-split #"\+" (undecoded-url-piece url 3)))
+ user-id (user-id-from-nick nick)
+ url (str "u/" nick "/tag/" (str-join "+" (map url-encode tags)))
+ page-title (str "dumps " nick " tagged as '" (escape-html (str-join "' and '" tags)) "'")]
+ (tagged-dumps-template session params tags url page-title :tag-user-id user-id)))
+
+(defn tagged-dumps [session params url]
+ (let [tags (map url-decode (re-split #"\+" (undecoded-url-piece url 1)))
+ url (str "tag/" (str-join "+" (map url-encode tags)))
+ page-title (str "dumps tagged as '" (escape-html (str-join "' and '" tags)) "'")]
+ (tagged-dumps-template session params tags url page-title)))
+
+(defn favorites [session params]
+ (let [nick (params :nick)
+ user-id (user-id-from-nick nick)
+ url (str "u/" nick "/favorites")
+ page-title (str nick "'s favorites")]
+ (tagged-dumps-template session params "favorite" url page-title :tag-user-id user-id)))
+
+(defn json-favorites [session params]
+ (let [nick (params :nick)
+ user-id (user-id-from-nick nick)
+ raw-favs (tags/fetch-dumps-by-tag :tags "favorite"
+ :image-only false
+ :amount 50
+ :offset 0
+ :tag-user-id user-id
+ :with-tags false)
+ favs (reduce (fn [m fav] (assoc m (str (fav :message_id)) (fav :content))) {} raw-favs)]
+ (str "RawFavs=" (json-str favs))))
+
+
+
+
;; Account resets
(defn reset-page [session]
@@ -691,6 +860,7 @@
;; Upload
+
(def *max-image-height* 2000)
(def *max-image-width* 2000)
(def *max-avatar-height* 2000)
@@ -841,9 +1011,14 @@
(GET "/favicon.ico" (serve-static "static" "favicon.ico"))
(GET "/u/:nick" (profile session (params :nick) "0"))
(GET "/u/:nick/" (profile session (params :nick) "0"))
+ (GET "/u/:nick/tag/:tag" (tagged-dumps-by-nick session params (request-url request)))
+ (GET "/u/:nick/tag/:tag/:offset" (tagged-dumps-by-nick session params (request-url request)))
+ (GET "/u/:nick/favorites" (favorites session params))
+ (GET "/u/:nick/favorites/:offset" (favorites session params))
+ (GET "/json/:nick/favorites" (json-favorites session params))
(GET "/u/:nick/:offset" (profile session
(params :nick)
- (params :offset)))
+ (params :offset))) ; have to put this route after favs
(GET "/p/:nick/:postid" (single-message session (params :nick) (params :postid)))
(GET "/login" (login session params cookies))
(GET "/logout" (logout session))
@@ -853,6 +1028,10 @@
(GET "/chat/:t" (no-cache (validated-chat session "dumpfm" (-> request :route-params :t))))
(GET "/browser" (browser session))
(GET "/refresh" (validated-refresh session params))
+ (GET "/tag/:tag" (tagged-dumps session params (request-url request)))
+ (GET "/tag/:tag/:offset" (tagged-dumps session params (request-url request)))
+ (POST "/cmd/tag/add" (validated-add-tag session params))
+ (POST "/cmd/tag/rm" (validated-remove-tag session params))
(GET "/log" (validated-log session "dumpfm" "0" params))
(GET "/:room/log" (validated-log session
(-> request :route-params :room)
@@ -881,7 +1060,8 @@
(GET "/help" (serve-template "help" session))
(GET "/privacy" (serve-template "privacy" session))
(GET "/terms" (serve-template "terms" session))
- (ANY "*" (unknown-page)))
+ (GET "/error/ie" (serve-template "error_ie" session))
+ (ANY "*" (unknown-page params)))
(defroutes multipart
(POST "/upload/message" (upload session params))