From 1c5fad6dae5a2e8491735199cfc04cd3b85a2055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 16 Jul 2019 12:47:55 -0400 Subject: [PATCH] feat: #7743 user/notifications.js --- src/user/notifications.js | 399 ++++++++++++++------------------------ 1 file changed, 144 insertions(+), 255 deletions(-) diff --git a/src/user/notifications.js b/src/user/notifications.js index c5fd55e2cd..68c6dc879f 100644 --- a/src/user/notifications.js +++ b/src/user/notifications.js @@ -1,7 +1,6 @@ 'use strict'; -var async = require('async'); var winston = require('winston'); var _ = require('lodash'); @@ -14,315 +13,205 @@ var utils = require('../utils'); var UserNotifications = module.exports; -UserNotifications.get = function (uid, callback) { +UserNotifications.get = async function (uid) { if (parseInt(uid, 10) <= 0) { - return setImmediate(callback, null, { read: [], unread: [] }); + return { read: [], unread: [] }; } - let unread; - async.waterfall([ - function (next) { - getNotificationsFromSet('uid:' + uid + ':notifications:unread', uid, 0, 29, next); - }, - function (_unread, next) { - unread = _unread.filter(Boolean); - if (unread.length < 30) { - getNotificationsFromSet('uid:' + uid + ':notifications:read', uid, 0, 29 - unread.length, next); - } else { - next(null, []); - } - }, - function (read, next) { - next(null, { - read: read.filter(Boolean), - unread: unread, - }); - }, - ], callback); + let unread = await getNotificationsFromSet('uid:' + uid + ':notifications:unread', uid, 0, 29); + unread = unread.filter(Boolean); + let read = []; + if (unread.length < 30) { + read = await getNotificationsFromSet('uid:' + uid + ':notifications:read', uid, 0, 29 - unread.length); + } + return { + read: read.filter(Boolean), + unread: unread, + }; }; -function filterNotifications(nids, filter, callback) { +async function filterNotifications(nids, filter) { if (!filter) { - return setImmediate(callback, null, nids); + return nids; } - async.waterfall([ - function (next) { - const keys = nids.map(nid => 'notifications:' + nid); - db.getObjectsFields(keys, ['nid', 'type'], next); - }, - function (notifications, next) { - nids = notifications.filter(n => n && n.nid && n.type === filter).map(n => n.nid); - next(null, nids); - }, - ], callback); + const keys = nids.map(nid => 'notifications:' + nid); + const notifications = await db.getObjectsFields(keys, ['nid', 'type']); + return notifications.filter(n => n && n.nid && n.type === filter).map(n => n.nid); } -UserNotifications.getAll = function (uid, filter, callback) { - var nids; - async.waterfall([ - function (next) { - db.getSortedSetRevRange([ - 'uid:' + uid + ':notifications:unread', - 'uid:' + uid + ':notifications:read', - ], 0, -1, next); - }, - function (_nids, next) { - nids = _.uniq(_nids); - db.isSortedSetMembers('notifications', nids, next); - }, - function (exists, next) { - var deleteNids = []; - - nids = nids.filter(function (nid, index) { - if (!nid || !exists[index]) { - deleteNids.push(nid); - } - return nid && exists[index]; - }); - - deleteUserNids(deleteNids, uid, next); - }, - function (next) { - filterNotifications(nids, filter, next); - }, - ], callback); +UserNotifications.getAll = async function (uid, filter) { + let nids = await db.getSortedSetRevRange([ + 'uid:' + uid + ':notifications:unread', + 'uid:' + uid + ':notifications:read', + ], 0, -1); + nids = _.uniq(nids); + const exists = await db.isSortedSetMembers('notifications', nids); + var deleteNids = []; + + nids = nids.filter(function (nid, index) { + if (!nid || !exists[index]) { + deleteNids.push(nid); + } + return nid && exists[index]; + }); + + await deleteUserNids(deleteNids, uid); + return await filterNotifications(nids, filter); }; -function deleteUserNids(nids, uid, callback) { - callback = callback || function () {}; - if (!nids.length) { - return setImmediate(callback); - } - db.sortedSetRemove([ +async function deleteUserNids(nids, uid) { + await db.sortedSetRemove([ 'uid:' + uid + ':notifications:read', 'uid:' + uid + ':notifications:unread', - ], nids, callback); + ], nids); } -function getNotificationsFromSet(set, uid, start, stop, callback) { - async.waterfall([ - function (next) { - db.getSortedSetRevRange(set, start, stop, next); - }, - function (nids, next) { - UserNotifications.getNotifications(nids, uid, next); - }, - ], callback); +async function getNotificationsFromSet(set, uid, start, stop) { + const nids = await db.getSortedSetRevRange(set, start, stop); + return await UserNotifications.getNotifications(nids, uid); } -UserNotifications.getNotifications = function (nids, uid, callback) { +UserNotifications.getNotifications = async function (nids, uid) { if (!Array.isArray(nids) || !nids.length) { - return setImmediate(callback, null, []); + return []; } - var notificationData = []; - async.waterfall([ - function (next) { - async.parallel({ - notifications: function (next) { - notifications.getMultiple(nids, next); - }, - hasRead: function (next) { - db.isSortedSetMembers('uid:' + uid + ':notifications:read', nids, next); - }, - }, next); - }, - function (results, next) { - var deletedNids = []; - notificationData = results.notifications.filter(function (notification, index) { - if (!notification || !notification.nid) { - deletedNids.push(nids[index]); - } - if (notification) { - notification.read = results.hasRead[index]; - notification.readClass = !notification.read ? 'unread' : ''; - } - - return notification && notification.path; - }); - - deleteUserNids(deletedNids, uid, next); - }, - function (next) { - notifications.merge(notificationData, next); - }, - function (notifications, next) { - plugins.fireHook('filter:user.notifications.getNotifications', { - uid: uid, - notifications: notifications, - }, function (err, result) { - next(err, result && result.notifications); - }); - }, - ], callback); + const [notifObjs, hasRead] = await Promise.all([ + notifications.getMultiple(nids), + db.isSortedSetMembers('uid:' + uid + ':notifications:read', nids), + ]); + + const deletedNids = []; + let notificationData = notifObjs.filter(function (notification, index) { + if (!notification || !notification.nid) { + deletedNids.push(nids[index]); + } + if (notification) { + notification.read = hasRead[index]; + notification.readClass = !notification.read ? 'unread' : ''; + } + + return notification && notification.path; + }); + + await deleteUserNids(deletedNids, uid); + notificationData = await notifications.merge(notificationData); + const result = await plugins.fireHook('filter:user.notifications.getNotifications', { + uid: uid, + notifications: notificationData, + }); + return result && result.notifications; }; -UserNotifications.getDailyUnread = function (uid, callback) { - var yesterday = Date.now() - (1000 * 60 * 60 * 24); // Approximate, can be more or less depending on time changes, makes no difference really. - - async.waterfall([ - function (next) { - db.getSortedSetRevRangeByScore('uid:' + uid + ':notifications:unread', 0, 20, '+inf', yesterday, next); - }, - function (nids, next) { - UserNotifications.getNotifications(nids, uid, next); - }, - ], callback); +UserNotifications.getDailyUnread = async function (uid) { + const yesterday = Date.now() - (1000 * 60 * 60 * 24); // Approximate, can be more or less depending on time changes, makes no difference really. + const nids = await db.getSortedSetRevRangeByScore('uid:' + uid + ':notifications:unread', 0, 20, '+inf', yesterday); + return await UserNotifications.getNotifications(nids, uid); }; -UserNotifications.getUnreadCount = function (uid, callback) { +UserNotifications.getUnreadCount = async function (uid) { if (parseInt(uid, 10) <= 0) { - return setImmediate(callback, null, 0); + return 0; } + let nids = await db.getSortedSetRevRange('uid:' + uid + ':notifications:unread', 0, 99); + nids = await notifications.filterExists(nids); + const keys = nids.map(nid => 'notifications:' + nid); + const notifData = await db.getObjectsFields(keys, ['mergeId']); + const mergeIds = notifData.map(n => n.mergeId); + + // Collapse any notifications with identical mergeIds + return mergeIds.reduce(function (count, mergeId, idx, arr) { + // A missing (null) mergeId means that notification is counted separately. + if (mergeId === null || idx === arr.indexOf(mergeId)) { + count += 1; + } - async.waterfall([ - function (next) { - db.getSortedSetRevRange('uid:' + uid + ':notifications:unread', 0, 99, next); - }, - function (nids, next) { - notifications.filterExists(nids, next); - }, - function (nids, next) { - const keys = nids.map(nid => 'notifications:' + nid); - db.getObjectsFields(keys, ['mergeId'], next); - }, - function (mergeIds, next) { - // Collapse any notifications with identical mergeIds - mergeIds = mergeIds.map(set => set.mergeId); - - next(null, mergeIds.reduce(function (count, mergeId, idx, arr) { - // A missing (null) mergeId means that notification is counted separately. - if (mergeId === null || idx === arr.indexOf(mergeId)) { - count += 1; - } - - return count; - }, 0)); - }, - ], callback); + return count; + }, 0); }; -UserNotifications.getUnreadByField = function (uid, field, values, callback) { - var nids; - async.waterfall([ - function (next) { - db.getSortedSetRevRange('uid:' + uid + ':notifications:unread', 0, 99, next); - }, - function (_nids, next) { - nids = _nids; - if (!nids.length) { - return callback(null, []); - } - - const keys = nids.map(nid => 'notifications:' + nid); - db.getObjectsFields(keys, ['nid', field], next); - }, - function (notifications, next) { - const valuesSet = new Set(values.map(value => String(value))); - nids = notifications.filter(n => n && n[field] && valuesSet.has(String(n[field]))).map(n => n.nid); - next(null, nids); - }, - ], callback); +UserNotifications.getUnreadByField = async function (uid, field, values) { + const nids = await db.getSortedSetRevRange('uid:' + uid + ':notifications:unread', 0, 99); + if (!nids.length) { + return []; + } + const keys = nids.map(nid => 'notifications:' + nid); + const notifData = await db.getObjectsFields(keys, ['nid', field]); + const valuesSet = new Set(values.map(value => String(value))); + return notifData.filter(n => n && n[field] && valuesSet.has(String(n[field]))).map(n => n.nid); }; -UserNotifications.deleteAll = function (uid, callback) { +UserNotifications.deleteAll = async function (uid) { if (parseInt(uid, 10) <= 0) { - return setImmediate(callback); + return; } - db.deleteAll([ + await db.deleteAll([ 'uid:' + uid + ':notifications:unread', 'uid:' + uid + ':notifications:read', - ], callback); + ]); }; -UserNotifications.sendTopicNotificationToFollowers = function (uid, topicData, postData) { - var followers; - async.waterfall([ - function (next) { - db.getSortedSetRange('followers:' + uid, 0, -1, next); - }, - function (followers, next) { - privileges.categories.filterUids('read', topicData.cid, followers, next); - }, - function (_followers, next) { - followers = _followers; - if (!followers.length) { - return; - } - - var title = topicData.title; - if (title) { - title = utils.decodeHTMLEntities(title); - } - - notifications.create({ - type: 'new-topic', - bodyShort: '[[notifications:user_posted_topic, ' + postData.user.username + ', ' + title + ']]', - bodyLong: postData.content, - pid: postData.pid, - path: '/post/' + postData.pid, - nid: 'tid:' + postData.tid + ':uid:' + uid, - tid: postData.tid, - from: uid, - }, next); - }, - ], function (err, notification) { - if (err) { - return winston.error(err); +UserNotifications.sendTopicNotificationToFollowers = async function (uid, topicData, postData) { + try { + let followers = await db.getSortedSetRange('followers:' + uid, 0, -1); + followers = await privileges.categories.filterUids('read', topicData.cid, followers); + if (!followers.length) { + return; + } + let title = topicData.title; + if (title) { + title = utils.decodeHTMLEntities(title); } - if (notification) { - notifications.push(notification, followers); + const notifObj = await notifications.create({ + type: 'new-topic', + bodyShort: '[[notifications:user_posted_topic, ' + postData.user.username + ', ' + title + ']]', + bodyLong: postData.content, + pid: postData.pid, + path: '/post/' + postData.pid, + nid: 'tid:' + postData.tid + ':uid:' + uid, + tid: postData.tid, + from: uid, + }); + + await notifications.push(notifObj, followers); + } catch (err) { + if (err) { + return winston.error(err); } - }); + throw err; + } }; -UserNotifications.sendWelcomeNotification = function (uid, callback) { - callback = callback || function () {}; +UserNotifications.sendWelcomeNotification = async function (uid) { if (!meta.config.welcomeNotification) { - return callback(); + return; } var path = meta.config.welcomeLink ? meta.config.welcomeLink : '#'; + const notifObj = await notifications.create({ + bodyShort: meta.config.welcomeNotification, + path: path, + nid: 'welcome_' + uid, + from: meta.config.welcomeUid ? meta.config.welcomeUid : null, + }); - async.waterfall([ - function (next) { - notifications.create({ - bodyShort: meta.config.welcomeNotification, - path: path, - nid: 'welcome_' + uid, - from: meta.config.welcomeUid ? meta.config.welcomeUid : null, - }, next); - }, - function (notification, next) { - if (!notification) { - return next(); - } - notifications.push(notification, [uid], next); - }, - ], callback); + await notifications.push(notifObj, [uid]); }; -UserNotifications.sendNameChangeNotification = function (uid, username) { - notifications.create({ +UserNotifications.sendNameChangeNotification = async function (uid, username) { + const notifObj = await notifications.create({ bodyShort: '[[user:username_taken_workaround, ' + username + ']]', image: 'brand:logo', nid: 'username_taken:' + uid, datetime: Date.now(), - }, function (err, notification) { - if (!err && notification) { - notifications.push(notification, uid); - } }); + + await notifications.push(notifObj, uid); }; -UserNotifications.pushCount = function (uid) { +UserNotifications.pushCount = async function (uid) { var websockets = require('./../socket.io'); - UserNotifications.getUnreadCount(uid, function (err, count) { - if (err) { - return winston.error(err.stack); - } - - websockets.in('uid_' + uid).emit('event:notifications.updateCount', count); - }); + const count = await UserNotifications.getUnreadCount(uid); + websockets.in('uid_' + uid).emit('event:notifications.updateCount', count); };