Files
NodeBB/src/topics/posts.js

446 lines
12 KiB
JavaScript
Raw Normal View History

2014-03-21 17:04:15 -04:00
'use strict';
2016-03-21 17:49:44 +02:00
var async = require('async');
2017-05-26 01:39:40 -06:00
var _ = require('lodash');
2016-03-21 17:49:44 +02:00
var validator = require('validator');
2014-03-21 17:04:15 -04:00
2016-03-21 17:49:44 +02:00
var db = require('../database');
var user = require('../user');
var posts = require('../posts');
var meta = require('../meta');
var plugins = require('../plugins');
2017-03-15 18:56:12 -04:00
var utils = require('../../public/src/utils');
2014-03-21 17:04:15 -04:00
module.exports = function (Topics) {
Topics.onNewPostMade = function (postData, callback) {
async.series([
function (next) {
Topics.updateLastPostTime(postData.tid, postData.timestamp, next);
2014-09-19 15:54:13 -04:00
},
function (next) {
2016-04-19 20:04:32 +03:00
Topics.addPostToTopic(postData.tid, postData, next);
2017-02-17 19:31:21 -07:00
},
2014-09-19 15:54:13 -04:00
], callback);
2014-03-21 17:04:15 -04:00
};
Topics.getTopicPosts = function (tid, set, start, stop, uid, reverse, callback) {
2017-02-23 22:42:45 +03:00
async.waterfall([
function (next) {
async.parallel({
posts: function (next) {
posts.getPostsFromSet(set, start, stop, uid, reverse, next);
},
postCount: function (next) {
Topics.getTopicField(tid, 'postcount', next);
},
2017-02-23 22:42:45 +03:00
}, next);
2017-02-17 19:31:21 -07:00
},
2017-02-23 22:42:45 +03:00
function (results, next) {
Topics.calculatePostIndices(results.posts, start, results.postCount, reverse);
2014-03-21 17:04:15 -04:00
2017-02-23 22:42:45 +03:00
Topics.addPostData(results.posts, uid, next);
},
2017-02-23 22:42:45 +03:00
], callback);
};
2014-03-21 17:04:15 -04:00
Topics.addPostData = function (postData, uid, callback) {
2014-11-11 19:47:56 -05:00
if (!Array.isArray(postData) || !postData.length) {
return callback(null, []);
}
2018-10-25 17:02:59 -04:00
var pids = postData.map(post => post && post.pid);
2014-08-30 11:56:29 -04:00
if (!Array.isArray(pids) || !pids.length) {
return callback(null, []);
}
2014-03-21 17:04:15 -04:00
2017-02-23 22:42:45 +03:00
function getPostUserData(field, method, callback) {
var uidsMap = {};
postData.forEach((post) => {
if (post && parseInt(post[field], 10) >= 0) {
uidsMap[post[field]] = 1;
}
2017-02-23 22:42:45 +03:00
});
const uids = Object.keys(uidsMap);
2018-11-12 00:20:44 -05:00
2017-02-23 22:42:45 +03:00
async.waterfall([
function (next) {
method(uids, next);
},
function (users, next) {
next(null, _.zipObject(uids, users));
},
2017-02-23 22:42:45 +03:00
], callback);
}
2017-02-23 22:42:45 +03:00
async.waterfall([
function (next) {
async.parallel({
bookmarks: function (next) {
posts.hasBookmarked(pids, uid, next);
},
voteData: function (next) {
posts.getVoteStatusByPostIDs(pids, uid, next);
},
userData: function (next) {
getPostUserData('uid', function (uids, next) {
posts.getUserInfoForPosts(uids, uid, next);
}, next);
},
editors: function (next) {
getPostUserData('editor', function (uids, next) {
user.getUsersFields(uids, ['uid', 'username', 'userslug'], next);
}, next);
},
parents: function (next) {
Topics.addParentPosts(postData, next);
},
2017-03-15 18:56:12 -04:00
replies: function (next) {
2017-03-17 13:35:24 -04:00
getPostReplies(pids, uid, next);
2017-03-15 18:56:12 -04:00
},
2017-02-23 22:42:45 +03:00
}, next);
},
function (results, next) {
postData.forEach(function (postObj, i) {
if (postObj) {
2018-10-25 17:02:59 -04:00
postObj.user = postObj.uid ? results.userData[postObj.uid] : _.clone(results.userData[postObj.uid]);
2017-02-23 22:42:45 +03:00
postObj.editor = postObj.editor ? results.editors[postObj.editor] : null;
postObj.bookmarked = results.bookmarks[i];
postObj.upvoted = results.voteData.upvotes[i];
postObj.downvoted = results.voteData.downvotes[i];
postObj.votes = postObj.votes || 0;
2017-03-15 18:56:12 -04:00
postObj.replies = results.replies[i];
2018-11-12 00:20:44 -05:00
postObj.selfPost = parseInt(uid, 10) > 0 && parseInt(uid, 10) === postObj.uid;
2017-02-23 22:42:45 +03:00
// Username override for guests, if enabled
if (meta.config.allowGuestHandles && postObj.uid === 0 && postObj.handle) {
2017-02-23 22:42:45 +03:00
postObj.user.username = validator.escape(String(postObj.handle));
}
}
});
2017-02-23 22:42:45 +03:00
plugins.fireHook('filter:topics.addPostData', {
posts: postData,
uid: uid,
2017-02-23 22:42:45 +03:00
}, next);
},
2017-02-23 22:42:45 +03:00
function (data, next) {
next(null, data.posts);
2017-02-17 19:31:21 -07:00
},
2017-02-23 22:42:45 +03:00
], callback);
2014-03-21 17:04:15 -04:00
};
Topics.modifyPostsByPrivilege = function (topicData, topicPrivileges) {
2018-11-12 00:20:44 -05:00
var loggedIn = parseInt(topicPrivileges.uid, 10) > 0;
topicData.posts.forEach(function (post) {
if (post) {
2016-08-09 09:50:49 -05:00
post.display_edit_tools = topicPrivileges.isAdminOrMod || (post.selfPost && topicPrivileges['posts:edit']);
post.display_delete_tools = topicPrivileges.isAdminOrMod || (post.selfPost && topicPrivileges['posts:delete']);
post.display_moderator_tools = post.display_edit_tools || post.display_delete_tools;
post.display_move_tools = topicPrivileges.isAdminOrMod && post.index !== 0;
post.display_post_menu = topicPrivileges.isAdminOrMod ||
(post.selfPost && !topicData.locked && !post.deleted) ||
(post.selfPost && post.deleted && parseInt(post.deleterUid, 10) === parseInt(topicPrivileges.uid, 10)) ||
((loggedIn || topicData.postSharing.length) && !post.deleted);
2016-03-08 19:06:59 +02:00
post.ip = topicPrivileges.isAdminOrMod ? post.ip : undefined;
2018-06-08 16:01:11 -04:00
posts.modifyPostByPrivilege(post, topicPrivileges);
}
});
};
Topics.addParentPosts = function (postData, callback) {
var parentPids = postData.map(function (postObj) {
2015-09-29 16:36:37 -04:00
return postObj && postObj.hasOwnProperty('toPid') ? parseInt(postObj.toPid, 10) : null;
}).filter(Boolean);
if (!parentPids.length) {
2018-11-12 00:20:44 -05:00
return setImmediate(callback);
2015-09-29 16:36:37 -04:00
}
2018-12-17 18:56:09 -05:00
parentPids = _.uniq(parentPids);
2015-09-29 16:36:37 -04:00
var parentPosts;
async.waterfall([
async.apply(posts.getPostsFields, parentPids, ['uid']),
function (_parentPosts, next) {
2015-09-29 16:36:37 -04:00
parentPosts = _parentPosts;
2018-11-12 00:20:44 -05:00
var parentUids = _.uniq(parentPosts.map(postObj => postObj && postObj.uid));
2015-09-29 16:36:37 -04:00
user.getUsersFields(parentUids, ['username'], next);
},
function (userData, next) {
var usersMap = {};
userData.forEach(function (user) {
2015-09-29 16:36:37 -04:00
usersMap[user.uid] = user.username;
});
var parents = {};
parentPosts.forEach(function (post, i) {
2017-02-18 12:30:49 -07:00
parents[parentPids[i]] = { username: usersMap[post.uid] };
2015-09-29 16:36:37 -04:00
});
postData.forEach(function (post) {
2015-09-29 16:36:37 -04:00
post.parent = parents[post.toPid];
});
next();
2017-02-17 19:31:21 -07:00
},
2015-09-29 16:36:37 -04:00
], callback);
};
Topics.calculatePostIndices = function (posts, start, postCount, reverse) {
posts.forEach(function (post, index) {
if (reverse) {
2015-09-15 17:19:03 -04:00
post.index = postCount - (start + index + 1);
} else {
2015-09-15 17:19:03 -04:00
post.index = start + index + 1;
}
2015-09-15 17:19:03 -04:00
});
};
Topics.getLatestUndeletedPid = function (tid, callback) {
async.waterfall([
function (next) {
Topics.getLatestUndeletedReply(tid, next);
},
function (pid, next) {
2018-10-23 21:36:00 -04:00
if (pid) {
return callback(null, pid);
}
Topics.getTopicField(tid, 'mainPid', next);
},
function (mainPid, next) {
posts.getPostFields(mainPid, ['pid', 'deleted'], next);
},
function (mainPost, next) {
2018-10-23 21:36:00 -04:00
next(null, mainPost.pid && !mainPost.deleted ? mainPost.pid : null);
2017-02-17 19:31:21 -07:00
},
], callback);
2014-04-24 20:05:05 -04:00
};
Topics.getLatestUndeletedReply = function (tid, callback) {
var isDeleted = false;
var done = false;
var latestPid = null;
var index = 0;
2017-02-23 22:42:45 +03:00
var pids;
async.doWhilst(
function (next) {
2017-02-23 22:42:45 +03:00
async.waterfall([
function (_next) {
db.getSortedSetRevRange('tid:' + tid + ':posts', index, index, _next);
},
function (_pids, _next) {
pids = _pids;
2017-05-27 00:30:07 -04:00
if (!pids.length) {
2017-02-23 22:42:45 +03:00
done = true;
return next();
}
2017-02-23 22:42:45 +03:00
posts.getPostField(pids[0], 'deleted', _next);
},
function (deleted, _next) {
2018-10-25 17:02:59 -04:00
isDeleted = deleted;
if (!isDeleted) {
latestPid = pids[0];
}
index += 1;
2017-02-23 22:42:45 +03:00
_next();
},
2017-02-23 22:42:45 +03:00
], next);
},
function (next) {
next(null, isDeleted && !done);
},
function (err) {
2018-10-23 21:36:00 -04:00
callback(err, parseInt(latestPid, 10));
2017-02-17 20:20:42 -07:00
}
);
2014-04-24 20:05:05 -04:00
};
Topics.addPostToTopic = function (tid, postData, callback) {
2016-04-19 20:04:32 +03:00
async.waterfall([
function (next) {
Topics.getTopicField(tid, 'mainPid', next);
},
function (mainPid, next) {
if (!parseInt(mainPid, 10)) {
Topics.setTopicField(tid, 'mainPid', postData.pid, next);
} else {
2019-06-19 20:01:20 -04:00
const upvotes = parseInt(postData.upvotes, 10) || 0;
const downvotes = parseInt(postData.downvotes, 10) || 0;
const votes = upvotes - downvotes;
db.sortedSetsAdd([
'tid:' + tid + ':posts', 'tid:' + tid + ':posts:votes',
], [postData.timestamp, votes], postData.pid, next);
2016-04-19 20:04:32 +03:00
}
},
function (next) {
Topics.increasePostCount(tid, next);
},
2016-04-19 20:04:32 +03:00
function (next) {
db.sortedSetIncrBy('tid:' + tid + ':posters', 1, postData.uid, next);
},
function (count, next) {
Topics.updateTeaser(tid, next);
2017-02-17 19:31:21 -07:00
},
2016-04-19 20:04:32 +03:00
], callback);
2014-03-21 17:04:15 -04:00
};
Topics.removePostFromTopic = function (tid, postData, callback) {
2016-04-19 20:04:32 +03:00
async.waterfall([
function (next) {
db.sortedSetsRemove([
'tid:' + tid + ':posts',
2017-02-17 19:31:21 -07:00
'tid:' + tid + ':posts:votes',
2016-04-19 20:04:32 +03:00
], postData.pid, next);
},
function (next) {
Topics.decreasePostCount(tid, next);
},
function (next) {
2016-04-19 20:04:32 +03:00
db.sortedSetIncrBy('tid:' + tid + ':posters', -1, postData.uid, next);
},
function (count, next) {
Topics.updateTeaser(tid, next);
2017-02-17 19:31:21 -07:00
},
2016-04-19 20:04:32 +03:00
], callback);
2014-03-21 17:04:15 -04:00
};
Topics.getPids = function (tid, callback) {
2017-02-23 22:42:45 +03:00
async.waterfall([
function (next) {
async.parallel({
mainPid: function (next) {
Topics.getTopicField(tid, 'mainPid', next);
},
pids: function (next) {
db.getSortedSetRange('tid:' + tid + ':posts', 0, -1, next);
},
2017-02-23 22:42:45 +03:00
}, next);
},
2017-02-23 22:42:45 +03:00
function (results, next) {
2017-11-30 12:39:03 -05:00
if (parseInt(results.mainPid, 10)) {
2017-02-23 22:42:45 +03:00
results.pids = [results.mainPid].concat(results.pids);
}
next(null, results.pids);
2017-02-17 19:31:21 -07:00
},
2017-02-23 22:42:45 +03:00
], callback);
2014-03-21 17:04:15 -04:00
};
Topics.increasePostCount = function (tid, callback) {
2014-03-21 17:04:15 -04:00
incrementFieldAndUpdateSortedSet(tid, 'postcount', 1, 'topics:posts', callback);
};
Topics.decreasePostCount = function (tid, callback) {
2014-03-21 17:04:15 -04:00
incrementFieldAndUpdateSortedSet(tid, 'postcount', -1, 'topics:posts', callback);
};
Topics.increaseViewCount = function (tid, callback) {
2014-03-21 17:04:15 -04:00
incrementFieldAndUpdateSortedSet(tid, 'viewcount', 1, 'topics:views', callback);
};
function incrementFieldAndUpdateSortedSet(tid, field, by, set, callback) {
callback = callback || function () {};
2017-02-23 22:42:45 +03:00
async.waterfall([
function (next) {
db.incrObjectFieldBy('topic:' + tid, field, by, next);
},
function (value, next) {
db.sortedSetAdd(set, value, tid, next);
},
2017-02-23 22:42:45 +03:00
], callback);
2014-03-21 17:04:15 -04:00
}
Topics.getTitleByPid = function (pid, callback) {
2014-03-21 17:04:15 -04:00
Topics.getTopicFieldByPid('title', pid, callback);
};
Topics.getTopicFieldByPid = function (field, pid, callback) {
2017-02-23 22:42:45 +03:00
async.waterfall([
function (next) {
posts.getPostField(pid, 'tid', next);
},
function (tid, next) {
Topics.getTopicField(tid, field, next);
},
2017-02-23 22:42:45 +03:00
], callback);
2014-03-21 17:04:15 -04:00
};
Topics.getTopicDataByPid = function (pid, callback) {
2017-02-23 22:42:45 +03:00
async.waterfall([
function (next) {
posts.getPostField(pid, 'tid', next);
},
function (tid, next) {
Topics.getTopicData(tid, next);
},
2017-02-23 22:42:45 +03:00
], callback);
2014-03-21 17:04:15 -04:00
};
Topics.getPostCount = function (tid, callback) {
2014-10-14 23:12:47 -04:00
db.getObjectField('topic:' + tid, 'postcount', callback);
2014-06-02 20:41:03 -04:00
};
2017-03-15 18:56:12 -04:00
function getPostReplies(pids, callerUid, callback) {
2017-07-10 00:28:15 -04:00
var arrayOfReplyPids;
var replyData;
var uniqueUids;
var uniquePids;
async.waterfall([
function (next) {
2018-11-12 00:20:44 -05:00
const keys = pids.map(pid => 'pid:' + pid + ':replies');
2017-07-10 00:28:15 -04:00
db.getSortedSetsMembers(keys, next);
},
function (arrayOfPids, next) {
arrayOfReplyPids = arrayOfPids;
2017-03-15 18:56:12 -04:00
2017-07-10 00:28:15 -04:00
uniquePids = _.uniq(_.flatten(arrayOfPids));
posts.getPostsFields(uniquePids, ['pid', 'uid', 'timestamp'], next);
},
function (_replyData, next) {
replyData = _replyData;
2018-11-12 00:20:44 -05:00
const uids = replyData.map(replyData => replyData && replyData.uid);
2017-07-10 00:28:15 -04:00
uniqueUids = _.uniq(uids);
user.getUsersWithFields(uniqueUids, ['uid', 'username', 'userslug', 'picture'], callerUid, next);
},
function (userData, next) {
var uidMap = _.zipObject(uniqueUids, userData);
var pidMap = _.zipObject(uniquePids, replyData);
var returnData = arrayOfReplyPids.map(function (replyPids) {
var uidsUsed = {};
var currentData = {
hasMore: false,
users: [],
text: replyPids.length > 1 ? '[[topic:replies_to_this_post, ' + replyPids.length + ']]' : '[[topic:one_reply_to_this_post]]',
count: replyPids.length,
timestampISO: replyPids.length ? utils.toISOString(pidMap[replyPids[0]].timestamp) : undefined,
};
2017-07-10 00:37:48 -04:00
replyPids.sort(function (a, b) {
2017-07-10 00:28:15 -04:00
return parseInt(a, 10) - parseInt(b, 10);
});
replyPids.forEach(function (replyPid) {
var replyData = pidMap[replyPid];
if (!uidsUsed[replyData.uid] && currentData.users.length < 6) {
currentData.users.push(uidMap[replyData.uid]);
uidsUsed[replyData.uid] = true;
}
});
if (currentData.users.length > 5) {
currentData.users.pop();
2017-07-10 00:37:48 -04:00
currentData.hasMore = true;
}
2017-07-10 00:28:15 -04:00
return currentData;
});
next(null, returnData);
},
], callback);
2017-03-15 18:56:12 -04:00
}
2014-03-21 17:04:15 -04:00
};