Baris Usakli 11 years ago
commit 22a3b227a3

@ -55,7 +55,7 @@
winston.info(''); winston.info('');
if (fs.existsSync(__dirname + '/config.json') && (!nconf.get('setup') && !nconf.get('upgrade'))) { if (!nconf.get('help') && !nconf.get('setup') && !nconf.get('upgrade') && fs.existsSync(__dirname + '/config.json')) {
// Load server-side configs // Load server-side configs
nconf.file({ nconf.file({
file: __dirname + '/config.json' file: __dirname + '/config.json'
@ -66,7 +66,7 @@
nconf.set('upload_url', nconf.get('url') + 'uploads/'); nconf.set('upload_url', nconf.get('url') + 'uploads/');
winston.info('Initializing NodeBB v' + pkg.version + ', on port ' + nconf.get('port') + ', using Redis store at ' + nconf.get('redis:host') + ':' + nconf.get('redis:port') + '.'); winston.info('Initializing NodeBB v' + pkg.version + ', on port ' + nconf.get('port') + ', using Redis store at ' + nconf.get('redis:host') + ':' + nconf.get('redis:port') + '.');
winston.info('NodeBB instance bound to: ' + (nconf.get('bind_address') || 'Any address')); winston.info('NodeBB instance bound to: ' + ((nconf.get('bind_address') === "0.0.0.0" || !nconf.get('bind_address')) ? 'Any address (0.0.0.0)' : nconf.get('bind_address')));
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
winston.info('Base Configuration OK.'); winston.info('Base Configuration OK.');
@ -87,7 +87,8 @@
SocketIO = require('socket.io').listen(global.server, { log: false, transports: ['websocket', 'xhr-polling', 'jsonp-polling', 'flashsocket']}), SocketIO = require('socket.io').listen(global.server, { log: false, transports: ['websocket', 'xhr-polling', 'jsonp-polling', 'flashsocket']}),
websockets = require('./src/websockets.js'), websockets = require('./src/websockets.js'),
posts = require('./src/posts.js'), posts = require('./src/posts.js'),
plugins = require('./src/plugins'); // Don't remove this - plugins initializes itself plugins = require('./src/plugins'), // Don't remove this - plugins initializes itself
Notifications = require('./src/notifications');
websockets.init(SocketIO); websockets.init(SocketIO);
@ -106,18 +107,10 @@
]); ]);
templates.ready(webserver.init); templates.ready(webserver.init);
});
} else if (nconf.get('upgrade')) {
nconf.file({
file: __dirname + '/config.json'
});
meta = require('./src/meta.js');
meta.configs.init(function () { Notifications.init();
require('./src/upgrade').upgrade();
}); });
} else { } else if (nconf.get('setup') || !fs.existsSync(__dirname + '/config.json')) {
// New install, ask setup questions // New install, ask setup questions
if (nconf.get('setup')) { if (nconf.get('setup')) {
winston.info('NodeBB Setup Triggered via Command Line'); winston.info('NodeBB Setup Triggered via Command Line');
@ -135,10 +128,29 @@
if (err) { if (err) {
winston.error('There was a problem completing NodeBB setup: ', err.message); winston.error('There was a problem completing NodeBB setup: ', err.message);
} else { } else {
winston.info('NodeBB Setup Completed.'); winston.info('NodeBB Setup Completed. Run \'node app\' to manually start your NodeBB server.');
} }
process.exit(); process.exit();
}); });
}
}()); } else if (nconf.get('upgrade')) {
nconf.file({
file: __dirname + '/config.json'
});
meta = require('./src/meta.js');
meta.configs.init(function () {
require('./src/upgrade').upgrade();
});
} else/* if (nconf.get('help') */{
winston.info('Usage: node app [options] [arguments]');
winston.info(' [NODE_ENV=development | NODE_ENV=production] node app [--start] [arguments]');
winston.info('');
winston.info('Options:');
winston.info(' --help displays this usage information');
winston.info(' --setup configure your environment and setup NodeBB');
winston.info(' --upgrade upgrade NodeBB, first read: github.com/designcreateplay/NodeBB/wiki/Upgrading-NodeBB');
winston.info(' --start manually start NodeBB (default when no options are given)');
};
}());

