summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--db/0-create.psql12
-rw-r--r--src/admin.clj69
-rw-r--r--src/site.clj103
-rwxr-xr-xsrc/utils.clj59
-rw-r--r--static/css/admin.css3
-rw-r--r--static/js/admin.js59
-rw-r--r--template/head.st14
-rw-r--r--template/profile.st5
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$