diff options
| -rwxr-xr-x | db/0-create.psql | 14 | ||||
| -rw-r--r-- | src/site.clj | 119 | ||||
| -rwxr-xr-x | static/index.html | 2 | ||||
| -rwxr-xr-x | static/pichat.css | 3 | ||||
| -rwxr-xr-x | static/pichat.js | 84 | ||||
| -rwxr-xr-x | static/register.html | 30 |
6 files changed, 149 insertions, 103 deletions
diff --git a/db/0-create.psql b/db/0-create.psql index 39b4efd..d34ccdd 100755 --- a/db/0-create.psql +++ b/db/0-create.psql @@ -9,7 +9,7 @@ CREATE TABLE users ( CREATE TABLE rooms ( room_id SERIAL PRIMARY KEY, name text UNIQUE NOT NULL, - created_on timestamp NOT NULL + created_on timestamp NOT NULL DEFAULT now() ); CREATE TABLE messages ( @@ -17,12 +17,14 @@ CREATE TABLE messages ( user_id integer NOT NULL REFERENCES users, room_id integer NOT NULL REFERENCES rooms, content text NOT NULL, - created_on timestamp NOT NULL + created_on timestamp NOT NULL DEFAULT now() ); CREATE TABLE user_session ( - session_id bigint PRIMARY KEY, + session_id bigint NOT NULL, user_id integer NOT NULL REFERENCES users, - id_address text NOT NULL, - ttl timestamp NOT NULL -);
\ No newline at end of file + ttl timestamp NOT NULL, + PRIMARY KEY (session_id, user_id) +); + +INSERT INTO rooms (room_id, name) VALUES (1, 'dumpfm');
\ No newline at end of file diff --git a/src/site.clj b/src/site.clj index 417130e..2f4f5d8 100644 --- a/src/site.clj +++ b/src/site.clj @@ -1,8 +1,10 @@ ; site.clj (ns pichat - (:import java.lang.System - org.apache.commons.codec.digest.DigestUtils) + (:import java.lang.System + clojure.lang.PersistentQueue + org.apache.commons.codec.digest.DigestUtils + javax.servlet.http.Cookie) (:use compojure clojure.contrib.json.write clojure.contrib.sql)) @@ -13,10 +15,10 @@ (def db {:classname "org.postgresql.Driver" :subprotocol "postgresql" :subname (str "//" db-host ":" db-port "/" db-name) - :user "postgres" + :user "postgres" :password "root"})) -(defstruct user-struct :nick :last-seen) +(defstruct user-struct :user-id :nick :last-seen) (defstruct message-struct :nick :content :timestamp) (def users (ref {})) @@ -37,7 +39,7 @@ (dosync (let [now (System/currentTimeMillis) alive? (fn [[n u]] (> (u :last-seen) (- now user-timeout-ms)))] - (ref-set users + (ref-set users (into {} (filter alive? @users))))) (. Thread (sleep flusher-sleep-ms)) x) @@ -67,22 +69,31 @@ (defn do-select [query] (with-connection db - (with-query-results rs [query] + (with-query-results rs query (doall rs)))) -(defn retrieve-nick [nick] - (let [query (str "SELECT * FROM users WHERE nick = '" nick "'")] - (first (do-select query)))) +(defn fetch-messages [room_id] + (let [query (str "SELECT m.content, m.created_on, u.nick FROM messages m, users u " + "WHERE room_id = ? AND m.user_id = u.user_id") + res (do-select [query room_id])] + (map (fn [r] (struct message-struct (r :nick) (r :content) (.getTime (r :created_on)))) + res))) + +(defn fetch-nick [nick] + (let [query "SELECT * FROM users WHERE nick = ?"] + (first (do-select [query nick])))) (defn authorize-nick-hash [nick hash] - (let [db-user (retrieve-nick nick)] - (and db-user (= (db-user :hash) hash)))) + (let [db-user (fetch-nick nick)] + (if (and db-user (= (db-user :hash) hash)) + (db-user :user_id) + false))) (defn register [session params] (let [nick (params :nick) email (params :email) hash (params :hash)] - (if (retrieve-nick nick) + (if (fetch-nick nick) (resp-error "NICK_TAKEN") (with-connection db (insert-values :users @@ -90,52 +101,71 @@ [nick hash email]) (resp-success "OK"))))) -(defn renamed-user-struct [old-nick nick] - (let [old-struct (@users old-nick)] - (if old-struct - (assoc old-struct :nick nick) - (struct user-struct nick (System/currentTimeMillis))))) +(defn init [session] + (prn session) + (let [now (System/currentTimeMillis) + user-id (session :user-id) + nick (session :nick) + resp (updates)] + (dosync + (if (and user-id nick) + (let [user-struct (struct user-struct user-id nick now)] + (alter users assoc nick user-struct) + (resp-success (merge resp {"nick" nick}))) + [(session-assoc :last-seen now) + (resp-success resp)])))) (defn login [session params] (let [old-nick (session :nick) nick (params :nick) hash (params :hash) - ts (params :ts)] - (if (authorize-nick-hash nick hash) + user-id (authorize-nick-hash nick hash)] + (if user-id (dosync - (set-session {:nick nick :logged-in true}) - (let [user-struct (renamed-user-struct old-nick nick)] + (set-session {:user-id user-id :nick nick}) + (let [user-struct (struct user-struct user-id nick (System/currentTimeMillis))] (alter users dissoc old-nick) (alter users assoc nick user-struct) - [(session-assoc :nick nick :logged-in true) + [(session-assoc :user-id user-id :nick nick) (resp-success "OK")])) (resp-error "BAD_LOGIN")))) -(defn init [session] - (let [new-nick (make-random-nick)] +(defn refresh [session] + (prn session) + (let [nick (session :nick) + now (System/currentTimeMillis)] (dosync - (alter users assoc new-nick - (struct user-struct new-nick (System/currentTimeMillis))) - [(session-assoc :nick new-nick) - (resp-success (assoc (updates) :nick new-nick :loggedin false))]))) + (if (contains? (ensure users) nick) + (let [last-seen (get-in @users [nick :last-seen])] + (alter users assoc-in [nick :last-seen] now) + (resp-success (updates last-seen))) + (let [last-seen (max (or (session :last-seen) 0) (- now (* 60 20)))] + [(session-assoc :last-seen now) + (updates last-seen)]))))) -(defn refresh [nick] +(defn msg-transaction [nick msg] (dosync - (if (contains? @users nick) - (let [last-seen (get-in @users [nick :last-seen])] - (alter users assoc-in [nick :last-seen] (System/currentTimeMillis)) - (resp-success (updates last-seen))) - (resp-error "UNKNOWN_USER")))) + (if (contains? (ensure users) nick) + (do (alter messages (swap cons) msg) + true) + false))) + +(defn msg-db [user-id room-id msg] + (with-connection db + (insert-values :messages + [:user_id :room_id :content] + [user-id room-id (msg :content)]))) (defn msg [session params] - (dosync - (let [nick (session :nick) + (let [user-id (session :user-id) + nick (session :nick) content (params :content) msg (struct message-struct nick content (System/currentTimeMillis))] - (if (contains? @users nick) - (do (alter messages (swap cons) msg) - (resp-success "OK")) - (resp-error "UNKNOWN_USER"))))) + (if (msg-transaction nick msg) + (do + (msg-db user-id 1 msg) + (resp-success "OK")) + (resp-error "MUST_LOGIN")))) (defroutes pichat (GET "/" (serve-file "static" "index.html")) @@ -144,9 +174,9 @@ (GET "/favicon.ico" (serve-file "static" "favicon.ico")) (GET "/register" (serve-file "static" "register.html")) (GET "/submit-registration" (register session params)) - (GET "/init" (init (session :nick))) (GET "/login" (login session params)) - (GET "/refresh" (refresh (session :nick))) + (GET "/init" (init session)) + (GET "/refresh" (refresh session)) (GET "/msg" (msg session params)) (ANY "*" [404 "Page not found"])) @@ -154,6 +184,11 @@ (with-mimetypes) (with-session {:type :memory, :expires (* 60 60)})) + +; Load messages from database +(dosync + (ref-set messages (fetch-messages 1))) + (run-server {:port 8080} "/*" (servlet pichat)) diff --git a/static/index.html b/static/index.html index ae4a48f..8087996 100755 --- a/static/index.html +++ b/static/index.html @@ -25,7 +25,7 @@ </head> <body> - <div id="loginbar" style="display: none"> + <div id="loginbar" style="display: none;"> <span>Username:</span><input id="nickInput" type="input" /> <br /> <span>Password:</span><input id="passwordInput" type="password" /> diff --git a/static/pichat.css b/static/pichat.css index 28e7431..584189e 100755 --- a/static/pichat.css +++ b/static/pichat.css @@ -174,12 +174,13 @@ height: expression(this.width > 400 ? 400: true);max-width:400px;} } #loginbar { + background: white; padding: 5px; position: absolute; bottom: 40px; left: 40px; line-height: 8px; - z-index: 2; + z-index: 5; border: 1px solid #15fff3; } diff --git a/static/pichat.js b/static/pichat.js index d665e2b..56dbd36 100755 --- a/static/pichat.js +++ b/static/pichat.js @@ -1,7 +1,6 @@ // pichat.js var Nick = null; -var LoggedIn = false; function handleMsgError(resp) { var respText = resp.responseText ? resp.responseText.trim() : false; @@ -54,6 +53,9 @@ function setNick(nick) { Nick = nick; $('#nickspan').text(nick); $('#welcomebar').show(); + $('#msgInput, #msgSubmit').removeAttr('disabled'); + $('#msgInput').keyup(ifEnter(submitMessage)); + $('#msgSubmit').click(submitMessage); } function submitMessage() { @@ -100,7 +102,6 @@ function login() { var onSuccess = function(json) { $('#loginbar').hide(); - LoggedIn = true; setNick(nick); }; @@ -128,25 +129,37 @@ function scrollToBottom(div) { div.scrollTop = div.scrollHeight; } -function refresh() { - var onSuccess = function(json) { - if (json.messages.length > 0) { - var shouldScroll = isScrolledToBottom($('#messageList')[0]); - - // Ignore our own messages +function updateUI(json, initialUpdate) { + if (json.messages.length > 0) { + if (initialUpdate) { + var messages = json.messages; + } else { + // Our own messages have already been displayed. var filterFunc = function(m) { return m.nick != Nick }; - var msgStr = $.map($.grep(json.messages, filterFunc), - buildMessageDiv).join(''); - $('#messageList').append(msgStr); - - if (shouldScroll) { - scrollToBottom($('#messageList')[0]); - } + var messages = $.grep(json.messages, filterFunc); } - $("#userList").html($.map(json.users, buildUserDiv).join('')); - }; + + var msgStr = $.map(messages, + buildMessageDiv).join(''); + var wasScrolledToBottom = isScrolledToBottom($('#messageList')[0]); + $('#messageList').append(msgStr); + + if (initialUpdate || wasScrolledToBottom) { + // Delay scrolling by .5 seconds so images can start loading. + setTimeout(scrollToBottom, 500, $('#messageList')[0]); + } + } + $("#userList").html($.map(json.users, buildUserDiv).join('')); +} - var onError = function(resp, textStatus, errorThrown) {}; +function refresh() { + var onSuccess = function(json) { + updateUI(json, false); + setTimeout(refresh, 1000); + }; + var onError = function(resp, textStatus, errorThrown) { + setTimeout(refresh, 1000); + }; $.ajax({ type: 'GET', @@ -159,34 +172,22 @@ function refresh() { }); } -function init() { +function init() { var onSuccess = function(json) { - $('#loadingbox').hide(); - Nick = json.nick; - - setNick(Nick); - if (json.loggedin) { - LoggedIn = true; + if (json.nick) { + setNick(json.nick); } else { $('#loginbar').show(); - $('#loginSubmit').click(login); $('#passwordInput').keyup(ifEnter(login)); + $('#loginSubmit').click(login); } - - var msgStr = $.map(json.messages, buildMessageDiv).join(''); - $('#messageList').append(msgStr); - $("#userList").html($.map(json.users, buildUserDiv).join('')); - $('#nickInput, #nickSubmit, #msgInput, #msgSubmit').removeAttr('disabled'); - - // Delay scrolling by .5 seconds so images can start loading. - setTimeout(scrollToBottom, 500, $('#messageList')[0]); - setInterval(refresh, 1000); + updateUI(json, true); + setTimeout(refresh, 1000); }; - var onError = function(resp, textStatus, errorThrown) { - alert("Error connecting to chat server!"); - }; - + alert("Error initializing!"); + }; + $.ajax({ type: 'GET', timeout: 5000, @@ -194,9 +195,8 @@ function init() { cache: false, dataType: 'json', success: onSuccess, - error: onError + error: onError }); - $('#msgInput').keyup(ifEnter(submitMessage)); - $('#msgSubmit').click(submitMessage); + } diff --git a/static/register.html b/static/register.html index e5ae761..acc13da 100755 --- a/static/register.html +++ b/static/register.html @@ -11,16 +11,24 @@ </script> </head> <body> - <h1>Register</h1> - <span>Nickname:</span> - <input type="text" id="nickInput" /> - <br /> - <span>Email:</span> - <input type="text" id="emailInput" /> - <br /> - <span>Password:</span> - <input type="password" id="passwordInput" /> - <br /> - <input type="submit" id="submit" value="register" /> + <div id="registerbox"> + <h1>Register</h1> + <span>Nickname:</span> + <input type="text" id="nickInput" /> + <br /> + <span>Email:</span> + <input type="text" id="emailInput" /> + <br /> + <span>Password:</span> + <input type="password" id="passwordInput" /> + <br /> + <input type="submit" id="submit" value="register" /> + </div> + + <div id="confirmationbox" style="display: none"> + <h1>You've registered!</h1> + <span>Please wait as you're redirected to the chat.</span> + </div> + </body> </html> |