@ -44,7 +44,8 @@
"nodebb-plugin-mentions": "~0.1.13", "nodebb-plugin-mentions": "~0.1.13",
"nodebb-plugin-markdown": "~0.1.7", "nodebb-plugin-markdown": "~0.1.7",
"nodebb-theme-vanilla": "designcreateplay/nodebb-theme-vanilla", "nodebb-theme-vanilla": "designcreateplay/nodebb-theme-vanilla",
"nodebb-theme-cerulean": "0.0.3" "nodebb-theme-cerulean": "0.0.3",
"cron": "~1.0.1"
}, },
"optionalDependencies": { "optionalDependencies": {
"hiredis": "~0.1.15" "hiredis": "~0.1.15"

@ -101,6 +101,7 @@
numUnread = data.unread.length, numUnread = data.unread.length,
x; x;
notifList.innerHTML = ''; notifList.innerHTML = '';
console.log(data);
if ((data.read.length + data.unread.length) > 0) { if ((data.read.length + data.unread.length) > 0) {
for (x = 0; x < numUnread; x++) { for (x = 0; x < numUnread; x++) {
notifEl.setAttribute('data-nid', data.unread[x].nid); notifEl.setAttribute('data-nid', data.unread[x].nid);
@ -115,15 +116,16 @@
notifFrag.appendChild(notifEl.cloneNode(true)); notifFrag.appendChild(notifEl.cloneNode(true));
} }
} else { } else {
notifEl.className = 'no-notifs';
notifEl.innerHTML = '<a>You have no notifications</a>'; notifEl.innerHTML = '<a>You have no notifications</a>';
notifFrag.appendChild(notifEl); notifFrag.appendChild(notifEl.cloneNode(true));
} }
// Add dedicated link to /notifications // Add dedicated link to /notifications
notifEl.removeAttribute('data-nid'); notifEl.removeAttribute('data-nid');
notifEl.className = 'pagelink'; notifEl.className = 'pagelink';
notifEl.innerHTML = '<a href="' + RELATIVE_PATH + '/notifications">See all Notifications</a>'; notifEl.innerHTML = '<a href="' + RELATIVE_PATH + '/notifications">See all Notifications</a>';
notifFrag.appendChild(notifEl); notifFrag.appendChild(notifEl.cloneNode(true));
notifList.appendChild(notifFrag); notifList.appendChild(notifFrag);

@ -11,7 +11,7 @@
generateUUID: function() { generateUUID: function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, var r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8); v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16); return v.toString(16);
}); });
}, },
@ -225,4 +225,4 @@
module: { module: {
exports: {} exports: {}
} }
} : module) } : module);

@ -24,9 +24,6 @@
"forum": '../forum' "forum": '../forum'
} }
}); });
requirejs.onError = function(err) {
console.log(err);
}
</script> </script>
<link rel="stylesheet" type="text/css" href="{relative_path}/css/theme.css" /> <link rel="stylesheet" type="text/css" href="{relative_path}/css/theme.css" />

