mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-30 18:46:01 +01:00 
			
		
		
		
	feat: #7743 notifications
This commit is contained in:
		| @@ -34,18 +34,12 @@ Notifications.privilegedTypes = [ | |||||||
| 	'notificationType_new-user-flag', | 	'notificationType_new-user-flag', | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| Notifications.getAllNotificationTypes = function (callback) { | Notifications.getAllNotificationTypes = async function () { | ||||||
| 	async.waterfall([ | 	const results = await plugins.fireHook('filter:user.notificationTypes', { | ||||||
| 		function (next) { | 		types: Notifications.baseTypes.slice(), | ||||||
| 			plugins.fireHook('filter:user.notificationTypes', { | 		privilegedTypes: Notifications.privilegedTypes.slice(), | ||||||
| 				types: Notifications.baseTypes.slice(), | 	}); | ||||||
| 				privilegedTypes: Notifications.privilegedTypes.slice(), | 	return results.types.concat(results.privilegedTypes); | ||||||
| 			}, next); |  | ||||||
| 		}, |  | ||||||
| 		function (results, next) { |  | ||||||
| 			next(null, results.types.concat(results.privilegedTypes)); |  | ||||||
| 		}, |  | ||||||
| 	], callback); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Notifications.startJobs = function () { | Notifications.startJobs = function () { | ||||||
| @@ -53,133 +47,85 @@ Notifications.startJobs = function () { | |||||||
| 	new cron('*/30 * * * *', Notifications.prune, null, true); | 	new cron('*/30 * * * *', Notifications.prune, null, true); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Notifications.get = function (nid, callback) { | Notifications.get = async function (nid) { | ||||||
| 	Notifications.getMultiple([nid], function (err, notifications) { | 	const notifications = await Notifications.getMultiple([nid]); | ||||||
| 		callback(err, Array.isArray(notifications) && notifications.length ? notifications[0] : null); | 	return Array.isArray(notifications) && notifications.length ? notifications[0] : null; | ||||||
| 	}); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Notifications.getMultiple = function (nids, callback) { | Notifications.getMultiple = async function (nids) { | ||||||
| 	if (!Array.isArray(nids) || !nids.length) { | 	if (!Array.isArray(nids) || !nids.length) { | ||||||
| 		return setImmediate(callback, null, []); | 		return []; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var notifications; | 	const keys = nids.map(nid => 'notifications:' + nid); | ||||||
|  | 	const notifications = await db.getObjects(keys); | ||||||
|  |  | ||||||
| 	async.waterfall([ | 	const userKeys = notifications.map(n => n && n.from); | ||||||
| 		function (next) { | 	const usersData = await User.getUsersFields(userKeys, ['username', 'userslug', 'picture']); | ||||||
| 			const keys = nids.map(nid => 'notifications:' + nid); |  | ||||||
| 			db.getObjects(keys, next); |  | ||||||
| 		}, |  | ||||||
| 		function (_notifications, next) { |  | ||||||
| 			notifications = _notifications; |  | ||||||
|  |  | ||||||
| 			const userKeys = notifications.map(n => n && n.from); | 	notifications.forEach(function (notification, index) { | ||||||
| 			User.getUsersFields(userKeys, ['username', 'userslug', 'picture'], next); | 		if (notification) { | ||||||
| 		}, | 			notification.datetimeISO = utils.toISOString(notification.datetime); | ||||||
| 		function (usersData, next) { |  | ||||||
| 			notifications.forEach(function (notification, index) { |  | ||||||
| 				if (notification) { |  | ||||||
| 					notification.datetimeISO = utils.toISOString(notification.datetime); |  | ||||||
|  |  | ||||||
| 					if (notification.bodyLong) { | 			if (notification.bodyLong) { | ||||||
| 						notification.bodyLong = utils.escapeHTML(notification.bodyLong); | 				notification.bodyLong = utils.escapeHTML(notification.bodyLong); | ||||||
| 					} | 			} | ||||||
|  |  | ||||||
| 					notification.user = usersData[index]; | 			notification.user = usersData[index]; | ||||||
| 					if (notification.user) { | 			if (notification.user) { | ||||||
| 						notification.image = notification.user.picture || null; | 				notification.image = notification.user.picture || null; | ||||||
| 						if (notification.user.username === '[[global:guest]]') { | 				if (notification.user.username === '[[global:guest]]') { | ||||||
| 							notification.bodyShort = notification.bodyShort.replace(/([\s\S]*?),[\s\S]*?,([\s\S]*?)/, '$1, [[global:guest]], $2'); | 					notification.bodyShort = notification.bodyShort.replace(/([\s\S]*?),[\s\S]*?,([\s\S]*?)/, '$1, [[global:guest]], $2'); | ||||||
| 						} |  | ||||||
| 					} else if (notification.image === 'brand:logo' || !notification.image) { |  | ||||||
| 						notification.image = meta.config['brand:logo'] || nconf.get('relative_path') + '/logo.png'; |  | ||||||
| 					} |  | ||||||
| 				} | 				} | ||||||
| 			}); | 			} else if (notification.image === 'brand:logo' || !notification.image) { | ||||||
| 			next(null, notifications); | 				notification.image = meta.config['brand:logo'] || nconf.get('relative_path') + '/logo.png'; | ||||||
| 		}, | 			} | ||||||
| 	], callback); | 		} | ||||||
|  | 	}); | ||||||
|  | 	return notifications; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Notifications.filterExists = function (nids, callback) { | Notifications.filterExists = async function (nids) { | ||||||
| 	async.waterfall([ | 	const exists = await db.isSortedSetMembers('notifications', nids); | ||||||
| 		function (next) { | 	return nids.filter((nid, idx) => exists[idx]); | ||||||
| 			db.isSortedSetMembers('notifications', nids, next); |  | ||||||
| 		}, |  | ||||||
| 		function (exists, next) { |  | ||||||
| 			nids = nids.filter((notifId, idx) => exists[idx]); |  | ||||||
| 			next(null, nids); |  | ||||||
| 		}, |  | ||||||
| 	], callback); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Notifications.findRelated = function (mergeIds, set, callback) { | Notifications.findRelated = async function (mergeIds, set) { | ||||||
| 	// A related notification is one in a zset that has the same mergeId | 	// A related notification is one in a zset that has the same mergeId | ||||||
| 	var nids; | 	const nids = await db.getSortedSetRevRange(set, 0, -1); | ||||||
|  |  | ||||||
| 	async.waterfall([ | 	const keys = nids.map(nid => 'notifications:' + nid); | ||||||
| 		async.apply(db.getSortedSetRevRange, set, 0, -1), | 	let sets = await db.getObjectsFields(keys, ['mergeId']); | ||||||
| 		function (_nids, next) { | 	sets = sets.map(set => String(set.mergeId)); | ||||||
| 			nids = _nids; | 	var mergeSet = new Set(mergeIds.map(id => String(id))); | ||||||
|  | 	return nids.filter((nid, idx) => mergeSet.has(sets[idx])); | ||||||
| 			var keys = nids.map(nid => 'notifications:' + nid); |  | ||||||
| 			db.getObjectsFields(keys, ['mergeId'], next); |  | ||||||
| 		}, |  | ||||||
| 		function (sets, next) { |  | ||||||
| 			sets = sets.map(set => String(set.mergeId)); |  | ||||||
| 			var mergeSet = new Set(mergeIds.map(id => String(id))); |  | ||||||
| 			next(null, nids.filter((nid, idx) => mergeSet.has(sets[idx]))); |  | ||||||
| 		}, |  | ||||||
| 	], callback); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Notifications.create = function (data, callback) { | Notifications.create = async function (data) { | ||||||
| 	if (!data.nid) { | 	if (!data.nid) { | ||||||
| 		return callback(new Error('[[error:no-notification-id]]')); | 		throw new Error('[[error:no-notification-id]]'); | ||||||
| 	} | 	} | ||||||
| 	data.importance = data.importance || 5; | 	data.importance = data.importance || 5; | ||||||
| 	async.waterfall([ | 	const oldNotif = await db.getObject('notifications:' + data.nid); | ||||||
| 		function (next) { | 	if (oldNotif && parseInt(oldNotif.pid, 10) === parseInt(data.pid, 10) && parseInt(oldNotif.importance, 10) > parseInt(data.importance, 10)) { | ||||||
| 			db.getObject('notifications:' + data.nid, next); | 		return null; | ||||||
| 		}, | 	} | ||||||
| 		function (oldNotification, next) { | 	const now = Date.now(); | ||||||
| 			if (oldNotification) { | 	data.datetime = now; | ||||||
| 				if (parseInt(oldNotification.pid, 10) === parseInt(data.pid, 10) && parseInt(oldNotification.importance, 10) > parseInt(data.importance, 10)) { | 	await Promise.all([ | ||||||
| 					return callback(null, null); | 		db.sortedSetAdd('notifications', now, data.nid), | ||||||
| 				} | 		db.setObject('notifications:' + data.nid, data), | ||||||
| 			} | 	]); | ||||||
| 			var now = Date.now(); | 	return data; | ||||||
| 			data.datetime = now; |  | ||||||
| 			async.parallel([ |  | ||||||
| 				function (next) { |  | ||||||
| 					db.sortedSetAdd('notifications', now, data.nid, next); |  | ||||||
| 				}, |  | ||||||
| 				function (next) { |  | ||||||
| 					db.setObject('notifications:' + data.nid, data, next); |  | ||||||
| 				}, |  | ||||||
| 			], function (err) { |  | ||||||
| 				next(err, data); |  | ||||||
| 			}); |  | ||||||
| 		}, |  | ||||||
| 	], callback); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Notifications.push = function (notification, uids, callback) { | Notifications.push = async function (notification, uids) { | ||||||
| 	callback = callback || function () {}; |  | ||||||
|  |  | ||||||
| 	if (!notification || !notification.nid) { | 	if (!notification || !notification.nid) { | ||||||
| 		return callback(); | 		return; | ||||||
| 	} | 	} | ||||||
|  | 	uids = Array.isArray(uids) ? _.uniq(uids) : [uids]; | ||||||
| 	if (!Array.isArray(uids)) { |  | ||||||
| 		uids = [uids]; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	uids = _.uniq(uids); |  | ||||||
|  |  | ||||||
| 	if (!uids.length) { | 	if (!uids.length) { | ||||||
| 		return callback(); | 		return; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	setTimeout(function () { | 	setTimeout(function () { | ||||||
| @@ -191,55 +137,35 @@ Notifications.push = function (notification, uids, callback) { | |||||||
| 			} | 			} | ||||||
| 		}); | 		}); | ||||||
| 	}, 1000); | 	}, 1000); | ||||||
|  |  | ||||||
| 	callback(); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| function pushToUids(uids, notification, callback) { | async function pushToUids(uids, notification) { | ||||||
| 	function sendNotification(uids, callback) { | 	async function sendNotification(uids) { | ||||||
| 		if (!uids.length) { | 		if (!uids.length) { | ||||||
| 			return callback(); | 			return; | ||||||
|  | 		} | ||||||
|  | 		const oneWeekAgo = Date.now() - 604800000; | ||||||
|  | 		const unreadKeys = uids.map(uid => 'uid:' + uid + ':notifications:unread'); | ||||||
|  | 		const readKeys = uids.map(uid => 'uid:' + uid + ':notifications:read'); | ||||||
|  | 		await db.sortedSetsAdd(unreadKeys, notification.datetime, notification.nid); | ||||||
|  | 		await db.sortedSetsRemove(readKeys, notification.nid); | ||||||
|  | 		await db.sortedSetsRemoveRangeByScore(unreadKeys, '-inf', oneWeekAgo); | ||||||
|  | 		await db.sortedSetsRemoveRangeByScore(readKeys, '-inf', oneWeekAgo); | ||||||
|  | 		const websockets = require('./socket.io'); | ||||||
|  | 		if (websockets.server) { | ||||||
|  | 			uids.forEach(function (uid) { | ||||||
|  | 				websockets.in('uid_' + uid).emit('event:new_notification', notification); | ||||||
|  | 			}); | ||||||
| 		} | 		} | ||||||
| 		var oneWeekAgo = Date.now() - 604800000; |  | ||||||
| 		var unreadKeys = []; |  | ||||||
| 		var readKeys = []; |  | ||||||
| 		async.waterfall([ |  | ||||||
| 			function (next) { |  | ||||||
| 				uids.forEach(function (uid) { |  | ||||||
| 					unreadKeys.push('uid:' + uid + ':notifications:unread'); |  | ||||||
| 					readKeys.push('uid:' + uid + ':notifications:read'); |  | ||||||
| 				}); |  | ||||||
|  |  | ||||||
| 				db.sortedSetsAdd(unreadKeys, notification.datetime, notification.nid, next); |  | ||||||
| 			}, |  | ||||||
| 			function (next) { |  | ||||||
| 				db.sortedSetsRemove(readKeys, notification.nid, next); |  | ||||||
| 			}, |  | ||||||
| 			function (next) { |  | ||||||
| 				db.sortedSetsRemoveRangeByScore(unreadKeys, '-inf', oneWeekAgo, next); |  | ||||||
| 			}, |  | ||||||
| 			function (next) { |  | ||||||
| 				db.sortedSetsRemoveRangeByScore(readKeys, '-inf', oneWeekAgo, next); |  | ||||||
| 			}, |  | ||||||
| 			function (next) { |  | ||||||
| 				var websockets = require('./socket.io'); |  | ||||||
| 				if (websockets.server) { |  | ||||||
| 					uids.forEach(function (uid) { |  | ||||||
| 						websockets.in('uid_' + uid).emit('event:new_notification', notification); |  | ||||||
| 					}); |  | ||||||
| 				} |  | ||||||
| 				next(); |  | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	function sendEmail(uids, callback) { | 	async function sendEmail(uids) { | ||||||
| 		// Update CTA messaging (as not all notification types need custom text) | 		// Update CTA messaging (as not all notification types need custom text) | ||||||
| 		if (['new-reply', 'new-chat'].includes(notification.type)) { | 		if (['new-reply', 'new-chat'].includes(notification.type)) { | ||||||
| 			notification['cta-type'] = notification.type; | 			notification['cta-type'] = notification.type; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		async.eachLimit(uids, 3, function (uid, next) { | 		await async.eachLimit(uids, 3, function (uid, next) { | ||||||
| 			emailer.send('notification', uid, { | 			emailer.send('notification', uid, { | ||||||
| 				path: notification.path, | 				path: notification.path, | ||||||
| 				subject: utils.stripHTMLTags(notification.subject || '[[notifications:new_notification]]'), | 				subject: utils.stripHTMLTags(notification.subject || '[[notifications:new_notification]]'), | ||||||
| @@ -248,237 +174,148 @@ function pushToUids(uids, notification, callback) { | |||||||
| 				notification: notification, | 				notification: notification, | ||||||
| 				showUnsubscribe: true, | 				showUnsubscribe: true, | ||||||
| 			}, next); | 			}, next); | ||||||
| 		}, callback); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	function getUidsBySettings(uids, callback) { | 	async function getUidsBySettings(uids) { | ||||||
| 		var uidsToNotify = []; | 		const uidsToNotify = []; | ||||||
| 		var uidsToEmail = []; | 		const uidsToEmail = []; | ||||||
| 		async.waterfall([ | 		const usersSettings = await User.getMultipleUserSettings(uids); | ||||||
| 			function (next) { | 		usersSettings.forEach(function (userSettings) { | ||||||
| 				User.getMultipleUserSettings(uids, next); | 			const setting = userSettings['notificationType_' + notification.type] || 'notification'; | ||||||
| 			}, |  | ||||||
| 			function (usersSettings, next) { |  | ||||||
| 				usersSettings.forEach(function (userSettings) { |  | ||||||
| 					var setting = userSettings['notificationType_' + notification.type] || 'notification'; |  | ||||||
|  |  | ||||||
| 					if (setting === 'notification' || setting === 'notificationemail') { | 			if (setting === 'notification' || setting === 'notificationemail') { | ||||||
| 						uidsToNotify.push(userSettings.uid); | 				uidsToNotify.push(userSettings.uid); | ||||||
| 					} | 			} | ||||||
|  |  | ||||||
| 					if (setting === 'email' || setting === 'notificationemail') { | 			if (setting === 'email' || setting === 'notificationemail') { | ||||||
| 						uidsToEmail.push(userSettings.uid); | 				uidsToEmail.push(userSettings.uid); | ||||||
| 					} | 			} | ||||||
| 				}); | 		}); | ||||||
| 				next(null, { uidsToNotify: uidsToNotify, uidsToEmail: uidsToEmail }); | 		return { uidsToNotify: uidsToNotify, uidsToEmail: uidsToEmail }; | ||||||
| 			}, |  | ||||||
| 		], callback); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	async.waterfall([ | 	// Remove uid from recipients list if they have blocked the user triggering the notification | ||||||
| 		function (next) { | 	uids = await User.blocks.filterUids(notification.from, uids); | ||||||
| 			// Remove uid from recipients list if they have blocked the user triggering the notification | 	const data = await plugins.fireHook('filter:notification.push', { notification: notification, uids: uids }); | ||||||
| 			User.blocks.filterUids(notification.from, uids, next); | 	if (!data || !data.notification || !data.uids || !data.uids.length) { | ||||||
| 		}, | 		return; | ||||||
| 		function (uids, next) { | 	} | ||||||
| 			plugins.fireHook('filter:notification.push', { notification: notification, uids: uids }, next); |  | ||||||
| 		}, | 	notification = data.notification; | ||||||
| 		function (data, next) { | 	let results = { uidsToNotify: data.uids, uidsToEmail: [] }; | ||||||
| 			if (!data || !data.notification || !data.uids || !data.uids.length) { | 	if (notification.type) { | ||||||
| 				return callback(); | 		results = await getUidsBySettings(data.uids); | ||||||
| 			} | 	} | ||||||
| 			notification = data.notification; | 	await Promise.all([ | ||||||
| 			if (notification.type) { | 		sendNotification(results.uidsToNotify), | ||||||
| 				getUidsBySettings(data.uids, next); | 		sendEmail(results.uidsToEmail), | ||||||
| 			} else { | 	]); | ||||||
| 				next(null, { uidsToNotify: data.uids, uidsToEmail: [] }); | 	plugins.fireHook('action:notification.pushed', { | ||||||
| 			} | 		notification: notification, | ||||||
| 		}, | 		uids: results.uidsToNotify, | ||||||
| 		function (results, next) { | 		uidsNotified: results.uidsToNotify, | ||||||
| 			async.parallel([ | 		uidsEmailed: results.uidsToEmail, | ||||||
| 				function (next) { | 	}); | ||||||
| 					sendNotification(results.uidsToNotify, next); |  | ||||||
| 				}, |  | ||||||
| 				function (next) { |  | ||||||
| 					sendEmail(results.uidsToEmail, next); |  | ||||||
| 				}, |  | ||||||
| 			], function (err) { |  | ||||||
| 				next(err, results); |  | ||||||
| 			}); |  | ||||||
| 		}, |  | ||||||
| 		function (results, next) { |  | ||||||
| 			plugins.fireHook('action:notification.pushed', { |  | ||||||
| 				notification: notification, |  | ||||||
| 				uids: results.uidsToNotify, |  | ||||||
| 				uidsNotified: results.uidsToNotify, |  | ||||||
| 				uidsEmailed: results.uidsToEmail, |  | ||||||
| 			}); |  | ||||||
| 			next(); |  | ||||||
| 		}, |  | ||||||
| 	], callback); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| Notifications.pushGroup = function (notification, groupName, callback) { | Notifications.pushGroup = async function (notification, groupName) { | ||||||
| 	callback = callback || function () {}; |  | ||||||
| 	if (!notification) { | 	if (!notification) { | ||||||
| 		return callback(); | 		return; | ||||||
| 	} | 	} | ||||||
| 	async.waterfall([ | 	const members = await groups.getMembers(groupName, 0, -1); | ||||||
| 		function (next) { | 	await Notifications.push(notification, members); | ||||||
| 			groups.getMembers(groupName, 0, -1, next); |  | ||||||
| 		}, |  | ||||||
| 		function (members, next) { |  | ||||||
| 			Notifications.push(notification, members, next); |  | ||||||
| 		}, |  | ||||||
| 	], callback); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Notifications.pushGroups = function (notification, groupNames, callback) { | Notifications.pushGroups = async function (notification, groupNames) { | ||||||
| 	callback = callback || function () {}; |  | ||||||
| 	if (!notification) { | 	if (!notification) { | ||||||
| 		return callback(); | 		return; | ||||||
| 	} | 	} | ||||||
| 	async.waterfall([ | 	let groupMembers = await groups.getMembersOfGroups(groupNames); | ||||||
| 		function (next) { | 	groupMembers = _.uniq(_.flatten(groupMembers)); | ||||||
| 			groups.getMembersOfGroups(groupNames, next); | 	await Notifications.push(notification, groupMembers); | ||||||
| 		}, |  | ||||||
| 		function (groupMembers, next) { |  | ||||||
| 			var members = _.uniq(_.flatten(groupMembers)); |  | ||||||
| 			Notifications.push(notification, members, next); |  | ||||||
| 		}, |  | ||||||
| 	], callback); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Notifications.rescind = function (nid, callback) { | Notifications.rescind = async function (nid) { | ||||||
| 	callback = callback || function () {}; | 	await Promise.all([ | ||||||
|  | 		db.sortedSetRemove('notifications', nid), | ||||||
| 	async.parallel([ | 		db.delete('notifications:' + nid), | ||||||
| 		async.apply(db.sortedSetRemove, 'notifications', nid), | 	]); | ||||||
| 		async.apply(db.delete, 'notifications:' + nid), |  | ||||||
| 	], function (err) { |  | ||||||
| 		callback(err); |  | ||||||
| 	}); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Notifications.markRead = function (nid, uid, callback) { | Notifications.markRead = async function (nid, uid) { | ||||||
| 	callback = callback || function () {}; |  | ||||||
| 	if (parseInt(uid, 10) <= 0 || !nid) { | 	if (parseInt(uid, 10) <= 0 || !nid) { | ||||||
| 		return setImmediate(callback); | 		return; | ||||||
| 	} | 	} | ||||||
| 	Notifications.markReadMultiple([nid], uid, callback); | 	await Notifications.markReadMultiple([nid], uid); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Notifications.markUnread = function (nid, uid, callback) { | Notifications.markUnread = async function (nid, uid) { | ||||||
| 	callback = callback || function () {}; |  | ||||||
| 	if (parseInt(uid, 10) <= 0 || !nid) { | 	if (parseInt(uid, 10) <= 0 || !nid) { | ||||||
| 		return setImmediate(callback); | 		return; | ||||||
| 	} | 	} | ||||||
| 	async.waterfall([ | 	const notification = await db.getObject('notifications:' + nid); | ||||||
| 		function (next) { | 	if (!notification) { | ||||||
| 			db.getObject('notifications:' + nid, next); | 		throw new Error('[[error:no-notification]]'); | ||||||
| 		}, | 	} | ||||||
| 		function (notification, next) { | 	notification.datetime = notification.datetime || Date.now(); | ||||||
| 			if (!notification) { |  | ||||||
| 				return callback(new Error('[[error:no-notification]]')); |  | ||||||
| 			} |  | ||||||
| 			notification.datetime = notification.datetime || Date.now(); |  | ||||||
|  |  | ||||||
| 			async.parallel([ | 	await Promise.all([ | ||||||
| 				async.apply(db.sortedSetRemove, 'uid:' + uid + ':notifications:read', nid), | 		db.sortedSetRemove('uid:' + uid + ':notifications:read', nid), | ||||||
| 				async.apply(db.sortedSetAdd, 'uid:' + uid + ':notifications:unread', notification.datetime, nid), | 		db.sortedSetAdd('uid:' + uid + ':notifications:unread', notification.datetime, nid), | ||||||
| 			], next); | 	]); | ||||||
| 		}, |  | ||||||
| 	], function (err) { |  | ||||||
| 		callback(err); |  | ||||||
| 	}); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Notifications.markReadMultiple = function (nids, uid, callback) { | Notifications.markReadMultiple = async function (nids, uid) { | ||||||
| 	callback = callback || function () {}; |  | ||||||
| 	nids = nids.filter(Boolean); | 	nids = nids.filter(Boolean); | ||||||
| 	if (!Array.isArray(nids) || !nids.length) { | 	if (!Array.isArray(nids) || !nids.length) { | ||||||
| 		return callback(); | 		return; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	let notificationKeys = nids.map(nid => 'notifications:' + nid); | 	let notificationKeys = nids.map(nid => 'notifications:' + nid); | ||||||
|  | 	let mergeIds = await db.getObjectsFields(notificationKeys, ['mergeId']); | ||||||
|  | 	// Isolate mergeIds and find related notifications | ||||||
|  | 	mergeIds = _.uniq(mergeIds.map(set => set.mergeId)); | ||||||
|  |  | ||||||
| 	async.waterfall([ | 	const relatedNids = await Notifications.findRelated(mergeIds, 'uid:' + uid + ':notifications:unread'); | ||||||
| 		async.apply(db.getObjectsFields, notificationKeys, ['mergeId']), | 	notificationKeys = _.union(nids, relatedNids).map(nid => 'notifications:' + nid); | ||||||
| 		function (mergeIds, next) { |  | ||||||
| 			// Isolate mergeIds and find related notifications |  | ||||||
| 			mergeIds = _.uniq(mergeIds.map(set => set.mergeId)); |  | ||||||
|  |  | ||||||
| 			Notifications.findRelated(mergeIds, 'uid:' + uid + ':notifications:unread', next); | 	let notificationData = await db.getObjectsFields(notificationKeys, ['nid', 'datetime']); | ||||||
| 		}, | 	notificationData = notificationData.filter(n => n && n.nid); | ||||||
| 		function (relatedNids, next) { |  | ||||||
| 			notificationKeys = _.union(nids, relatedNids).map(nid => 'notifications:' + nid); |  | ||||||
|  |  | ||||||
| 			db.getObjectsFields(notificationKeys, ['nid', 'datetime'], next); | 	nids = notificationData.map(n => n.nid); | ||||||
| 		}, | 	const datetimes = notificationData.map(n => (n && n.datetime) || Date.now()); | ||||||
| 		function (notificationData, next) { | 	await Promise.all([ | ||||||
| 			notificationData = notificationData.filter(n => n && n.nid); | 		db.sortedSetRemove('uid:' + uid + ':notifications:unread', nids), | ||||||
|  | 		db.sortedSetAdd('uid:' + uid + ':notifications:read', datetimes, nids), | ||||||
| 			nids = notificationData.map(n => n.nid); | 	]); | ||||||
|  |  | ||||||
| 			async.parallel([ |  | ||||||
| 				function (next) { |  | ||||||
| 					db.sortedSetRemove('uid:' + uid + ':notifications:unread', nids, next); |  | ||||||
| 				}, |  | ||||||
| 				function (next) { |  | ||||||
| 					const datetimes = notificationData.map(n => (n && n.datetime) || Date.now()); |  | ||||||
| 					db.sortedSetAdd('uid:' + uid + ':notifications:read', datetimes, nids, next); |  | ||||||
| 				}, |  | ||||||
| 			], next); |  | ||||||
| 		}, |  | ||||||
| 	], function (err) { |  | ||||||
| 		callback(err); |  | ||||||
| 	}); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Notifications.markAllRead = function (uid, callback) { | Notifications.markAllRead = async function (uid) { | ||||||
| 	async.waterfall([ | 	const nids = await db.getSortedSetRevRange('uid:' + uid + ':notifications:unread', 0, 99); | ||||||
| 		function (next) { | 	await Notifications.markReadMultiple(nids, uid); | ||||||
| 			db.getSortedSetRevRange('uid:' + uid + ':notifications:unread', 0, 99, next); |  | ||||||
| 		}, |  | ||||||
| 		function (nids, next) { |  | ||||||
| 			Notifications.markReadMultiple(nids, uid, next); |  | ||||||
| 		}, |  | ||||||
| 	], callback); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Notifications.prune = function (callback) { | Notifications.prune = async function () { | ||||||
| 	callback = callback || function () {}; | 	const week = 604800000; | ||||||
|  | 	const cutoffTime = Date.now() - week; | ||||||
| 	async.waterfall([ | 	const nids = await db.getSortedSetRangeByScore('notifications', 0, 500, '-inf', cutoffTime); | ||||||
| 		function (next) { | 	if (!nids.length) { | ||||||
| 			const week = 604800000; | 		return; | ||||||
| 			const cutoffTime = Date.now() - week; | 	} | ||||||
| 			db.getSortedSetRangeByScore('notifications', 0, 500, '-inf', cutoffTime, next); | 	try { | ||||||
| 		}, | 		await Promise.all([ | ||||||
| 		function (nids, next) { | 			db.sortedSetRemove('notifications', nids), | ||||||
| 			if (!nids.length) { | 			db.deleteAll(nids.map(nid => 'notifications:' + nid)), | ||||||
| 				return callback(); | 		]); | ||||||
| 			} | 	} catch (err) { | ||||||
|  |  | ||||||
| 			async.parallel([ |  | ||||||
| 				function (next) { |  | ||||||
| 					db.sortedSetRemove('notifications', nids, next); |  | ||||||
| 				}, |  | ||||||
| 				function (next) { |  | ||||||
| 					const keys = nids.map(nid => 'notifications:' + nid); |  | ||||||
| 					db.deleteAll(keys, next); |  | ||||||
| 				}, |  | ||||||
| 			], next); |  | ||||||
| 		}, |  | ||||||
| 	], function (err) { |  | ||||||
| 		if (err) { | 		if (err) { | ||||||
| 			winston.error('Encountered error pruning notifications', err); | 			winston.error('Encountered error pruning notifications', err); | ||||||
| 		} | 		} | ||||||
| 		callback(err); | 	} | ||||||
| 	}); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Notifications.merge = function (notifications, callback) { | Notifications.merge = async function (notifications) { | ||||||
| 	// When passed a set of notification objects, merge any that can be merged | 	// When passed a set of notification objects, merge any that can be merged | ||||||
| 	var mergeIds = [ | 	var mergeIds = [ | ||||||
| 		'notifications:upvoted_your_post_in', | 		'notifications:upvoted_your_post_in', | ||||||
| @@ -575,11 +412,10 @@ Notifications.merge = function (notifications, callback) { | |||||||
| 		return notifications; | 		return notifications; | ||||||
| 	}, notifications); | 	}, notifications); | ||||||
|  |  | ||||||
| 	plugins.fireHook('filter:notifications.merge', { | 	const data = await plugins.fireHook('filter:notifications.merge', { | ||||||
| 		notifications: notifications, | 		notifications: notifications, | ||||||
| 	}, function (err, data) { |  | ||||||
| 		callback(err, data.notifications); |  | ||||||
| 	}); | 	}); | ||||||
|  | 	return data && data.notifications; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Notifications.async = require('./promisify')(Notifications); | Notifications.async = require('./promisify')(Notifications); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user