Merge remote-tracking branch 'origin' into category-whitelisting

This commit is contained in:
Julian Lam
2013-11-27 08:17:23 -05:00
132 changed files with 4223 additions and 4912 deletions

View File

@@ -4,10 +4,6 @@ var RDB = require('./../redis.js'),
(function(CategoriesAdmin) {
CategoriesAdmin.create = function(data, callback) {
categories.create(data, callback);
};
CategoriesAdmin.update = function(modified, socket) {
var updated = [];

View File

@@ -25,7 +25,8 @@ var RDB = require('./redis.js'),
name: data.name,
description: data.description,
icon: data.icon,
blockclass: data.blockclass,
bgColor: data.bgColor,
color: data.color,
slug: slug,
topic_count: 0,
disabled: 0,
@@ -140,10 +141,16 @@ var RDB = require('./redis.js'),
RDB.smembers('cid:' + cid + ':active_users', callback);
};
Categories.getAllCategories = function(callback, current_user) {
Categories.getAllCategories = function(current_user, callback) {
RDB.lrange('categories:cid', 0, -1, function(err, cids) {
RDB.handle(err);
Categories.getCategories(cids, callback, current_user);
if(err) {
return callback(err);
}
if(cids && cids.length === 0) {
return callback(null, {categories : []});
}
Categories.getCategories(cids, current_user, callback);
});
};
@@ -252,26 +259,28 @@ var RDB = require('./redis.js'),
Categories.moveRecentReplies = function(tid, oldCid, cid, callback) {
function movePost(pid, callback) {
posts.getPostField(pid, 'timestamp', function(timestamp) {
posts.getPostField(pid, 'timestamp', function(err, timestamp) {
if(err) {
return callback(err);
}
RDB.zrem('categories:recent_posts:cid:' + oldCid, pid);
RDB.zadd('categories:recent_posts:cid:' + cid, timestamp, pid);
callback(null);
});
}
topics.getPids(tid, function(err, pids) {
if (!err) {
async.each(pids, movePost, function(err) {
if (!err) {
callback(null, 1);
} else {
winston.err(err);
callback(err, null);
}
});
} else {
winston.err(err);
callback(err, null);
if(err) {
return callback(err, null);
}
async.each(pids, movePost, function(err) {
if(err) {
return callback(err, null);
}
callback(null, 1);
});
});
};
@@ -321,24 +330,20 @@ var RDB = require('./redis.js'),
RDB.hincrby('category:' + cid, field, value);
};
Categories.getCategories = function(cids, callback, current_user) {
Categories.getCategories = function(cids, uid, callback) {
if (!cids || !Array.isArray(cids) || cids.length === 0) {
callback({
'categories': []
});
return;
return callback(new Error('invalid-cids'), null);
}
function getCategory(cid, callback) {
Categories.getCategoryData(cid, function(err, categoryData) {
if (err) {
winston.warn('Attempted to retrieve cid ' + cid + ', but nothing was returned!');
callback(null, null);
return;
return callback(err, null);
}
Categories.hasReadCategory(cid, current_user, function(hasRead) {
categoryData.badgeclass = (parseInt(categoryData.topic_count, 10) === 0 || (hasRead && current_user !== 0)) ? '' : 'badge-important';
Categories.hasReadCategory(cid, uid, function(hasRead) {
categoryData.badgeclass = (parseInt(categoryData.topic_count, 10) === 0 || (hasRead && uid !== 0)) ? '' : 'badge-important';
callback(null, categoryData);
});
@@ -348,8 +353,7 @@ var RDB = require('./redis.js'),
async.map(cids, getCategory, function(err, categories) {
if (err) {
winston.err(err);
callback(null);
return;
return callback(err, null);
}
categories = categories.filter(function(category) {
@@ -358,7 +362,7 @@ var RDB = require('./redis.js'),
return parseInt(a.order, 10) - parseInt(b.order, 10);
});
callback({
callback(null, {
'categories': categories
});
});
@@ -372,19 +376,6 @@ var RDB = require('./redis.js'),
return callback(err, null);
}
function getPostCategory(pid, callback) {
posts.getPostField(pid, 'tid', function(tid) {
topics.getTopicField(tid, 'cid', function(err, postCid) {
if (err) {
return callback(err, null);
}
return callback(null, postCid);
});
});
}
var index = 0,
active = false;
@@ -393,7 +384,7 @@ var RDB = require('./redis.js'),
return active === false && index < pids.length;
},
function(callback) {
getPostCategory(pids[index], function(err, postCid) {
posts.getCidByPid(pids[index], function(err, postCid) {
if (err) {
return callback(err);
}
@@ -411,7 +402,6 @@ var RDB = require('./redis.js'),
return callback(err, null);
}
callback(null, active);
}
);

View File

@@ -1,7 +1,8 @@
var RDB = require('./redis.js'),
posts = require('./posts.js'),
user = require('./user.js'),
translator = require('./../public/src/translator.js');
var RDB = require('./redis'),
posts = require('./posts'),
user = require('./user'),
websockets = require('./websockets')
translator = require('./../public/src/translator');
(function (Favourites) {
"use strict";
@@ -21,7 +22,7 @@ var RDB = require('./redis.js'),
return;
}
posts.getPostFields(pid, ['uid', 'timestamp'], function (postData) {
posts.getPostFields(pid, ['uid', 'timestamp'], function (err, postData) {
Favourites.hasFavourited(pid, uid, function (hasFavourited) {
if (hasFavourited === 0) {
@@ -37,7 +38,7 @@ var RDB = require('./redis.js'),
}
if (room_id) {
io.sockets. in (room_id).emit('event:rep_up', {
websockets.in(room_id).emit('event:rep_up', {
uid: uid !== postData.uid ? postData.uid : 0,
pid: pid
});
@@ -57,7 +58,7 @@ var RDB = require('./redis.js'),
return;
}
posts.getPostField(pid, 'uid', function (uid_of_poster) {
posts.getPostField(pid, 'uid', function (err, uid_of_poster) {
Favourites.hasFavourited(pid, uid, function (hasFavourited) {
if (hasFavourited === 1) {
RDB.srem('pid:' + pid + ':users_favourited', uid);
@@ -72,7 +73,7 @@ var RDB = require('./redis.js'),
}
if (room_id) {
io.sockets. in (room_id).emit('event:rep_down', {
websockets.in(room_id).emit('event:rep_down', {
uid: uid !== uid_of_poster ? uid_of_poster : 0,
pid: pid
});

View File

@@ -2,12 +2,14 @@
var RDB = require('./redis.js'),
posts = require('./posts.js'),
topics = require('./topics.js'),
categories = require('./categories'),
fs = require('fs'),
rss = require('rss'),
winston = require('winston'),
path = require('path'),
nconf = require('nconf'),
categories = require('./categories');
async = require('async');
Feed.defaults = {
ttl: 60,
@@ -26,43 +28,43 @@
}
Feed.updateTopic = function (tid, callback) {
if (process.env.NODE_ENV === 'development') winston.info('[rss] Updating RSS feeds for topic ' + tid);
topics.getTopicWithPosts(tid, 0, 0, -1, function (err, topicData) {
if (err) return callback(new Error('topic-invalid'));
var feed = new rss({
title: topicData.topic_name,
description: topicData.main_posts[0].content,
feed_url: Feed.defaults.baseUrl + '/topics/' + tid + '.rss',
site_url: nconf.get('url') + 'topic/' + topicData.slug,
image_url: topicData.main_posts[0].picture,
author: topicData.main_posts[0].username,
ttl: Feed.defaults.ttl
}),
topic_posts = topicData.main_posts.concat(topicData.posts),
title, postData, dateStamp;
title: topicData.topic_name,
description: topicData.posts[0].content,
feed_url: Feed.defaults.baseUrl + '/topics/' + tid + '.rss',
site_url: nconf.get('url') + 'topic/' + topicData.slug,
image_url: topicData.posts[0].picture,
author: topicData.posts[0].username,
ttl: Feed.defaults.ttl
}),
topic_posts = topicData.posts.concat(topicData.posts),
dateStamp;
// Add pubDate if topic contains posts
if (topicData.main_posts.length > 0) feed.pubDate = new Date(parseInt(topicData.main_posts[0].timestamp, 10)).toUTCString();
if (topicData.posts.length > 0) feed.pubDate = new Date(parseInt(topicData.posts[0].timestamp, 10)).toUTCString();
for (var i = 0, ii = topic_posts.length; i < ii; i++) {
if (topic_posts[i].deleted === '0') {
postData = topic_posts[i];
async.each(topic_posts, function(postData, next) {
if (postData.deleted === '0') {
dateStamp = new Date(parseInt(postData.edited === '0' ? postData.timestamp : postData.edited, 10)).toUTCString();
title = 'Reply to ' + topicData.topic_name + ' on ' + dateStamp;
feed.item({
title: title,
title: 'Reply to ' + topicData.topic_name + ' on ' + dateStamp,
description: postData.content,
url: nconf.get('url') + 'topic/' + topicData.slug + '#' + postData.pid,
author: postData.username,
date: dateStamp
});
}
}
Feed.saveFeed('feeds/topics/' + tid + '.rss', feed, function (err) {
next();
}, function() {
if (process.env.NODE_ENV === 'development') {
winston.info('[rss] Re-generated RSS Feed for tid ' + tid + '.');
}
if (callback) callback();
});
});
@@ -70,40 +72,75 @@
};
Feed.updateCategory = function (cid, callback) {
if (process.env.NODE_ENV === 'development') winston.info('[rss] Updating RSS feeds for category ' + cid);
categories.getCategoryById(cid, 0, function (err, categoryData) {
if (err) return callback(new Error('category-invalid'));
var feed = new rss({
title: categoryData.category_name,
description: categoryData.category_description,
feed_url: Feed.defaults.baseUrl + '/categories/' + cid + '.rss',
site_url: nconf.get('url') + 'category/' + categoryData.category_id,
ttl: Feed.defaults.ttl
}),
topics = categoryData.topics,
title, topicData, dateStamp;
title: categoryData.category_name,
description: categoryData.category_description,
feed_url: Feed.defaults.baseUrl + '/categories/' + cid + '.rss',
site_url: nconf.get('url') + 'category/' + categoryData.category_id,
ttl: Feed.defaults.ttl
});
// Add pubDate if category has topics
if (categoryData.topics.length > 0) feed.pubDate = new Date(parseInt(categoryData.topics[0].lastposttime, 10)).toUTCString();
for (var i = 0, ii = topics.length; i < ii; i++) {
topicData = topics[i];
dateStamp = new Date(parseInt(topicData.lastposttime, 10)).toUTCString();
title = topics[i].title;
async.eachSeries(categoryData.topics, function(topicData, next) {
feed.item({
title: title,
title: topicData.title,
url: nconf.get('url') + 'topic/' + topicData.slug,
author: topicData.username,
date: dateStamp
date: new Date(parseInt(topicData.lastposttime, 10)).toUTCString()
});
}
Feed.saveFeed('feeds/categories/' + cid + '.rss', feed, function (err) {
if (callback) callback();
next();
}, function() {
Feed.saveFeed('feeds/categories/' + cid + '.rss', feed, function (err) {
if (process.env.NODE_ENV === 'development') {
winston.info('[rss] Re-generated RSS Feed for cid ' + cid + '.');
}
if (callback) callback();
});
});
});
};
Feed.updateRecent = function(callback) {
console.log('entered');
if (process.env.NODE_ENV === 'development') winston.info('[rss] Updating Recent Posts RSS feed');
topics.getLatestTopics(0, 0, 19, undefined, function (err, recentData) {
var feed = new rss({
title: 'Recently Active Topics',
description: 'A list of topics that have been active within the past 24 hours',
feed_url: Feed.defaults.baseUrl + '/recent.rss',
site_url: nconf.get('url') + 'recent',
ttl: Feed.defaults.ttl
});
// Add pubDate if recent topics list contains topics
if (recentData.topics.length > 0) {
feed.pubDate = new Date(parseInt(recentData.topics[0].lastposttime, 10)).toUTCString();
}
async.eachSeries(recentData.topics, function(topicData, next) {
feed.item({
title: topicData.title,
url: nconf.get('url') + 'topic/' + topicData.slug,
author: topicData.username,
date: new Date(parseInt(topicData.lastposttime, 10)).toUTCString()
});
next();
}, function() {
Feed.saveFeed('feeds/recent.rss', feed, function (err) {
if (process.env.NODE_ENV === 'development') {
winston.info('[rss] Re-generated "recent posts" RSS Feed.');
}
if (callback) callback();
});
});
});
};
}(exports));

View File

@@ -56,6 +56,8 @@
results.base.count = results.users.length;
results.base.members = results.users;
results.base.deletable = (results.base.gid !== '1');
callback(err, results.base);
});
},
@@ -112,7 +114,9 @@
});
},
destroy: function (gid, callback) {
RDB.hset('gid:' + gid, 'deleted', '1', callback);
if (gid !== 1) {
RDB.hset('gid:' + gid, 'deleted', '1', callback);
}
},
join: function (gid, uid, callback) {
RDB.sadd('gid:' + gid + ':members', uid, callback);

View File

@@ -1,12 +1,11 @@
var request = require('request');
var request = require('request'),
winston = require('winston');
(function (imgur) {
"use strict";
var clientID = '';
imgur.upload = function (image, type, callback) {
imgur.upload = function (clientID, image, type, callback) {
var options = {
url: 'https://api.imgur.com/3/upload.json',
headers: {
@@ -15,21 +14,27 @@ var request = require('request');
};
var post = request.post(options, function (err, req, body) {
if(err) {
return callback(err, null);
}
try {
callback(err, JSON.parse(body));
} catch (e) {
callback(err, body);
var response = JSON.parse(body);
if(response.success) {
callback(null, response.data);
} else {
callback(new Error(response.data.error.message), null);
}
} catch(e) {
winston.error('Unable to parse Imgur json response. [' + body +']');
callback(e, null);
}
});
var upload = post.form({
post.form({
type: type,
image: image
});
};
imgur.setClientID = function (id) {
clientID = id;
};
}(exports));

View File

@@ -63,13 +63,14 @@ var async = require('async'),
}
if (setupVal && setupVal instanceof Object) {
if (setupVal['admin:username'] && setupVal['admin:password'] && setupVal['admin:email']) {
if (setupVal['admin:username'] && setupVal['admin:password'] && setupVal['admin:password:confirm'] && setupVal['admin:email']) {
install.values = setupVal;
next();
} else {
winston.error('Required values are missing for automated setup:');
if (!setupVal['admin:username']) winston.error(' admin:username');
if (!setupVal['admin:password']) winston.error(' admin:password');
if (!setupVal['admin:password:confirm']) winston.error(' admin:password:confirm');
if (!setupVal['admin:email']) winston.error(' admin:email');
process.exit();
}
@@ -135,8 +136,11 @@ var async = require('async'),
winston.info('Populating database with default configs, if not already set...');
var meta = require('./meta'),
defaults = [{
field: 'title',
value: 'NodeBB'
}, {
field: 'postDelay',
value: 10000
value: 10
}, {
field: 'minimumPostLength',
value: 8
@@ -200,12 +204,9 @@ var async = require('async'),
},
function (next) {
// Categories
var Categories = require('./categories'),
admin = {
categories: require('./admin/categories')
};
var Categories = require('./categories');
Categories.getAllCategories(function (data) {
Categories.getAllCategories(0, function (err, data) {
if (data.categories.length === 0) {
winston.warn('No categories found, populating instance with default categories');
@@ -213,7 +214,7 @@ var async = require('async'),
default_categories = JSON.parse(default_categories);
async.eachSeries(default_categories, function (category, next) {
admin.categories.create(category, next);
Categories.create(category, next);
}, function (err) {
if (!err) {
next();
@@ -249,6 +250,11 @@ var async = require('async'),
}
});
}, next);
},
function (next) {
// Upgrading schema
var Upgrade = require('./upgrade');
Upgrade.upgrade(next);
}
], function (err) {
if (err) {
@@ -274,18 +280,32 @@ var async = require('async'),
description: 'Administrator email address',
pattern: /.+@.+/,
required: true
}, {
}],
passwordQuestions = [{
name: 'password',
description: 'Password',
required: true,
hidden: true,
type: 'string'
}, {
name: 'password:confirm',
description: 'Confirm Password',
required: true,
hidden: true,
type: 'string'
}],
success = function(err, results) {
if (!results) {
return callback(new Error('aborted'));
}
// Check if the passwords match
if (results['password:confirm'] !== results.password) {
winston.warn("Passwords did not match, please try again");
// Re-prompt password questions.
return retryPassword(results);
}
nconf.set('bcrypt_rounds', 12);
User.create(results.username, results.password, results.email, function (err, uid) {
if (err) {
@@ -303,14 +323,33 @@ var async = require('async'),
}
});
});
},
retryPassword = function (originalResults) {
// Ask only the password questions
prompt.get(passwordQuestions, function (err, results) {
if (!results) {
return callback(new Error('aborted'));
}
// Update the original data with newly collected password
originalResults.password = results.password;
originalResults['password:confirm'] = results['password:confirm'];
// Send back to success to handle
success(err, originalResults);
});
};
// Add the password questions
questions = questions.concat(passwordQuestions);
if (!install.values) prompt.get(questions, success);
else {
var results = {
username: install.values['admin:username'],
email: install.values['admin:email'],
password: install.values['admin:password']
password: install.values['admin:password'],
'password:confirm': install.values['admin:password:confirm']
};
success(null, results);

View File

@@ -1,5 +1,6 @@
var utils = require('./../public/src/utils.js'),
RDB = require('./redis.js'),
plugins = require('./plugins'),
async = require('async'),
path = require('path'),
fs = require('fs'),
@@ -102,7 +103,9 @@ var utils = require('./../public/src/utils.js'),
var themeData = {
'theme:type': data.type,
'theme:id': data.id,
'theme:staticDir': ''
'theme:staticDir': '',
'theme:templates': '',
'theme:src': ''
};
switch(data.type) {
@@ -205,44 +208,46 @@ var utils = require('./../public/src/utils.js'),
],
minFile: path.join(__dirname, '..', 'public/src/nodebb.min.js'),
get: function (callback) {
var mtime,
jsPaths = this.scripts.map(function (jsPath) {
return path.join(__dirname, '..', '/public', jsPath);
});
plugins.fireHook('filter:scripts.get', this.scripts, function(err, scripts) {
var mtime,
jsPaths = scripts.map(function (jsPath) {
return path.join(__dirname, '..', '/public', jsPath);
});
if (process.env.NODE_ENV !== 'development') {
async.parallel({
mtime: function (next) {
async.map(jsPaths, fs.stat, function (err, stats) {
async.reduce(stats, 0, function (memo, item, callback) {
mtime = +new Date(item.mtime);
callback(null, mtime > memo ? mtime : memo);
}, next);
});
},
minFile: function (next) {
if (!fs.existsSync(Meta.js.minFile)) {
if (process.env.NODE_ENV === 'development') winston.warn('No minified client-side library found');
return next(null, 0);
if (process.env.NODE_ENV !== 'development') {
async.parallel({
mtime: function (next) {
async.map(jsPaths, fs.stat, function (err, stats) {
async.reduce(stats, 0, function (memo, item, callback) {
mtime = +new Date(item.mtime);
callback(null, mtime > memo ? mtime : memo);
}, next);
});
},
minFile: function (next) {
if (!fs.existsSync(Meta.js.minFile)) {
if (process.env.NODE_ENV === 'development') winston.warn('No minified client-side library found');
return next(null, 0);
}
fs.stat(Meta.js.minFile, function (err, stat) {
next(err, +new Date(stat.mtime));
});
}
fs.stat(Meta.js.minFile, function (err, stat) {
next(err, +new Date(stat.mtime));
});
}
}, function (err, results) {
if (results.minFile > results.mtime) {
if (process.env.NODE_ENV === 'development') winston.info('No changes to client-side libraries -- skipping minification');
callback(null, [path.relative(path.join(__dirname, '../public'), Meta.js.minFile)]);
} else {
Meta.js.minify(function () {
}, function (err, results) {
if (results.minFile > results.mtime) {
if (process.env.NODE_ENV === 'development') winston.info('No changes to client-side libraries -- skipping minification');
callback(null, [path.relative(path.join(__dirname, '../public'), Meta.js.minFile)]);
});
}
});
} else {
callback(null, this.scripts);
}
} else {
Meta.js.minify(function () {
callback(null, [path.relative(path.join(__dirname, '../public'), Meta.js.minFile)]);
});
}
});
} else {
callback(null, scripts);
}
});
},
minify: function (callback) {
var uglifyjs = require('uglify-js'),

View File

@@ -1,146 +1,158 @@
var RDB = require('./redis.js'),
var RDB = require('./redis'),
async = require('async'),
utils = require('../public/src/utils.js'),
utils = require('../public/src/utils'),
winston = require('winston'),
cron = require('cron').CronJob,
notifications = {
init: function() {
if (process.env.NODE_ENV === 'development') {
winston.info('[notifications.init] Registering jobs.');
}
websockets = require('./websockets');
new cron('0 0 * * *', notifications.prune, null, true);
},
get: function(nid, uid, callback) {
RDB.multi()
.hmget('notifications:' + nid, 'text', 'score', 'path', 'datetime', 'uniqueId')
.zrank('uid:' + uid + ':notifications:read', nid)
.exists('notifications:' + nid)
.exec(function(err, results) {
var notification = results[0],
readIdx = results[1];
if (!results[2]) {
return callback(null);
}
callback({
nid: nid,
text: notification[0],
score: notification[1],
path: notification[2],
datetime: notification[3],
uniqueId: notification[4],
read: readIdx !== null ? true : false
});
});
},
create: function(text, path, uniqueId, callback) {
/**
* uniqueId is used solely to override stale nids.
* If a new nid is pushed to a user and an existing nid in the user's
* (un)read list contains the same uniqueId, it will be removed, and
* the new one put in its place.
*/
RDB.incr('notifications:next_nid', function(err, nid) {
RDB.sadd('notifications', nid);
RDB.hmset('notifications:' + nid, {
text: text || '',
path: path || null,
datetime: Date.now(),
uniqueId: uniqueId || utils.generateUUID()
}, function(err, status) {
if (!err) {
callback(nid);
}
(function(Notifications) {
Notifications.init = function() {
if (process.env.NODE_ENV === 'development') {
winston.info('[notifications.init] Registering jobs.');
}
new cron('0 0 * * *', Notifications.prune, null, true);
};
Notifications.get = function(nid, uid, callback) {
RDB.multi()
.hmget('notifications:' + nid, 'text', 'score', 'path', 'datetime', 'uniqueId')
.zrank('uid:' + uid + ':notifications:read', nid)
.exists('notifications:' + nid)
.exec(function(err, results) {
var notification = results[0],
readIdx = results[1];
if (!results[2]) {
return callback(null);
}
callback({
nid: nid,
text: notification[0],
score: notification[1],
path: notification[2],
datetime: notification[3],
uniqueId: notification[4],
read: readIdx !== null ? true : false
});
});
},
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) {
if (!Array.isArray(uids)) uids = [uids];
var numUids = uids.length,
x;
notifications.get(nid, null, function(notif_data) {
for (x = 0; x < numUids; x++) {
if (parseInt(uids[x], 10) > 0) {
(function(uid) {
notifications.remove_by_uniqueId(notif_data.uniqueId, uid, function() {
RDB.zadd('uid:' + uid + ':notifications:unread', notif_data.datetime, nid);
global.io.sockets.in('uid_' + uid).emit('event:new_notification');
if (callback) {
callback(true);
}
});
})(uids[x]);
}
}
});
},
remove_by_uniqueId: function(uniqueId, uid, callback) {
async.parallel([
function(next) {
RDB.zrange('uid:' + uid + ':notifications:unread', 0, -1, function(err, nids) {
if (nids && nids.length > 0) {
async.each(nids, function(nid, next) {
notifications.get(nid, uid, function(nid_info) {
if (nid_info.uniqueId === uniqueId) {
RDB.zrem('uid:' + uid + ':notifications:unread', nid);
}
next();
});
}, function(err) {
next();
});
} else {
next();
}
});
},
function(next) {
RDB.zrange('uid:' + uid + ':notifications:read', 0, -1, function(err, nids) {
if (nids && nids.length > 0) {
async.each(nids, function(nid, next) {
notifications.get(nid, uid, function(nid_info) {
if (nid_info && nid_info.uniqueId === uniqueId) {
RDB.zrem('uid:' + uid + ':notifications:read', nid);
}
next();
});
}, function(err) {
next();
});
} else {
next();
}
});
}
], function(err) {
};
Notifications.create = function(text, path, uniqueId, callback) {
/**
* uniqueId is used solely to override stale nids.
* If a new nid is pushed to a user and an existing nid in the user's
* (un)read list contains the same uniqueId, it will be removed, and
* the new one put in its place.
*/
RDB.incr('notifications:next_nid', function(err, nid) {
RDB.sadd('notifications', nid);
RDB.hmset('notifications:' + nid, {
text: text || '',
path: path || null,
datetime: Date.now(),
uniqueId: uniqueId || utils.generateUUID()
}, function(err, status) {
if (!err) {
callback(true);
callback(nid);
}
});
},
mark_read: function(nid, uid, callback) {
});
};
function destroy(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);
}
});
}
Notifications.push = function(nid, uids, callback) {
if (!Array.isArray(uids)) uids = [uids];
var numUids = uids.length,
x;
Notifications.get(nid, null, function(notif_data) {
for (x = 0; x < numUids; x++) {
if (parseInt(uids[x], 10) > 0) {
(function(uid) {
remove_by_uniqueId(notif_data.uniqueId, uid, function() {
RDB.zadd('uid:' + uid + ':notifications:unread', notif_data.datetime, nid);
websockets.in('uid_' + uid).emit('event:new_notification');
if (callback) {
callback(true);
}
});
})(uids[x]);
}
}
});
};
function remove_by_uniqueId(uniqueId, uid, callback) {
async.parallel([
function(next) {
RDB.zrange('uid:' + uid + ':notifications:unread', 0, -1, function(err, nids) {
if (nids && nids.length > 0) {
async.each(nids, function(nid, next) {
Notifications.get(nid, uid, function(nid_info) {
if (nid_info.uniqueId === uniqueId) {
RDB.zrem('uid:' + uid + ':notifications:unread', nid);
}
next();
});
}, function(err) {
next();
});
} else {
next();
}
});
},
function(next) {
RDB.zrange('uid:' + uid + ':notifications:read', 0, -1, function(err, nids) {
if (nids && nids.length > 0) {
async.each(nids, function(nid, next) {
Notifications.get(nid, uid, function(nid_info) {
if (nid_info && nid_info.uniqueId === uniqueId) {
RDB.zrem('uid:' + uid + ':notifications:read', nid);
}
next();
});
}, function(err) {
next();
});
} else {
next();
}
});
}
], function(err) {
if (!err) {
callback(true);
}
});
}
Notifications.mark_read = function(nid, uid, callback) {
if (parseInt(uid) > 0) {
notifications.get(nid, uid, function(notif_data) {
Notifications.get(nid, uid, function(notif_data) {
RDB.zrem('uid:' + uid + ':notifications:unread', nid);
RDB.zadd('uid:' + uid + ':notifications:read', notif_data.datetime, nid);
if (callback) {
@@ -148,121 +160,115 @@ var RDB = require('./redis.js'),
}
});
}
},
mark_read_multiple: function(nids, uid, callback) {
if (!Array.isArray(nids) && parseInt(nids, 10) > 0) {
nids = [nids];
}
Notifications.mark_read_multiple = function(nids, uid, callback) {
if (!Array.isArray(nids) && parseInt(nids, 10) > 0) {
nids = [nids];
}
async.each(nids, function(nid, next) {
Notifications.mark_read(nid, uid, function(err) {
if (!err) {
next(null);
}
});
}, function(err) {
if (callback) {
callback(err);
}
});
};
Notifications.mark_all_read = function(uid, callback) {
RDB.zrange('uid:' + uid + ':notifications:unread', 0, 10, function(err, nids) {
if (err) {
return callback(err);
}
async.each(nids, function(nid, next) {
notifications.mark_read(nid, uid, function(err) {
if (!err) {
next(null);
}
});
}, function(err) {
if (callback) {
if (nids.length > 0) {
Notifications.mark_read_multiple(nids, uid, function(err) {
callback(err);
}
});
},
mark_all_read: function(uid, callback) {
RDB.zrange('uid:' + uid + ':notifications:unread', 0, 10, function(err, nids) {
if (err) {
return callback(err);
}
});
} else {
callback();
}
});
};
Notifications.prune = function(cutoff) {
if (process.env.NODE_ENV === 'development') {
winston.info('[notifications.prune] Removing expired notifications from the database.');
}
if (nids.length > 0) {
notifications.mark_read_multiple(nids, uid, function(err) {
callback(err);
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);
});
} else {
callback();
}
});
},
prune: function(cutoff) {
if (process.env.NODE_ENV === 'development') {
winston.info('[notifications.prune] Removing expired notifications from the database.');
});
}
}, function(err, results) {
if (!err) {
var numInboxes = results.inboxes.length,
x;
var today = new Date(),
numPruned = 0;
async.eachSeries(results.nids, function(nid, next) {
var multi = RDB.multi();
if (!cutoff) {
cutoff = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 7);
}
for(x=0;x<numInboxes;x++) {
multi.zscore(results.inboxes[x], nid);
}
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);
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;
}
});
}, 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);
if (expired) {
destroy(nid);
numPruned++;
}
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.');
}
next();
});
} else {
}, function(err) {
if (process.env.NODE_ENV === 'development') {
winston.error('[notifications.prune] Ran into trouble pruning expired notifications. Stack trace to follow.');
winston.error(err.stack);
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);
}
});
}
}
});
};
}(exports));
module.exports = {
init: notifications.init,
get: notifications.get,
create: notifications.create,
push: notifications.push,
mark_read: notifications.mark_read_multiple,
mark_all_read: notifications.mark_all_read,
prune: notifications.prune
};

View File

@@ -284,14 +284,14 @@ var fs = require('fs'),
winston.warn("Plugin: " + file + " is corrupted or invalid. Please check plugin.json for errors.")
return next(err, null);
}
_self.isActive(config.id, function(err, active) {
if (err) next(new Error('no-active-state'));
delete config.library;
delete config.hooks;
config.active = active;
config.activeText = '<i class="icon-off"></i> ' + (active ? 'Dea' : 'A') + 'ctivate';
config.activeText = '<i class="fa fa-power-off"></i> ' + (active ? 'Dea' : 'A') + 'ctivate';
next(null, config);
});
}

View File

@@ -1,8 +1,9 @@
var RDB = require('./redis.js'),
posts = require('./posts.js'),
var RDB = require('./redis'),
posts = require('./posts'),
topics = require('./topics'),
threadTools = require('./threadTools.js'),
user = require('./user.js'),
threadTools = require('./threadTools'),
user = require('./user'),
websockets = require('./websockets'),
async = require('async'),
nconf = require('nconf'),
validator = require('validator'),
@@ -13,7 +14,7 @@ var RDB = require('./redis.js'),
postSearch = reds.createSearch('nodebbpostsearch'),
topicSearch = reds.createSearch('nodebbtopicsearch'),
winston = require('winston'),
meta = require('./meta.js'),
meta = require('./meta'),
Feed = require('./feed');
(function(PostTools) {
@@ -34,7 +35,7 @@ var RDB = require('./redis.js'),
}
function getThreadPrivileges(next) {
posts.getPostField(pid, 'tid', function(tid) {
posts.getPostField(pid, 'tid', function(err, tid) {
threadTools.privileges(tid, uid, function(privileges) {
next(null, privileges);
});
@@ -42,7 +43,7 @@ var RDB = require('./redis.js'),
}
function isOwnPost(next) {
posts.getPostField(pid, 'uid', function(author) {
posts.getPostField(pid, 'uid', function(err, author) {
next(null, parseInt(author, 10) === parseInt(uid, 10));
});
}
@@ -87,7 +88,7 @@ var RDB = require('./redis.js'),
async.parallel([
function(next) {
posts.getPostField(pid, 'tid', function(tid) {
posts.getPostField(pid, 'tid', function(err, tid) {
PostTools.isMain(pid, tid, function(isMainPost) {
if (isMainPost) {
topics.setTopicField(tid, 'title', title);
@@ -107,7 +108,7 @@ var RDB = require('./redis.js'),
PostTools.parse(content, next);
}
], function(err, results) {
io.sockets.in('topic_' + results[0].tid).emit('event:post_edited', {
websockets.in('topic_' + results[0].tid).emit('event:post_edited', {
pid: pid,
title: validator.sanitize(title).escape(),
isMainPost: results[0].isMainPost,
@@ -132,41 +133,47 @@ var RDB = require('./redis.js'),
RDB.decr('totalpostcount');
postSearch.remove(pid);
posts.getPostFields(pid, ['tid', 'uid'], function(postData) {
posts.getPostFields(pid, ['tid', 'uid'], function(err, postData) {
RDB.hincrby('topic:' + postData.tid, 'postcount', -1);
user.decrementUserFieldBy(postData.uid, 'postcount', 1, function(err, postcount) {
RDB.zadd('users:postcount', postcount, postData.uid);
});
io.sockets. in ('topic_' + postData.tid).emit('event:post_deleted', {
pid: pid
});
// Delete the thread if it is the last undeleted post
threadTools.getLatestUndeletedPid(postData.tid, function(err, pid) {
if (err && err.message === 'no-undeleted-pids-found') {
threadTools.delete(postData.tid, -1, function(err) {
if (err) winston.error('Could not delete topic (tid: ' + postData.tid + ')', err.stack);
threadTools.delete(postData.tid, function(err) {
if (err) {
winston.error('Could not delete topic (tid: ' + postData.tid + ')', err.stack);
}
});
} else {
posts.getPostField(pid, 'timestamp', function(timestamp) {
posts.getPostField(pid, 'timestamp', function(err, timestamp) {
topics.updateTimestamp(postData.tid, timestamp);
});
}
});
Feed.updateTopic(postData.tid);
Feed.updateRecent();
callback();
callback(null);
});
};
PostTools.privileges(pid, uid, function(privileges) {
if (privileges.editable) {
success();
posts.getPostField(pid, 'deleted', function(err, deleted) {
if(deleted === '1') {
return callback(new Error('Post already deleted!'));
}
PostTools.privileges(pid, uid, function(privileges) {
if (privileges.editable) {
success();
}
});
});
}
PostTools.restore = function(uid, pid, callback) {
@@ -174,17 +181,13 @@ var RDB = require('./redis.js'),
posts.setPostField(pid, 'deleted', 0);
RDB.incr('totalpostcount');
posts.getPostFields(pid, ['tid', 'uid', 'content'], function(postData) {
posts.getPostFields(pid, ['tid', 'uid', 'content'], function(err, postData) {
RDB.hincrby('topic:' + postData.tid, 'postcount', 1);
user.incrementUserFieldBy(postData.uid, 'postcount', 1);
io.sockets. in ('topic_' + postData.tid).emit('event:post_restored', {
pid: pid
});
threadTools.getLatestUndeletedPid(postData.tid, function(err, pid) {
posts.getPostField(pid, 'timestamp', function(timestamp) {
posts.getPostField(pid, 'timestamp', function(err, timestamp) {
topics.updateTimestamp(postData.tid, timestamp);
});
});
@@ -197,6 +200,7 @@ var RDB = require('./redis.js'),
});
Feed.updateTopic(postData.tid);
Feed.updateRecent();
postSearch.index(postData.content, pid);
@@ -204,10 +208,16 @@ var RDB = require('./redis.js'),
});
};
PostTools.privileges(pid, uid, function(privileges) {
if (privileges.editable) {
success();
posts.getPostField(pid, 'deleted', function(err, deleted) {
if(deleted === '0') {
return callback(new Error('Post already restored'));
}
PostTools.privileges(pid, uid, function(privileges) {
if (privileges.editable) {
success();
}
});
});
}

View File

@@ -2,6 +2,7 @@ var RDB = require('./redis.js'),
utils = require('./../public/src/utils.js'),
user = require('./user.js'),
topics = require('./topics.js'),
categories = require('./categories.js'),
favourites = require('./favourites.js'),
threadTools = require('./threadTools.js'),
postTools = require('./postTools'),
@@ -13,11 +14,175 @@ var RDB = require('./redis.js'),
postSearch = reds.createSearch('nodebbpostsearch'),
nconf = require('nconf'),
meta = require('./meta.js'),
validator = require('validator'),
winston = require('winston');
(function(Posts) {
var customUserInfo = {};
Posts.create = function(uid, tid, content, callback) {
if (uid === null) {
callback(new Error('invalid-user'), null);
return;
}
topics.isLocked(tid, function(err, locked) {
if(err) {
return callback(err, null);
} else if(locked) {
callback(new Error('topic-locked'), null);
}
RDB.incr('global:next_post_id', function(err, pid) {
if(err) {
return callback(err, null);
}
plugins.fireHook('filter:post.save', content, function(err, newContent) {
if(err) {
return callback(err, null);
}
content = newContent;
var timestamp = Date.now(),
postData = {
'pid': pid,
'uid': uid,
'tid': tid,
'content': content,
'timestamp': timestamp,
'reputation': 0,
'editor': '',
'edited': 0,
'deleted': 0,
'fav_button_class': '',
'fav_star_class': 'fa-star-o',
'show_banned': 'hide',
'relativeTime': new Date(timestamp).toISOString(),
'post_rep': '0',
'edited-class': 'none',
'relativeEditTime': ''
};
RDB.hmset('post:' + pid, postData);
topics.addPostToTopic(tid, pid);
topics.increasePostCount(tid);
topics.updateTimestamp(tid, timestamp);
RDB.incr('totalpostcount');
topics.getTopicFields(tid, ['cid', 'pinned'], function(err, topicData) {
RDB.handle(err);
var cid = topicData.cid;
feed.updateTopic(tid);
feed.updateRecent();
RDB.zadd('categories:recent_posts:cid:' + cid, timestamp, pid);
if(topicData.pinned === '0') {
RDB.zadd('categories:' + cid + ':tid', timestamp, tid);
}
RDB.scard('cid:' + cid + ':active_users', function(err, amount) {
if (amount > 16) {
RDB.spop('cid:' + cid + ':active_users');
}
categories.addActiveUser(cid, uid);
});
});
user.onNewPostMade(uid, tid, pid, timestamp);
plugins.fireHook('filter:post.get', postData, function(err, newPostData) {
if(err) {
return callback(err, null);
}
postData = newPostData;
postTools.parse(postData.content, function(err, content) {
if(err) {
return callback(err, null);
}
postData.content = content;
plugins.fireHook('action:post.save', postData);
postSearch.index(content, pid);
callback(null, postData);
});
});
});
});
});
};
Posts.reply = function(tid, uid, content, callback) {
if(content) {
content = content.trim();
}
if (!content || content.length < meta.config.minimumPostLength) {
callback(new Error('content-too-short'), null);
return;
}
Posts.create(uid, tid, content, function(err, postData) {
if(err) {
return callback(err, null);
} else if(!postData) {
callback(new Error('reply-error'), null);
}
async.parallel([
function(next) {
topics.markUnRead(tid, function(err) {
if(err) {
return next(err);
}
topics.markAsRead(tid, uid);
next();
});
},
function(next) {
Posts.getCidByPid(postData.pid, function(err, cid) {
if(err) {
return next(err);
}
RDB.del('cid:' + cid + ':read_by_uid');
next();
});
},
function(next) {
threadTools.notifyFollowers(tid, uid);
next();
},
function(next) {
Posts.addUserInfoToPost(postData, function(err) {
if(err) {
return next(err);
}
next();
});
}
], function(err, results) {
if(err) {
return callback(err, null);
}
callback(null, postData);
});
});
}
Posts.getPostsByTid = function(tid, start, end, callback) {
RDB.lrange('tid:' + tid + ':posts', start, end, function(err, pids) {
RDB.handle(err);
@@ -86,7 +251,7 @@ var RDB = require('./redis.js'),
function getPostSummary(pid, callback) {
async.waterfall([
function(next) {
Posts.getPostFields(pid, ['pid', 'tid', 'content', 'uid', 'timestamp', 'deleted'], function(postData) {
Posts.getPostFields(pid, ['pid', 'tid', 'content', 'uid', 'timestamp', 'deleted'], function(err, postData) {
if (postData.deleted === '1') return callback(null);
else {
postData.relativeTime = new Date(parseInt(postData.timestamp || 0, 10)).toISOString();
@@ -100,12 +265,15 @@ var RDB = require('./redis.js'),
});
},
function(postData, next) {
topics.getTopicFields(postData.tid, ['slug', 'deleted'], function(err, topicData) {
topics.getTopicFields(postData.tid, ['title', 'cid', 'slug', 'deleted'], function(err, topicData) {
if (err) return callback(err);
else if (topicData.deleted === '1') return callback(null);
postData.topicSlug = topicData.slug;
next(null, postData);
categories.getCategoryField(topicData.cid, 'name', function(err, categoryData) {
postData.category_name = categoryData;
postData.title = validator.sanitize(topicData.title).escape();
postData.topicSlug = topicData.slug;
next(null, postData);
})
});
},
function(postData, next) {
@@ -131,51 +299,48 @@ var RDB = require('./redis.js'),
});
};
// TODO: this function is never called except from some debug route. clean up?
Posts.getPostData = function(pid, callback) {
RDB.hgetall('post:' + pid, function(err, data) {
if (err === null) {
plugins.fireHook('filter:post.get', data, function(err, newData) {
if (!err) callback(newData);
else callback(data);
});
} else {
winston.error(err);
if(err) {
return callback(err, null);
}
plugins.fireHook('filter:post.get', data, function(err, newData) {
if(err) {
return callback(err, null);
}
callback(null, newData);
});
});
}
Posts.getPostFields = function(pid, fields, callback) {
RDB.hmgetObject('post:' + pid, fields, function(err, data) {
if (err === null) {
// TODO: I think the plugins system needs an optional 'parameters' paramter so I don't have to do this:
data = data || {};
data.pid = pid;
data.fields = fields;
plugins.fireHook('filter:post.getFields', data, function(err, data) {
callback(data);
});
} else {
console.log(err);
if(err) {
return callback(err, null);
}
// TODO: I think the plugins system needs an optional 'parameters' paramter so I don't have to do this:
data = data || {};
data.pid = pid;
data.fields = fields;
plugins.fireHook('filter:post.getFields', data, function(err, data) {
if(err) {
return callback(err, null);
}
callback(null, data);
});
});
}
Posts.getPostField = function(pid, field, callback) {
RDB.hget('post:' + pid, field, function(err, data) {
if (err === null) {
// TODO: I think the plugins system needs an optional 'parameters' paramter so I don't have to do this:
data = data || {};
data.pid = pid;
data.field = field;
plugins.fireHook('filter:post.getField', data, function(err, data) {
callback(data);
});
} else {
console.log(err);
Posts.getPostFields(pid, [field], function(err, data) {
if(err) {
return callback(err, null);
}
callback(null, data[field]);
});
}
@@ -192,8 +357,8 @@ var RDB = require('./redis.js'),
var posts = [],
multi = RDB.multi();
for(var x=0,numPids=pids.length;x<numPids;x++) {
multi.hgetall("post:"+pids[x]);
for(var x=0, numPids=pids.length; x<numPids; x++) {
multi.hgetall("post:" + pids[x]);
}
multi.exec(function (err, replies) {
@@ -204,12 +369,11 @@ var RDB = require('./redis.js'),
postData['edited-class'] = postData.editor !== '' ? '' : 'none';
try {
postData.relativeTime = new Date(parseInt(postData.timestamp,10)).toISOString();
postData['relativeEditTime'] = postData.edited !== '0' ? (new Date(parseInt(postData.edited,10)).toISOString()) : '';
postData.relativeEditTime = postData.edited !== '0' ? (new Date(parseInt(postData.edited,10)).toISOString()) : '';
} catch(e) {
winston.err('invalid time value');
}
if (postData.uploadedImages) {
try {
postData.uploadedImages = JSON.parse(postData.uploadedImages);
@@ -238,17 +402,23 @@ var RDB = require('./redis.js'),
})
}
Posts.get_cid_by_pid = function(pid, callback) {
Posts.getPostField(pid, 'tid', function(tid) {
if (tid) {
topics.getTopicField(tid, 'cid', function(err, cid) {
if (cid) {
callback(cid);
} else {
callback(false);
}
});
Posts.getCidByPid = function(pid, callback) {
Posts.getPostField(pid, 'tid', function(err, tid) {
if(err) {
return callback(err, null);
}
topics.getTopicField(tid, 'cid', function(err, cid) {
if(err) {
return callback(err, null);
}
if (cid) {
callback(null, cid);
} else {
callback(new Error('invalid-category-id'), null);
}
});
});
}
@@ -265,163 +435,25 @@ var RDB = require('./redis.js'),
Posts.emitTooManyPostsAlert = function(socket) {
socket.emit('event:alert', {
title: 'Too many posts!',
message: 'You can only post every ' + meta.config.postDelay / 1000 + ' seconds.',
message: 'You can only post every ' + meta.config.postDelay + ' seconds.',
type: 'danger',
timeout: 2000
});
}
Posts.reply = function(tid, uid, content, callback) {
if(content) {
content = content.trim();
}
if (!content || content.length < meta.config.minimumPostLength) {
callback(new Error('content-too-short'), null);
return;
}
Posts.create(uid, tid, content, function(postData) {
if (postData) {
topics.markUnRead(tid);
Posts.get_cid_by_pid(postData.pid, function(cid) {
RDB.del('cid:' + cid + ':read_by_uid', function(err, data) {
topics.markAsRead(tid, uid);
});
});
threadTools.notifyFollowers(tid, uid);
Posts.addUserInfoToPost(postData, function() {
var socketData = {
posts: [postData]
};
io.sockets.in('topic_' + tid).emit('event:new_post', socketData);
io.sockets.in('recent_posts').emit('event:new_post', socketData);
io.sockets.in('user/' + uid).emit('event:new_post', socketData);
});
callback(null, 'Reply successful');
} else {
callback(new Error('reply-error'), null);
}
});
}
Posts.create = function(uid, tid, content, callback) {
if (uid === null) {
callback(null);
return;
}
topics.isLocked(tid, function(locked) {
if (!locked || locked === '0') {
RDB.incr('global:next_post_id', function(err, pid) {
RDB.handle(err);
plugins.fireHook('filter:post.save', content, function(err, newContent) {
if (!err) content = newContent;
var timestamp = Date.now(),
postData = {
'pid': pid,
'uid': uid,
'tid': tid,
'content': content,
'timestamp': timestamp,
'reputation': 0,
'editor': '',
'edited': 0,
'deleted': 0,
'fav_button_class': '',
'fav_star_class': 'icon-star-empty',
'show_banned': 'hide',
'relativeTime': new Date(timestamp).toISOString(),
'post_rep': '0',
'edited-class': 'none',
'relativeEditTime': ''
};
RDB.hmset('post:' + pid, postData);
topics.addPostToTopic(tid, pid);
topics.increasePostCount(tid);
topics.updateTimestamp(tid, timestamp);
RDB.incr('totalpostcount');
topics.getTopicFields(tid, ['cid', 'pinned'], function(err, topicData) {
RDB.handle(err);
var cid = topicData.cid;
feed.updateTopic(tid);
RDB.zadd('categories:recent_posts:cid:' + cid, timestamp, pid);
if(topicData.pinned === '0')
RDB.zadd('categories:' + cid + ':tid', timestamp, tid);
RDB.scard('cid:' + cid + ':active_users', function(err, amount) {
if (amount > 10) {
RDB.spop('cid:' + cid + ':active_users');
}
categories.addActiveUser(cid, uid);
});
});
user.onNewPostMade(uid, tid, pid, timestamp);
async.parallel({
content: function(next) {
plugins.fireHook('filter:post.get', postData, function(err, newPostData) {
if (!err) postData = newPostData;
postTools.parse(postData.content, function(err, content) {
next(null, content);
});
});
}
}, function(err, results) {
postData.content = results.content;
callback(postData);
});
plugins.fireHook('action:post.save', postData);
postSearch.index(content, pid);
});
});
} else {
callback(null);
}
});
}
Posts.uploadPostImage = function(image, callback) {
var imgur = require('./imgur');
imgur.setClientID(meta.config.imgurClientID);
if(!image)
return callback('invalid image', null);
imgur.upload(image.data, 'base64', function(err, data) {
require('./imgur').upload(meta.config.imgurClientID, image.data, 'base64', function(err, data) {
if(err) {
callback('Can\'t upload image!', null);
callback(err.message, null);
} else {
if(data.success) {
var img= {url:data.data.link, name:image.name};
callback(null, img);
} else {
winston.error('Can\'t upload image, did you set imgurClientID?');
callback("upload error", null);
}
callback(null, {
url: data.link,
name: image.name
});
}
});
}
@@ -444,25 +476,12 @@ var RDB = require('./redis.js'),
});
}
Posts.getTopicPostStats = function() {
RDB.mget(['totaltopiccount', 'totalpostcount'], function(err, data) {
if (err === null) {
var stats = {
topics: data[0] ? data[0] : 0,
posts: data[1] ? data[1] : 0
};
io.sockets.emit('post.stats', stats);
} else
console.log(err);
});
}
Posts.reIndexPids = function(pids, callback) {
function reIndex(pid, callback) {
Posts.getPostField(pid, 'content', function(content) {
Posts.getPostField(pid, 'content', function(err, content) {
postSearch.remove(pid, function() {
if (content && content.length) {

View File

@@ -58,18 +58,18 @@
*/
RedisDB.hmgetObject = function(key, fields, callback) {
RedisDB.hmget(key, fields, function(err, data) {
if (err === null) {
var returnData = {};
for (var i = 0, ii = fields.length; i < ii; ++i) {
returnData[fields[i]] = data[i];
}
callback(null, returnData);
} else {
console.log(err);
callback(err, null);
if(err) {
return callback(err, null);
}
var returnData = {};
for (var i = 0, ii = fields.length; i < ii; ++i) {
returnData[fields[i]] = data[i];
}
callback(null, returnData);
});
};

View File

@@ -19,21 +19,27 @@ var user = require('./../user.js'),
});
}
Admin.build_header = function (res, callback) {
Admin.buildHeader = function (req, res, callback) {
var custom_header = {
'plugins': []
};
plugins.fireHook('filter:admin.header.build', custom_header, function(err, custom_header) {
callback(err, templates['admin/header'].parse({
csrf: res.locals.csrf_token,
relative_path: nconf.get('relative_path'),
plugins: custom_header.plugins
}));
user.getUserFields(req.user.uid, ['username', 'userslug', 'picture'], function(err, userData) {
plugins.fireHook('filter:admin.header.build', custom_header, function(err, custom_header) {
callback(err, templates['admin/header'].parse({
csrf: res.locals.csrf_token,
relative_path: nconf.get('relative_path'),
plugins: custom_header.plugins,
userpicture: userData.picture,
username: userData.username,
userslug: userData.userslug
}));
});
});
}
Admin.create_routes = function (app) {
Admin.createRoutes = function (app) {
(function () {
var routes = [
@@ -46,7 +52,7 @@ var user = require('./../user.js'),
for (var i = 0, ii = routes.length; i < ii; i++) {
(function (route) {
app.get('/admin/' + route, Admin.isAdmin, function (req, res) {
Admin.build_header(res, function(err, header) {
Admin.buildHeader(req, res, function(err, header) {
res.send(header + app.create_route('admin/' + route) + templates['admin/footer']);
});
});
@@ -58,7 +64,7 @@ var user = require('./../user.js'),
for (var i = 0, ii = unit_tests.length; i < ii; i++) {
(function (route) {
app.get('/admin/testing/' + route, Admin.isAdmin, function (req, res) {
Admin.build_header(res, function(err, header) {
Admin.buildHeader(req, res, function(err, header) {
res.send(header + app.create_route('admin/testing/' + route) + templates['admin/footer']);
});
});
@@ -69,13 +75,13 @@ var user = require('./../user.js'),
app.namespace('/admin', function () {
app.get('/', Admin.isAdmin, function (req, res) {
Admin.build_header(res, function(err, header) {
Admin.buildHeader(req, res, function(err, header) {
res.send(header + app.create_route('admin/index') + templates['admin/footer']);
});
});
app.get('/index', Admin.isAdmin, function (req, res) {
Admin.build_header(res, function(err, header) {
Admin.buildHeader(req, res, function(err, header) {
res.send(header + app.create_route('admin/index') + templates['admin/footer']);
});
});
@@ -105,7 +111,7 @@ var user = require('./../user.js'),
}
var filename = 'site-logo' + extension;
var uploadPath = path.join(process.cwd(), nconf.get('upload_path'), filename);
var uploadPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), filename);
winston.info('Attempting upload to: ' + uploadPath);
@@ -144,7 +150,7 @@ var user = require('./../user.js'),
(function(route) {
app[routes[route].method || 'get']('/admin' + routes[route].route, function(req, res) {
routes[route].options(req, res, function(options) {
Admin.build_header(res, function (err, header) {
Admin.buildHeader(req, res, function (err, header) {
res.send(header + options.content + templates['admin/footer']);
});
});
@@ -158,7 +164,7 @@ var user = require('./../user.js'),
app.namespace('/api/admin', function () {
app.get('/index', function (req, res) {
res.json({
version: pkg.version
version: pkg.version,
});
});
@@ -214,13 +220,13 @@ var user = require('./../user.js'),
});
app.get('/categories', function (req, res) {
categories.getAllCategories(function (data) {
categories.getAllCategories(0, function (err, data) {
res.json(data);
});
});
app.get('/categories/active', function (req, res) {
categories.getAllCategories(function (data) {
categories.getAllCategories(0, function (err, data) {
data.categories = data.categories.filter(function (category) {
return (!category.disabled || category.disabled === "0");
});
@@ -229,7 +235,7 @@ var user = require('./../user.js'),
});
app.get('/categories/disabled', function (req, res) {
categories.getAllCategories(function (data) {
categories.getAllCategories(0, function (err, data) {
data.categories = data.categories.filter(function (category) {
return category.disabled === "1";
});
@@ -238,9 +244,10 @@ var user = require('./../user.js'),
});
app.get('/topics', function (req, res) {
topics.getAllTopics(10, null, function (topics) {
topics.getAllTopics(10, null, function (err, topics) {
res.json({
topics: topics
topics: topics,
notopics: topics.length === 0
});
});
});

View File

@@ -13,7 +13,7 @@ var user = require('./../user.js'),
(function (Api) {
Api.create_routes = function (app) {
Api.createRoutes = function (app) {
app.namespace('/api', function () {
app.get('/get_templates_listing', function (req, res) {
utils.walk(path.join(__dirname, '../../', 'public/templates'), function (err, data) {
@@ -32,13 +32,14 @@ var user = require('./../user.js'),
config.maximumUsernameLength = meta.config.maximumUsernameLength;
config.minimumPasswordLength = meta.config.minimumPasswordLength;
config.useOutgoingLinksPage = meta.config.useOutgoingLinksPage;
config.emailSetup = !!meta.config['email:from'];
res.json(200, config);
});
app.get('/home', function (req, res) {
var uid = (req.user) ? req.user.uid : 0;
categories.getAllCategories(function (data) {
categories.getAllCategories(uid, function (err, data) {
data.categories = data.categories.filter(function (category) {
return (!category.disabled || category.disabled === "0");
});
@@ -55,11 +56,10 @@ var user = require('./../user.js'),
data.motd_class = (meta.config.show_motd === '1' || meta.config.show_motd === undefined) ? '' : ' none';
data.motd_class += (meta.config.motd && meta.config.motd.length > 0 ? '' : ' default');
data.motd = require('marked')(meta.config.motd || "<div class=\"pull-right btn-group\"><a target=\"_blank\" href=\"http://www.nodebb.org\" class=\"btn btn-default btn-lg\"><i class=\"icon-comment\"></i><span class='hidden-mobile'>&nbsp;Get NodeBB</span></a> <a target=\"_blank\" href=\"https://github.com/designcreateplay/NodeBB\" class=\"btn btn-default btn-lg\"><i class=\"icon-github-alt\"></i><span class='hidden-mobile'>&nbsp;Fork us on Github</span></a> <a target=\"_blank\" href=\"https://twitter.com/dcplabs\" class=\"btn btn-default btn-lg\"><i class=\"icon-twitter\"></i><span class='hidden-mobile'>&nbsp;@dcplabs</span></a></div>\n\n# NodeBB <span>v" + pkg.version + "</span>\nWelcome to NodeBB, the discussion platform of the future.");
data.motd = require('marked')(meta.config.motd || "<div class=\"pull-right btn-group\"><a target=\"_blank\" href=\"http://www.nodebb.org\" class=\"btn btn-default btn-lg\"><i class=\"fa fa-comment\"></i><span class='hidden-mobile'>&nbsp;Get NodeBB</span></a> <a target=\"_blank\" href=\"https://github.com/designcreateplay/NodeBB\" class=\"btn btn-default btn-lg\"><i class=\"fa fa-github\"></i><span class='hidden-mobile'>&nbsp;Fork us on Github</span></a> <a target=\"_blank\" href=\"https://twitter.com/dcplabs\" class=\"btn btn-default btn-lg\"><i class=\"fa fa-twitter\"></i><span class='hidden-mobile'>&nbsp;@dcplabs</span></a></div>\n\n# NodeBB <span>v" + pkg.version + "</span>\nWelcome to NodeBB, the discussion platform of the future.");
res.json(data);
});
}, uid);
});
});
app.get('/login', function (req, res) {
@@ -152,8 +152,12 @@ var user = require('./../user.js'),
app.get('/recent/:term?', function (req, res) {
var uid = (req.user) ? req.user.uid : 0;
topics.getLatestTopics(uid, 0, 19, req.params.term, function (data) {
res.json(data);
topics.getLatestTopics(uid, 0, 19, req.params.term, function (err, data) {
if (!err) {
res.json(data);
} else {
res.send(500);
}
});
});
@@ -204,7 +208,8 @@ var user = require('./../user.js'),
if (url) {
res.json({
url: url
url: url,
title: meta.config.title
});
} else {
res.status(404);

View File

@@ -89,7 +89,7 @@
return login_strategies;
}
Auth.create_routes = function(app) {
Auth.createRoutes = function(app) {
app.post('/logout', function(req, res) {
if (req.user && req.user.uid > 0) {
winston.info('[Auth] Session ' + req.sessionID + ' logout (uid: ' + req.user.uid + ')');
@@ -178,11 +178,13 @@
app.post('/register', function(req, res) {
user.create(req.body.username, req.body.password, req.body.email, function(err, uid) {
if (err === null && uid) {
req.login({
uid: uid
}, function() {
require('./../websockets').emitUserCount();
if(req.body.referrer)
res.redirect(req.body.referrer);
else

View File

@@ -1,20 +1,75 @@
var user = require('./../user'),
categories = require('./../categories'),
topics = require('./../topics'),
posts = require('./../posts');
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.namespace('/debug', function() {
app.get('/uuidtest', function(req, res) {
var Utils = require('../../public/src/utils.js');
app.get('/uid/:uid', function (req, res) {
res.send(Utils.generateUUID());
if (!req.params.uid)
return res.redirect('/404');
user.getUserData(req.params.uid, function (err, data) {
if (data) {
res.send(data);
} else {
res.json(404, {
error: "User doesn't exist!"
});
}
});
});
};
app.get('/cid/:cid', function (req, res) {
categories.getCategoryData(req.params.cid, function (err, data) {
if (data) {
res.send(data);
} else {
res.send(404, "Category doesn't exist!");
}
});
});
app.get('/tid/:tid', function (req, res) {
topics.getTopicData(req.params.tid, function (err, data) {
if (data) {
res.send(data);
} else {
res.send(404, "Topic doesn't exist!");
}
});
});
app.get('/pid/:pid', function (req, res) {
posts.getPostData(req.params.pid, function (err, data) {
if (data) {
res.send(data);
} else {
res.send(404, "Post doesn't exist!");
}
});
});
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;

View File

@@ -1,9 +1,29 @@
"use strict";
var nconf = require('nconf'),
path = require('path'),
fs = require('fs'),
validator = require('validator'),
Plugins = require('../plugins'),
PluginRoutes = function(app) {
app.get('/plugins/fireHook', function(req, res) {
// GET = filter
Plugins.fireHook('filter:' + req.query.hook, req.query.args, function(err, returnData) {
if (typeof returnData === 'object') {
res.json(200, returnData);
} else {
res.send(200, validator.sanitize(returnData).escape());
}
});
});
app.put('/plugins/fireHook', function(req, res) {
// PUT = action
Plugins.fireHook('action:' + req.body.hook, req.body.args);
res.send(200);
});
// Static Assets
app.get('/plugins/:id/*', function(req, res) {
var relPath = req.url.replace('/plugins/' + req.params.id, '');
@@ -15,7 +35,7 @@ var nconf = require('nconf'),
} else {
res.redirect('/404');
}
})
});
} else {
res.redirect('/404');
}

View File

@@ -12,23 +12,7 @@ var user = require('./../user.js'),
websockets = require('./../websockets.js');
(function (User) {
User.create_routes = function (app) {
app.get('/uid/:uid', function (req, res) {
if (!req.params.uid)
return res.redirect('/404');
user.getUserData(req.params.uid, function (err, data) {
if (data) {
res.send(data);
} else {
res.json(404, {
error: "User doesn't exist!"
});
}
});
});
User.createRoutes = function (app) {
app.namespace('/users', function () {
app.get('', function (req, res) {
@@ -175,7 +159,7 @@ var user = require('./../user.js'),
return;
}
var absolutePath = path.join(process.cwd(), nconf.get('upload_path'), path.basename(oldpicture));
var absolutePath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), path.basename(oldpicture));
fs.unlink(absolutePath, function (err) {
if (err) {
@@ -197,7 +181,7 @@ var user = require('./../user.js'),
}
var filename = uid + '-profileimg' + extension;
var uploadPath = path.join(process.cwd(), nconf.get('upload_path'), filename);
var uploadPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), filename);
winston.info('Attempting upload to: ' + uploadPath);
@@ -207,11 +191,6 @@ var user = require('./../user.js'),
is.on('end', function () {
fs.unlinkSync(tempPath);
var imageUrl = nconf.get('upload_url') + filename;
user.setUserField(uid, 'uploadedpicture', imageUrl);
user.setUserField(uid, 'picture', imageUrl);
require('node-imagemagick').crop({
srcPath: uploadPath,
dstPath: uploadPath,
@@ -220,8 +199,17 @@ var user = require('./../user.js'),
}, function (err, stdout, stderr) {
if (err) {
winston.err(err);
res.send({
error: 'Invalid image file!'
});
return;
}
var imageUrl = nconf.get('upload_url') + filename;
user.setUserField(uid, 'uploadedpicture', imageUrl);
user.setUserField(uid, 'picture', imageUrl);
res.json({
path: imageUrl
});
@@ -555,6 +543,8 @@ var user = require('./../user.js'),
else
data.emailClass = "hide";
data.websiteName = data.website.replace('http://', '').replace('https://', '');
data.show_banned = data.banned === '1' ? '' : 'hide';
data.uid = uid;

View File

@@ -27,7 +27,7 @@ var path = require('path'),
async.parallel([
function(next) {
var categoryUrls = [];
categories.getAllCategories(function(data) {
categories.getAllCategories(0, function(err, data) {
data.categories.forEach(function(category) {
if (!category.disabled) {
categoryUrls.push({
@@ -43,7 +43,7 @@ var path = require('path'),
},
function(next) {
var topicUrls = [];
topics.getAllTopics(null, null, function(topics) {
topics.getAllTopics(null, null, function(err, topics) {
topics.forEach(function(topic) {
if (topic.deleted !== '1') {
topicUrls.push({

View File

@@ -9,7 +9,8 @@ var RDB = require('./redis.js'),
topicSearch = reds.createSearch('nodebbtopicsearch'),
winston = require('winston'),
meta = require('./meta'),
nconf = require('nconf');
nconf = require('nconf'),
websockets = require('./websockets');
(function(ThreadTools) {
@@ -47,136 +48,119 @@ var RDB = require('./redis.js'),
});
}
ThreadTools.lock = function(tid, uid, socket) {
ThreadTools.privileges(tid, uid, function(privileges) {
if (privileges.editable) {
topics.setTopicField(tid, 'locked', 1);
ThreadTools.lock = function(tid, socket) {
topics.setTopicField(tid, 'locked', 1);
if (socket) {
io.sockets. in ('topic_' + tid).emit('event:topic_locked', {
tid: tid,
status: 'ok'
});
if (socket) {
websockets.in('topic_' + tid).emit('event:topic_locked', {
tid: tid,
status: 'ok'
});
socket.emit('api:topic.lock', {
status: 'ok',
tid: tid
});
}
if (socket) {
socket.emit('api:topic.lock', {
status: 'ok',
tid: tid
});
}
});
}
}
ThreadTools.unlock = function(tid, uid, socket) {
ThreadTools.privileges(tid, uid, function(privileges) {
if (privileges.editable) {
topics.setTopicField(tid, 'locked', 0);
ThreadTools.unlock = function(tid, socket) {
topics.setTopicField(tid, 'locked', 0);
if (socket) {
io.sockets. in ('topic_' + tid).emit('event:topic_unlocked', {
tid: tid,
status: 'ok'
});
if (socket) {
websockets.in('topic_' + tid).emit('event:topic_unlocked', {
tid: tid,
status: 'ok'
});
socket.emit('api:topic.unlock', {
status: 'ok',
tid: tid
});
}
if (socket) {
socket.emit('api:topic.unlock', {
status: 'ok',
tid: tid
});
}
});
}
}
ThreadTools.delete = function(tid, uid, callback) {
ThreadTools.privileges(tid, uid, function(privileges) {
if (privileges.editable || uid === -1) {
ThreadTools.delete = function(tid, callback) {
topics.delete(tid);
topics.delete(tid);
RDB.decr('totaltopiccount');
RDB.decr('totaltopiccount');
ThreadTools.lock(tid);
ThreadTools.lock(tid, uid);
topicSearch.remove(tid);
topicSearch.remove(tid);
io.sockets. in ('topic_' + tid).emit('event:topic_deleted', {
tid: tid,
status: 'ok'
});
callback(null);
} else callback(new Error('not-enough-privs'));
websockets.in('topic_' + tid).emit('event:topic_deleted', {
tid: tid,
status: 'ok'
});
if (callback) {
callback(null);
}
}
ThreadTools.restore = function(tid, uid, socket, callback) {
ThreadTools.privileges(tid, uid, function(privileges) {
if (privileges.editable) {
ThreadTools.restore = function(tid, socket, callback) {
topics.restore(tid);
RDB.incr('totaltopiccount');
ThreadTools.unlock(tid);
topics.restore(tid);
RDB.incr('totaltopiccount');
ThreadTools.unlock(tid, uid);
io.sockets. in ('topic_' + tid).emit('event:topic_restored', {
tid: tid,
status: 'ok'
});
topics.getTopicField(tid, 'title', function(err, title) {
topicSearch.index(title, tid);
});
if(callback)
callback(null);
}
websockets.in('topic_' + tid).emit('event:topic_restored', {
tid: tid,
status: 'ok'
});
topics.getTopicField(tid, 'title', function(err, title) {
topicSearch.index(title, tid);
});
if(callback) {
callback(null);
}
}
ThreadTools.pin = function(tid, uid, socket) {
ThreadTools.privileges(tid, uid, function(privileges) {
if (privileges.editable) {
topics.setTopicField(tid, 'pinned', 1);
topics.getTopicField(tid, 'cid', function(err, cid) {
RDB.zadd('categories:' + cid + ':tid', Math.pow(2, 53), tid);
});
if (socket) {
io.sockets. in ('topic_' + tid).emit('event:topic_pinned', {
tid: tid,
status: 'ok'
});
socket.emit('api:topic.pin', {
status: 'ok',
tid: tid
});
}
}
ThreadTools.pin = function(tid, socket) {
topics.setTopicField(tid, 'pinned', 1);
topics.getTopicField(tid, 'cid', function(err, cid) {
RDB.zadd('categories:' + cid + ':tid', Math.pow(2, 53), tid);
});
if (socket) {
websockets.in('topic_' + tid).emit('event:topic_pinned', {
tid: tid,
status: 'ok'
});
if (socket) {
socket.emit('api:topic.pin', {
status: 'ok',
tid: tid
});
}
}
}
ThreadTools.unpin = function(tid, uid, socket) {
ThreadTools.privileges(tid, uid, function(privileges) {
if (privileges.editable) {
topics.setTopicField(tid, 'pinned', 0);
topics.getTopicFields(tid, ['cid', 'lastposttime'], function(err, topicData) {
RDB.zadd('categories:' + topicData.cid + ':tid', topicData.lastposttime, tid);
});
if (socket) {
io.sockets. in ('topic_' + tid).emit('event:topic_unpinned', {
tid: tid,
status: 'ok'
});
socket.emit('api:topic.unpin', {
status: 'ok',
tid: tid
});
}
}
ThreadTools.unpin = function(tid, socket) {
topics.setTopicField(tid, 'pinned', 0);
topics.getTopicFields(tid, ['cid', 'lastposttime'], function(err, topicData) {
RDB.zadd('categories:' + topicData.cid + ':tid', topicData.lastposttime, tid);
});
if (socket) {
websockets.in('topic_' + tid).emit('event:topic_unpinned', {
tid: tid,
status: 'ok'
});
if (socket) {
socket.emit('api:topic.unpin', {
status: 'ok',
tid: tid
});
}
}
}
ThreadTools.move = function(tid, cid, socket) {
@@ -213,7 +197,7 @@ var RDB = require('./redis.js'),
status: 'ok'
});
io.sockets. in ('topic_' + tid).emit('event:topic_moved', {
websockets.in('topic_' + tid).emit('event:topic_moved', {
tid: tid
});
} else {
@@ -303,7 +287,7 @@ var RDB = require('./redis.js'),
pids.reverse();
async.detectSeries(pids, function(pid, next) {
posts.getPostField(pid, 'deleted', function(deleted) {
posts.getPostField(pid, 'deleted', function(err, deleted) {
if (deleted === '0') next(true);
else next(false);
});

View File

@@ -6,7 +6,7 @@ var RDB = require('./redis.js'),
posts = require('./posts.js'),
threadTools = require('./threadTools.js'),
postTools = require('./postTools'),
Notifications = require('./notifications'),
notifications = require('./notifications'),
async = require('async'),
feed = require('./feed.js'),
favourites = require('./favourites.js'),
@@ -18,30 +18,134 @@ var RDB = require('./redis.js'),
(function(Topics) {
Topics.getTopicData = function(tid, callback) {
RDB.hgetall('topic:' + tid, function(err, data) {
if (err === null) {
if(data) {
data.title = validator.sanitize(data.title).escape();
if(data.timestamp) {
data.relativeTime = new Date(parseInt(data.timestamp, 10)).toISOString();
}
Topics.post = function(uid, title, content, category_id, callback) {
if (!category_id)
throw new Error('Attempted to post without a category_id');
if (content)
content = content.trim();
if (title)
title = title.trim();
if (!uid) {
callback(new Error('not-logged-in'), null);
return;
} else if (!title || title.length < meta.config.minimumTitleLength) {
callback(new Error('title-too-short'), null);
return;
} else if (!content || content.length < meta.config.miminumPostLength) {
callback(new Error('content-too-short'), null);
return;
}
user.getUserField(uid, 'lastposttime', function(err, lastposttime) {
if (err) lastposttime = 0;
if (Date.now() - lastposttime < meta.config.postDelay * 1000) {
callback(new Error('too-many-posts'), null);
return;
}
RDB.incr('next_topic_id', function(err, tid) {
RDB.handle(err);
// Global Topics
if (uid == null) uid = 0;
if (uid !== null) {
RDB.sadd('topics:tid', tid);
} else {
// need to add some unique key sent by client so we can update this with the real uid later
RDB.lpush('topics:queued:tid', tid);
}
callback(data);
} else {
console.log(err);
var slug = tid + '/' + utils.slugify(title);
var timestamp = Date.now();
RDB.hmset('topic:' + tid, {
'tid': tid,
'uid': uid,
'cid': category_id,
'title': title,
'slug': slug,
'timestamp': timestamp,
'lastposttime': 0,
'postcount': 0,
'viewcount': 0,
'locked': 0,
'deleted': 0,
'pinned': 0
});
topicSearch.index(title, tid);
user.addTopicIdToUser(uid, tid);
// let everyone know that there is an unread topic in this category
RDB.del('cid:' + category_id + ':read_by_uid', function(err, data) {
Topics.markAsRead(tid, uid);
});
// in future it may be possible to add topics to several categories, so leaving the door open here.
RDB.zadd('categories:' + category_id + ':tid', timestamp, tid);
RDB.hincrby('category:' + category_id, 'topic_count', 1);
RDB.incr('totaltopiccount');
feed.updateCategory(category_id);
posts.create(uid, tid, content, function(err, postData) {
if(err) {
return callback(err, null);
} else if(!postData) {
return callback(new Error('invalid-post'), null);
}
// Auto-subscribe the post creator to the newly created topic
threadTools.toggleFollow(tid, uid);
Topics.getTopicForCategoryView(tid, uid, function(topicData) {
topicData.unreplied = 1;
callback(null, {
topicData: topicData,
postData: postData
});
});
});
});
});
};
Topics.getTopicData = function(tid, callback) {
RDB.hgetall('topic:' + tid, function(err, data) {
if(err) {
return callback(err, null);
}
if(data) {
data.title = validator.sanitize(data.title).escape();
if(data.timestamp) {
data.relativeTime = new Date(parseInt(data.timestamp, 10)).toISOString();
}
}
callback(null, data);
});
}
Topics.getTopicDataWithUser = function(tid, callback) {
Topics.getTopicData(tid, function(topic) {
Topics.getTopicData(tid, function(err, topic) {
if(err) {
return callback(err, null);
}
user.getUserFields(topic.uid, ['username', 'userslug', 'picture'] , function(err, userData) {
if(err) {
return callback(err, null);
}
topic.username = userData.username;
topic.userslug = userData.userslug
topic.picture = userData.picture;
callback(topic);
callback(null, topic);
});
});
}
@@ -85,7 +189,7 @@ var RDB = require('./redis.js'),
for (var i = 0; i < postData.length; ++i) {
postData[i].fav_button_class = fav_data[postData[i].pid] ? 'btn-warning' : '';
postData[i].fav_star_class = fav_data[postData[i].pid] ? 'icon-star' : 'icon-star-empty';
postData[i].fav_star_class = fav_data[postData[i].pid] ? 'fa-star' : 'fa-star-o';
postData[i]['display_moderator_tools'] = ((current_user != 0) && (postData[i].uid == current_user || privileges.editable)) ? 'show' : 'none';
postData[i].show_banned = postData[i].user_banned === '1' ? 'show' : 'hide';
@@ -119,26 +223,24 @@ var RDB = require('./redis.js'),
var args = ['topics:recent', '+inf', timestamp - since, 'LIMIT', start, end - start + 1];
RDB.zrevrangebyscore(args, function(err, tids) {
if (err) {
return callback(err);
}
var latestTopics = {
'category_name': 'Recent',
'show_sidebar': 'hidden',
'show_topic_button': 'hidden',
'no_topics_message': 'hidden',
'topic_row_size': 'col-md-12',
'category_id': false,
'topics': []
};
if (!tids || !tids.length) {
latestTopics.no_topics_message = 'show';
callback(latestTopics);
callback(err, latestTopics);
return;
}
Topics.getTopicsByTids(tids, current_user, function(topicData) {
latestTopics.topics = topicData;
callback(latestTopics);
callback(err, latestTopics);
});
});
}
@@ -331,17 +433,18 @@ var RDB = require('./redis.js'),
}
function loadTopic(tid, callback) {
Topics.getTopicData(tid, function(topicData) {
Topics.getTopicData(tid, function(err, topicData) {
if (!topicData) {
return callback(null);
}
getTopicInfo(topicData, function(topicInfo) {
topicData['pin-icon'] = topicData.pinned === '1' ? 'icon-pushpin' : 'none';
topicData['lock-icon'] = topicData.locked === '1' ? 'icon-lock' : 'none';
topicData['pin-icon'] = topicData.pinned === '1' ? 'fa-thumb-tack' : 'none';
topicData['lock-icon'] = topicData.locked === '1' ? 'fa-lock' : 'none';
topicData['deleted-class'] = topicData.deleted === '1' ? 'deleted' : '';
topicData.unreplied = topicData.postcount === '1';
topicData.username = topicInfo.username;
topicData.userslug = topicInfo.userslug;
topicData.picture = topicInfo.picture;
@@ -381,9 +484,7 @@ var RDB = require('./redis.js'),
Topics.increaseViewCount(tid);
function getTopicData(next) {
Topics.getTopicData(tid, function(topicData) {
next(null, topicData);
});
Topics.getTopicData(tid, next);
}
function getTopicPosts(next) {
@@ -414,8 +515,6 @@ var RDB = require('./redis.js'),
privileges = results[2],
categoryData = results[3];
var main_posts = topicPosts.splice(0, 1);
callback(null, {
'topic_name': topicData.title,
'category_name': categoryData.name,
@@ -426,10 +525,10 @@ var RDB = require('./redis.js'),
'slug': topicData.slug,
'postcount': topicData.postcount,
'viewcount': topicData.viewcount,
'unreplied': topicData.postcount > 1,
'topic_id': tid,
'expose_tools': privileges.editable ? 1 : 0,
'posts': topicPosts,
'main_posts': main_posts,
'twitter-intent-url': 'https://twitter.com/intent/tweet?url=' + encodeURIComponent(nconf.get('url') + 'topic/' + topicData.slug) + '&text=' + encodeURIComponent(topicData.title),
'facebook-share-url': 'https://www.facebook.com/sharer/sharer.php?u=' + encodeURIComponent(nconf.get('url') + 'topic/' + topicData.slug),
'google-share-url': 'https://plus.google.com/share?url=' + encodeURIComponent(nconf.get('url') + 'topic/' + topicData.slug)
@@ -442,9 +541,7 @@ var RDB = require('./redis.js'),
Topics.getTopicForCategoryView = function(tid, uid, callback) {
function getTopicData(next) {
Topics.getTopicDataWithUser(tid, function(topic) {
next(null, topic);
});
Topics.getTopicDataWithUser(tid, next);
}
function getReadStatus(next) {
@@ -473,6 +570,9 @@ var RDB = require('./redis.js'),
hasRead = results[1],
teaser = results[2];
topicData['pin-icon'] = topicData.pinned === '1' ? 'fa-thumb-tack' : 'none';
topicData['lock-icon'] = topicData.locked === '1' ? 'fa-lock' : 'none';
topicData.badgeclass = hasRead ? '' : 'badge-important';
topicData.teaser_text = teaser.text || '';
topicData.teaser_username = teaser.username || '';
@@ -487,6 +587,10 @@ var RDB = require('./redis.js'),
Topics.getAllTopics = function(limit, after, callback) {
RDB.smembers('topics:tid', function(err, tids) {
if(err) {
return callback(err, null);
}
var topics = [],
numTids, x;
@@ -517,12 +621,12 @@ var RDB = require('./redis.js'),
});
async.each(tids, function(tid, next) {
Topics.getTopicDataWithUser(tid, function(topicData) {
Topics.getTopicDataWithUser(tid, function(err, topicData) {
topics.push(topicData);
next();
});
}, function(err) {
callback(topics);
callback(err, topics);
});
});
}
@@ -546,15 +650,15 @@ var RDB = require('./redis.js'),
}
Topics.getTitleByPid = function(pid, callback) {
posts.getPostField(pid, 'tid', function(tid) {
posts.getPostField(pid, 'tid', function(err, tid) {
Topics.getTopicField(tid, 'title', function(err, title) {
callback(title);
});
});
}
Topics.markUnRead = function(tid) {
RDB.del('tid:' + tid + ':read_by_uid');
Topics.markUnRead = function(tid, callback) {
RDB.del('tid:' + tid + ':read_by_uid', callback);
}
Topics.markAsRead = function(tid, uid) {
@@ -571,11 +675,9 @@ var RDB = require('./redis.js'),
});
user.notifications.getUnreadByUniqueId(uid, 'topic:' + tid, function(err, nids) {
if (nids.length > 0) {
async.each(nids, function(nid, next) {
Notifications.mark_read(nid, uid, next);
});
}
notifications.mark_read_multiple(nids, uid, function() {
});
});
}
@@ -620,36 +722,44 @@ var RDB = require('./redis.js'),
Topics.getTeaser = function(tid, callback) {
threadTools.getLatestUndeletedPid(tid, function(err, pid) {
if (!err) {
posts.getPostFields(pid, ['pid', 'content', 'uid', 'timestamp'], function(postData) {
if (err) {
return callback(err, null);
}
user.getUserFields(postData.uid, ['username', 'userslug', 'picture'], function(err, userData) {
if (err)
return callback(err, null);
posts.getPostFields(pid, ['pid', 'content', 'uid', 'timestamp'], function(err, postData) {
if (err) {
return callback(err, null);
} else if(!postData) {
return callback(new Error('no-teaser-found'));
}
var stripped = postData.content,
timestamp = postData.timestamp,
returnObj = {
"pid": postData.pid,
"username": userData.username,
"userslug": userData.userslug,
"picture": userData.picture,
"timestamp": timestamp
};
user.getUserFields(postData.uid, ['username', 'userslug', 'picture'], function(err, userData) {
if (err) {
return callback(err, null);
}
if (postData.content) {
stripped = postData.content.replace(/>.+\n\n/, '');
postTools.parse(stripped, function(err, stripped) {
returnObj.text = utils.strip_tags(stripped);
callback(null, returnObj);
});
} else {
returnObj.text = '';
var stripped = postData.content,
timestamp = postData.timestamp,
returnObj = {
"pid": postData.pid,
"username": userData.username,
"userslug": userData.userslug,
"picture": userData.picture,
"timestamp": timestamp
};
if (postData.content) {
stripped = postData.content.replace(/>.+\n\n/, '');
postTools.parse(stripped, function(err, stripped) {
returnObj.text = utils.strip_tags(stripped);
callback(null, returnObj);
}
});
});
} else {
returnObj.text = '';
callback(null, returnObj);
}
});
} else callback(new Error('no-teaser-found'));
});
});
}
@@ -663,101 +773,6 @@ var RDB = require('./redis.js'),
});
}
Topics.post = function(uid, title, content, category_id, callback) {
if (!category_id)
throw new Error('Attempted to post without a category_id');
if (content)
content = content.trim();
if (title)
title = title.trim();
if (uid === 0) {
callback(new Error('not-logged-in'), null);
return;
} else if (!title || title.length < meta.config.minimumTitleLength) {
callback(new Error('title-too-short'), null);
return;
} else if (!content || content.length < meta.config.miminumPostLength) {
callback(new Error('content-too-short'), null);
return;
}
user.getUserField(uid, 'lastposttime', function(err, lastposttime) {
if (err) lastposttime = 0;
if (Date.now() - lastposttime < meta.config.postDelay) {
callback(new Error('too-many-posts'), null);
return;
}
RDB.incr('next_topic_id', function(err, tid) {
RDB.handle(err);
// Global Topics
if (uid == null) uid = 0;
if (uid !== null) {
RDB.sadd('topics:tid', tid);
} else {
// need to add some unique key sent by client so we can update this with the real uid later
RDB.lpush('topics:queued:tid', tid);
}
var slug = tid + '/' + utils.slugify(title);
var timestamp = Date.now();
RDB.hmset('topic:' + tid, {
'tid': tid,
'uid': uid,
'cid': category_id,
'title': title,
'slug': slug,
'timestamp': timestamp,
'lastposttime': 0,
'postcount': 0,
'viewcount': 0,
'locked': 0,
'deleted': 0,
'pinned': 0
});
topicSearch.index(title, tid);
user.addTopicIdToUser(uid, tid);
// let everyone know that there is an unread topic in this category
RDB.del('cid:' + category_id + ':read_by_uid', function(err, data) {
Topics.markAsRead(tid, uid);
});
// in future it may be possible to add topics to several categories, so leaving the door open here.
RDB.zadd('categories:' + category_id + ':tid', timestamp, tid);
RDB.hincrby('category:' + category_id, 'topic_count', 1);
RDB.incr('totaltopiccount');
feed.updateCategory(category_id);
posts.create(uid, tid, content, function(postData) {
if (postData) {
// Auto-subscribe the post creator to the newly created topic
threadTools.toggleFollow(tid, uid);
// Notify any users looking at the category that a new topic has arrived
Topics.getTopicForCategoryView(tid, uid, function(topicData) {
io.sockets.in('category_' + category_id).emit('event:new_topic', topicData);
io.sockets.in('recent_posts').emit('event:new_topic', topicData);
io.sockets.in('user/' + uid).emit('event:new_post', {
posts: postData
});
});
callback(null, postData);
}
});
});
});
};
Topics.getTopicField = function(tid, field, callback) {
RDB.hget('topic:' + tid, field, callback);
}
@@ -780,7 +795,10 @@ var RDB = require('./redis.js'),
Topics.isLocked = function(tid, callback) {
Topics.getTopicField(tid, 'locked', function(err, locked) {
callback(locked);
if(err) {
return callback(err, null);
}
callback(null, locked === "1");
});
}
@@ -802,7 +820,7 @@ var RDB = require('./redis.js'),
Topics.getPids(tid, function(err, pids) {
function getUid(pid, next) {
posts.getPostField(pid, 'uid', function(uid) {
posts.getPostField(pid, 'uid', function(err, uid) {
if (err)
return next(err);
uids[uid] = 1;

View File

@@ -1,94 +1,235 @@
"use strict";
var RDB = require('./redis.js'),
async = require('async'),
winston = require('winston'),
notifications = require('./notifications')
Upgrade = {};
notifications = require('./notifications'),
categories = require('./categories'),
Upgrade = {},
Upgrade.upgrade = function() {
schemaDate, thisSchemaDate;
Upgrade.check = function(callback) {
// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema
var latestSchema = new Date(2013, 10, 26).getTime();
RDB.get('schemaDate', function(err, value) {
if (parseInt(value, 10) >= latestSchema) {
callback(true);
} else {
callback(false);
}
});
};
Upgrade.upgrade = function(callback) {
winston.info('Beginning Redis database schema update');
async.series([
function(next) {
RDB.hget('notifications:1', 'score', function(err, score) {
if (score) {
async.series([
function(next) {
RDB.keys('uid:*:notifications:flag', function(err, keys) {
if (keys.length > 0) {
winston.info('[2013/10/03] Removing deprecated Notification Flags');
async.each(keys, function(key, next) {
RDB.del(key, next);
}, next);
} else {
winston.info('[2013/10/03] No Notification Flags found. Good.');
next();
}
});
},
function(next) {
winston.info('[2013/10/03] Updating Notifications');
RDB.keys('uid:*:notifications:*', function(err, keys) {
async.each(keys, function(key, next) {
RDB.zrange(key, 0, -1, function(err, nids) {
async.each(nids, function(nid, next) {
notifications.get(nid, null, function(notif_data) {
RDB.zadd(key, notif_data.datetime, nid, next);
});
}, next);
});
}, next);
});
},
function(next) {
RDB.keys('notifications:*', function(err, keys) {
if (keys.length > 0) {
winston.info('[2013/10/03] Removing Notification Scores');
async.each(keys, function(key, next) {
if (key === 'notifications:next_nid') return next();
RDB.hdel(key, 'score', next);
}, next);
} else {
winston.info('[2013/10/03] No Notification Scores found. Good.');
next();
}
});
}
], next);
} else {
winston.info('[2013/10/03] Updates to Notifications skipped.');
next();
}
RDB.get('schemaDate', function(err, value) {
schemaDate = value;
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);
thisSchemaDate = new Date(2013, 9, 3).getTime();
if (schemaDate < thisSchemaDate) {
async.series([
function(next) {
RDB.keys('uid:*:notifications:flag', function(err, keys) {
if (keys.length > 0) {
winston.info('[2013/10/03] Removing deprecated Notification Flags');
async.each(keys, function(key, next) {
RDB.del(key, next);
}, next);
} else {
winston.info('[2013/10/03] No Notification Flags found. Good.');
next();
}
});
},
function(next) {
winston.info('[2013/10/03] Updating Notifications');
RDB.keys('uid:*:notifications:*', function(err, keys) {
async.each(keys, function(key, next) {
RDB.zrange(key, 0, -1, function(err, nids) {
async.each(nids, function(nid, next) {
notifications.get(nid, null, function(notif_data) {
if (notif_data) {
RDB.zadd(key, notif_data.datetime, nid, next);
} else {
next();
}
});
}, next);
});
}, next);
});
},
function(next) {
RDB.keys('notifications:*', function(err, keys) {
if (keys.length > 0) {
winston.info('[2013/10/03] Removing Notification Scores');
async.each(keys, function(key, next) {
if (key === 'notifications:next_nid') {
return next();
}
winston.info('[2013/10/23] Adding existing notifications to set');
RDB.sadd('notifications', keys, next);
RDB.hdel(key, 'score', next);
}, next);
} else {
winston.info('[2013/10/03] No Notification Scores found. Good.');
next();
}
});
}
], next);
} else {
winston.info('[2013/10/03] Updates to Notifications skipped.');
next();
}
},
function(next) {
thisSchemaDate = new Date(2013, 9, 23).getTime();
if (schemaDate < thisSchemaDate) {
RDB.keys('notifications:*', function(err, keys) {
keys = keys.filter(function(key) {
if (key === 'notifications:next_nid') {
return false;
} else {
return true;
}
}).map(function(key) {
return key.slice(14);
});
} else {
winston.info('[2013/10/23] Updates to Notifications skipped.');
winston.info('[2013/10/23] Adding existing notifications to set');
if(keys && Array.isArray(keys)) {
async.each(keys, function(key, cb) {
RDB.sadd('notifications', key, cb);
}, next);
} else next();
});
} else {
winston.info('[2013/10/23] Updates to Notifications skipped.');
next();
}
},
function(next) {
thisSchemaDate = new Date(2013, 10, 11).getTime();
if (schemaDate < thisSchemaDate) {
RDB.hset('config', 'postDelay', 10, function(err, success) {
winston.info('[2013/11/11] Updated postDelay to 10 seconds.');
next();
}
});
});
} else {
winston.info('[2013/11/11] Update to postDelay skipped.');
next();
}
},
function(next) {
thisSchemaDate = new Date(2013, 10, 22).getTime();
if (schemaDate < thisSchemaDate) {
RDB.keys('category:*', function(err, categories) {
async.each(categories, function(categoryStr, next) {
var hex;
RDB.hgetall(categoryStr, function(err, categoryObj) {
switch(categoryObj.blockclass) {
case 'category-purple':
hex = '#ab1290';
break;
case 'category-darkblue':
hex = '#004c66';
break;
case 'category-blue':
hex = '#0059b2';
break;
case 'category-darkgreen':
hex = '#004000';
break;
case 'category-orange':
hex = '#ff7a4d';
break;
default:
hex = '#0059b2';
break;
}
RDB.hset(categoryStr, 'bgColor', hex, next);
RDB.hdel(categoryStr, 'blockclass');
});
}, function() {
winston.info('[2013/11/22] Updated Category colours.');
next();
});
});
} else {
winston.info('[2013/11/22] Update to Category colours skipped.');
next();
}
},
function(next) {
thisSchemaDate = new Date(2013, 10, 26).getTime();
if (schemaDate < thisSchemaDate || 1) {
categories.getAllCategories(0, function(err, categories) {
function updateIcon(category, next) {
var icon = '';
if(category.icon === 'icon-lightbulb') {
icon = 'fa-lightbulb-o';
} else if(category.icon === 'icon-plus-sign') {
icon = 'fa-plus';
} else if(category.icon === 'icon-screenshot') {
icon = 'fa-crosshairs';
} else {
icon = category.icon.replace('icon-', 'fa-');
}
RDB.hset('category:' + category.cid, 'icon', icon, next);
}
async.each(categories.categories, updateIcon, function(err) {
if(err) {
return next(err);
}
winston.info('[2013/11/26] Updated Category icons.');
next();
});
});
} else {
winston.info('[2013/11/26] Update to Category icons skipped.');
next();
}
}
// Add new schema updates here
// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 12!!!
], function(err) {
if (!err) {
winston.info('Redis schema update complete!');
process.exit();
RDB.set('schemaDate', thisSchemaDate, function(err) {
if (!err) {
winston.info('[upgrade] Redis schema update complete!');
if (callback) {
callback(err);
} else {
process.exit();
}
} else {
winston.error('[upgrade] Could not update NodeBB schema date!');
process.exit();
}
});
} else {
winston.error('Errors were encountered while updating the NodeBB schema: ' + err.message);
winston.error('[upgrade] Errors were encountered while updating the NodeBB schema: ' + err.message);
process.exit();
}
});
};

View File

@@ -1,16 +1,20 @@
var utils = require('./../public/src/utils.js'),
RDB = require('./redis.js'),
emailjs = require('emailjs'),
meta = require('./meta.js'),
emailjsServer = emailjs.server.connect(meta.config['email:smtp:host'] || '127.0.0.1'),
bcrypt = require('bcrypt'),
Groups = require('./groups'),
notifications = require('./notifications.js'),
topics = require('./topics.js'),
var bcrypt = require('bcrypt'),
async = require('async'),
emailjs = require('emailjs'),
nconf = require('nconf'),
winston = require('winston'),
userSearch = require('reds').createSearch('nodebbusersearch'),
winston = require('winston');
check = require('validator').check,
sanitize = require('validator').sanitize,
utils = require('./../public/src/utils'),
RDB = require('./redis'),
meta = require('./meta'),
emailjsServer = emailjs.server.connect(meta.config['email:smtp:host'] || '127.0.0.1'),
Groups = require('./groups'),
notifications = require('./notifications'),
topics = require('./topics');
(function(User) {
'use strict';
@@ -98,15 +102,7 @@ var utils = require('./../public/src/utils.js'),
User.sendConfirmationEmail(email);
}
RDB.incr('usercount', function(err, count) {
RDB.handle(err);
if (typeof io !== 'undefined') {
io.sockets.emit('user.count', {
count: count
});
}
});
RDB.incr('usercount');
RDB.zadd('users:joindate', timestamp, uid);
RDB.zadd('users:postcount', 0, uid);
@@ -114,13 +110,6 @@ var utils = require('./../public/src/utils.js'),
userSearch.index(username, uid);
if (typeof io !== 'undefined') {
io.sockets.emit('user.latest', {
userslug: userslug,
username: username
});
}
if (password !== undefined) {
User.hashPassword(password, function(err, hash) {
User.setUserField(uid, 'password', hash);
@@ -250,6 +239,9 @@ var utils = require('./../public/src/utils.js'),
function updateField(field, next) {
if (data[field] !== undefined && typeof data[field] === 'string') {
data[field] = data[field].trim();
data[field] = sanitize(data[field]).escape();
if (field === 'email') {
var gravatarpicture = User.createGravatarURLFromEmail(data[field]);
User.setUserField(uid, 'gravatarpicture', gravatarpicture);
@@ -271,6 +263,10 @@ var utils = require('./../public/src/utils.js'),
return;
} else if (field === 'signature') {
data[field] = utils.strip_tags(data[field]);
} else if (field === 'website') {
if(data[field].substr(0, 7) !== 'http://' && data[field].substr(0, 8) !== 'https://') {
data[field] = 'http://' + data[field];
}
}
User.setUserField(uid, field, data[field]);
@@ -642,21 +638,6 @@ var utils = require('./../public/src/utils.js'),
});
};
User.latest = function(socket) {
RDB.zrevrange('users:joindate', 0, 0, function(err, uid) {
RDB.handle(err);
User.getUserFields(uid, ['username', 'userslug'], function(err, userData) {
if (!err && userData) {
socket.emit('user.latest', {
userslug: userData.userslug,
username: userData.username
});
}
});
});
};
User.getUidByUsername = function(username, callback) {
RDB.hget('username:uid', username, callback);
};
@@ -1009,7 +990,9 @@ var utils = require('./../public/src/utils.js'),
next(null, notif_data);
});
}, function(err, notifs) {
notifs = notifs.sort(function(a, b) {
notifs = notifs.filter(function(notif) {
return notif !== null;
}).sort(function(a, b) {
return parseInt(b.datetime, 10) - parseInt(a.datetime, 10);
}).map(function(notif) {
notif.datetimeISO = new Date(parseInt(notif.datetime, 10)).toISOString();

View File

@@ -1,45 +1,55 @@
var express = require('express'),
var path = require('path'),
fs = require('fs'),
express = require('express'),
express_namespace = require('express-namespace'),
WebServer = express(),
server = require('http').createServer(WebServer),
RedisStore = require('connect-redis')(express),
path = require('path'),
RDB = require('./redis'),
utils = require('../public/src/utils.js'),
pkg = require('../package.json'),
fs = require('fs'),
user = require('./user.js'),
categories = require('./categories.js'),
posts = require('./posts.js'),
topics = require('./topics.js'),
notifications = require('./notifications.js'),
admin = require('./routes/admin.js'),
userRoute = require('./routes/user.js'),
apiRoute = require('./routes/api.js'),
auth = require('./routes/authentication.js'),
meta = require('./meta.js'),
feed = require('./feed'),
plugins = require('./plugins'),
nconf = require('nconf'),
winston = require('winston'),
validator = require('validator'),
async = require('async'),
logger = require('./logger.js');
pkg = require('../package.json'),
utils = require('../public/src/utils'),
RDB = require('./redis'),
user = require('./user'),
categories = require('./categories'),
posts = require('./posts'),
topics = require('./topics'),
notifications = require('./notifications'),
admin = require('./routes/admin'),
userRoute = require('./routes/user'),
apiRoute = require('./routes/api'),
auth = require('./routes/authentication'),
meta = require('./meta'),
feed = require('./feed'),
plugins = require('./plugins'),
logger = require('./logger');
(function (app) {
"use strict";
var templates = null,
clientScripts;
// Minify client-side libraries
meta.js.get(function (err, scripts) {
clientScripts = scripts.map(function (script) {
return script = {
script: script
}
plugins.ready(function() {
// Minify client-side libraries
meta.js.get(function (err, scripts) {
clientScripts = scripts.map(function (script) {
script = {
script: script
};
return script;
});
});
});
server.app = app;
/**
@@ -66,13 +76,17 @@ var express = require('express'),
content: meta.config.title || 'NodeBB'
}, {
property: 'keywords',
content: meta.config['keywords'] || ''
content: meta.config.keywords || ''
}],
defaultLinkTags = [{
rel: 'apple-touch-icon',
href: meta.config['brand:logo'] || nconf.get('relative_path') + '/logo.png'
}],
metaString = utils.buildMetaTags(defaultMetaTags.concat(options.metaTags || [])),
linkTags = utils.buildLinkTags(options.linkTags || []),
linkTags = utils.buildLinkTags(defaultLinkTags.concat(options.linkTags || [])),
templateValues = {
cssSrc: meta.config['theme:src'] || nconf.get('relative_path') + '/vendor/bootstrap/css/bootstrap.min.css',
pluginCSS: plugins.cssFiles.map(function(file) { return { path: file } }),
pluginCSS: plugins.cssFiles.map(function(file) { return { path: file }; }),
title: meta.config.title || '',
description: meta.config.description || '',
'brand:logo': meta.config['brand:logo'] || '',
@@ -88,8 +102,9 @@ var express = require('express'),
var uid = '0';
if(options.req.user && options.req.user.uid)
if(options.req.user && options.req.user.uid) {
uid = options.req.user.uid;
}
user.isAdministrator(uid, function(isAdmin) {
templateValues.adminDisplay = isAdmin ? 'show' : 'hide';
@@ -97,7 +112,7 @@ var express = require('express'),
translator.translate(templates.header.parse(templateValues), function(template) {
callback(null, template);
});
})
});
});
@@ -139,7 +154,7 @@ var express = require('express'),
res.locals.csrf_token = req.session._csrf;
// Disable framing
res.setHeader("X-Frame-Options", "DENY");
res.setHeader('X-Frame-Options', 'DENY');
next();
});
@@ -165,14 +180,18 @@ var express = require('express'),
// Theme's static directory
if (themeData[2]) {
app.use('/css/assets', express.static(path.join(__dirname, '../node_modules', themeData[1], themeData[2])));
app.use('/css/assets', express.static(path.join(__dirname, '../node_modules', themeData[1], themeData[2]), {
maxAge: 5184000000
}));
if (process.env.NODE_ENV === 'development') {
winston.info('Static directory routed for theme: ' + themeData[1]);
}
}
if (themeData[3]) {
app.use('/templates', express.static(path.join(__dirname, '../node_modules', themeData[1], themeData[3])));
app.use('/templates', express.static(path.join(__dirname, '../node_modules', themeData[1], themeData[3]), {
maxAge: 5184000000
}));
if (process.env.NODE_ENV === 'development') {
winston.info('Custom templates directory routed for theme: ' + themeData[1]);
}
@@ -232,7 +251,9 @@ var express = require('express'),
app.use(app.router);
// Static directory /public
app.use(nconf.get('relative_path'), express.static(path.join(__dirname, '../', 'public')));
app.use(nconf.get('relative_path'), express.static(path.join(__dirname, '../', 'public'), {
maxAge: 5184000000
}));
// 404 catch-all
app.use(function (req, res, next) {
@@ -249,11 +270,17 @@ var express = require('express'),
res.json(200, {});
} else if (req.accepts('html')) {
// respond with html page
if (process.env.NODE_ENV === 'development') winston.warn('Route requested but not found: ' + req.url);
if (process.env.NODE_ENV === 'development') {
winston.warn('Route requested but not found: ' + req.url);
}
res.redirect(nconf.get('relative_path') + '/404');
} else if (req.accepts('json')) {
// respond with json
if (process.env.NODE_ENV === 'development') winston.warn('Route requested but not found: ' + req.url);
if (process.env.NODE_ENV === 'development') {
winston.warn('Route requested but not found: ' + req.url);
}
res.json({
error: 'Not found'
});
@@ -284,7 +311,9 @@ var express = require('express'),
winston.error('Errors were encountered while attempting to initialise NodeBB.');
process.exit();
} else {
if (process.env.NODE_ENV === 'development') winston.info('Middlewares loaded.');
if (process.env.NODE_ENV === 'development') {
winston.info('Middlewares loaded.');
}
}
});
});
@@ -293,54 +322,62 @@ var express = require('express'),
templates = global.templates;
// translate all static templates served by webserver here. ex. footer, logout
translator.translate(templates['footer'].toString(), function(parsedTemplate) {
templates['footer'] = parsedTemplate;
plugins.fireHook('filter:footer.build', '', function(err, appendHTML) {
var footer = templates.footer.parse({
footerHTML: appendHTML
});
translator.translate(footer, function(parsedTemplate) {
templates.footer = parsedTemplate;
});
});
translator.translate(templates['logout'].toString(), function(parsedTemplate) {
templates['logout'] = parsedTemplate;
plugins.fireHook('action:app.load');
translator.translate(templates.logout.toString(), function(parsedTemplate) {
templates.logout = parsedTemplate;
});
winston.info('NodeBB Ready');
server.listen(nconf.get('PORT') || nconf.get('port'), nconf.get('bind_address'));
}
};
app.create_route = function (url, tpl) { // to remove
return '<script>templates.ready(function(){ajaxify.go("' + url + '", null, "' + tpl + '");});</script>';
return '<script>templates.ready(function(){ajaxify.go("' + url + '", null, "' + tpl + '", true);});</script>';
};
app.namespace(nconf.get('relative_path'), function () {
auth.create_routes(app);
admin.create_routes(app);
userRoute.create_routes(app);
apiRoute.create_routes(app);
auth.createRoutes(app);
admin.createRoutes(app);
userRoute.createRoutes(app);
apiRoute.createRoutes(app);
// 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', 'account', 'recent', 'unread', 'notifications', '403', '404'];
var routes = ['login', 'register', 'account', 'recent', '403', '404'],
loginRequired = ['unread', 'search', 'notifications'];
for (var i = 0, ii = routes.length; i < ii; i++) {
(function (route) {
async.each(routes.concat(loginRequired), function(route, next) {
app.get('/' + route, function (req, res) {
if ((route === 'login' || route === 'register') && (req.user && req.user.uid > 0)) {
app.get('/' + route, function (req, res) {
if ((route === 'login' || route === 'register') && (req.user && req.user.uid > 0)) {
user.getUserField(req.user.uid, 'userslug', function (err, userslug) {
res.redirect('/user/' + userslug);
});
return;
}
app.build_header({
req: req,
res: res
}, function (err, header) {
res.send((isNaN(parseInt(route, 10)) ? 200 : parseInt(route, 10)), header + app.create_route(route) + templates['footer']);
user.getUserField(req.user.uid, 'userslug', function (err, userslug) {
res.redirect('/user/' + userslug);
});
return;
} else if (loginRequired.indexOf(route) !== -1 && !req.user) {
return res.redirect('/403');
}
app.build_header({
req: req,
res: res
}, function (err, header) {
res.send((isNaN(parseInt(route, 10)) ? 200 : parseInt(route, 10)), header + app.create_route(route) + templates.footer);
});
}(routes[i]));
}
});
});
}());
@@ -366,26 +403,28 @@ var express = require('express'),
}, next);
},
"categories": function (next) {
categories.getAllCategories(function (returnData) {
categories.getAllCategories(0, function (err, returnData) {
returnData.categories = returnData.categories.filter(function (category) {
if (category.disabled !== '1') return true;
else return false;
if (category.disabled !== '1') {
return true;
} else {
return false;
}
});
next(null, returnData);
}, 0);
});
}
}, function (err, data) {
res.send(
data.header +
'\n\t<noscript>\n' + templates['noscript/header'] + templates['noscript/home'].parse(data.categories) + '\n\t</noscript>' +
app.create_route('') +
templates['footer']
templates.footer
);
})
});
});
app.get('/topic/:topic_id/:slug?', function (req, res) {
var tid = req.params.topic_id;
@@ -394,18 +433,26 @@ var express = require('express'),
var rssPath = path.join(__dirname, '../', 'feeds/topics', tid + '.rss'),
loadFeed = function () {
fs.readFile(rssPath, function (err, data) {
if (err) res.type('text').send(404, "Unable to locate an rss feed at this location.");
else res.type('xml').set('Content-Length', data.length).send(data);
if (err) {
res.type('text').send(404, "Unable to locate an rss feed at this location.");
} else {
res.type('xml').set('Content-Length', data.length).send(data);
}
});
};
if (!fs.existsSync(rssPath)) {
feed.updateTopic(tid, function (err) {
if (err) res.redirect('/404');
else loadFeed();
if (err) {
res.redirect('/404');
} else {
loadFeed();
}
});
} else loadFeed();
} else {
loadFeed();
}
return;
}
@@ -414,8 +461,9 @@ var express = require('express'),
function (next) {
topics.getTopicWithPosts(tid, ((req.user) ? req.user.uid : 0), 0, -1, function (err, topicData) {
if (topicData) {
if (topicData.deleted === '1' && topicData.expose_tools === 0)
if (topicData.deleted === '1' && topicData.expose_tools === 0) {
return next(new Error('Topic deleted'), null);
}
}
next(err, topicData);
@@ -428,7 +476,9 @@ var express = require('express'),
for (var x = 0, numPosts = topicData.posts.length; x < numPosts; x++) {
timestamp = parseInt(topicData.posts[x].timestamp, 10);
if (timestamp > lastMod) lastMod = timestamp;
if (timestamp > lastMod) {
lastMod = timestamp;
}
}
app.build_header({
@@ -439,7 +489,7 @@ var express = require('express'),
content: topicData.topic_name
}, {
name: "description",
content: sanitize(topicData.main_posts[0].content.substr(0, 255)).escape().replace('\n', '')
content: sanitize(topicData.posts[0].content.substr(0, 255)).escape().replace('\n', '')
}, {
property: 'og:title',
content: topicData.topic_name + ' | ' + (meta.config.title || 'NodeBB')
@@ -451,10 +501,10 @@ var express = require('express'),
content: nconf.get('url') + 'topic/' + topicData.slug
}, {
property: 'og:image',
content: topicData.main_posts[0].picture
content: topicData.posts[0].picture
}, {
property: "article:published_time",
content: new Date(parseInt(topicData.main_posts[0].timestamp, 10)).toISOString()
content: new Date(parseInt(topicData.posts[0].timestamp, 10)).toISOString()
}, {
property: 'article:modified_time',
content: new Date(lastMod).toISOString()
@@ -481,14 +531,17 @@ var express = require('express'),
});
},
], function (err, data) {
if (err) return res.redirect('404');
if (err) {
return res.redirect('404');
}
var topic_url = tid + (req.params.slug ? '/' + req.params.slug : '');
res.send(
data.header +
'\n\t<noscript>\n' + templates['noscript/header'] + templates['noscript/topic'].parse(data.topics) + '\n\t</noscript>' +
'\n\t<script>templates.ready(function(){ajaxify.go("topic/' + topic_url + '");});</script>' +
templates['footer']
'\n\t<script>templates.ready(function(){ajaxify.go("topic/' + topic_url + '", undefined, undefined, true);});</script>' +
templates.footer
);
});
});
@@ -501,18 +554,26 @@ var express = require('express'),
var rssPath = path.join(__dirname, '../', 'feeds/categories', cid + '.rss'),
loadFeed = function () {
fs.readFile(rssPath, function (err, data) {
if (err) res.type('text').send(404, "Unable to locate an rss feed at this location.");
else res.type('xml').set('Content-Length', data.length).send(data);
if (err) {
res.type('text').send(404, "Unable to locate an rss feed at this location.");
} else {
res.type('xml').set('Content-Length', data.length).send(data);
}
});
};
if (!fs.existsSync(rssPath)) {
feed.updateCategory(cid, function (err) {
if (err) res.redirect('/404');
else loadFeed();
if (err) {
res.redirect('/404');
} else {
loadFeed();
}
});
} else loadFeed();
} else {
loadFeed();
}
return;
}
@@ -522,9 +583,11 @@ var express = require('express'),
categories.getCategoryById(cid, 0, function (err, categoryData) {
if (categoryData) {
if (categoryData.disabled === '1')
if (categoryData.disabled === '1') {
return next(new Error('Category disabled'), null);
}
}
next(err, categoryData);
});
},
@@ -561,14 +624,17 @@ var express = require('express'),
});
}
], function (err, data) {
if (err) return res.redirect('404');
if (err) {
return res.redirect('404');
}
var category_url = cid + (req.params.slug ? '/' + req.params.slug : '');
res.send(
data.header +
'\n\t<noscript>\n' + templates['noscript/header'] + templates['noscript/category'].parse(data.categories) + '\n\t</noscript>' +
'\n\t<script>templates.ready(function(){ajaxify.go("category/' + category_url + '");});</script>' +
templates['footer']
'\n\t<script>templates.ready(function(){ajaxify.go("category/' + category_url + '", undefined, undefined, true);});</script>' +
templates.footer
);
});
});
@@ -578,7 +644,7 @@ var express = require('express'),
req: req,
res: res
}, function (err, header) {
res.send(header + '<script>templates.ready(function(){ajaxify.go("confirm/' + req.params.code + '");});</script>' + templates['footer']);
res.send(header + '<script>templates.ready(function(){ajaxify.go("confirm/' + req.params.code + '", undefined, undefined, true);});</script>' + templates.footer);
});
});
@@ -597,22 +663,30 @@ var express = require('express'),
"Sitemap: " + nconf.get('url') + "sitemap.xml");
});
app.get('/cid/:cid', function (req, res) {
categories.getCategoryData(req.params.cid, function (err, data) {
if (data)
res.send(data);
else
res.send(404, "Category doesn't exist!");
});
});
app.get('/recent.rss', function(req, res) {
var rssPath = path.join(__dirname, '../', 'feeds/recent.rss'),
loadFeed = function () {
fs.readFile(rssPath, function (err, data) {
if (err) {
res.type('text').send(404, "Unable to locate an rss feed at this location.");
} else {
res.type('xml').set('Content-Length', data.length).send(data);
}
});
app.get('/tid/:tid', function (req, res) {
topics.getTopicData(req.params.tid, function (data) {
if (data)
res.send(data);
else
res.send(404, "Topic doesn't exist!");
});
};
if (!fs.existsSync(rssPath)) {
feed.updateRecent(function (err) {
if (err) {
res.redirect('/404');
} else {
loadFeed();
}
});
} else {
loadFeed();
}
});
app.get('/recent/:term?', function (req, res) {
@@ -621,22 +695,15 @@ var express = require('express'),
req: req,
res: res
}, function (err, header) {
res.send(header + app.create_route("recent/" + req.params.term, null, "recent") + templates['footer']);
res.send(header + app.create_route('recent/' + req.params.term, null, 'recent') + templates.footer);
});
});
app.get('/pid/:pid', function (req, res) {
posts.getPostData(req.params.pid, function (data) {
if (data)
res.send(data);
else
res.send(404, "Post doesn't exist!");
});
});
app.get('/outgoing', function (req, res) {
if (!req.query.url) return res.redirect('/404');
if (!req.query.url) {
return res.redirect('/404');
}
app.build_header({
req: req,
@@ -645,30 +712,21 @@ var express = require('express'),
res.send(
header +
'\n\t<script>templates.ready(function(){ajaxify.go("outgoing?url=' + encodeURIComponent(req.query.url) + '", null, null, true);});</script>' +
templates['footer']
templates.footer
);
});
});
app.get('/search', function (req, res) {
if (!req.user)
return res.redirect('/403');
app.build_header({
req: req,
res: res
}, function (err, header) {
res.send(header + app.create_route("search", null, "search") + templates['footer']);
});
});
app.get('/search/:term', function (req, res) {
if (!req.user)
if (!req.user) {
return res.redirect('/403');
}
app.build_header({
req: req,
res: res
}, function (err, header) {
res.send(header + app.create_route("search/" + req.params.term, null, "search") + templates['footer']);
res.send(header + app.create_route('search/' + req.params.term, null, 'search') + templates.footer);
});
});
@@ -713,7 +771,7 @@ var express = require('express'),
req: options.req,
res: options.res
}, function (err, header) {
res.send(header + options.content + templates['footer']);
res.send(header + options.content + templates.footer);
});
});
});
@@ -728,4 +786,4 @@ var express = require('express'),
}(WebServer));
global.server = server;
global.server = server;

View File

@@ -1,36 +1,37 @@
var cookie = require('cookie'),
var cookie = require('cookie'),
express = require('express'),
user = require('./user.js'),
Groups = require('./groups'),
posts = require('./posts.js'),
favourites = require('./favourites.js'),
utils = require('../public/src/utils.js'),
util = require('util'),
topics = require('./topics.js'),
categories = require('./categories.js'),
notifications = require('./notifications.js'),
threadTools = require('./threadTools.js'),
postTools = require('./postTools.js'),
meta = require('./meta.js'),
async = require('async'),
fs = require('fs'),
nconf = require('nconf'),
winston = require('winston'),
RedisStoreLib = require('connect-redis')(express),
RDB = require('./redis'),
util = require('util'),
logger = require('./logger.js'),
fs = require('fs'),
RedisStore = new RedisStoreLib({
client: RDB,
ttl: 60 * 60 * 24 * 14
}),
nconf = require('nconf'),
user = require('./user'),
Groups = require('./groups'),
posts = require('./posts'),
favourites = require('./favourites'),
utils = require('../public/src/utils'),
topics = require('./topics'),
categories = require('./categories'),
notifications = require('./notifications'),
threadTools = require('./threadTools'),
postTools = require('./postTools'),
meta = require('./meta'),
logger = require('./logger'),
socketCookieParser = express.cookieParser(nconf.get('secret')),
admin = {
'categories': require('./admin/categories.js'),
'user': require('./admin/user.js')
'categories': require('./admin/categories'),
'user': require('./admin/user')
},
plugins = require('./plugins'),
winston = require('winston');
plugins = require('./plugins');
var users = {},
@@ -43,8 +44,9 @@ module.exports.logoutUser = function(uid) {
userSockets[uid][i].emit('event:disconnect');
userSockets[uid][i].disconnect();
if(!userSockets[uid])
if(!userSockets[uid]) {
return;
}
}
}
}
@@ -56,8 +58,6 @@ module.exports.isUserOnline = isUserOnline;
module.exports.init = function(io) {
global.io = io;
io.sockets.on('connection', function(socket) {
var hs = socket.handshake,
sessionID, uid, lastPostTime = 0;
@@ -157,7 +157,7 @@ module.exports.init = function(io) {
for (var i = 0; i < clients.length; ++i) {
var hs = clients[i].handshake;
if (hs && clients[i].state.user.uid === 0) {
if (hs && clients[i].state && clients[i].state.user.uid === 0) {
++anonCount;
}
}
@@ -169,11 +169,11 @@ module.exports.init = function(io) {
var anonymousCount = getAnonymousCount(roomName);
if (uids.length === 0) {
io.sockets. in (roomName).emit('api:get_users_in_room', { users: [], anonymousCount:0 });
io.sockets. in (roomName).emit('api:get_users_in_room', { users: [], anonymousCount: anonymousCount });
} else {
user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture'], function(err, users) {
if(!err)
io.sockets. in (roomName).emit('api:get_users_in_room', { users: users, anonymousCount:anonymousCount });
io.sockets. in (roomName).emit('api:get_users_in_room', { users: users, anonymousCount: anonymousCount });
});
}
}
@@ -244,11 +244,7 @@ module.exports.init = function(io) {
});
socket.on('post.stats', function(data) {
posts.getTopicPostStats();
});
socket.on('user.latest', function(data) {
user.latest(socket, data);
emitTopicPostStats();
});
socket.on('user.email.exists', function(data) {
@@ -368,12 +364,25 @@ module.exports.init = function(io) {
posts.emitContentTooShortAlert(socket);
} else if (err.message === 'too-many-posts') {
posts.emitTooManyPostsAlert(socket);
} else {
socket.emit('event:alert', {
title: 'Error',
message: err.message,
type: 'warning',
timeout: 7500
});
}
return;
}
if (result) {
posts.getTopicPostStats();
io.sockets.in('category_' + data.category_id).emit('event:new_topic', result.topicData);
io.sockets.in('recent_posts').emit('event:new_topic', result.topicData);
io.sockets.in('user/' + uid).emit('event:new_post', {
posts: result.postData
});
emitTopicPostStats();
socket.emit('event:alert', {
title: 'Thank you for posting',
@@ -407,12 +416,12 @@ module.exports.init = function(io) {
return;
}
if (Date.now() - lastPostTime < meta.config.postDelay) {
if (Date.now() - lastPostTime < meta.config.postDelay * 1000) {
posts.emitTooManyPostsAlert(socket);
return;
}
posts.reply(data.topic_id, uid, data.content, function(err, result) {
posts.reply(data.topic_id, uid, data.content, function(err, postData) {
if(err) {
if(err.message === 'content-too-short') {
@@ -430,9 +439,9 @@ module.exports.init = function(io) {
return;
}
if (result) {
if (postData) {
lastPostTime = Date.now();
posts.getTopicPostStats();
emitTopicPostStats();
socket.emit('event:alert', {
title: 'Reply Successful',
@@ -440,6 +449,12 @@ module.exports.init = function(io) {
type: 'success',
timeout: 2000
});
var socketData = {
posts: [postData]
};
io.sockets.in('topic_' + postData.tid).emit('event:new_post', socketData);
io.sockets.in('recent_posts').emit('event:new_post', socketData);
io.sockets.in('user/' + postData.uid).emit('event:new_post', socketData);
}
@@ -480,42 +495,66 @@ module.exports.init = function(io) {
});
socket.on('api:topic.delete', function(data) {
threadTools.delete(data.tid, uid, function(err) {
if (!err) {
posts.getTopicPostStats();
socket.emit('api:topic.delete', {
status: 'ok',
tid: data.tid
threadTools.privileges(data.tid, uid, function(privileges) {
if (privileges.editable) {
threadTools.delete(data.tid, function(err) {
if (!err) {
emitTopicPostStats();
socket.emit('api:topic.delete', {
status: 'ok',
tid: data.tid
});
}
});
}
});
});
socket.on('api:topic.restore', function(data) {
threadTools.restore(data.tid, uid, socket, function(err) {
posts.getTopicPostStats();
threadTools.privileges(data.tid, uid, function(privileges) {
if (privileges.editable) {
threadTools.restore(data.tid, socket, function(err) {
emitTopicPostStats();
socket.emit('api:topic.restore', {
status: 'ok',
tid: data.tid
});
socket.emit('api:topic.restore', {
status: 'ok',
tid: data.tid
});
});
}
});
});
socket.on('api:topic.lock', function(data) {
threadTools.lock(data.tid, uid, socket);
threadTools.privileges(data.tid, uid, function(privileges) {
if (privileges.editable) {
threadTools.lock(data.tid, socket);
}
});
});
socket.on('api:topic.unlock', function(data) {
threadTools.unlock(data.tid, uid, socket);
threadTools.privileges(data.tid, uid, function(privileges) {
if (privileges.editable) {
threadTools.unlock(data.tid, socket);
}
});
});
socket.on('api:topic.pin', function(data) {
threadTools.pin(data.tid, uid, socket);
threadTools.privileges(data.tid, uid, function(privileges) {
if (privileges.editable) {
threadTools.pin(data.tid, socket);
}
});
});
socket.on('api:topic.unpin', function(data) {
threadTools.unpin(data.tid, uid, socket);
threadTools.privileges(data.tid, uid, function(privileges) {
if (privileges.editable) {
threadTools.unpin(data.tid, socket);
}
});
});
socket.on('api:topic.move', function(data) {
@@ -523,7 +562,7 @@ module.exports.init = function(io) {
});
socket.on('api:categories.get', function() {
categories.getAllCategories(function(categories) {
categories.getAllCategories(0, function(err, categories) {
socket.emit('api:categories.get', categories);
});
});
@@ -533,7 +572,7 @@ module.exports.init = function(io) {
});
socket.on('api:posts.getRawPost', function(data) {
posts.getPostField(data.pid, 'content', function(raw) {
posts.getPostField(data.pid, 'content', function(err, raw) {
socket.emit('api:posts.getRawPost', {
post: raw
});
@@ -560,15 +599,34 @@ module.exports.init = function(io) {
postTools.edit(uid, data.pid, data.title, data.content, data.images);
});
socket.on('api:posts.delete', function(data) {
postTools.delete(uid, data.pid, function() {
posts.getTopicPostStats();
socket.on('api:posts.delete', function(data, callback) {
postTools.delete(uid, data.pid, function(err) {
if(err) {
return callback(err);
}
emitTopicPostStats();
io.sockets.in('topic_' + data.tid).emit('event:post_deleted', {
pid: data.pid
});
callback(null);
});
});
socket.on('api:posts.restore', function(data) {
postTools.restore(uid, data.pid, function() {
posts.getTopicPostStats();
socket.on('api:posts.restore', function(data, callback) {
postTools.restore(uid, data.pid, function(err) {
if(err) {
return callback(err);
}
emitTopicPostStats();
io.sockets.in('topic_' + data.tid).emit('event:post_restored', {
pid: data.pid
});
callback(null);
});
});
@@ -584,7 +642,9 @@ module.exports.init = function(io) {
socket.on('api:notifications.mark_all_read', function(data, callback) {
notifications.mark_all_read(uid, function(err) {
if (!err) callback();
if (!err) {
callback();
}
});
});
@@ -677,7 +737,7 @@ module.exports.init = function(io) {
});
}
logger.monitorConfig(this, data);
logger.monitorConfig({io: io}, data);
});
});
@@ -688,7 +748,7 @@ module.exports.init = function(io) {
socket.on('api:composer.push', function(data) {
if (uid > 0 || meta.config.allowGuestPosting === '1') {
if (parseInt(data.tid) > 0) {
topics.getTopicData(data.tid, function(topicData) {
topics.getTopicData(data.tid, function(err, topicData) {
if (data.body)
topicData.body = data.body;
@@ -714,9 +774,7 @@ module.exports.init = function(io) {
async.parallel([
function(next) {
posts.getPostFields(data.pid, ['content'], function(raw) {
next(null, raw);
});
posts.getPostFields(data.pid, ['content'], next);
},
function(next) {
topics.getTitleByPid(data.pid, function(title) {
@@ -739,7 +797,7 @@ module.exports.init = function(io) {
});
socket.on('api:composer.editCheck', function(pid) {
posts.getPostField(pid, 'tid', function(tid) {
posts.getPostField(pid, 'tid', function(err, tid) {
postTools.isMain(pid, tid, function(isMain) {
socket.emit('api:composer.editCheck', {
titleEditable: isMain
@@ -800,8 +858,12 @@ module.exports.init = function(io) {
var start = data.after,
end = start + 9;
topics.getLatestTopics(uid, start, end, data.term, function(latestTopics) {
callback(latestTopics);
topics.getLatestTopics(uid, start, end, data.term, function(err, latestTopics) {
if (!err) {
callback(latestTopics);
} else {
winston.error('[socket api:topics.loadMoreRecentTopics] ' + err.message);
}
});
});
@@ -830,13 +892,13 @@ module.exports.init = function(io) {
});
socket.on('api:admin.topics.getMore', function(data, callback) {
topics.getAllTopics(data.limit, data.after, function(topics) {
topics.getAllTopics(data.limit, data.after, function(err, topics) {
callback(JSON.stringify(topics));
});
});
socket.on('api:admin.categories.create', function(data, callback) {
admin.categories.create(data, function(err, data) {
categories.create(data, function(err, data) {
callback(err, data);
});
});
@@ -955,4 +1017,33 @@ module.exports.init = function(io) {
socket.on('api:admin.theme.set', meta.themes.set);
});
function emitTopicPostStats() {
RDB.mget(['totaltopiccount', 'totalpostcount'], function(err, data) {
if (err) {
return winston.err(err);
}
var stats = {
topics: data[0] ? data[0] : 0,
posts: data[1] ? data[1] : 0
};
io.sockets.emit('post.stats', stats);
});
}
module.exports.emitUserCount = function() {
RDB.get('usercount', function(err, count) {
io.sockets.emit('user.count', {
count: count
});
});
};
module.exports.in = function(room) {
return io.sockets.in(room);
};
}