(ns user (:use compojure utils)) (defstruct user-struct :nick :user_id :avatar :last-seen) (defn user-struct-from-session [session] (struct user-struct (session :nick) (session :user_id) (session :avatar) (System/currentTimeMillis))) (def *nick-regex* #"^[A-Za-z0-9\-_âˆb˚†]*$") (defn is-invalid-nick? [n] (cond (< (count n) 3) "NICK_TOO_SHORT" (> (count n) 16) "NICK_TOO_LONG" (not (re-matches *nick-regex* n)) "NICK_INVALID_CHARS")) ;;; User info cache (def user-cache-size 99999) (def user-nick-cache (ref {})) (def user-id-cache (ref {})) (defn update-cache! [uid attr val] (dosync (if-let [info (get @user-id-cache uid)] (let [nick (lower-case (:nick info)) new-info (assoc info attr val)] (alter user-id-cache assoc uid new-info) (alter user-nick-cache assoc nick new-info))))) (defn fetch-nick [nick] (let [lcnick (lower-case nick)] (if (contains? user-nick-cache lcnick) (get user-nick-cache lcnick) (let [info (first (do-select ["SELECT * FROM users WHERE lower(nick) = ? LIMIT 1" lcnick])) user-id (:user_id info)] (dosync (alter user-nick-cache assoc lcnick info) (if (and info user-id) (alter user-id-cache assoc user-id info))) info)))) (defn fetch-nicks [nicks] (let [lcnicks (map lower-case nicks) cache @user-nick-cache to-fetch (filter #(not (contains? cache %)) lcnicks) fetched-info (do-select ["SELECT * FROM users WHERE lower(nick) = ANY(?)" (sql-array "text" to-fetch)]) info-map (zipmap (map (comp lower-case :nick) fetched-info) fetched-info)] (doseq [nick to-fetch] (let [info (get info-map nick)] (dosync (alter user-nick-cache assoc nick info) (if info (alter user-id-cache assoc (:user_id info) info))))) (filter boolean (for [nick lcnicks] (get @user-nick-cache nick))))) (defn fetch-user-id [uid] (if (contains? @user-id-cache uid) (get @user-id-cache uid) (if-let [info (first (do-select ["SELECT * FROM users WHERE user_id = ? LIMIT 1" uid]))] (dosync (alter user-nick-cache assoc (lower-case (:nick info)) info) (alter user-id-cache assoc uid info))))) (defn authorize-nick-hash [nick hash] (let [db-user (fetch-nick nick)] (and db-user (= (db-user :hash) hash) db-user))) (defn update-nick-hash [nick hash] (if (not (assert-update (do-update :users ["nick=?" nick] {:hash hash}))) ; TODO: logging (println (format "Error updating hash for %s" nick)))) (defn reset-token [nick hash ts] (sha1-hash nick hash ts)) (defn reset-link [nick token ts] (url-params "http://dump.fm/reset" {"nick" nick "ts" ts "token" token})) (defn valid-reset-link? [nick token ts] (if-let [info (and nick (fetch-nick nick))] (and (= token (reset-token (info :nick) (info :hash) ts)) (>= ts (ms-ago (days 2))))))