mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 11:05:54 +01:00 
			
		
		
		
	feat: #7743, posts module
This commit is contained in:
		| @@ -1,99 +1,68 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| var async = require('async'); | const db = require('../database'); | ||||||
|  | const plugins = require('../plugins'); | ||||||
| var db = require('../database'); |  | ||||||
| var plugins = require('../plugins'); |  | ||||||
|  |  | ||||||
| module.exports = function (Posts) { | module.exports = function (Posts) { | ||||||
| 	Posts.bookmark = function (pid, uid, callback) { | 	Posts.bookmark = async function (pid, uid) { | ||||||
| 		toggleBookmark('bookmark', pid, uid, callback); | 		return await toggleBookmark('bookmark', pid, uid); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Posts.unbookmark = function (pid, uid, callback) { | 	Posts.unbookmark = async function (pid, uid) { | ||||||
| 		toggleBookmark('unbookmark', pid, uid, callback); | 		return await toggleBookmark('unbookmark', pid, uid); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	function toggleBookmark(type, pid, uid, callback) { | 	async function toggleBookmark(type, pid, uid) { | ||||||
| 		if (parseInt(uid, 10) <= 0) { | 		if (parseInt(uid, 10) <= 0) { | ||||||
| 			return callback(new Error('[[error:not-logged-in]]')); | 			throw new Error('[[error:not-logged-in]]'); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		var isBookmarking = type === 'bookmark'; | 		const isBookmarking = type === 'bookmark'; | ||||||
| 		var postData; |  | ||||||
| 		var hasBookmarked; | 		const [postData, hasBookmarked] = await Promise.all([ | ||||||
| 		var owner; | 			Posts.getPostFields(pid, ['pid', 'uid']), | ||||||
| 		async.waterfall([ | 			Posts.hasBookmarked(pid, uid), | ||||||
| 			function (next) { | 		]); | ||||||
| 				async.parallel({ |  | ||||||
| 					owner: function (next) { |  | ||||||
| 						Posts.getPostField(pid, 'uid', next); |  | ||||||
| 					}, |  | ||||||
| 					postData: function (next) { |  | ||||||
| 						Posts.getPostFields(pid, ['pid', 'uid'], next); |  | ||||||
| 					}, |  | ||||||
| 					hasBookmarked: function (next) { |  | ||||||
| 						Posts.hasBookmarked(pid, uid, next); |  | ||||||
| 					}, |  | ||||||
| 				}, next); |  | ||||||
| 			}, |  | ||||||
| 			function (results, next) { |  | ||||||
| 				owner = results.owner; |  | ||||||
| 				postData = results.postData; |  | ||||||
| 				hasBookmarked = results.hasBookmarked; |  | ||||||
|  |  | ||||||
| 		if (isBookmarking && hasBookmarked) { | 		if (isBookmarking && hasBookmarked) { | ||||||
| 					return callback(new Error('[[error:already-bookmarked]]')); | 			throw new Error('[[error:already-bookmarked]]'); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (!isBookmarking && !hasBookmarked) { | 		if (!isBookmarking && !hasBookmarked) { | ||||||
| 					return callback(new Error('[[error:already-unbookmarked]]')); | 			throw new Error('[[error:already-unbookmarked]]'); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (isBookmarking) { | 		if (isBookmarking) { | ||||||
| 					db.sortedSetAdd('uid:' + uid + ':bookmarks', Date.now(), pid, next); | 			await db.sortedSetAdd('uid:' + uid + ':bookmarks', Date.now(), pid); | ||||||
| 		} else { | 		} else { | ||||||
| 					db.sortedSetRemove('uid:' + uid + ':bookmarks', pid, next); | 			await db.sortedSetRemove('uid:' + uid + ':bookmarks', pid); | ||||||
| 		} | 		} | ||||||
| 			}, | 		await db[isBookmarking ? 'setAdd' : 'setRemove']('pid:' + pid + ':users_bookmarked', uid); | ||||||
| 			function (next) { | 		postData.bookmarks = await db.setCount('pid:' + pid + ':users_bookmarked'); | ||||||
| 				db[isBookmarking ? 'setAdd' : 'setRemove']('pid:' + pid + ':users_bookmarked', uid, next); | 		await Posts.setPostField(pid, 'bookmarks', postData.bookmarks); | ||||||
| 			}, |  | ||||||
| 			function (next) { |  | ||||||
| 				db.setCount('pid:' + pid + ':users_bookmarked', next); |  | ||||||
| 			}, |  | ||||||
| 			function (count, next) { |  | ||||||
| 				postData.bookmarks = count; |  | ||||||
| 				Posts.setPostField(pid, 'bookmarks', count, next); |  | ||||||
| 			}, |  | ||||||
| 			function (next) { |  | ||||||
| 				var current = hasBookmarked ? 'bookmarked' : 'unbookmarked'; |  | ||||||
|  |  | ||||||
| 		plugins.fireHook('action:post.' + type, { | 		plugins.fireHook('action:post.' + type, { | ||||||
| 			pid: pid, | 			pid: pid, | ||||||
| 			uid: uid, | 			uid: uid, | ||||||
| 					owner: owner, | 			owner: postData.uid, | ||||||
| 					current: current, | 			current: hasBookmarked ? 'bookmarked' : 'unbookmarked', | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 				next(null, { | 		return { | ||||||
| 			post: postData, | 			post: postData, | ||||||
| 			isBookmarked: isBookmarking, | 			isBookmarked: isBookmarking, | ||||||
| 				}); | 		}; | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	Posts.hasBookmarked = function (pid, uid, callback) { | 	Posts.hasBookmarked = async function (pid, uid) { | ||||||
| 		if (parseInt(uid, 10) <= 0) { | 		if (parseInt(uid, 10) <= 0) { | ||||||
| 			return callback(null, Array.isArray(pid) ? pid.map(() => false) : false); | 			return Array.isArray(pid) ? pid.map(() => false) : false; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (Array.isArray(pid)) { | 		if (Array.isArray(pid)) { | ||||||
| 			var sets = pid.map(pid => 'pid:' + pid + ':users_bookmarked'); | 			const sets = pid.map(pid => 'pid:' + pid + ':users_bookmarked'); | ||||||
| 			db.isMemberOfSets(sets, uid, callback); | 			return await db.isMemberOfSets(sets, uid); | ||||||
| 		} else { |  | ||||||
| 			db.isSetMember('pid:' + pid + ':users_bookmarked', uid, callback); |  | ||||||
| 		} | 		} | ||||||
|  | 		return await db.isSetMember('pid:' + pid + ':users_bookmarked', uid); | ||||||
| 	}; | 	}; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,83 +1,41 @@ | |||||||
|  |  | ||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| var async = require('async'); |  | ||||||
| var _ = require('lodash'); |  | ||||||
|  |  | ||||||
| var db = require('../database'); | const _ = require('lodash'); | ||||||
| var topics = require('../topics'); |  | ||||||
|  | const db = require('../database'); | ||||||
|  | const topics = require('../topics'); | ||||||
|  |  | ||||||
| module.exports = function (Posts) { | module.exports = function (Posts) { | ||||||
| 	Posts.getCidByPid = function (pid, callback) { | 	Posts.getCidByPid = async function (pid) { | ||||||
| 		async.waterfall([ | 		const tid = await Posts.getPostField(pid, 'tid'); | ||||||
| 			function (next) { | 		return await topics.getTopicField(tid, 'cid'); | ||||||
| 				Posts.getPostField(pid, 'tid', next); |  | ||||||
| 			}, |  | ||||||
| 			function (tid, next) { |  | ||||||
| 				topics.getTopicField(tid, 'cid', next); |  | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Posts.getCidsByPids = function (pids, callback) { | 	Posts.getCidsByPids = async function (pids) { | ||||||
| 		var tids; | 		const postData = await Posts.getPostsFields(pids, ['tid']); | ||||||
| 		var postData; | 		const tids = _.uniq(postData.map(post => post && post.tid).filter(Boolean)); | ||||||
| 		async.waterfall([ | 		const topicData = await topics.getTopicsFields(tids, ['cid']); | ||||||
| 			function (next) { | 		const tidToTopic = _.zipObject(tids, topicData); | ||||||
| 				Posts.getPostsFields(pids, ['tid'], next); | 		const cids = postData.map(post => tidToTopic[post.tid].cid); | ||||||
| 			}, | 		return cids; | ||||||
| 			function (_postData, next) { |  | ||||||
| 				postData = _postData; |  | ||||||
| 				tids = _.uniq(postData.map(post => post && post.tid).filter(Boolean)); |  | ||||||
|  |  | ||||||
| 				topics.getTopicsFields(tids, ['cid'], next); |  | ||||||
| 			}, |  | ||||||
| 			function (topicData, next) { |  | ||||||
| 				var map = {}; |  | ||||||
| 				topicData.forEach(function (topic, index) { |  | ||||||
| 					if (topic) { |  | ||||||
| 						map[tids[index]] = topic.cid; |  | ||||||
| 					} |  | ||||||
| 				}); |  | ||||||
|  |  | ||||||
| 				var cids = postData.map(post => map[post.tid]); |  | ||||||
| 				next(null, cids); |  | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Posts.filterPidsByCid = function (pids, cid, callback) { | 	Posts.filterPidsByCid = async function (pids, cid) { | ||||||
| 		if (!cid) { | 		if (!cid) { | ||||||
| 			return setImmediate(callback, null, pids); | 			return pids; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (!Array.isArray(cid) || cid.length === 1) { | 		if (!Array.isArray(cid) || cid.length === 1) { | ||||||
| 			return filterPidsBySingleCid(pids, cid, callback); | 			return await filterPidsBySingleCid(pids, cid); | ||||||
| 		} | 		} | ||||||
|  | 		const pidsArr = await Promise.all(cid.map(c => Posts.filterPidsByCid(pids, c))); | ||||||
| 		async.waterfall([ | 		return _.union.apply(_, pidsArr); | ||||||
| 			function (next) { |  | ||||||
| 				async.map(cid, function (cid, next) { |  | ||||||
| 					Posts.filterPidsByCid(pids, cid, next); |  | ||||||
| 				}, next); |  | ||||||
| 			}, |  | ||||||
| 			function (pidsArr, next) { |  | ||||||
| 				next(null, _.union.apply(_, pidsArr)); |  | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	function filterPidsBySingleCid(pids, cid, callback) { | 	async function filterPidsBySingleCid(pids, cid) { | ||||||
| 		async.waterfall([ | 		const isMembers = await db.isSortedSetMembers('cid:' + parseInt(cid, 10) + ':pids', pids); | ||||||
| 			function (next) { | 		return pids.filter((pid, index) => pid && isMembers[index]); | ||||||
| 				db.isSortedSetMembers('cid:' + parseInt(cid, 10) + ':pids', pids, next); |  | ||||||
| 			}, |  | ||||||
| 			function (isMembers, next) { |  | ||||||
| 				pids = pids.filter(function (pid, index) { |  | ||||||
| 					return pid && isMembers[index]; |  | ||||||
| 				}); |  | ||||||
| 				next(null, pids); |  | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| var async = require('async'); |  | ||||||
| var _ = require('lodash'); | var _ = require('lodash'); | ||||||
|  |  | ||||||
| var meta = require('../meta'); | var meta = require('../meta'); | ||||||
| @@ -13,30 +12,24 @@ var groups = require('../groups'); | |||||||
| var utils = require('../utils'); | var utils = require('../utils'); | ||||||
|  |  | ||||||
| module.exports = function (Posts) { | module.exports = function (Posts) { | ||||||
| 	Posts.create = function (data, callback) { | 	Posts.create = async function (data) { | ||||||
| 		// This is an internal method, consider using Topics.reply instead | 		// This is an internal method, consider using Topics.reply instead | ||||||
| 		var uid = data.uid; | 		const uid = data.uid; | ||||||
| 		var tid = data.tid; | 		const tid = data.tid; | ||||||
| 		var content = data.content.toString(); | 		const content = data.content.toString(); | ||||||
| 		var timestamp = data.timestamp || Date.now(); | 		const timestamp = data.timestamp || Date.now(); | ||||||
| 		var isMain = data.isMain || false; | 		const isMain = data.isMain || false; | ||||||
|  |  | ||||||
| 		if (!uid && parseInt(uid, 10) !== 0) { | 		if (!uid && parseInt(uid, 10) !== 0) { | ||||||
| 			return callback(new Error('[[error:invalid-uid]]')); | 			throw new Error('[[error:invalid-uid]]'); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (data.toPid && !utils.isNumber(data.toPid)) { | 		if (data.toPid && !utils.isNumber(data.toPid)) { | ||||||
| 			return callback(new Error('[[error:invalid-pid]]')); | 			throw new Error('[[error:invalid-pid]]'); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		var postData; | 		const pid = await db.incrObjectField('global', 'nextPid'); | ||||||
|  | 		let postData = { | ||||||
| 		async.waterfall([ |  | ||||||
| 			function (next) { |  | ||||||
| 				db.incrObjectField('global', 'nextPid', next); |  | ||||||
| 			}, |  | ||||||
| 			function (pid, next) { |  | ||||||
| 				postData = { |  | ||||||
| 			pid: pid, | 			pid: pid, | ||||||
| 			uid: uid, | 			uid: uid, | ||||||
| 			tid: tid, | 			tid: tid, | ||||||
| @@ -48,67 +41,44 @@ module.exports = function (Posts) { | |||||||
| 		if (data.toPid) { | 		if (data.toPid) { | ||||||
| 			postData.toPid = data.toPid; | 			postData.toPid = data.toPid; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (data.ip && meta.config.trackIpPerPost) { | 		if (data.ip && meta.config.trackIpPerPost) { | ||||||
| 			postData.ip = data.ip; | 			postData.ip = data.ip; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (data.handle && !parseInt(uid, 10)) { | 		if (data.handle && !parseInt(uid, 10)) { | ||||||
| 			postData.handle = data.handle; | 			postData.handle = data.handle; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 				plugins.fireHook('filter:post.create', { post: postData, data: data }, next); | 		let result = await plugins.fireHook('filter:post.create', { post: postData, data: data }); | ||||||
| 			}, | 		postData = result.post; | ||||||
| 			function (data, next) { | 		await db.setObject('post:' + postData.pid, postData); | ||||||
| 				postData = data.post; |  | ||||||
| 				db.setObject('post:' + postData.pid, postData, next); | 		const topicData = await topics.getTopicFields(tid, ['cid', 'pinned']); | ||||||
| 			}, |  | ||||||
| 			function (next) { |  | ||||||
| 				topics.getTopicFields(tid, ['cid', 'pinned'], next); |  | ||||||
| 			}, |  | ||||||
| 			function (topicData, next) { |  | ||||||
| 		postData.cid = topicData.cid; | 		postData.cid = topicData.cid; | ||||||
| 				async.parallel([ |  | ||||||
| 					function (next) { | 		await Promise.all([ | ||||||
| 						user.onNewPostMade(postData, next); | 			db.sortedSetAdd('posts:pid', timestamp, postData.pid), | ||||||
| 					}, | 			db.incrObjectField('global', 'postCount'), | ||||||
| 					function (next) { | 			user.onNewPostMade(postData), | ||||||
| 						topics.onNewPostMade(postData, next); | 			topics.onNewPostMade(postData), | ||||||
| 					}, | 			categories.onNewPostMade(topicData.cid, topicData.pinned, postData), | ||||||
| 					function (next) { | 			groups.onNewPostMade(postData), | ||||||
| 						categories.onNewPostMade(topicData.cid, topicData.pinned, postData, next); | 			addReplyTo(postData, timestamp), | ||||||
| 					}, | 			Posts.uploads.sync(postData.pid), | ||||||
| 					function (next) { | 		]); | ||||||
| 						groups.onNewPostMade(postData, next); |  | ||||||
| 					}, | 		result = await plugins.fireHook('filter:post.get', { post: postData, uid: data.uid }); | ||||||
| 					function (next) { | 		result.post.isMain = isMain; | ||||||
| 						db.sortedSetAdd('posts:pid', timestamp, postData.pid, next); | 		plugins.fireHook('action:post.save', { post: _.clone(result.post) }); | ||||||
| 					}, | 		return result.post; | ||||||
| 					function (next) { | 	}; | ||||||
|  |  | ||||||
|  | 	async function addReplyTo(postData, timestamp) { | ||||||
| 		if (!postData.toPid) { | 		if (!postData.toPid) { | ||||||
| 							return next(null); | 			return; | ||||||
|  | 		} | ||||||
|  | 		await Promise.all([ | ||||||
|  | 			db.sortedSetAdd('pid:' + postData.toPid + ':replies', timestamp, postData.pid), | ||||||
|  | 			db.incrObjectField('post:' + postData.toPid, 'replies'), | ||||||
|  | 		]); | ||||||
| 	} | 	} | ||||||
| 						async.parallel([ |  | ||||||
| 							async.apply(db.sortedSetAdd, 'pid:' + postData.toPid + ':replies', timestamp, postData.pid), |  | ||||||
| 							async.apply(db.incrObjectField, 'post:' + postData.toPid, 'replies'), |  | ||||||
| 						], next); |  | ||||||
| 					}, |  | ||||||
| 					function (next) { |  | ||||||
| 						db.incrObjectField('global', 'postCount', next); |  | ||||||
| 					}, |  | ||||||
| 					async.apply(Posts.uploads.sync, postData.pid), |  | ||||||
| 				], function (err) { |  | ||||||
| 					next(err); |  | ||||||
| 				}); |  | ||||||
| 			}, |  | ||||||
| 			function (next) { |  | ||||||
| 				plugins.fireHook('filter:post.get', { post: postData, uid: data.uid }, next); |  | ||||||
| 			}, |  | ||||||
| 			function (data, next) { |  | ||||||
| 				data.post.isMain = isMain; |  | ||||||
| 				plugins.fireHook('action:post.save', { post: _.clone(data.post) }); |  | ||||||
| 				next(null, data.post); |  | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	}; |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| var async = require('async'); | const db = require('../database'); | ||||||
|  | const plugins = require('../plugins'); | ||||||
| var db = require('../database'); | const utils = require('../utils'); | ||||||
| var plugins = require('../plugins'); |  | ||||||
| var utils = require('../utils'); |  | ||||||
|  |  | ||||||
| const intFields = [ | const intFields = [ | ||||||
| 	'uid', 'pid', 'tid', 'deleted', 'timestamp', | 	'uid', 'pid', 'tid', 'deleted', 'timestamp', | ||||||
| @@ -12,67 +10,49 @@ const intFields = [ | |||||||
| ]; | ]; | ||||||
|  |  | ||||||
| module.exports = function (Posts) { | module.exports = function (Posts) { | ||||||
| 	Posts.getPostsFields = function (pids, fields, callback) { | 	Posts.getPostsFields = async function (pids, fields) { | ||||||
| 		if (!Array.isArray(pids) || !pids.length) { | 		if (!Array.isArray(pids) || !pids.length) { | ||||||
| 			return callback(null, []); | 			return []; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		async.waterfall([ |  | ||||||
| 			function (next) { |  | ||||||
| 		const keys = pids.map(pid => 'post:' + pid); | 		const keys = pids.map(pid => 'post:' + pid); | ||||||
|  | 		let postData; | ||||||
| 		if (fields.length) { | 		if (fields.length) { | ||||||
| 					db.getObjectsFields(keys, fields, next); | 			postData = await db.getObjectsFields(keys, fields); | ||||||
| 		} else { | 		} else { | ||||||
| 					db.getObjects(keys, next); | 			postData = await db.getObjects(keys); | ||||||
| 		} | 		} | ||||||
| 			}, | 		const result = await plugins.fireHook('filter:post.getFields', { posts: postData, fields: fields }); | ||||||
| 			function (posts, next) { | 		result.posts.forEach(post => modifyPost(post, fields)); | ||||||
| 				plugins.fireHook('filter:post.getFields', { posts: posts, fields: fields }, next); | 		return Array.isArray(result.posts) ? result.posts : null; | ||||||
| 			}, |  | ||||||
| 			function (data, next) { |  | ||||||
| 				data.posts.forEach(post => modifyPost(post, fields)); |  | ||||||
| 				next(null, Array.isArray(data.posts) ? data.posts : null); |  | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Posts.getPostData = function (pid, callback) { | 	Posts.getPostData = async function (pid) { | ||||||
| 		Posts.getPostsFields([pid], [], function (err, posts) { | 		const posts = await Posts.getPostsFields([pid], []); | ||||||
| 			callback(err, posts && posts.length ? posts[0] : null); | 		return posts && posts.length ? posts[0] : null; | ||||||
| 		}); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Posts.getPostsData = function (pids, callback) { | 	Posts.getPostsData = async function (pids) { | ||||||
| 		Posts.getPostsFields(pids, [], callback); | 		return await Posts.getPostsFields(pids, []); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Posts.getPostField = function (pid, field, callback) { | 	Posts.getPostField = async function (pid, field) { | ||||||
| 		Posts.getPostFields(pid, [field], function (err, post) { | 		const post = await Posts.getPostFields(pid, [field]); | ||||||
| 			callback(err, post ? post[field] : null); | 		return post ? post[field] : null; | ||||||
| 		}); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Posts.getPostFields = function (pid, fields, callback) { | 	Posts.getPostFields = async function (pid, fields) { | ||||||
| 		Posts.getPostsFields([pid], fields, function (err, posts) { | 		const posts = await Posts.getPostsFields([pid], fields); | ||||||
| 			callback(err, posts ? posts[0] : null); | 		return posts ? posts[0] : null; | ||||||
| 		}); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Posts.setPostField = function (pid, field, value, callback) { | 	Posts.setPostField = async function (pid, field, value) { | ||||||
| 		Posts.setPostFields(pid, { [field]: value }, callback); | 		await Posts.setPostFields(pid, { [field]: value }); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Posts.setPostFields = function (pid, data, callback) { | 	Posts.setPostFields = async function (pid, data) { | ||||||
| 		async.waterfall([ | 		await db.setObject('post:' + pid, data); | ||||||
| 			function (next) { |  | ||||||
| 				db.setObject('post:' + pid, data, next); |  | ||||||
| 			}, |  | ||||||
| 			function (next) { |  | ||||||
| 		data.pid = pid; | 		data.pid = pid; | ||||||
| 		plugins.fireHook('action:post.setFields', { data: data }); | 		plugins.fireHook('action:post.setFields', { data: data }); | ||||||
| 				next(); |  | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	}; | 	}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,212 +1,132 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| var async = require('async'); | const _ = require('lodash'); | ||||||
| var _ = require('lodash'); |  | ||||||
|  |  | ||||||
| var db = require('../database'); | const db = require('../database'); | ||||||
| var topics = require('../topics'); | const topics = require('../topics'); | ||||||
| var user = require('../user'); | const categories = require('../categories'); | ||||||
| var groups = require('../groups'); | const user = require('../user'); | ||||||
| var notifications = require('../notifications'); | const groups = require('../groups'); | ||||||
| var plugins = require('../plugins'); | const notifications = require('../notifications'); | ||||||
|  | const plugins = require('../plugins'); | ||||||
|  |  | ||||||
| module.exports = function (Posts) { | module.exports = function (Posts) { | ||||||
| 	Posts.delete = function (pid, uid, callback) { | 	Posts.delete = async function (pid, uid) { | ||||||
| 		deleteOrRestore('delete', pid, uid, callback); | 		return await deleteOrRestore('delete', pid, uid); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Posts.restore = function (pid, uid, callback) { | 	Posts.restore = async function (pid, uid) { | ||||||
| 		deleteOrRestore('restore', pid, uid, callback); | 		return await deleteOrRestore('restore', pid, uid); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	function deleteOrRestore(type, pid, uid, callback) { | 	async function deleteOrRestore(type, pid, uid) { | ||||||
| 		var postData; |  | ||||||
| 		const isDeleting = type === 'delete'; | 		const isDeleting = type === 'delete'; | ||||||
| 		async.waterfall([ | 		await plugins.fireHook('filter:post.' + type, { pid: pid, uid: uid }); | ||||||
| 			function (next) { | 		await Posts.setPostFields(pid, { | ||||||
| 				plugins.fireHook('filter:post.' + type, { pid: pid, uid: uid }, next); |  | ||||||
| 			}, |  | ||||||
| 			function (data, next) { |  | ||||||
| 				Posts.setPostFields(pid, { |  | ||||||
| 			deleted: isDeleting ? 1 : 0, | 			deleted: isDeleting ? 1 : 0, | ||||||
| 			deleterUid: isDeleting ? uid : 0, | 			deleterUid: isDeleting ? uid : 0, | ||||||
| 				}, next); | 		}); | ||||||
| 			}, | 		const postData = await Posts.getPostFields(pid, ['pid', 'tid', 'uid', 'content', 'timestamp']); | ||||||
| 			function (next) { | 		const topicData = await topics.getTopicFields(postData.tid, ['tid', 'cid', 'pinned']); | ||||||
| 				Posts.getPostFields(pid, ['pid', 'tid', 'uid', 'content', 'timestamp'], next); |  | ||||||
| 			}, |  | ||||||
| 			function (_post, next) { |  | ||||||
| 				postData = _post; |  | ||||||
| 				topics.getTopicFields(_post.tid, ['tid', 'cid', 'pinned'], next); |  | ||||||
| 			}, |  | ||||||
| 			function (topicData, next) { |  | ||||||
| 		postData.cid = topicData.cid; | 		postData.cid = topicData.cid; | ||||||
| 				async.parallel([ | 		await Promise.all([ | ||||||
| 					function (next) { | 			topics.updateLastPostTimeFromLastPid(postData.tid), | ||||||
| 						topics.updateLastPostTimeFromLastPid(postData.tid, next); | 			topics.updateTeaser(postData.tid), | ||||||
| 					}, | 			isDeleting ? | ||||||
| 					function (next) { | 				db.sortedSetRemove('cid:' + topicData.cid + ':pids', pid) : | ||||||
| 						if (isDeleting) { | 				db.sortedSetAdd('cid:' + topicData.cid + ':pids', postData.timestamp, pid), | ||||||
| 							db.sortedSetRemove('cid:' + topicData.cid + ':pids', pid, next); | 		]); | ||||||
| 						} else { |  | ||||||
| 							db.sortedSetAdd('cid:' + topicData.cid + ':pids', postData.timestamp, pid, next); |  | ||||||
| 						} |  | ||||||
| 					}, |  | ||||||
| 					function (next) { |  | ||||||
| 						topics.updateTeaser(postData.tid, next); |  | ||||||
| 					}, |  | ||||||
| 				], next); |  | ||||||
| 			}, |  | ||||||
| 			function (results, next) { |  | ||||||
| 		plugins.fireHook('action:post.' + type, { post: _.clone(postData), uid: uid }); | 		plugins.fireHook('action:post.' + type, { post: _.clone(postData), uid: uid }); | ||||||
| 				next(null, postData); | 		return postData; | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	Posts.purge = function (pid, uid, callback) { | 	Posts.purge = async function (pid, uid) { | ||||||
| 		let postData; | 		const postData = await Posts.getPostData(pid); | ||||||
| 		async.waterfall([ |  | ||||||
| 			function (next) { |  | ||||||
| 				Posts.getPostData(pid, next); |  | ||||||
| 			}, |  | ||||||
| 			function (_postData, next) { |  | ||||||
| 				postData = _postData; |  | ||||||
| 		if (!postData) { | 		if (!postData) { | ||||||
| 					return callback(); | 			return; | ||||||
| 		} | 		} | ||||||
| 				plugins.fireHook('filter:post.purge', { post: postData, pid: pid, uid: uid }, next); |  | ||||||
| 			}, | 		await plugins.fireHook('filter:post.purge', { post: postData, pid: pid, uid: uid }); | ||||||
| 			function (data, next) { | 		await Promise.all([ | ||||||
| 				async.parallel([ | 			deletePostFromTopicUserNotification(postData), | ||||||
| 					async.apply(deletePostFromTopicUserNotification, postData), | 			deletePostFromCategoryRecentPosts(pid), | ||||||
| 					async.apply(deletePostFromCategoryRecentPosts, pid), | 			deletePostFromUsersBookmarks(pid), | ||||||
| 					async.apply(deletePostFromUsersBookmarks, pid), | 			deletePostFromUsersVotes(pid), | ||||||
| 					async.apply(deletePostFromUsersVotes, pid), | 			deletePostFromReplies(postData), | ||||||
| 					async.apply(deletePostFromReplies, postData), | 			deletePostFromGroups(postData), | ||||||
| 					async.apply(deletePostFromGroups, postData), | 			db.sortedSetsRemove(['posts:pid', 'posts:votes', 'posts:flagged'], pid), | ||||||
| 					async.apply(db.sortedSetsRemove, ['posts:pid', 'posts:votes', 'posts:flagged'], pid), | 		]); | ||||||
| 				], err => next(err)); |  | ||||||
| 			}, |  | ||||||
| 			function (next) { |  | ||||||
| 		plugins.fireHook('action:post.purge', { post: postData, uid: uid }); | 		plugins.fireHook('action:post.purge', { post: postData, uid: uid }); | ||||||
| 				db.delete('post:' + pid, next); | 		await db.delete('post:' + pid); | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	function deletePostFromTopicUserNotification(postData, callback) { | 	async function deletePostFromTopicUserNotification(postData) { | ||||||
| 		async.waterfall([ | 		await db.sortedSetsRemove([ | ||||||
| 			function (next) { |  | ||||||
| 				db.sortedSetsRemove([ |  | ||||||
| 			'tid:' + postData.tid + ':posts', | 			'tid:' + postData.tid + ':posts', | ||||||
| 			'tid:' + postData.tid + ':posts:votes', | 			'tid:' + postData.tid + ':posts:votes', | ||||||
| 			'uid:' + postData.uid + ':posts', | 			'uid:' + postData.uid + ':posts', | ||||||
| 				], postData.pid, next); | 		], postData.pid); | ||||||
| 			}, | 		const topicData = await topics.getTopicFields(postData.tid, ['tid', 'cid', 'pinned']); | ||||||
| 			function (next) { |  | ||||||
| 				topics.getTopicFields(postData.tid, ['tid', 'cid', 'pinned'], next); |  | ||||||
| 			}, |  | ||||||
| 			function (topicData, next) { |  | ||||||
| 		const tasks = [ | 		const tasks = [ | ||||||
| 					async.apply(db.decrObjectField, 'global', 'postCount'), | 			db.decrObjectField('global', 'postCount'), | ||||||
| 					async.apply(db.decrObjectField, 'category:' + topicData.cid, 'post_count'), | 			db.decrObjectField('category:' + topicData.cid, 'post_count'), | ||||||
| 					async.apply(db.sortedSetRemove, 'cid:' + topicData.cid + ':uid:' + postData.uid + ':pids', postData.pid), | 			db.sortedSetRemove('cid:' + topicData.cid + ':uid:' + postData.uid + ':pids', postData.pid), | ||||||
| 					async.apply(db.sortedSetRemove, 'cid:' + topicData.cid + ':uid:' + postData.uid + ':pids:votes', postData.pid), | 			db.sortedSetRemove('cid:' + topicData.cid + ':uid:' + postData.uid + ':pids:votes', postData.pid), | ||||||
| 					async.apply(topics.decreasePostCount, postData.tid), | 			topics.decreasePostCount(postData.tid), | ||||||
| 					async.apply(topics.updateTeaser, postData.tid), | 			topics.updateTeaser(postData.tid), | ||||||
| 					async.apply(topics.updateLastPostTimeFromLastPid, postData.tid), | 			topics.updateLastPostTimeFromLastPid(postData.tid), | ||||||
| 					async.apply(db.sortedSetIncrBy, 'tid:' + postData.tid + ':posters', -1, postData.uid), | 			db.sortedSetIncrBy('tid:' + postData.tid + ':posters', -1, postData.uid), | ||||||
| 					async.apply(user.incrementUserPostCountBy, postData.uid, -1), | 			user.incrementUserPostCountBy(postData.uid, -1), | ||||||
| 					async.apply(notifications.rescind, 'new_post:tid:' + postData.tid + ':pid:' + postData.pid + ':uid:' + postData.uid), | 			notifications.rescind('new_post:tid:' + postData.tid + ':pid:' + postData.pid + ':uid:' + postData.uid), | ||||||
| 		]; | 		]; | ||||||
| 		if (!topicData.pinned) { | 		if (!topicData.pinned) { | ||||||
| 					tasks.push(async.apply(db.sortedSetIncrBy, 'cid:' + topicData.cid + ':tids:posts', -1, postData.tid)); | 			tasks.push(db.sortedSetIncrBy, 'cid:' + topicData.cid + ':tids:posts', -1, postData.tid); | ||||||
| 		} | 		} | ||||||
| 				async.parallel(tasks, next); | 		await Promise.all(tasks); | ||||||
| 			}, |  | ||||||
| 		], function (err) { |  | ||||||
| 			callback(err); |  | ||||||
| 		}); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	function deletePostFromCategoryRecentPosts(pid, callback) { | 	async function deletePostFromCategoryRecentPosts(pid) { | ||||||
| 		async.waterfall([ | 		const cids = await categories.getAllCidsFromSet('categories:cid'); | ||||||
| 			function (next) { |  | ||||||
| 				db.getSortedSetRange('categories:cid', 0, -1, next); |  | ||||||
| 			}, |  | ||||||
| 			function (cids, next) { |  | ||||||
| 		const sets = cids.map(cid => 'cid:' + cid + ':pids'); | 		const sets = cids.map(cid => 'cid:' + cid + ':pids'); | ||||||
| 				db.sortedSetsRemove(sets, pid, next); | 		await db.sortedSetsRemove(sets, pid); | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	function deletePostFromUsersBookmarks(pid, callback) { | 	async function deletePostFromUsersBookmarks(pid) { | ||||||
| 		async.waterfall([ | 		const uids = await db.getSetMembers('pid:' + pid + ':users_bookmarked'); | ||||||
| 			function (next) { |  | ||||||
| 				db.getSetMembers('pid:' + pid + ':users_bookmarked', next); |  | ||||||
| 			}, |  | ||||||
| 			function (uids, next) { |  | ||||||
| 		const sets = uids.map(uid => 'uid:' + uid + ':bookmarks'); | 		const sets = uids.map(uid => 'uid:' + uid + ':bookmarks'); | ||||||
| 				db.sortedSetsRemove(sets, pid, next); | 		await db.sortedSetsRemove(sets, pid); | ||||||
| 			}, | 		await db.delete('pid:' + pid + ':users_bookmarked'); | ||||||
| 			function (next) { |  | ||||||
| 				db.delete('pid:' + pid + ':users_bookmarked', next); |  | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	function deletePostFromUsersVotes(pid, callback) { | 	async function deletePostFromUsersVotes(pid) { | ||||||
| 		async.waterfall([ | 		const [upvoters, downvoters] = await Promise.all([ | ||||||
| 			function (next) { | 			db.getSetMembers('pid:' + pid + ':upvote'), | ||||||
| 				async.parallel({ | 			db.getSetMembers('pid:' + pid + ':downvote'), | ||||||
| 					upvoters: function (next) { | 		]); | ||||||
| 						db.getSetMembers('pid:' + pid + ':upvote', next); | 		const upvoterSets = upvoters.map(uid => 'uid:' + uid + ':upvote'); | ||||||
| 					}, | 		const downvoterSets = downvoters.map(uid => 'uid:' + uid + ':downvote'); | ||||||
| 					downvoters: function (next) { | 		await Promise.all([ | ||||||
| 						db.getSetMembers('pid:' + pid + ':downvote', next); | 			db.sortedSetsRemove(upvoterSets.concat(downvoterSets), pid), | ||||||
| 					}, | 			db.deleteAll(['pid:' + pid + ':upvote', 'pid:' + pid + ':downvote']), | ||||||
| 				}, next); | 		]); | ||||||
| 			}, |  | ||||||
| 			function (results, next) { |  | ||||||
| 				async.parallel([ |  | ||||||
| 					function (next) { |  | ||||||
| 						const upvoterSets = results.upvoters.map(uid => 'uid:' + uid + ':upvote'); |  | ||||||
| 						const downvoterSets = results.downvoters.map(uid => 'uid:' + uid + ':downvote'); |  | ||||||
| 						db.sortedSetsRemove(upvoterSets.concat(downvoterSets), pid, next); |  | ||||||
| 					}, |  | ||||||
| 					function (next) { |  | ||||||
| 						db.deleteAll(['pid:' + pid + ':upvote', 'pid:' + pid + ':downvote'], next); |  | ||||||
| 					}, |  | ||||||
| 				], next); |  | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	function deletePostFromReplies(postData, callback) { | 	async function deletePostFromReplies(postData) { | ||||||
| 		if (!parseInt(postData.toPid, 10)) { | 		if (!parseInt(postData.toPid, 10)) { | ||||||
| 			return setImmediate(callback); | 			return; | ||||||
| 		} | 		} | ||||||
| 		async.parallel([ | 		await Promise.all([ | ||||||
| 			async.apply(db.sortedSetRemove, 'pid:' + postData.toPid + ':replies', postData.pid), | 			db.sortedSetRemove('pid:' + postData.toPid + ':replies', postData.pid), | ||||||
| 			async.apply(db.decrObjectField, 'post:' + postData.toPid, 'replies'), | 			db.decrObjectField('post:' + postData.toPid, 'replies'), | ||||||
| 		], callback); | 		]); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	function deletePostFromGroups(postData, callback) { | 	async function deletePostFromGroups(postData) { | ||||||
| 		if (!parseInt(postData.uid, 10)) { | 		if (!parseInt(postData.uid, 10)) { | ||||||
| 			return setImmediate(callback); | 			return; | ||||||
| 		} | 		} | ||||||
| 		async.waterfall([ | 		const groupNames = await groups.getUserGroupMembership('groups:visible:createtime', [postData.uid]); | ||||||
| 			function (next) { | 		const keys = groupNames[0].map(groupName => 'group:' + groupName + ':member:pids'); | ||||||
| 				groups.getUserGroupMembership('groups:visible:createtime', [postData.uid], next); | 		await db.sortedSetsRemove(keys, postData.pid); | ||||||
| 			}, |  | ||||||
| 			function (groupNames, next) { |  | ||||||
| 				groupNames = groupNames[0]; |  | ||||||
| 				const keys = groupNames.map(groupName => 'group:' + groupName + ':member:pids'); |  | ||||||
| 				db.sortedSetsRemove(keys, postData.pid, next); |  | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -901,6 +901,14 @@ describe('Post\'s', function () { | |||||||
| 				done(); | 				done(); | ||||||
| 			}); | 			}); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
|  | 		it('should filter pids by multiple cids', function (done) { | ||||||
|  | 			posts.filterPidsByCid([postData.pid, 100, 101], [cid], function (err, pids) { | ||||||
|  | 				assert.ifError(err); | ||||||
|  | 				assert.deepEqual([postData.pid], pids); | ||||||
|  | 				done(); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	it('should error if user does not exist', function (done) { | 	it('should error if user does not exist', function (done) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user