@ -1,16 +1,30 @@
var RDB = require('./redis.js'), var RDB = require('./redis.js'),
async = require('async'), async = require('async'),
utils = require('../public/src/utils.js'), utils = require('../public/src/utils.js'),
winston = require('winston'),
cron = require('cron').CronJob,
notifications = { notifications = {
init: function() {
if (process.env.NODE_ENV === 'development') {
winston.info('[notifications.init] Registering jobs.');
}
new cron('0 0 * * *', notifications.prune, null, true);
},
get: function(nid, uid, callback) { get: function(nid, uid, callback) {
RDB.multi() RDB.multi()
.hmget('notifications:' + nid, 'text', 'score', 'path', 'datetime', 'uniqueId') .hmget('notifications:' + nid, 'text', 'score', 'path', 'datetime', 'uniqueId')
.zrank('uid:' + uid + ':notifications:read', nid) .zrank('uid:' + uid + ':notifications:read', nid)
.exists('notifications:' + nid)
.exec(function(err, results) { .exec(function(err, results) {
var notification = results[0] var notification = results[0],
readIdx = results[1]; readIdx = results[1];
if (!results[2]) {
return callback(null);
}
callback({ callback({
nid: nid, nid: nid,
text: notification[0], text: notification[0],
@ -30,16 +44,32 @@ var RDB = require('./redis.js'),
* the new one put in its place. * the new one put in its place.
*/ */
RDB.incr('notifications:next_nid', function(err, nid) { RDB.incr('notifications:next_nid', function(err, nid) {
RDB.sadd('notifications', nid);
RDB.hmset('notifications:' + nid, { RDB.hmset('notifications:' + nid, {
text: text || '', text: text || '',
path: path || null, path: path || null,
datetime: Date.now(), datetime: Date.now(),
uniqueId: uniqueId || utils.generateUUID() uniqueId: uniqueId || utils.generateUUID()
}, function(err, status) { }, function(err, status) {
if (!err) callback(nid); if (!err) {
callback(nid);
}
}); });
}); });
}, },
destroy: function(nid) {
var multi = RDB.multi();
multi.del('notifications:' + nid);
multi.srem('notifications', nid);
multi.exec(function(err) {
if (err) {
winston.error('Problem deleting expired notifications. Stack follows.');
winston.error(err.stack);
}
});
},
push: function(nid, uids, callback) { push: function(nid, uids, callback) {
if (!Array.isArray(uids)) uids = [uids]; if (!Array.isArray(uids)) uids = [uids];
@ -48,12 +78,14 @@ var RDB = require('./redis.js'),
notifications.get(nid, null, function(notif_data) { notifications.get(nid, null, function(notif_data) {
for (x = 0; x < numUids; x++) { for (x = 0; x < numUids; x++) {
if (parseInt(uids[x]) > 0) { if (parseInt(uids[x], 10) > 0) {
(function(uid) { (function(uid) {
notifications.remove_by_uniqueId(notif_data.uniqueId, uid, function() { notifications.remove_by_uniqueId(notif_data.uniqueId, uid, function() {
RDB.zadd('uid:' + uid + ':notifications:unread', notif_data.datetime, nid); RDB.zadd('uid:' + uid + ':notifications:unread', notif_data.datetime, nid);
global.io.sockets.in('uid_' + uid).emit('event:new_notification'); global.io.sockets.in('uid_' + uid).emit('event:new_notification');
if (callback) callback(true); if (callback) {
callback(true);
}
}); });
})(uids[x]); })(uids[x]);
} }
@ -67,13 +99,18 @@ var RDB = require('./redis.js'),
if (nids && nids.length > 0) { if (nids && nids.length > 0) {
async.each(nids, function(nid, next) { async.each(nids, function(nid, next) {
notifications.get(nid, uid, function(nid_info) { notifications.get(nid, uid, function(nid_info) {
if (nid_info.uniqueId === uniqueId) RDB.zrem('uid:' + uid + ':notifications:unread', nid); if (nid_info.uniqueId === uniqueId) {
RDB.zrem('uid:' + uid + ':notifications:unread', nid);
}
next(); next();
}); });
}, function(err) { }, function(err) {
next(); next();
}); });
} else next(); } else {
next();
}
}); });
}, },
function(next) { function(next) {
@ -81,17 +118,24 @@ var RDB = require('./redis.js'),
if (nids && nids.length > 0) { if (nids && nids.length > 0) {
async.each(nids, function(nid, next) { async.each(nids, function(nid, next) {
notifications.get(nid, uid, function(nid_info) { notifications.get(nid, uid, function(nid_info) {
if (nid_info.uniqueId === uniqueId) RDB.zrem('uid:' + uid + ':notifications:read', nid); if (nid_info.uniqueId === uniqueId) {
RDB.zrem('uid:' + uid + ':notifications:read', nid);
}
next(); next();
}); });
}, function(err) { }, function(err) {
next(); next();
}); });
} else next(); } else {
next();
}
}); });
} }
], function(err) { ], function(err) {
if (!err) callback(true); if (!err) {
callback(true);
}
}); });
}, },
mark_read: function(nid, uid, callback) { mark_read: function(nid, uid, callback) {
@ -99,38 +143,126 @@ var RDB = require('./redis.js'),
notifications.get(nid, uid, function(notif_data) { notifications.get(nid, uid, function(notif_data) {
RDB.zrem('uid:' + uid + ':notifications:unread', nid); RDB.zrem('uid:' + uid + ':notifications:unread', nid);
RDB.zadd('uid:' + uid + ':notifications:read', notif_data.datetime, nid); RDB.zadd('uid:' + uid + ':notifications:read', notif_data.datetime, nid);
if (callback) callback(); if (callback) {
callback();
}
}); });
} }
}, },
mark_read_multiple: function(nids, uid, callback) { mark_read_multiple: function(nids, uid, callback) {
if (!Array.isArray(nids) && parseInt(nids, 10) > 0) nids = [nids]; if (!Array.isArray(nids) && parseInt(nids, 10) > 0) {
nids = [nids];
}
async.each(nids, function(nid, next) { async.each(nids, function(nid, next) {
notifications.mark_read(nid, uid, function(err) { notifications.mark_read(nid, uid, function(err) {
if (!err) next(null); if (!err) {
next(null);
}
}); });
}, function(err) { }, function(err) {
if (callback) callback(err); if (callback) {
callback(err);
}
}); });
}, },
mark_all_read: function(uid, callback) { mark_all_read: function(uid, callback) {
RDB.zrange('uid:' + uid + ':notifications:unread', 0, 10, function(err, nids) { RDB.zrange('uid:' + uid + ':notifications:unread', 0, 10, function(err, nids) {
if (err) return callback(err); if (err) {
return callback(err);
}
if (nids.length > 0) { if (nids.length > 0) {
notifications.mark_read_multiple(nids, uid, function(err) { notifications.mark_read_multiple(nids, uid, function(err) {
callback(err); callback(err);
}); });
} else callback(); } else {
callback();
}
});
},
prune: function(cutoff) {
if (process.env.NODE_ENV === 'development') {
winston.info('[notifications.prune] Removing expired notifications from the database.');
}
var today = new Date(),
numPruned = 0;
if (!cutoff) {
cutoff = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 7);
}
var cutoffTime = cutoff.getTime();
async.parallel({
"inboxes": function(next) {
RDB.keys('uid:*:notifications:unread', next);
},
"nids": function(next) {
RDB.smembers('notifications', function(err, nids) {
async.filter(nids, function(nid, next) {
RDB.hget('notifications:' + nid, 'datetime', function(err, datetime) {
if (parseInt(datetime, 10) < cutoffTime) {
next(true);
} else {
next(false);
}
});
}, function(expiredNids) {
next(null, expiredNids);
});
});
}
}, function(err, results) {
if (!err) {
var numInboxes = results.inboxes.length,
x;
async.eachSeries(results.nids, function(nid, next) {
var multi = RDB.multi();
for(x=0;x<numInboxes;x++) {
multi.zscore(results.inboxes[x], nid);
}
multi.exec(function(err, results) {
// If the notification is not present in any inbox, delete it altogether
var expired = results.every(function(present) {
if (present === null) {
return true;
}
});
if (expired) {
notifications.destroy(nid);
numPruned++;
}
next();
});
}, function(err) {
if (process.env.NODE_ENV === 'development') {
winston.info('[notifications.prune] Notification pruning completed. ' + numPruned + ' expired notification' + (numPruned !== 1 ? 's' : '') + ' removed.');
}
});
} else {
if (process.env.NODE_ENV === 'development') {
winston.error('[notifications.prune] Ran into trouble pruning expired notifications. Stack trace to follow.');
winston.error(err.stack);
}
}
}); });
} }
} };
module.exports = { module.exports = {
init: notifications.init,
get: notifications.get, get: notifications.get,
create: notifications.create, create: notifications.create,
push: notifications.push, push: notifications.push,
mark_read: notifications.mark_read_multiple, mark_read: notifications.mark_read_multiple,
mark_all_read: notifications.mark_all_read mark_all_read: notifications.mark_all_read,
} prune: notifications.prune
};

