diff --git a/src/categories.js b/src/categories.js index df155d69f1..f630cd7ebb 100644 --- a/src/categories.js +++ b/src/categories.js @@ -252,26 +252,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); + }); }); }; @@ -372,19 +374,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 +382,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 +400,6 @@ var RDB = require('./redis.js'), return callback(err, null); } - callback(null, active); } ); diff --git a/src/favourites.js b/src/favourites.js index 6e6bd40990..5845646064 100644 --- a/src/favourites.js +++ b/src/favourites.js @@ -21,7 +21,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) { @@ -57,7 +57,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); diff --git a/src/postTools.js b/src/postTools.js index c7e5633121..899ce9fb9a 100644 --- a/src/postTools.js +++ b/src/postTools.js @@ -34,7 +34,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 +42,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 +87,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); @@ -132,7 +132,7 @@ 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) { @@ -150,7 +150,7 @@ var RDB = require('./redis.js'), 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); }); } @@ -174,7 +174,7 @@ 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); @@ -184,7 +184,7 @@ var RDB = require('./redis.js'), }); 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); }); }); diff --git a/src/posts.js b/src/posts.js index c4d914e9ab..6ae83c6b86 100644 --- a/src/posts.js +++ b/src/posts.js @@ -18,6 +18,176 @@ var RDB = require('./redis.js'), (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': '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); + + 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); + } + + 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); + + next(); + }); + } + ], function(err, results) { + if(err) { + return callback(err, null); + } + + callback(null, 'Reply successful'); + }); + }); + } + Posts.getPostsByTid = function(tid, start, end, callback) { RDB.lrange('tid:' + tid + ':posts', start, end, function(err, pids) { RDB.handle(err); @@ -86,7 +256,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(); @@ -148,35 +318,31 @@ var RDB = require('./redis.js'), 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]); }); } @@ -193,8 +359,8 @@ var RDB = require('./redis.js'), var posts = [], multi = RDB.multi(); - for(var x=0,numPids=pids.length;x 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); @@ -463,7 +502,7 @@ var RDB = require('./redis.js'), 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) { diff --git a/src/redis.js b/src/redis.js index 8b9ebbcdb8..6689158938 100644 --- a/src/redis.js +++ b/src/redis.js @@ -58,18 +58,17 @@ */ RedisDB.hmgetObject = function(key, fields, callback) { RedisDB.hmget(key, fields, function(err, data) { - if (err === null) { - var returnData = {}; + if(err) { + return callback(err, null); + } - for (var i = 0, ii = fields.length; i < ii; ++i) { - returnData[fields[i]] = data[i]; - } + var returnData = {}; - callback(null, returnData); - } else { - console.log(err); - callback(err, null); + for (var i = 0, ii = fields.length; i < ii; ++i) { + returnData[fields[i]] = data[i]; } + + callback(null, returnData); }); }; diff --git a/src/threadTools.js b/src/threadTools.js index c3f5297918..6a2b81c146 100644 --- a/src/threadTools.js +++ b/src/threadTools.js @@ -303,7 +303,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); }); diff --git a/src/topics.js b/src/topics.js index 2c070f2a7a..9a24db2d36 100644 --- a/src/topics.js +++ b/src/topics.js @@ -477,6 +477,9 @@ var RDB = require('./redis.js'), hasRead = results[1], teaser = results[2]; + topicData['pin-icon'] = topicData.pinned === '1' ? 'icon-pushpin' : 'none'; + topicData['lock-icon'] = topicData.locked === '1' ? 'icon-lock' : 'none'; + topicData.badgeclass = hasRead ? '' : 'badge-important'; topicData.teaser_text = teaser.text || ''; topicData.teaser_username = teaser.username || ''; @@ -550,15 +553,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) { @@ -624,36 +627,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) { - - user.getUserFields(postData.uid, ['username', 'userslug', 'picture'], function(err, userData) { - if (err) - return callback(err, null); - - 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 = ''; + 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')); + } + + user.getUserFields(postData.uid, ['username', 'userslug', 'picture'], function(err, userData) { + if (err) { + return callback(err, null); + } + + 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')); + }); }); } @@ -740,23 +751,26 @@ var RDB = require('./redis.js'), feed.updateCategory(category_id); - posts.create(uid, tid, content, function(postData) { - if (postData) { + 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); + // 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 - }); + // 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); - } + callback(null, postData); }); }); }); @@ -784,7 +798,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"); }); } @@ -806,7 +823,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; diff --git a/src/websockets.js b/src/websockets.js index e3db21d2ae..15193ea45c 100644 --- a/src/websockets.js +++ b/src/websockets.js @@ -368,6 +368,13 @@ 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; } @@ -533,7 +540,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 }); @@ -714,9 +721,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 +744,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