mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 02:55:58 +01:00 
			
		
		
		
	feat: #7743, posts/votes
This commit is contained in:
		| @@ -1,7 +1,5 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| var async = require('async'); |  | ||||||
|  |  | ||||||
| var meta = require('../meta'); | var meta = require('../meta'); | ||||||
| var db = require('../database'); | var db = require('../database'); | ||||||
| var user = require('../user'); | var user = require('../user'); | ||||||
| @@ -12,97 +10,80 @@ var privileges = require('../privileges'); | |||||||
| module.exports = function (Posts) { | module.exports = function (Posts) { | ||||||
| 	var votesInProgress = {}; | 	var votesInProgress = {}; | ||||||
|  |  | ||||||
| 	Posts.upvote = function (pid, uid, callback) { | 	Posts.upvote = async function (pid, uid) { | ||||||
| 		if (meta.config['reputation:disabled']) { | 		if (meta.config['reputation:disabled']) { | ||||||
| 			return callback(new Error('[[error:reputation-system-disabled]]')); | 			throw new Error('[[error:reputation-system-disabled]]'); | ||||||
| 		} | 		} | ||||||
|  | 		const canUpvote = await privileges.posts.can('posts:upvote', pid, uid); | ||||||
| 		async.waterfall([ |  | ||||||
| 			function (next) { |  | ||||||
| 				privileges.posts.can('posts:upvote', pid, uid, next); |  | ||||||
| 			}, |  | ||||||
| 			function (canUpvote, next) { |  | ||||||
| 		if (!canUpvote) { | 		if (!canUpvote) { | ||||||
| 					return next(new Error('[[error:no-privileges]]')); | 			throw new Error('[[error:no-privileges]]'); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (voteInProgress(pid, uid)) { | 		if (voteInProgress(pid, uid)) { | ||||||
| 					return next(new Error('[[error:already-voting-for-this-post]]')); | 			throw new Error('[[error:already-voting-for-this-post]]'); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		putVoteInProgress(pid, uid); | 		putVoteInProgress(pid, uid); | ||||||
|  |  | ||||||
| 				toggleVote('upvote', pid, uid, function (err, data) { | 		try { | ||||||
|  | 			const data = await toggleVote('upvote', pid, uid); | ||||||
|  | 			return data; | ||||||
|  | 		} finally { | ||||||
| 			clearVoteProgress(pid, uid); | 			clearVoteProgress(pid, uid); | ||||||
| 					next(err, data); | 		} | ||||||
| 				}); |  | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Posts.downvote = function (pid, uid, callback) { | 	Posts.downvote = async function (pid, uid) { | ||||||
| 		if (meta.config['reputation:disabled']) { | 		if (meta.config['reputation:disabled']) { | ||||||
| 			return callback(new Error('[[error:reputation-system-disabled]]')); | 			throw new Error('[[error:reputation-system-disabled]]'); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (meta.config['downvote:disabled']) { | 		if (meta.config['downvote:disabled']) { | ||||||
| 			return callback(new Error('[[error:downvoting-disabled]]')); | 			throw new Error('[[error:downvoting-disabled]]'); | ||||||
| 		} | 		} | ||||||
|  | 		const canDownvote = await privileges.posts.can('posts:downvote', pid, uid); | ||||||
| 		async.waterfall([ | 		if (!canDownvote) { | ||||||
| 			function (next) { | 			throw new Error('[[error:no-privileges]]'); | ||||||
| 				privileges.posts.can('posts:downvote', pid, uid, next); |  | ||||||
| 			}, |  | ||||||
| 			function (canUpvote, next) { |  | ||||||
| 				if (!canUpvote) { |  | ||||||
| 					return next(new Error('[[error:no-privileges]]')); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (voteInProgress(pid, uid)) { | 		if (voteInProgress(pid, uid)) { | ||||||
| 					return next(new Error('[[error:already-voting-for-this-post]]')); | 			throw new Error('[[error:already-voting-for-this-post]]'); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		putVoteInProgress(pid, uid); | 		putVoteInProgress(pid, uid); | ||||||
|  | 		try { | ||||||
| 				toggleVote('downvote', pid, uid, function (err, data) { | 			const data = toggleVote('downvote', pid, uid); | ||||||
|  | 			return data; | ||||||
|  | 		} finally { | ||||||
| 			clearVoteProgress(pid, uid); | 			clearVoteProgress(pid, uid); | ||||||
| 					next(err, data); | 		} | ||||||
| 				}); |  | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Posts.unvote = function (pid, uid, callback) { | 	Posts.unvote = async function (pid, uid) { | ||||||
| 		if (voteInProgress(pid, uid)) { | 		if (voteInProgress(pid, uid)) { | ||||||
| 			return callback(new Error('[[error:already-voting-for-this-post]]')); | 			throw new Error('[[error:already-voting-for-this-post]]'); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		putVoteInProgress(pid, uid); | 		putVoteInProgress(pid, uid); | ||||||
|  | 		try { | ||||||
| 		unvote(pid, uid, 'unvote', function (err, data) { | 			const data = await unvote(pid, uid, 'unvote'); | ||||||
|  | 			return data; | ||||||
|  | 		} finally { | ||||||
| 			clearVoteProgress(pid, uid); | 			clearVoteProgress(pid, uid); | ||||||
| 			callback(err, data); |  | ||||||
| 		}); |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	Posts.hasVoted = function (pid, uid, callback) { |  | ||||||
| 		if (parseInt(uid, 10) <= 0) { |  | ||||||
| 			return setImmediate(callback, null, { upvoted: false, downvoted: false }); |  | ||||||
| 		} | 		} | ||||||
| 		async.waterfall([ |  | ||||||
| 			function (next) { |  | ||||||
| 				db.isMemberOfSets(['pid:' + pid + ':upvote', 'pid:' + pid + ':downvote'], uid, next); |  | ||||||
| 			}, |  | ||||||
| 			function (hasVoted, next) { |  | ||||||
| 				next(null, { upvoted: hasVoted[0], downvoted: hasVoted[1] }); |  | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Posts.getVoteStatusByPostIDs = function (pids, uid, callback) { | 	Posts.hasVoted = async function (pid, uid) { | ||||||
| 		if (parseInt(uid, 10) <= 0) { | 		if (parseInt(uid, 10) <= 0) { | ||||||
| 			var data = pids.map(() => false); | 			return { upvoted: false, downvoted: false }; | ||||||
| 			return setImmediate(callback, null, { upvotes: data, downvotes: data }); | 		} | ||||||
|  | 		const hasVoted = await db.isMemberOfSets(['pid:' + pid + ':upvote', 'pid:' + pid + ':downvote'], uid); | ||||||
|  | 		return { upvoted: hasVoted[0], downvoted: hasVoted[1] }; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	Posts.getVoteStatusByPostIDs = async function (pids, uid) { | ||||||
|  | 		if (parseInt(uid, 10) <= 0) { | ||||||
|  | 			const data = pids.map(() => false); | ||||||
|  | 			return { upvotes: data, downvotes: data }; | ||||||
| 		} | 		} | ||||||
| 		var upvoteSets = []; | 		var upvoteSets = []; | ||||||
| 		var downvoteSets = []; | 		var downvoteSets = []; | ||||||
| @@ -111,24 +92,15 @@ module.exports = function (Posts) { | |||||||
| 			upvoteSets.push('pid:' + pids[i] + ':upvote'); | 			upvoteSets.push('pid:' + pids[i] + ':upvote'); | ||||||
| 			downvoteSets.push('pid:' + pids[i] + ':downvote'); | 			downvoteSets.push('pid:' + pids[i] + ':downvote'); | ||||||
| 		} | 		} | ||||||
| 		async.waterfall([ | 		const data = await db.isMemberOfSets(upvoteSets.concat(downvoteSets), uid); | ||||||
| 			function (next) { | 		return { | ||||||
| 				db.isMemberOfSets(upvoteSets.concat(downvoteSets), uid, next); |  | ||||||
| 			}, |  | ||||||
| 			function (data, next) { |  | ||||||
| 				next(null, { |  | ||||||
| 			upvotes: data.slice(0, pids.length), | 			upvotes: data.slice(0, pids.length), | ||||||
| 			downvotes: data.slice(pids.length, pids.length * 2), | 			downvotes: data.slice(pids.length, pids.length * 2), | ||||||
| 				}); | 		}; | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Posts.getUpvotedUidsByPids = function (pids, callback) { | 	Posts.getUpvotedUidsByPids = async function (pids) { | ||||||
| 		var sets = pids.map(function (pid) { | 		return await db.getSetsMembers(pids.map(pid => 'pid:' + pid + ':upvote')); | ||||||
| 			return 'pid:' + pid + ':upvote'; |  | ||||||
| 		}); |  | ||||||
| 		db.getSetsMembers(sets, callback); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	function voteInProgress(pid, uid) { | 	function voteInProgress(pid, uid) { | ||||||
| @@ -149,44 +121,26 @@ module.exports = function (Posts) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	function toggleVote(type, pid, uid, callback) { | 	async function toggleVote(type, pid, uid) { | ||||||
| 		async.waterfall([ | 		await unvote(pid, uid, type); | ||||||
| 			function (next) { | 		return await vote(type, false, pid, uid); | ||||||
| 				unvote(pid, uid, type, function (err) { |  | ||||||
| 					next(err); |  | ||||||
| 				}); |  | ||||||
| 			}, |  | ||||||
| 			function (next) { |  | ||||||
| 				vote(type, false, pid, uid, next); |  | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	function unvote(pid, uid, command, callback) { | 	async function unvote(pid, uid, command) { | ||||||
| 		async.waterfall([ | 		const [owner, voteStatus, reputation] = await Promise.all([ | ||||||
| 			function (next) { | 			Posts.getPostField(pid, 'uid'), | ||||||
| 				async.parallel({ | 			Posts.hasVoted(pid, uid), | ||||||
| 					owner: function (next) { | 			user.getUserField(uid, 'reputation'), | ||||||
| 						Posts.getPostField(pid, 'uid', next); | 		]); | ||||||
| 					}, |  | ||||||
| 					voteStatus: function (next) { | 		if (parseInt(uid, 10) === parseInt(owner, 10)) { | ||||||
| 						Posts.hasVoted(pid, uid, next); | 			throw new Error('[[error:self-vote]]'); | ||||||
| 					}, |  | ||||||
| 					reputation: function (next) { |  | ||||||
| 						user.getUserField(uid, 'reputation', next); |  | ||||||
| 					}, |  | ||||||
| 				}, next); |  | ||||||
| 			}, |  | ||||||
| 			function (results, next) { |  | ||||||
| 				if (parseInt(uid, 10) === parseInt(results.owner, 10)) { |  | ||||||
| 					return callback(new Error('[[error:self-vote]]')); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 				if (command === 'downvote' && results.reputation < meta.config['min:rep:downvote']) { | 		if (command === 'downvote' && reputation < meta.config['min:rep:downvote']) { | ||||||
| 					return callback(new Error('[[error:not-enough-reputation-to-downvote]]')); | 			throw new Error('[[error:not-enough-reputation-to-downvote]]'); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 				var voteStatus = results.voteStatus; |  | ||||||
| 		var hook; | 		var hook; | ||||||
| 		var current = voteStatus.upvoted ? 'upvote' : 'downvote'; | 		var current = voteStatus.upvoted ? 'upvote' : 'downvote'; | ||||||
|  |  | ||||||
| @@ -202,33 +156,24 @@ module.exports = function (Posts) { | |||||||
| 		plugins.fireHook('action:post.' + hook, { | 		plugins.fireHook('action:post.' + hook, { | ||||||
| 			pid: pid, | 			pid: pid, | ||||||
| 			uid: uid, | 			uid: uid, | ||||||
| 					owner: results.owner, | 			owner: owner, | ||||||
| 			current: current, | 			current: current, | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		if (!voteStatus || (!voteStatus.upvoted && !voteStatus.downvoted)) { | 		if (!voteStatus || (!voteStatus.upvoted && !voteStatus.downvoted)) { | ||||||
| 					return callback(); | 			return; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 				vote(voteStatus.upvoted ? 'downvote' : 'upvote', true, pid, uid, next); | 		return await vote(voteStatus.upvoted ? 'downvote' : 'upvote', true, pid, uid); | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	function vote(type, unvote, pid, uid, callback) { | 	async function vote(type, unvote, pid, uid) { | ||||||
| 		uid = parseInt(uid, 10); | 		uid = parseInt(uid, 10); | ||||||
|  |  | ||||||
| 		if (uid <= 0) { | 		if (uid <= 0) { | ||||||
| 			return callback(new Error('[[error:not-logged-in]]')); | 			throw new Error('[[error:not-logged-in]]'); | ||||||
| 		} | 		} | ||||||
| 		var postData; | 		const postData = await Posts.getPostFields(pid, ['pid', 'uid', 'tid']); | ||||||
| 		var newreputation; |  | ||||||
| 		async.waterfall([ |  | ||||||
| 			function (next) { |  | ||||||
| 				Posts.getPostFields(pid, ['pid', 'uid', 'tid'], next); |  | ||||||
| 			}, |  | ||||||
| 			function (_postData, next) { |  | ||||||
| 				postData = _postData; |  | ||||||
| 		var now = Date.now(); | 		var now = Date.now(); | ||||||
|  |  | ||||||
| 		if (type === 'upvote' && !unvote) { | 		if (type === 'upvote' && !unvote) { | ||||||
| @@ -243,66 +188,41 @@ module.exports = function (Posts) { | |||||||
| 			db.sortedSetAdd('uid:' + uid + ':downvote', now, pid); | 			db.sortedSetAdd('uid:' + uid + ':downvote', now, pid); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 				user[type === 'upvote' ? 'incrementUserFieldBy' : 'decrementUserFieldBy'](postData.uid, 'reputation', 1, next); | 		const newReputation = await user[type === 'upvote' ? 'incrementUserFieldBy' : 'decrementUserFieldBy'](postData.uid, 'reputation', 1); | ||||||
| 			}, |  | ||||||
| 			function (_newreputation, next) { |  | ||||||
| 				newreputation = _newreputation; |  | ||||||
| 		if (parseInt(postData.uid, 10)) { | 		if (parseInt(postData.uid, 10)) { | ||||||
| 					db.sortedSetAdd('users:reputation', newreputation, postData.uid); | 			db.sortedSetAdd('users:reputation', newReputation, postData.uid); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 				adjustPostVotes(postData, uid, type, unvote, next); | 		await adjustPostVotes(postData, uid, type, unvote); | ||||||
| 			}, |  | ||||||
| 			function (next) { | 		return { | ||||||
| 				next(null, { |  | ||||||
| 			user: { | 			user: { | ||||||
| 						reputation: newreputation, | 				reputation: newReputation, | ||||||
| 			}, | 			}, | ||||||
| 			fromuid: uid, | 			fromuid: uid, | ||||||
| 			post: postData, | 			post: postData, | ||||||
| 			upvote: type === 'upvote' && !unvote, | 			upvote: type === 'upvote' && !unvote, | ||||||
| 			downvote: type === 'downvote' && !unvote, | 			downvote: type === 'downvote' && !unvote, | ||||||
| 				}); | 		}; | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	function adjustPostVotes(postData, uid, type, unvote, callback) { | 	async function adjustPostVotes(postData, uid, type, unvote) { | ||||||
| 		var notType = (type === 'upvote' ? 'downvote' : 'upvote'); | 		var notType = (type === 'upvote' ? 'downvote' : 'upvote'); | ||||||
| 		async.waterfall([ |  | ||||||
| 			function (next) { |  | ||||||
| 				async.series([ |  | ||||||
| 					function (next) { |  | ||||||
| 		if (unvote) { | 		if (unvote) { | ||||||
| 							db.setRemove('pid:' + postData.pid + ':' + type, uid, next); | 			await db.setRemove('pid:' + postData.pid + ':' + type, uid); | ||||||
| 		} else { | 		} else { | ||||||
| 							db.setAdd('pid:' + postData.pid + ':' + type, uid, next); | 			await db.setAdd('pid:' + postData.pid + ':' + type, uid); | ||||||
| 		} | 		} | ||||||
| 					}, | 		await db.setRemove('pid:' + postData.pid + ':' + notType, uid); | ||||||
| 					function (next) { |  | ||||||
| 						db.setRemove('pid:' + postData.pid + ':' + notType, uid, next); | 		const [upvotes, downvotes] = await Promise.all([ | ||||||
| 					}, | 			db.setCount('pid:' + postData.pid + ':upvote'), | ||||||
| 				], function (err) { | 			db.setCount('pid:' + postData.pid + ':downvote'), | ||||||
| 					next(err); | 		]); | ||||||
| 				}); | 		postData.upvotes = upvotes; | ||||||
| 			}, | 		postData.downvotes = downvotes; | ||||||
| 			function (next) { |  | ||||||
| 				async.parallel({ |  | ||||||
| 					upvotes: function (next) { |  | ||||||
| 						db.setCount('pid:' + postData.pid + ':upvote', next); |  | ||||||
| 					}, |  | ||||||
| 					downvotes: function (next) { |  | ||||||
| 						db.setCount('pid:' + postData.pid + ':downvote', next); |  | ||||||
| 					}, |  | ||||||
| 				}, next); |  | ||||||
| 			}, |  | ||||||
| 			function (results, next) { |  | ||||||
| 				postData.upvotes = results.upvotes; |  | ||||||
| 				postData.downvotes = results.downvotes; |  | ||||||
| 		postData.votes = postData.upvotes - postData.downvotes; | 		postData.votes = postData.upvotes - postData.downvotes; | ||||||
| 				Posts.updatePostVoteCount(postData, next); | 		await Posts.updatePostVoteCount(postData); | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	Posts.updatePostVoteCount = async function (postData) { | 	Posts.updatePostVoteCount = async function (postData) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user