diff options
| author | sostler <sbostler@gmail.com> | 2010-04-11 16:50:40 -0400 |
|---|---|---|
| committer | sostler <sbostler@gmail.com> | 2010-04-11 16:50:40 -0400 |
| commit | 57c3298cbb88181ab2a9e2dd5309b7a5df6eb30f (patch) | |
| tree | e9ca474c5a1fa584728b152bf95ac5471943253c | |
| parent | 57be29ee7b7782830cf981d9437b18c88620f8ed (diff) | |
Initial muting implementation
| -rw-r--r-- | db/0-create.psql | 12 | ||||
| -rw-r--r-- | src/admin.clj | 69 | ||||
| -rw-r--r-- | src/site.clj | 103 | ||||
| -rwxr-xr-x | src/utils.clj | 59 | ||||
| -rw-r--r-- | static/css/admin.css | 3 | ||||
| -rw-r--r-- | static/js/admin.js | 59 | ||||
| -rw-r--r-- | template/head.st | 14 | ||||
| -rw-r--r-- | template/profile.st | 5 |
8 files changed, 229 insertions, 95 deletions
diff --git a/db/0-create.psql b/db/0-create.psql index e0c5443..10c57a2 100644 --- a/db/0-create.psql +++ b/db/0-create.psql @@ -55,6 +55,18 @@ CREATE INDEX tags_message_id_idx ON tags (message_id); CREATE INDEX tags_created_on_id_idx ON tags (created_on DESC); CREATE INDEX tags_tag_lowercase_idx ON tags (lower(tag)); +CREATE TABLE mutes ( + user_id integer NOT NULL REFERENCES users, + admin_id integer NOT NULL REFERENCES users, + set_on timestamp NOT NULL DEFAULT now(), + duration interval NOT NULL, + reason text NOT NULL, + is_canceled bool NOT NULL DEFAULT false, + cancel_admin_id integer REFERENCES users +); + +CREATE INDEX mutes_expires_idx ON mutes ((set_on + duration)); + INSERT INTO rooms (key, name, description, admin_only) VALUES ('dumpfm', 'Room A', 'Hangout', false); INSERT INTO rooms (key, name, description, admin_only) diff --git a/src/admin.clj b/src/admin.clj new file mode 100644 index 0000000..a7eced1 --- /dev/null +++ b/src/admin.clj @@ -0,0 +1,69 @@ +(ns admin + (:import java.io.File) + (:require [clojure.contrib.str-utils2 :as s]) + (:use compojure + email + utils)) + +;; Debug Page + +(defn exception-to-string [e] + (let [sw (java.io.StringWriter.) + pw (java.io.PrintWriter. sw)] + (.printStackTrace e pw) + (.toString sw))) + +(defn lookup-templates [dir selected] + (for [f (.listFiles (File. dir)) + :when (and (.isFile f) (.endsWith (.getName f) ".st"))] + (let [n (s/butlast (.getName f) 3)] + {"template" n + "selected" (= selected n)}))) + +(defn debug-page [session flash] + (if-vip + (let [st (fetch-template "debug" session)] + (.setAttribute st "flash" (:msg flash)) + (.setAttribute st "mailtemps" (lookup-templates "template/mail" "welcome")) + (.toString st)))) + +(defn debug-commmand! [session params] + (if-vip + (let [action (:action params) + msg (try + (cond (= action "regemail") + (do (send-registration-email (params :nick) (params :to) (params :template)) + (str "Sent registration mail to " (params :to))) + :else (str "Unknown action: " action)) + (catch Exception e + (str "<h2 color=\"red\">Caught Exception in " action " --" + (.getMessage e) + "</h2><br><pre>" + (exception-to-string e) + "</pre>")))] + [(flash-assoc :msg msg) + (redirect-to "/debug")]))) + +;; Muting + +(defn mute-status [session] + (if-vip + (println session))) + +(defn parse-pos-interval [time unit] + (let [t (maybe-parse-int time 0) + u (lower-case unit)] + (and (> t 0) + (#{"minute" "hour" "day"} u) + (str time " " u)))) + +(defn mute! [session params] + (if-vip + (let [nick (params :user) + user_id (:user_id (fetch-nick nick)) + interval (parse-pos-interval (params :time) (params :unit)) + reason (params :reason) + admin-user-id (session :user_id)] + (cond (not user_id) [400 "INVALID_NICK"] + (not interval) [400 "INVALID_INTERVAL"] + :else (do "OK"))))) diff --git a/src/site.clj b/src/site.clj index 084f34c..85e93d1 100644 --- a/src/site.clj +++ b/src/site.clj @@ -6,9 +6,7 @@ java.io.File javax.imageio.ImageIO org.apache.commons.codec.digest.DigestUtils - javax.servlet.http.Cookie - org.antlr.stringtemplate.StringTemplateGroup) - (:require [clojure.contrib.str-utils2 :as s]) + javax.servlet.http.Cookie) (:use clojure.xml clojure.contrib.command-line clojure.contrib.duck-streams @@ -16,6 +14,7 @@ clojure.contrib.sql clojure.contrib.str-utils clojure.contrib.def + admin compojure email utils @@ -28,9 +27,6 @@ (def *flusher-sleep* (seconds 4)) (def *user-timeout* (seconds 15)) -(def template-group (new StringTemplateGroup "dumpfm" "template")) -(.setRefreshInterval template-group 3) - (defstruct user-struct :nick :user_id :avatar :last-seen) (defstruct message-struct :nick :content :created_on :msg_id) @@ -74,6 +70,9 @@ ;; Utils +(defn id [x] + x) + (defn open-file [dir-comps filename] (let [d (str-join (System/getProperty "file.separator") (cons *root-directory* dir-comps)) @@ -92,31 +91,6 @@ (.setTimeZone df (TimeZone/getTimeZone "GMT")) (.format df dt)))) -;; 404 - -(defn unknown-page [] - [404 "Page not Found"]) - -;; User authentication - -(def nick-regex #"^[A-Za-z0-9\-_∆˚†]*$") - -(defn is-invalid-nick? [n] - (cond - (< (count n) 3) "NICK_TOO_SHORT" - (not (re-matches nick-regex n)) "NICK_INVALID_CHARS")) - -(defn check-nick [nick] - (let [query "SELECT * FROM users WHERE LOWER(nick) = ? LIMIT 1"] - (> (count (do-select [query (s/lower-case nick)])) 0))) - -(defn fetch-nick [nick] - (let [query "SELECT * FROM users WHERE nick = ? LIMIT 1"] - (first (do-select [query nick])))) - -(defn authorize-nick-hash [nick hash] - (let [db-user (fetch-nick nick)] - (and db-user (= (db-user :hash) hash) db-user))) ;; Room handling @@ -290,9 +264,6 @@ ;; Login code -(defn is-vip? [session] - (session :is_admin)) - (defn session-map-from-db [user-info] {:user_id (user-info :user_id) @@ -309,21 +280,6 @@ :avatar (user-info :avatar) :password_login true)) -;; Templates - -;; TODO: avoid exception -(defn fetch-template [template session] - (let [st (.getInstanceOf template-group template)] - (if (session :nick) - (do (.setAttribute st "user_email" (session :email)) - (.setAttribute st "user_nick" (session :nick)) - (if (non-empty-string? (session :avatar)) (.setAttribute st "user_avatar" (session :avatar))) - (.setAttribute st "isadmin" (is-vip? session)))) - st)) - -(defn serve-template [template session] - (.toString (fetch-template template session))) - ;; login-token functions (defn logged-in? @@ -847,8 +803,6 @@ (str "RawFavs=" (json-str favs)))) - - ;; Account resets (defn reset-page [session] @@ -947,47 +901,6 @@ (not (session :nick)) [200 "NOT_LOGGED_IN"] :else (do-upload-avatar session image)))) -;; Debug Page - -(defn exception-to-string [e] - (let [sw (java.io.StringWriter.) - pw (java.io.PrintWriter. sw)] - (.printStackTrace e pw) - (.toString sw))) - -(defn lookup-templates [dir selected] - (for [f (.listFiles (File. dir)) - :when (and (.isFile f) (.endsWith (.getName f) ".st"))] - (let [n (s/butlast (.getName f) 3)] - {"template" n - "selected" (= selected n)}))) - -(defn debug-page [session flash] - (if (is-vip? session) - (let [st (fetch-template "debug" session)] - (.setAttribute st "flash" (:msg flash)) - (.setAttribute st "mailtemps" (lookup-templates "template/mail" "welcome")) - (.toString st)) - (unknown-page))) - -(defn debug-commmand! [session params] - (if (is-vip? session) - (let [action (:action params) - msg (try - (cond (= action "regemail") - (do (send-registration-email (params :nick) (params :to) (params :template)) - (str "Sent registration mail to " (params :to))) - :else (str "Unknown action: " action)) - (catch Exception e - (str "<h2 color=\"red\">Caught Exception in " action " --" - (.getMessage e) - "</h2><br><pre>" - (exception-to-string e) - "</pre>")))] - [(flash-assoc :msg msg) - (redirect-to "/debug")]) - (unknown-page))) - ;; Compojure Routes @@ -1055,8 +968,14 @@ (GET "/reset" (reset-page session)) (POST "/reset-request" (reset-account-request! session params)) (POST "/reset/:key" (reset-account! session (-> request :route-params :key))) + + ;; Admin stuff (should be own route?) (GET "/debug" (debug-page session flash)) (POST "/debug" (debug-commmand! session params)) + (GET "/mute-status" (mute-status session)) + (POST "/mute" (mute! session params)) + + ;; Footer pages (GET "/about_us" (serve-template "about_us" session)) (GET "/goodies" (serve-template "goodies" session)) (GET "/help" (serve-template "help" session)) diff --git a/src/utils.clj b/src/utils.clj index 3f2f4e4..3e77710 100755 --- a/src/utils.clj +++ b/src/utils.clj @@ -1,7 +1,8 @@ (ns utils (:import java.text.SimpleDateFormat java.util.Date - java.net.URLDecoder) + java.net.URLDecoder + org.antlr.stringtemplate.StringTemplateGroup) (:use clojure.contrib.json.write clojure.contrib.sql)) @@ -94,4 +95,58 @@ (defn #^String lower-case "Converts string to all lower-case." [#^String s] - (.toLowerCase s))
\ No newline at end of file + (.toLowerCase s)) + +;; 404 + +(defn unknown-page [& more] + [404 "Page not Found"]) + +;; Templates + +(def template-group (new StringTemplateGroup "dumpfm" "template")) +(.setRefreshInterval template-group 3) + +;; TODO: handle exception +(defn fetch-template [template session] + (let [st (.getInstanceOf template-group template)] + (if (session :nick) + (do (.setAttribute st "user_email" (session :email)) + (.setAttribute st "user_nick" (session :nick)) + (if (non-empty-string? (session :avatar)) (.setAttribute st "user_avatar" (session :avatar))) + (.setAttribute st "isadmin" (session :is_admin)))) ;; TODO: consolidate session/user code + st)) + +(defn serve-template [template session] + (.toString (fetch-template template session))) + + +;; User authentication +; TODO: move to user module + +(def nick-regex #"^[A-Za-z0-9\-_∆˚†]*$") + +(defn is-invalid-nick? [n] + (cond + (< (count n) 3) "NICK_TOO_SHORT" + (not (re-matches nick-regex n)) "NICK_INVALID_CHARS")) + +(defn check-nick [nick] + (let [query "SELECT * FROM users WHERE LOWER(nick) = ? LIMIT 1"] + (> (count (do-select [query (lower-case nick)])) 0))) + +(defn fetch-nick [nick] + (let [query "SELECT * FROM users WHERE nick = ? LIMIT 1"] + (first (do-select [query nick])))) + +(defn authorize-nick-hash [nick hash] + (let [db-user (fetch-nick nick)] + (and db-user (= (db-user :hash) hash) db-user))) + +(defn is-vip? [session] + (session :is_admin)) + +(defmacro if-vip [e] + "Evaluates expr if user is vipm otherwise returns 404 string. Can only be used + where session is defined." + `(if (is-vip? ~'session) ~e (unknown-page))) diff --git a/static/css/admin.css b/static/css/admin.css new file mode 100644 index 0000000..f04af5d --- /dev/null +++ b/static/css/admin.css @@ -0,0 +1,3 @@ +.errorbox { + border: 2px solid red; +}
\ No newline at end of file diff --git a/static/js/admin.js b/static/js/admin.js new file mode 100644 index 0000000..a5a5f71 --- /dev/null +++ b/static/js/admin.js @@ -0,0 +1,59 @@ +var Admin = {}; + +Admin._dialogHtml = '<div class="dialog">'; + +Admin._select = function(name, opts) { + var sel = $('<select>').attr('name', name); + $.each(opts, function(i, o) { + sel.append($('<option>').html(o)); + }); + return sel; +} + +Admin.mute = function(nick) { + var errorbox = $('<div class="errorbox" style="display: none">'); + var time = $('<input type="text" name="time" size="3">'); + var unit = Admin._select('unit', ['minutes', 'hours', 'days']); + var reason = $('<textarea name="reason" rows="4" cols="30">'); + var html = $('<div>') + .append(errorbox) + .append($('<div>').text(nick + ' will be muted for:')) + .append(time) + .append(unit) + .append($('<br>')) + .append($('<br>')) + .append($('<div>').text('Reason:')) + .append(reason) + .appendTo($(Admin._dialogHtml)); + var title = 'Mute ' + nick; + var cancel = function() { html.dialog('close'); } + var submit = function() { + html.find('[name]').removeClass('ui-state-error'); + + var t = parseInt(time.val()); + var u = unit.val(); + var r = reason.val(); + + if (!t) { + time.addClass('ui-state-error'); + } + + if (!u) { + reason.addClass('ui-state-error'); + } + + if (!r) { + reason.addClass('ui-state-error'); + } + + if (!t || !u || !r) { + return; + } + }; + html.dialog({ + modal: true, + title: title, + width: 400, + buttons: { 'OK': submit , 'Cancel': cancel } + }); +};
\ No newline at end of file diff --git a/template/head.st b/template/head.st index e77e454..7bd71ec 100644 --- a/template/head.st +++ b/template/head.st @@ -10,6 +10,18 @@ $if(!user_nick)$ <link href="/static/form_login/front.css" media="screen, projection" rel="stylesheet" type="text/css"> $endif$ + +$if(isadmin)$ +<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/ui-lightness/jquery-ui.css" + type="text/css" media="all" /> +<link rel="stylesheet" href="/static/css/admin.css" + type="text/css" media="all" /> +<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.0/jquery-ui.min.js" + type="text/javascript"></script> +<script src="/static/js/admin.js" + type="text/javascript"></script> +$endif$ + <link rel="shortcut icon" href="/static/favicon.ico"> <script type="text/javascript"> </script> @@ -17,4 +29,4 @@ $endif$ <script> window.location.pathname = "/error/ie" </script> -<![endif]-->
\ No newline at end of file +<![endif]--> diff --git a/template/profile.st b/template/profile.st index 2af0072..dcf4497 100644 --- a/template/profile.st +++ b/template/profile.st @@ -31,6 +31,11 @@ <div id="profile"> <h2>$nick$</h2> + + $if(isadmin)$ + <a href="#" onclick="Admin.mute('$nick$'); return false">Mute $nick$!</a><br> + $endif$ + $if(avatar)$ <img id="avatarPic" src="$avatar$" width="150px"/> $else$ |