@ -0,0 +1,20 @@
var DebugRoute = function(app) {
app.namespace('/debug', function() {
app.get('/prune', function(req, res) {
var Notifications = require('../notifications');
Notifications.prune(new Date(), function() {
console.log('done');
});
res.send();
});
app.get('/uuidtest', function(req, res) {
var Utils = require('../../public/src/utils.js');
res.send(Utils.generateUUID());
});
});
};
module.exports = DebugRoute;

@ -59,6 +59,28 @@ Upgrade.upgrade = function() {
next(); next();
} }
}); });
},
function(next) {
RDB.exists('notifications', function(err, exists) {
if (!exists) {
RDB.keys('notifications:*', function(err, keys) {
var multi = RDB.multi();
keys = keys.filter(function(key) {
if (key === 'notifications:next_nid') return false;
else return true;
}).map(function(key) {
return key.slice(14);
});
winston.info('[2013/10/23] Adding existing notifications to set');
RDB.sadd('notifications', keys, next);
});
} else {
winston.info('[2013/10/23] Updates to Notifications skipped.');
next();
}
});
} }
// Add new schema updates here // Add new schema updates here
], function(err) { ], function(err) {

@ -903,7 +903,13 @@ var utils = require('./../public/src/utils.js'),
if (nids && nids.length > 0) { if (nids && nids.length > 0) {
async.eachSeries(nids, function(nid, next) { async.eachSeries(nids, function(nid, next) {
notifications.get(nid, uid, function(notif_data) { notifications.get(nid, uid, function(notif_data) {
unread.push(notif_data); // If the notification could not be found, silently drop it
if (notif_data) {
unread.push(notif_data);
} else {
RDB.zrem('uid:' + uid + ':notifications:unread', nid);
}
next(); next();
}); });
}, function(err) { }, function(err) {
@ -925,7 +931,13 @@ var utils = require('./../public/src/utils.js'),
if (nids && nids.length > 0) { if (nids && nids.length > 0) {
async.eachSeries(nids, function(nid, next) { async.eachSeries(nids, function(nid, next) {
notifications.get(nid, uid, function(notif_data) { notifications.get(nid, uid, function(notif_data) {
read.push(notif_data); // If the notification could not be found, silently drop it
if (notif_data) {
read.push(notif_data);
} else {
RDB.zrem('uid:' + uid + ':notifications:read', nid);
}
next(); next();
}); });
}, function(err) { }, function(err) {

@ -136,6 +136,10 @@ var express = require('express'),
app.use(function (req, res, next) { app.use(function (req, res, next) {
nconf.set('https', req.secure); nconf.set('https', req.secure);
res.locals.csrf_token = req.session._csrf; res.locals.csrf_token = req.session._csrf;
// Disable framing
res.setHeader("X-Frame-Options", "DENY");
next(); next();
}); });
@ -667,6 +671,10 @@ var express = require('express'),
}); });
}); });
// Debug routes
if (process.env.NODE_ENV === 'development') {
require('./routes/debug')(app);
}
var custom_routes = { var custom_routes = {
'routes': [], 'routes': [],

@ -0,0 +1,9 @@
{
"strict" : false, // true: Requires all functions run in ES5 Strict Mode
// Custom Globals
"globals" : {
"it": false,
"describe": false
}
}

@ -8,5 +8,28 @@ describe("Utility Methods", function(){
var username = "John\"'-. Doeäâèéë1234"; var username = "John\"'-. Doeäâèéë1234";
assert(utils.isUserNameValid(username), 'invalid username'); assert(utils.isUserNameValid(username), 'invalid username');
}); });
it("rejects empty string", function(){
var username = "";
assert.ifError(utils.isUserNameValid(username), 'accepted as valid username');
});
});
describe("email validation", function(){
it("accepts sample address", function(){
var email = 'sample@example.com';
assert(utils.isEmailValid(email), 'invalid email');
});
it("rejects empty address", function(){
var email = '';
assert.ifError(utils.isEmailValid(email), 'accepted as valid email');
});
});
describe("UUID generation", function(){
it("return unique random value every time", function(){
var uuid1 = utils.generateUUID(),
uuid2 = utils.generateUUID();
assert.notEqual(uuid1, uuid2, "matches");
});
}); });
}); });

Loading…
Cancel
Save