diff --git a/.project b/.project
new file mode 100644
index 0000000000..d382cb0b53
--- /dev/null
+++ b/.project
@@ -0,0 +1,12 @@
+
+
+ node-forum
+
+
+
+
+
+
+ com.aptana.projects.webnature
+
+
diff --git a/app.js b/app.js
index 8732c4aee4..b9759de5bc 100644
--- a/app.js
+++ b/app.js
@@ -17,6 +17,12 @@ global.modules = modules;
// global.uid = 1;
+process.on('uncaughtException', function(err) {
+ // handle the error safely
+ console.log("error message "+err);
+ global.socket.emit('event:consolelog',{type:'uncaughtException',stack:err.stack,error:err.toString()});
+});
+
(function(config) {
config['ROOT_DIRECTORY'] = __dirname;
@@ -25,6 +31,6 @@ global.modules = modules;
// modules.webserver.init();
modules.websockets.init();
-
+
}(global.configuration));
\ No newline at end of file
diff --git a/config.default.js b/config.default.js
index 5393551a9e..ed2c5eacc2 100644
--- a/config.default.js
+++ b/config.default.js
@@ -1,11 +1,18 @@
var config = {
+ "secret": 'nodebb-secret',
"base_url": "http://localhost",
"port": 4567,
- "url": undefined, // Leave this alone
"mailer": {
host: 'localhost',
port: '25',
from: 'mailer@localhost.lan'
+ },
+ "redis": {
+ port: "6379",
+ host: "127.0.0.1",
+ options: {
+
+ }
}
}
diff --git a/nodebb b/nodebb
old mode 100644
new mode 100755
diff --git a/package.json b/package.json
index a496ed42f6..645c519fe6 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,9 @@
"cookie": "0.0.6",
"connect-redis": "1.4.5",
"path": "0.4.9",
- "crypto": "0.0.3"
+ "crypto": "0.0.3",
+ "passport": "0.1.16",
+ "passport-local": "0.1.6"
},
"devDependencies": {},
"optionalDependencies": {},
diff --git a/public/config.default.json b/public/config.default.json
index 67acc5b7d3..b3794291ce 100644
--- a/public/config.default.json
+++ b/public/config.default.json
@@ -2,5 +2,6 @@
"socket" : {
"address" : "localhost",
"port" : "4567"
- }
+ },
+ "api_url" : "http://localhost:4567/api/"
}
\ No newline at end of file
diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js
index ac3d7d4db0..ac96a7d6be 100644
--- a/public/src/ajaxify.js
+++ b/public/src/ajaxify.js
@@ -22,8 +22,8 @@ var ajaxify = {};
ajaxify.go = function(url, callback) {
var url = url.replace(/\/$/, "");
- var tpl_url = (url === '') ? 'home' : url;
-
+ var tpl_url = (url === '' || url === '/') ? 'home' : url.split('/')[0];
+
if (templates[tpl_url]) {
window.history.pushState({}, url, "/" + url);
diff --git a/public/src/app.js b/public/src/app.js
index 0fdd83a550..a45effa90d 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -1,6 +1,8 @@
var socket,
config,
- app = {};
+ app = {},
+
+ API_URL = null;
// todo: cleanup,etc
(function() {
@@ -8,6 +10,8 @@ var socket,
$.ajax({
url: '/config.json?v=' + new Date().getTime(),
success: function(data) {
+ API_URL = data.api_url;
+
config = data;
socket = io.connect('http://' + config.socket.address + config.socket.port? ':' + config.socket.port : '');
@@ -18,6 +22,10 @@ var socket,
socket.on('event:alert', function(data) {
app.alert(data);
});
+
+ socket.on('event:consolelog', function(data) {
+ console.log(data);
+ });
},
async: false
@@ -74,14 +82,63 @@ var socket,
}
}
- var post_window = null;
- app.open_post_window = function() {
+ var post_window = null,
+ submit_post_btn = null,
+ post_title = null,
+ reply_title = null,
+ post_content = null;
+
+
+ app.open_post_window = function(post_mode, id, title) {
+ submit_post_btn = submit_post_btn || document.getElementById('submit_post_btn');
+ post_title = post_title || document.getElementById('post_title');
+ reply_title = reply_title || document.getElementById('reply_title');
+ post_content = post_content || document.getElementById('post_content');
+
+
post_window = post_window || document.getElementById('post_window');
jQuery(post_window).slideToggle(250);
- document.getElementById('post_title').focus();
+
+ if (post_mode == null || post_mode == 'topic') {
+ post_title.style.display = "block";
+ reply_title.style.display = "none";
+ post_title.focus();
+ submit_post_btn.onclick = function() {
+ app.post_topic();
+ }
+ } else {
+ post_title.style.display = "none";
+ reply_title.style.display = "block";
+ reply_title.innerHTML = 'You are replying to "' + title + '"';
+ post_content.focus();
+ submit_post_btn.onclick = function() {
+ app.post_reply(id)
+ }
+ }
};
+ app.post_reply = function(topic_id) {
+ var content = document.getElementById('post_content').value;
+
+ if (content.length < 5) {
+ app.alert({
+ title: 'Reply Failure',
+ message: 'You need to write more dude.',
+ type: 'error',
+ timeout: 2000
+ });
+
+ return;
+ }
+
+ socket.emit('api:posts.reply', {
+ 'topic_id' : topic_id,
+ 'content' : content
+ });
+ jQuery(post_window).slideToggle(250);
+
+ };
app.post_topic = function() {
var title = document.getElementById('post_title').value,
content = document.getElementById('post_content').value;
@@ -91,10 +148,7 @@ var socket,
title: 'Topic Post Failure',
message: 'You need to write more dude.',
type: 'error',
- timeout: 2000,
- clickfn: function() {
- ajaxify.go('register');
- }
+ timeout: 2000
});
return;
diff --git a/public/src/templates.js b/public/src/templates.js
index 29e3850d33..f18b1e50b6 100644
--- a/public/src/templates.js
+++ b/public/src/templates.js
@@ -1,9 +1,17 @@
var templates = {};
(function() {
+ var ready_callback;
+
+ templates.ready = function(callback) {
+ //quick implementation because introducing a lib to handle several async callbacks
+ if (callback == null) ready_callback();
+ else ready_callback = callback;
+ }
function loadTemplates(templatesToLoad) {
var timestamp = new Date().getTime();
+ var loaded = templatesToLoad.length;
for (var t in templatesToLoad) {
(function(file) {
@@ -18,6 +26,12 @@ var templates = {};
template.prototype.html = String(html);
templates[file] = new template;
+
+ loaded--;
+ if (loaded == 0) templates.ready();
+ }).fail(function() {
+ loaded--;
+ if (loaded == 0) templates.ready();
});
}(templatesToLoad[t]));
}
@@ -26,7 +40,7 @@ var templates = {};
function init() {
loadTemplates([
- 'header', 'footer', 'register', 'home',
+ 'header', 'footer', 'register', 'home', 'topic',
'login', 'reset', 'reset_code', 'account_settings',
'emails/reset', 'emails/reset_plaintext'
]);
@@ -62,6 +76,10 @@ var templates = {};
var template = this.html, regex, block;
return (function parse(data, namespace, template) {
+ if (data.length == 0) {
+ regex = makeRegex('[^]*');
+ template = template.replace(regex, '');
+ }
for (var d in data) {
if (data.hasOwnProperty(d)) {
@@ -112,10 +130,10 @@ function load_template(callback) {
rootUrl = location.protocol + '//' + (location.hostname || location.host) + (location.port ? ':' + location.port : '');
var url = location.href.replace(rootUrl +'/', '');
- if (url == '') url = 'home';
- jQuery.get('api/' + url, function(data) {
-
- document.getElementById('content').innerHTML = templates[url].parse(JSON.parse(data));
+ url = (url === '' || url === '/') ? 'home' : url;
+
+ jQuery.get(API_URL + url, function(data) {
+ document.getElementById('content').innerHTML = templates[url.split('/')[0]].parse(JSON.parse(data));
if (callback) callback();
});
}
\ No newline at end of file
diff --git a/public/templates/account_settings.tpl b/public/templates/account_settings.tpl
deleted file mode 100644
index d29898b8e4..0000000000
--- a/public/templates/account_settings.tpl
+++ /dev/null
@@ -1,18 +0,0 @@
-
Account Settings
-
-
-
-
- If you see this, you are logged in.
-
-
-
\ No newline at end of file
diff --git a/public/templates/footer.tpl b/public/templates/footer.tpl
index 0d5ee09518..dd3de05b00 100644
--- a/public/templates/footer.tpl
+++ b/public/templates/footer.tpl
@@ -4,7 +4,7 @@
@@ -148,6 +180,7 @@
@@ -172,4 +205,5 @@
+
\ No newline at end of file
diff --git a/public/templates/home.tpl b/public/templates/home.tpl
index 3149404251..b49f4c2e85 100644
--- a/public/templates/home.tpl
+++ b/public/templates/home.tpl
@@ -1,9 +1,9 @@
New Post
diff --git a/public/templates/login.tpl b/public/templates/login.tpl
index e9a96e7862..c430b90f17 100644
--- a/public/templates/login.tpl
+++ b/public/templates/login.tpl
@@ -4,33 +4,10 @@
×
Failed Login Attempt
- Username
- Password
- Login
+
Forgot Password?
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/templates/topic.tpl b/public/templates/topic.tpl
index 56c230a31f..4c8ebfa9cb 100644
--- a/public/templates/topic.tpl
+++ b/public/templates/topic.tpl
@@ -1,8 +1,23 @@
-
+
+
+ Home /
+ {TOPIC_NAME}
+
+
+
+
-
+
{posts.content}
- Posted on {posts.timestamp} by user {posts.uid}.
+ Posted {posts.relativeTime} by user {posts.uid}.
-
\ No newline at end of file
+
+
+Reply
+
diff --git a/src/posts.js b/src/posts.js
index 78e55d4562..9e46407864 100644
--- a/src/posts.js
+++ b/src/posts.js
@@ -1,4 +1,5 @@
-var RDB = require('./redis.js');
+var RDB = require('./redis.js'),
+ utils = require('./utils.js');
(function(Posts) {
//data structure
@@ -15,60 +16,70 @@ var RDB = require('./redis.js');
if (start == null) start = 0;
if (end == null) end = start + 10;
- RDB.lrange('tid:' + tid + ':posts', start, end, function(pids) {
-
- var content = [],
- uid = [],
- timestamp = [];
-
- for (var i=0, ii=pids.length; i 0) {
- RDB.multi()
- .mget(content)
- .mget(uid)
- .mget(timestamp)
- .exec(function(err, replies) {
- content = replies[0];
- uid = replies[1];
- timestamp = replies[2];
-
- var posts = [];
- for (var i=0, ii=content.length; i 0) {
+ RDB.multi()
+ .mget(content)
+ .mget(uid)
+ .mget(timestamp)
+ .exec(function(err, replies) {
+ content = replies[0];
+ uid = replies[1];
+ timestamp = replies[2];
+
+ var posts = [];
+ for (var i=0, ii=content.length; i 0) {
var keys = [],
- returnData = {},
+ returnData = {
+ uid: uid
+ },
removeEmail = false;
if (!(fields instanceof Array)) fields = ['username', 'email'];
@@ -27,7 +29,7 @@ var config = require('../config.js'),
for(var x=0,numData=data.length;x= +new Date()/1000|0) {
- if (!callback) global.socket.emit('user:reset.valid', { valid: true });
+ if (!callback) socket.emit('user:reset.valid', { valid: true });
else callback(true);
} else {
// Expired, delete from db
RDB.del('reset:' + code + ':uid');
RDB.del('reset:' + code + ':expiry');
- if (!callback) global.socket.emit('user:reset.valid', { valid: false });
+ if (!callback) socket.emit('user:reset.valid', { valid: false });
else callback(false);
}
});
} else {
- if (!callback) global.socket.emit('user:reset.valid', { valid: false });
+ if (!callback) socket.emit('user:reset.valid', { valid: false });
else callback(false);
}
});
@@ -208,13 +263,13 @@ var config = require('../config.js'),
emailjsServer.send(message, function(err, success) {
if (err === null) {
- global.socket.emit('user.send_reset', {
+ socket.emit('user.send_reset', {
status: "ok",
message: "code-sent",
email: email
});
} else {
- global.socket.emit('user.send_reset', {
+ socket.emit('user.send_reset', {
status: "error",
message: "send-failed"
});
@@ -222,7 +277,7 @@ var config = require('../config.js'),
}
});
} else {
- global.socket.emit('user.send_reset', {
+ socket.emit('user.send_reset', {
status: "error",
message: "invalid-email",
email: email
@@ -238,7 +293,7 @@ var config = require('../config.js'),
RDB.del('reset:' + code + ':uid');
RDB.del('reset:' + code + ':expiry');
- global.socket.emit('user:reset.commit', { status: 'ok' });
+ socket.emit('user:reset.commit', { status: 'ok' });
});
}
});
@@ -249,14 +304,29 @@ var config = require('../config.js'),
exists: function(email, callback) {
User.get_uid_by_email(email, function(exists) {
exists = !!exists;
- if (typeof callback !== 'function') global.socket.emit('user.email.exists', { exists: exists });
+ if (typeof callback !== 'function') socket.emit('user.email.exists', { exists: exists });
else callback(exists);
});
}
}
User.active = {
+ get_record : function() {
+ RDB.mget(['global:active_user_record', 'global:active_user_record_date'], function(data) {
+ socket.emit('api:user.active.get_record', {record: data[0], timestamp: data[1]});
+ });
+ },
+
get: function(callback) {
+ function user_record(total) {
+ RDB.get('global:active_user_record', function(record) {
+ if (total > record) {
+ RDB.set('global:active_user_record', total);
+ RDB.set('global:active_user_record_date', new Date().getTime());
+ }
+ });
+ }
+
RDB.keys('active:*', function(active) {
var returnObj = {
users: 0,
@@ -282,6 +352,8 @@ var config = require('../config.js'),
}
}
+ user_record(returnObj.anon + returnObj.users);
+
if (callback === undefined) {
io.sockets.emit('api:user.active.get', returnObj)
} else {
diff --git a/src/utils.js b/src/utils.js
new file mode 100644
index 0000000000..6728175336
--- /dev/null
+++ b/src/utils.js
@@ -0,0 +1,35 @@
+var utils = {
+ generateUUID: function() {
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ });
+ },
+ relativeTime: function(timestamp) {
+ var now = +new Date(),
+ difference = now - Math.floor(parseFloat(timestamp));
+
+ difference = Math.floor(difference / 1000);
+ if (difference < 60) return difference + ' second' + (difference !== 1 ? 's' : '') + ' ago';
+
+ difference = Math.floor(difference / 60);
+ if (difference < 60) return difference + ' minute' + (difference !== 1 ? 's' : '') + ' ago';
+
+ difference = Math.floor(difference / 60);
+ if (difference < 24) return difference + ' hour' + (difference !== 1 ? 's' : '') + ' ago';
+
+ difference = Math.floor(difference / 24);
+ if (difference < 3) return difference + ' day' + (difference !== 1 ? 's' : '') + ' ago';
+
+ // Lastly, just return a formatted date
+ var date = new Date(timestamp);
+ // hour = date.getHours(),
+ // minute = date.getMinutes(),
+ // day = date.getDate(),
+ // month = date.getMonth(),
+ // months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+ return date.toDateString();
+ }
+}
+
+module.exports = utils;
\ No newline at end of file
diff --git a/src/webserver.js b/src/webserver.js
index c4da2ac510..da96c649c3 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -3,7 +3,28 @@ var express = require('express'),
server = require('http').createServer(WebServer),
RedisStore = require('connect-redis')(express),
path = require('path'),
- config = require('../config.js');
+ config = require('../config.js'),
+ redis = require('redis'),
+ redisServer = redis.createClient(config.redis.port, config.redis.host, config.redis.options),
+ passport = require('passport'),
+ passportLocal = require('passport-local').Strategy;
+
+passport.use(new passportLocal(function(user, password, next) {
+ global.modules.user.loginViaLocal(user, password, function(login) {
+ if (login.status === 'ok') next(null, login.user);
+ else next(null, false, login);
+ });
+}));
+
+passport.serializeUser(function(user, done) {
+ done(null, user.uid);
+});
+
+passport.deserializeUser(function(uid, done) {
+ done(null, {
+ uid: uid
+ });
+});
(function(app) {
var templates = global.templates;
@@ -24,31 +45,39 @@ var express = require('express'),
app.use(express.compress());
app.use(express.session({
store: new RedisStore({
+ client: redisServer,
ttl: 60*60*24*14
}),
- secret: 'nodebb',
+ secret: config.secret,
key: 'express.sid'
}));
+ app.use(passport.initialize());
+ app.use(passport.session());
app.use(function(req, res, next) {
// Don't bother with session handling for API requests
if (/^\/api\//.test(req.url)) return next();
- if (req.session.uid === undefined) {
- console.log('info: [Auth] First load, retrieving uid...');
- global.modules.user.get_uid_by_session(req.sessionID, function(uid) {
- if (uid !== null) {
- req.session.uid = uid;
- console.log('info: [Auth] uid ' + req.session.uid + ' found. Welcome back.');
- } else {
- req.session.uid = 0;
- console.log('info: [Auth] No login session found.');
- }
- });
- } else {
- // console.log('SESSION: ' + req.sessionID);
- // console.log('info: [Auth] Ping from uid ' + req.session.uid);
+ if (req.user && req.user.uid) {
+ global.modules.user.session_ping(req.sessionID, req.user.uid);
}
+ // if (req.session.uid === undefined) {
+ // console.log('info: [Auth] First load, retrieving uid...');
+
+ // global.modules.user.get_uid_by_session(req.sessionID, function(uid) {
+ // if (uid !== null) {
+ // req.session.uid = uid;
+ // console.log('info: [Auth] uid ' + req.session.uid + ' found. Welcome back.');
+ // } else {
+ // req.session.uid = 0;
+ // console.log('info: [Auth] No login session found.');
+ // }
+ // });
+ // } else {
+ // // console.log('SESSION: ' + req.sessionID);
+ // // console.log('info: [Auth] Ping from uid ' + req.session.uid);
+ // }
+
// (Re-)register the session as active
global.modules.user.active.register(req.sessionID);
@@ -59,58 +88,67 @@ var express = require('express'),
// Useful if you want to use app.put and app.delete (instead of app.post all the time)
// app.use(express.methodOverride());
- app.get('/', function(req, res) {
- global.modules.topics.generate_forum_body(function(forum_body) {
- res.send(templates['header'] + forum_body + templates['footer']);
- });
- });
app.get('/403', function(req, res) {
res.send(templates['header'] + templates['403'] + templates['footer']);
});
+ // Basic Routes (entirely client-side parsed, goal is to move the rest of the crap in this file into this one section)
+ (function() {
+ var routes = ['', 'login', 'register'];
- // need a proper way to combine these two routes together
- app.get('/topics/:topic_id', function(req, res) {
- global.modules.topics.generate_topic_body(function(topic_body) {
- res.send(templates['header'] + topic_body + templates['footer']);
- }, req.params.topic_id)
- });
- app.get('/topics/:topic_id/:slug', function(req, res) {
+ for (var i=0, ii=routes.length; itemplates.ready(function(){ajaxify.go("' + route + '");});' + templates['footer']);
+ });
+ }(routes[i]));
+ }
+ }());
+
+
+ function generate_topic_body(req, res) {
global.modules.topics.generate_topic_body(function(topic_body) {
res.send(templates['header'] + topic_body + templates['footer']);
- }, req.params.topic_id)
- });
+ }, req.params.topic_id);
+ }
+ app.get('/topic/:topic_id', generate_topic_body);
+ app.get('/topic/:topic_id*', generate_topic_body);
- app.get('/api/:method', function(req, res) {
+ function api_method(req, res) {
switch(req.params.method) {
case 'home' :
global.modules.topics.get(function(data) {
res.send(JSON.stringify(data));
});
break;
+ case 'topic' :
+ global.modules.posts.get(function(data) {
+ res.send(JSON.stringify(data));
+ }, req.params.id);
+ break;
default :
res.send('{}');
break;
}
- });
+ }
+ app.get('/api/:method', api_method);
+ app.get('/api/:method/:id', api_method);
+ app.get('/api/:method/:id*', api_method);
- app.get('/login', function(req, res) {
- res.send(templates['header'] + templates['login'] + templates['footer']);
- });
+ app.post('/login', passport.authenticate('local', {
+ successRedirect: '/',
+ failureRedirect: '/login'
+ }));
app.get('/logout', function(req, res) {
console.log('info: [Auth] Session ' + res.sessionID + ' logout (uid: ' + global.uid + ')');
global.modules.user.logout(req.sessionID, function(logout) {
- if (logout === true) {
- delete(req.session.uid);
- req.session.destroy();
- }
+ req.logout();
+ res.send(templates['header'] + templates['logout'] + templates['footer']);
});
-
- res.send(templates['header'] + templates['logout'] + templates['footer']);
});
app.get('/reset/:code', function(req, res) {
diff --git a/src/websockets.js b/src/websockets.js
index bbe3bf4b8a..832d07964c 100644
--- a/src/websockets.js
+++ b/src/websockets.js
@@ -1,6 +1,7 @@
var SocketIO = require('socket.io').listen(global.server),
cookie = require('cookie'),
- connect = require('connect');
+ connect = require('connect'),
+ config = require('../config.js');
(function(io) {
var modules = null,
@@ -16,7 +17,7 @@ var SocketIO = require('socket.io').listen(global.server),
io.set('authorization', function(handshakeData, accept) {
if (handshakeData.headers.cookie) {
handshakeData.cookie = cookie.parse(handshakeData.headers.cookie);
- handshakeData.sessionID = connect.utils.parseSignedCookie(handshakeData.cookie['express.sid'], 'nodebb');
+ handshakeData.sessionID = connect.utils.parseSignedCookie(handshakeData.cookie['express.sid'], config.secret);
if (handshakeData.cookie['express.sid'] == handshakeData.sessionID) {
return accept('Cookie is invalid.', false);
@@ -93,9 +94,17 @@ var SocketIO = require('socket.io').listen(global.server),
modules.topics.post(uid, data.title, data.content);
});
+ socket.on('api:posts.reply', function(data) {
+ modules.posts.reply(data.topic_id, uid, data.content);
+ });
+
socket.on('api:user.active.get', function() {
modules.user.active.get();
});
+
+ socket.on('api:user.active.get_record', function() {
+ modules.user.active.get_record();
+ });
});
}(SocketIO));
diff --git a/utils.js b/utils.js
deleted file mode 100644
index 7b003e466a..0000000000
--- a/utils.js
+++ /dev/null
@@ -1,10 +0,0 @@
-var utils = {
- generateUUID: function() {
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
- var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
- return v.toString(16);
- });
- }
-}
-
-module.exports = utils;
\ No newline at end of file