diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index 87b6b798ca..fdc2f16374 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -260,5 +260,6 @@ User.search = function (socket, data, callback) { }; User.restartJobs = function (socket, data, callback) { - user.startJobs(callback); + user.startJobs(); + callback(); }; diff --git a/src/user/index.js b/src/user/index.js index 596871065f..75d3243465 100644 --- a/src/user/index.js +++ b/src/user/index.js @@ -1,6 +1,5 @@ 'use strict'; -var async = require('async'); var _ = require('lodash'); var groups = require('../groups'); @@ -9,6 +8,7 @@ var db = require('../database'); var privileges = require('../privileges'); var categories = require('../categories'); var meta = require('../meta'); +const utils = require('../utils'); var User = module.exports; @@ -40,68 +40,51 @@ require('./online')(User); require('./blocks')(User); require('./uploads')(User); -User.getUidsFromSet = function (set, start, stop, callback) { +User.exists = async function (uid) { + return await db.exists('user:' + uid); +}; + +User.existsBySlug = async function (userslug) { + const exists = await User.getUidByUserslug(userslug); + return !!exists; +}; + +User.getUidsFromSet = async function (set, start, stop) { if (set === 'users:online') { - var count = parseInt(stop, 10) === -1 ? stop : stop - start + 1; - var now = Date.now(); - db.getSortedSetRevRangeByScore(set, start, count, '+inf', now - (meta.config.onlineCutoff * 60000), callback); - } else { - db.getSortedSetRevRange(set, start, stop, callback); + const count = parseInt(stop, 10) === -1 ? stop : stop - start + 1; + const now = Date.now(); + return await db.getSortedSetRevRangeByScore(set, start, count, '+inf', now - (meta.config.onlineCutoff * 60000)); } + return await db.getSortedSetRevRange(set, start, stop); }; -User.getUsersFromSet = function (set, uid, start, stop, callback) { - async.waterfall([ - function (next) { - User.getUidsFromSet(set, start, stop, next); - }, - function (uids, next) { - User.getUsers(uids, uid, next); - }, - ], callback); +User.getUsersFromSet = async function (set, uid, start, stop) { + const uids = await User.getUidsFromSet(set, start, stop); + return await User.getUsers(uids, uid); }; -User.getUsersWithFields = function (uids, fields, uid, callback) { - async.waterfall([ - function (next) { - plugins.fireHook('filter:users.addFields', { fields: fields }, next); - }, - function (data, next) { - data.fields = _.uniq(data.fields); - - async.parallel({ - userData: function (next) { - User.getUsersFields(uids, data.fields, next); - }, - isAdmin: function (next) { - User.isAdministrator(uids, next); - }, - }, next); - }, - function (results, next) { - results.userData.forEach(function (user, index) { - if (user) { - user.administrator = results.isAdmin[index]; - - if (user.hasOwnProperty('status')) { - user.status = User.getStatus(user); - } - } - }); - plugins.fireHook('filter:userlist.get', { users: results.userData, uid: uid }, next); - }, - function (data, next) { - next(null, data.users); - }, - ], callback); +User.getUsersWithFields = async function (uids, fields, uid) { + let results = await plugins.fireHook('filter:users.addFields', { fields: fields }); + results.fields = _.uniq(results.fields); + const [userData, isAdmin] = await Promise.all([ + User.getUsersFields(uids, results.fields), + User.isAdministrator(uids), + ]); + userData.forEach(function (user, index) { + if (user) { + user.administrator = isAdmin[index]; + } + }); + results = await plugins.fireHook('filter:userlist.get', { users: userData, uid: uid }); + return results.users; }; -User.getUsers = function (uids, uid, callback) { - User.getUsersWithFields(uids, [ +User.getUsers = async function (uids, uid) { + return await User.getUsersWithFields(uids, [ 'uid', 'username', 'userslug', 'picture', 'status', 'postcount', 'reputation', 'email:confirmed', 'lastonline', 'flags', 'banned', 'banned:expire', 'joindate', - ], uid, callback); + ], uid); }; User.getStatus = function (userData) { @@ -112,211 +95,135 @@ User.getStatus = function (userData) { return isOnline ? (userData.status || 'online') : 'offline'; }; -User.exists = function (uid, callback) { - db.exists('user:' + uid, callback); -}; - -User.existsBySlug = function (userslug, callback) { - User.getUidByUserslug(userslug, function (err, exists) { - callback(err, !!exists); - }); -}; - -User.getUidByUsername = function (username, callback) { +User.getUidByUsername = async function (username) { if (!username) { - return callback(null, 0); + return 0; } - db.sortedSetScore('username:uid', username, callback); + return await db.sortedSetScore('username:uid', username); }; -User.getUidsByUsernames = function (usernames, callback) { - db.sortedSetScores('username:uid', usernames, callback); +User.getUidsByUsernames = async function (usernames) { + return await db.sortedSetScores('username:uid', usernames); }; -User.getUidByUserslug = function (userslug, callback) { +User.getUidByUserslug = async function (userslug) { if (!userslug) { - return callback(null, 0); + return 0; } - db.sortedSetScore('userslug:uid', userslug, callback); + return await db.sortedSetScore('userslug:uid', userslug); }; -User.getUsernamesByUids = function (uids, callback) { - async.waterfall([ - function (next) { - User.getUsersFields(uids, ['username'], next); - }, - function (users, next) { - users = users.map(function (user) { - return user.username; - }); - - next(null, users); - }, - ], callback); +User.getUsernamesByUids = async function (uids) { + const users = await User.getUsersFields(uids, ['username']); + return users.map(user => user.username); }; -User.getUsernameByUserslug = function (slug, callback) { - async.waterfall([ - function (next) { - User.getUidByUserslug(slug, next); - }, - function (uid, next) { - User.getUserField(uid, 'username', next); - }, - ], callback); +User.getUsernameByUserslug = async function (slug) { + const uid = await User.getUidByUserslug(slug); + return await User.getUserField(uid, 'username'); }; -User.getUidByEmail = function (email, callback) { - db.sortedSetScore('email:uid', email.toLowerCase(), callback); +User.getUidByEmail = async function (email) { + return await db.sortedSetScore('email:uid', email.toLowerCase()); }; -User.getUidsByEmails = function (emails, callback) { - emails = emails.map(function (email) { - return email && email.toLowerCase(); - }); - db.sortedSetScores('email:uid', emails, callback); +User.getUidsByEmails = async function (emails) { + emails = emails.map(email => email && email.toLowerCase()); + return await db.sortedSetScores('email:uid', emails); }; -User.getUsernameByEmail = function (email, callback) { - async.waterfall([ - function (next) { - db.sortedSetScore('email:uid', email.toLowerCase(), next); - }, - function (uid, next) { - User.getUserField(uid, 'username', next); - }, - ], callback); +User.getUsernameByEmail = async function (email) { + const uid = await db.sortedSetScore('email:uid', String(email).toLowerCase()); + return await User.getUserField(uid, 'username'); }; -User.isModerator = function (uid, cid, callback) { - privileges.users.isModerator(uid, cid, callback); +User.isModerator = async function (uid, cid) { + return await privileges.users.isModerator(uid, cid); }; -User.isModeratorOfAnyCategory = function (uid, callback) { - User.getModeratedCids(uid, function (err, cids) { - callback(err, Array.isArray(cids) ? !!cids.length : false); - }); +User.isModeratorOfAnyCategory = async function (uid) { + const cids = await User.getModeratedCids(uid); + return Array.isArray(cids) ? !!cids.length : false; }; -User.isAdministrator = function (uid, callback) { - privileges.users.isAdministrator(uid, callback); +User.isAdministrator = async function (uid) { + return await privileges.users.isAdministrator(uid); }; -User.isGlobalModerator = function (uid, callback) { - privileges.users.isGlobalModerator(uid, callback); +User.isGlobalModerator = async function (uid) { + return await privileges.users.isGlobalModerator(uid); }; -User.getPrivileges = function (uid, callback) { - async.parallel({ - isAdmin: async.apply(User.isAdministrator, uid), - isGlobalModerator: async.apply(User.isGlobalModerator, uid), - isModeratorOfAnyCategory: async.apply(User.isModeratorOfAnyCategory, uid), - }, callback); +User.getPrivileges = async function (uid) { + return await utils.promiseParallel({ + isAdmin: User.isAdministrator(uid), + isGlobalModerator: User.isGlobalModerator(uid), + isModeratorOfAnyCategory: User.isModeratorOfAnyCategory(uid), + }); }; -User.isPrivileged = function (uid, callback) { - User.getPrivileges(uid, function (err, results) { - callback(err, results ? (results.isAdmin || results.isGlobalModerator || results.isModeratorOfAnyCategory) : false); - }); +User.isPrivileged = async function (uid) { + const results = await User.getPrivileges(uid); + return results ? (results.isAdmin || results.isGlobalModerator || results.isModeratorOfAnyCategory) : false; }; -User.isAdminOrGlobalMod = function (uid, callback) { - async.parallel({ - isAdmin: async.apply(User.isAdministrator, uid), - isGlobalMod: async.apply(User.isGlobalModerator, uid), - }, function (err, results) { - callback(err, results ? (results.isAdmin || results.isGlobalMod) : false); - }); +User.isAdminOrGlobalMod = async function (uid) { + const [isAdmin, isGlobalMod] = await Promise.all([ + User.isAdministrator(uid), + User.isGlobalModerator(uid), + ]); + return isAdmin || isGlobalMod; }; -User.isAdminOrSelf = function (callerUid, uid, callback) { - isSelfOrMethod(callerUid, uid, User.isAdministrator, callback); +User.isAdminOrSelf = async function (callerUid, uid) { + await isSelfOrMethod(callerUid, uid, User.isAdministrator); }; -User.isAdminOrGlobalModOrSelf = function (callerUid, uid, callback) { - isSelfOrMethod(callerUid, uid, User.isAdminOrGlobalMod, callback); +User.isAdminOrGlobalModOrSelf = async function (callerUid, uid) { + await isSelfOrMethod(callerUid, uid, User.isAdminOrGlobalMod); }; -User.isPrivilegedOrSelf = function (callerUid, uid, callback) { - isSelfOrMethod(callerUid, uid, User.isPrivileged, callback); +User.isPrivilegedOrSelf = async function (callerUid, uid) { + await isSelfOrMethod(callerUid, uid, User.isPrivileged); }; -function isSelfOrMethod(callerUid, uid, method, callback) { +async function isSelfOrMethod(callerUid, uid, method) { if (parseInt(callerUid, 10) === parseInt(uid, 10)) { - return callback(); + return; + } + const isPass = await method(callerUid); + if (!isPass) { + throw new Error('[[error:no-privileges]]'); } - async.waterfall([ - function (next) { - method(callerUid, next); - }, - function (isPass, next) { - if (!isPass) { - return next(new Error('[[error:no-privileges]]')); - } - next(); - }, - ], callback); } -User.getAdminsandGlobalMods = function (callback) { - async.waterfall([ - function (next) { - async.parallel([ - async.apply(groups.getMembers, 'administrators', 0, -1), - async.apply(groups.getMembers, 'Global Moderators', 0, -1), - ], next); - }, - function (results, next) { - User.getUsersData(_.union.apply(_, results), next); - }, - ], callback); +User.getAdminsandGlobalMods = async function () { + const results = await groups.getMembersOfGroups(['administrators', 'Global Moderators']); + return await User.getUsersData(_.union.apply(_, results)); }; -User.getAdminsandGlobalModsandModerators = function (callback) { - async.waterfall([ - function (next) { - async.parallel([ - async.apply(groups.getMembers, 'administrators', 0, -1), - async.apply(groups.getMembers, 'Global Moderators', 0, -1), - async.apply(User.getModeratorUids), - ], next); - }, - function (results, next) { - User.getUsersData(_.union.apply(_, results), next); - }, - ], callback); +User.getAdminsandGlobalModsandModerators = async function () { + const results = await Promise.all([ + groups.getMembers('administrators', 0, -1), + groups.getMembers('Global Moderators', 0, -1), + User.getModeratorUids(), + ]); + return await User.getUsersData(_.union.apply(_, results)); }; -User.getModeratorUids = function (callback) { - async.waterfall([ - async.apply(categories.getAllCidsFromSet, 'categories:cid'), - function (cids, next) { - categories.getModeratorUids(cids, next); - }, - function (uids, next) { - next(null, _.union(...uids)); - }, - ], callback); +User.getModeratorUids = async function () { + const cids = await categories.getAllCidsFromSet('categories:cid'); + const uids = await categories.getModeratorUids(cids); + return _.union(...uids); }; -User.getModeratedCids = function (uid, callback) { +User.getModeratedCids = async function (uid) { if (parseInt(uid, 10) <= 0) { - return setImmediate(callback, null, []); + return []; } - var cids; - async.waterfall([ - function (next) { - categories.getAllCidsFromSet('categories:cid', next); - }, - function (_cids, next) { - cids = _cids; - User.isModerator(uid, cids, next); - }, - function (isMods, next) { - cids = cids.filter((cid, index) => cid && isMods[index]); - next(null, cids); - }, - ], callback); + const cids = await categories.getAllCidsFromSet('categories:cid'); + const isMods = await User.isModerator(uid, cids); + return cids.filter((cid, index) => cid && isMods[index]); }; User.addInterstitials = function (callback) { diff --git a/src/user/info.js b/src/user/info.js index 5315abdb68..9eb2268355 100644 --- a/src/user/info.js +++ b/src/user/info.js @@ -1,6 +1,5 @@ 'use strict'; -var async = require('async'); var _ = require('lodash'); var validator = require('validator'); @@ -11,181 +10,126 @@ var topics = require('../topics'); var utils = require('../../public/src/utils'); module.exports = function (User) { - User.getLatestBanInfo = function (uid, callback) { + User.getLatestBanInfo = async function (uid) { // Simply retrieves the last record of the user's ban, even if they've been unbanned since then. - async.waterfall([ - function (next) { - db.getSortedSetRevRange('uid:' + uid + ':bans:timestamp', 0, 0, next); - }, - function (record, next) { - if (!record.length) { - return next(new Error('no-ban-info')); - } - db.getObject(record[0], next); - }, - function (banInfo, next) { - const expire = parseInt(banInfo.expire, 10); - const expire_readable = utils.toISOString(expire); - - next(null, { - uid: uid, - timestamp: banInfo.timestamp, - banned_until: expire, - expiry: expire, /* backward compatible alias */ - banned_until_readable: expire_readable, - expiry_readable: expire_readable, /* backward compatible alias */ - reason: validator.escape(String(banInfo.reason || '')), - }); - }, - ], callback); + const record = await db.getSortedSetRevRange('uid:' + uid + ':bans:timestamp', 0, 0); + if (!record.length) { + throw new Error('no-ban-info'); + } + const banInfo = await db.getObject(record[0]); + const expire = parseInt(banInfo.expire, 10); + const expire_readable = utils.toISOString(expire); + return { + uid: uid, + timestamp: banInfo.timestamp, + banned_until: expire, + expiry: expire, /* backward compatible alias */ + banned_until_readable: expire_readable, + expiry_readable: expire_readable, /* backward compatible alias */ + reason: validator.escape(String(banInfo.reason || '')), + }; }; - User.getModerationHistory = function (uid, callback) { - async.waterfall([ - function (next) { - async.parallel({ - flags: async.apply(db.getSortedSetRevRangeWithScores, 'flags:byTargetUid:' + uid, 0, 19), - bans: async.apply(db.getSortedSetRevRange, 'uid:' + uid + ':bans:timestamp', 0, 19), - }, next); - }, - function (data, next) { - // Get pids from flag objects - var keys = data.flags.map(function (flagObj) { - return 'flag:' + flagObj.value; - }); - db.getObjectsFields(keys, ['type', 'targetId'], function (err, payload) { - if (err) { - return next(err); - } - - // Only pass on flag ids from posts - data.flags = payload.reduce(function (memo, cur, idx) { - if (cur.type === 'post') { - memo.push({ - value: parseInt(cur.targetId, 10), - score: data.flags[idx].score, - }); - } - - return memo; - }, []); - - getFlagMetadata(data, next); + User.getModerationHistory = async function (uid) { + let [flags, bans] = await Promise.all([ + db.getSortedSetRevRangeWithScores('flags:byTargetUid:' + uid, 0, 19), + db.getSortedSetRevRange('uid:' + uid + ':bans:timestamp', 0, 19), + ]); + + // Get pids from flag objects + const keys = flags.map(flagObj => 'flag:' + flagObj.value); + const payload = await db.getObjectsFields(keys, ['type', 'targetId']); + + // Only pass on flag ids from posts + flags = payload.reduce(function (memo, cur, idx) { + if (cur.type === 'post') { + memo.push({ + value: parseInt(cur.targetId, 10), + score: flags[idx].score, }); - }, - function (data, next) { - formatBanData(data, next); - }, - ], callback); - }; + } + + return memo; + }, []); - User.getHistory = function (set, callback) { - async.waterfall([ - function (next) { - db.getSortedSetRevRangeWithScores(set, 0, -1, next); - }, - function (data, next) { - next(null, data.map(function (set) { - set.timestamp = set.score; - set.timestampISO = utils.toISOString(set.score); - set.value = validator.escape(String(set.value.split(':')[0])); - delete set.score; - return set; - })); - }, - ], callback); + [flags, bans] = await Promise.all([ + getFlagMetadata(flags), + formatBanData(bans), + ]); + + return { + flags: flags, + bans: bans, + }; }; - function getFlagMetadata(data, callback) { - var pids = data.flags.map(function (flagObj) { - return parseInt(flagObj.value, 10); + User.getHistory = async function (set) { + const data = await db.getSortedSetRevRangeWithScores(set, 0, -1); + return data.map(function (set) { + set.timestamp = set.score; + set.timestampISO = utils.toISOString(set.score); + set.value = validator.escape(String(set.value.split(':')[0])); + delete set.score; + return set; }); - async.waterfall([ - function (next) { - posts.getPostsFields(pids, ['tid'], next); - }, - function (postData, next) { - var tids = postData.map(function (post) { - return post.tid; - }); + }; - topics.getTopicsFields(tids, ['title'], next); - }, - function (topicData, next) { - data.flags = data.flags.map(function (flagObj, idx) { - flagObj.pid = flagObj.value; - flagObj.timestamp = flagObj.score; - flagObj.timestampISO = new Date(flagObj.score).toISOString(); - flagObj.timestampReadable = new Date(flagObj.score).toString(); + async function getFlagMetadata(flags) { + const pids = flags.map(flagObj => parseInt(flagObj.value, 10)); + const postData = await posts.getPostsFields(pids, ['tid']); + const tids = postData.map(post => post.tid); - delete flagObj.value; - delete flagObj.score; + const topicData = await topics.getTopicsFields(tids, ['title']); + flags = flags.map(function (flagObj, idx) { + flagObj.pid = flagObj.value; + flagObj.timestamp = flagObj.score; + flagObj.timestampISO = new Date(flagObj.score).toISOString(); + flagObj.timestampReadable = new Date(flagObj.score).toString(); - return _.extend(flagObj, topicData[idx]); - }); - next(null, data); - }, - ], callback); + delete flagObj.value; + delete flagObj.score; + + return _.extend(flagObj, topicData[idx]); + }); + return flags; } - function formatBanData(data, callback) { - var banData; - async.waterfall([ - function (next) { - db.getObjects(data.bans, next); - }, - function (_banData, next) { - banData = _banData; - var uids = banData.map(banData => banData.fromUid); - - user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture'], next); - }, - function (usersData, next) { - data.bans = banData.map(function (banObj, index) { - banObj.user = usersData[index]; - banObj.until = parseInt(banObj.expire, 10); - banObj.untilReadable = new Date(banObj.until).toString(); - banObj.timestampReadable = new Date(banObj.timestamp).toString(); - banObj.timestampISO = utils.toISOString(banObj.timestamp); - banObj.reason = validator.escape(String(banObj.reason || '')) || '[[user:info.banned-no-reason]]'; - return banObj; - }); - next(null, data); - }, - ], callback); + async function formatBanData(bans) { + const banData = await db.getObjects(bans); + const uids = banData.map(banData => banData.fromUid); + const usersData = await user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture']); + return banData.map(function (banObj, index) { + banObj.user = usersData[index]; + banObj.until = parseInt(banObj.expire, 10); + banObj.untilReadable = new Date(banObj.until).toString(); + banObj.timestampReadable = new Date(banObj.timestamp).toString(); + banObj.timestampISO = utils.toISOString(banObj.timestamp); + banObj.reason = validator.escape(String(banObj.reason || '')) || '[[user:info.banned-no-reason]]'; + return banObj; + }); } - User.getModerationNotes = function (uid, start, stop, callback) { - var noteData; - async.waterfall([ - function (next) { - db.getSortedSetRevRange('uid:' + uid + ':moderation:notes', start, stop, next); - }, - function (noteIds, next) { - const keys = noteIds.map(id => 'uid:' + uid + ':moderation:note:' + id); - db.getObjects(keys, next); - }, - function (notes, next) { - var uids = []; - noteData = notes.map(function (note) { - if (note) { - uids.push(note.uid); - note.timestampISO = utils.toISOString(note.timestamp); - note.note = validator.escape(String(note.note)); - } - return note; - }); + User.getModerationNotes = async function (uid, start, stop) { + const noteIds = await db.getSortedSetRevRange('uid:' + uid + ':moderation:notes', start, stop); + const keys = noteIds.map(id => 'uid:' + uid + ':moderation:note:' + id); + const notes = await db.getObjects(keys); + const uids = []; + + const noteData = notes.map(function (note) { + if (note) { + uids.push(note.uid); + note.timestampISO = utils.toISOString(note.timestamp); + note.note = validator.escape(String(note.note)); + } + return note; + }); - User.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture'], next); - }, - function (userData, next) { - noteData.forEach(function (note, index) { - if (note) { - note.user = userData[index]; - } - }); - next(null, noteData); - }, - ], callback); + const userData = await User.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture']); + noteData.forEach(function (note, index) { + if (note) { + note.user = userData[index]; + } + }); + return noteData; }; }; diff --git a/src/user/invite.js b/src/user/invite.js index 84dd975001..1ad7acd467 100644 --- a/src/user/invite.js +++ b/src/user/invite.js @@ -12,175 +12,95 @@ var translator = require('../translator'); var utils = require('../utils'); module.exports = function (User) { - User.getInvites = function (uid, callback) { - async.waterfall([ - function (next) { - db.getSetMembers('invitation:uid:' + uid, next); - }, - function (emails, next) { - emails = emails.map(function (email) { - return validator.escape(String(email)); - }); - next(null, emails); - }, - ], callback); + User.getInvites = async function (uid) { + const emails = await db.getSetMembers('invitation:uid:' + uid); + return emails.map(email => validator.escape(String(email))); }; - User.getInvitesNumber = function (uid, callback) { - db.setCount('invitation:uid:' + uid, callback); + User.getInvitesNumber = async function (uid) { + return await db.setCount('invitation:uid:' + uid); }; - User.getInvitingUsers = function (callback) { - db.getSetMembers('invitation:uids', callback); + User.getInvitingUsers = async function () { + return await db.getSetMembers('invitation:uids'); }; - User.getAllInvites = function (callback) { - var uids; - async.waterfall([ - User.getInvitingUsers, - function (_uids, next) { - uids = _uids; - async.map(uids, User.getInvites, next); - }, - function (invitations, next) { - invitations = invitations.map(function (invites, index) { - return { - uid: uids[index], - invitations: invites, - }; - }); - next(null, invitations); - }, - ], callback); + User.getAllInvites = async function () { + const uids = await User.getInvitingUsers(); + const invitations = await async.map(uids, User.getInvites); + return invitations.map(function (invites, index) { + return { + uid: uids[index], + invitations: invites, + }; + }); }; - User.sendInvitationEmail = function (uid, email, callback) { - callback = callback || function () {}; - - var token = utils.generateUUID(); - var registerLink = nconf.get('url') + '/register?token=' + token + '&email=' + encodeURIComponent(email); - - var expireDays = meta.config.inviteExpiration; - var expireIn = expireDays * 86400000; - - async.waterfall([ - function (next) { - User.getUidByEmail(email, next); - }, - function (exists, next) { - if (exists) { - return next(new Error('[[error:email-taken]]')); - } - db.setAdd('invitation:uid:' + uid, email, next); - }, - function (next) { - db.setAdd('invitation:uids', uid, next); - }, - function (next) { - db.set('invitation:email:' + email, token, next); - }, - function (next) { - db.pexpireAt('invitation:email:' + email, Date.now() + expireIn, next); - }, - function (next) { - User.getUserField(uid, 'username', next); - }, - function (username, next) { - var title = meta.config.title || meta.config.browserTitle || 'NodeBB'; - translator.translate('[[email:invite, ' + title + ']]', meta.config.defaultLang, function (subject) { - var data = { - site_title: title, - registerLink: registerLink, - subject: subject, - username: username, - template: 'invitation', - expireDays: expireDays, - }; - - // Append default data to this email payload - data = Object.assign({}, emailer._defaultPayload, data); - - emailer.sendToEmail('invitation', email, meta.config.defaultLang, data, next); - }); - }, - ], callback); + User.sendInvitationEmail = async function (uid, email) { + const token = utils.generateUUID(); + const registerLink = nconf.get('url') + '/register?token=' + token + '&email=' + encodeURIComponent(email); + + const expireDays = meta.config.inviteExpiration; + const expireIn = expireDays * 86400000; + + const exists = await User.getUidByEmail(email); + if (exists) { + throw new Error('[[error:email-taken]]'); + } + await db.setAdd('invitation:uid:' + uid, email); + await db.setAdd('invitation:uids', uid); + await db.set('invitation:email:' + email, token); + await db.pexpireAt('invitation:email:' + email, Date.now() + expireIn); + const username = await User.getUserField(uid, 'username'); + const title = meta.config.title || meta.config.browserTitle || 'NodeBB'; + const subject = await translator.translate('[[email:invite, ' + title + ']]', meta.config.defaultLang); + let data = { + site_title: title, + registerLink: registerLink, + subject: subject, + username: username, + template: 'invitation', + expireDays: expireDays, + }; + + // Append default data to this email payload + data = Object.assign({}, emailer._defaultPayload, data); + + await emailer.sendToEmail('invitation', email, meta.config.defaultLang, data); }; - User.verifyInvitation = function (query, callback) { + User.verifyInvitation = async function (query) { if (!query.token || !query.email) { - return callback(new Error('[[error:invalid-data]]')); + throw new Error('[[error:invalid-data]]'); + } + const token = await db.get('invitation:email:' + query.email); + if (!token || token !== query.token) { + throw new Error('[[error:invalid-token]]'); } - - async.waterfall([ - function (next) { - db.get('invitation:email:' + query.email, next); - }, - function (token, next) { - if (!token || token !== query.token) { - return next(new Error('[[error:invalid-token]]')); - } - - next(); - }, - ], callback); }; - User.deleteInvitation = function (invitedBy, email, callback) { - callback = callback || function () {}; - async.waterfall([ - function getInvitedByUid(next) { - User.getUidByUsername(invitedBy, next); - }, - function deleteRegistries(invitedByUid, next) { - if (!invitedByUid) { - return next(new Error('[[error:invalid-username]]')); - } - async.parallel([ - function (next) { - deleteFromReferenceList(invitedByUid, email, next); - }, - function (next) { - db.delete('invitation:email:' + email, next); - }, - ], function (err) { - next(err); - }); - }, - ], callback); + User.deleteInvitation = async function (invitedBy, email) { + const invitedByUid = await User.getUidByUsername(invitedBy); + if (!invitedByUid) { + throw new Error('[[error:invalid-username]]'); + } + await Promise.all([ + deleteFromReferenceList(invitedByUid, email), + db.delete('invitation:email:' + email), + ]); }; - User.deleteInvitationKey = function (email, callback) { - callback = callback || function () {}; - - async.waterfall([ - function (next) { - User.getInvitingUsers(next); - }, - function (uids, next) { - async.each(uids, function (uid, next) { - deleteFromReferenceList(uid, email, next); - }, next); - }, - function (next) { - db.delete('invitation:email:' + email, next); - }, - ], callback); + User.deleteInvitationKey = async function (email) { + const uids = await User.getInvitingUsers(); + await Promise.all(uids.map(uid => deleteFromReferenceList(uid, email))); + await db.delete('invitation:email:' + email); }; - function deleteFromReferenceList(uid, email, callback) { - async.waterfall([ - function (next) { - db.setRemove('invitation:uid:' + uid, email, next); - }, - function (next) { - db.setCount('invitation:uid:' + uid, next); - }, - function (count, next) { - if (count === 0) { - return db.setRemove('invitation:uids', uid, next); - } - setImmediate(next); - }, - ], callback); + async function deleteFromReferenceList(uid, email) { + await db.setRemove('invitation:uid:' + uid, email); + const count = await db.setCount('invitation:uid:' + uid); + if (count === 0) { + await db.setRemove('invitation:uids', uid); + } } }; diff --git a/src/user/jobs.js b/src/user/jobs.js index 2127706e7d..3ba4a2db13 100644 --- a/src/user/jobs.js +++ b/src/user/jobs.js @@ -8,7 +8,7 @@ var meta = require('../meta'); var jobs = {}; module.exports = function (User) { - User.startJobs = function (callback) { + User.startJobs = function () { winston.verbose('[user/jobs] (Re-)starting user jobs...'); var started = 0; @@ -33,10 +33,6 @@ module.exports = function (User) { started += 1; winston.verbose('[user/jobs] ' + started + ' jobs started'); - - if (typeof callback === 'function') { - callback(); - } }; function startDigestJob(name, cronString, term) { diff --git a/test/user.js b/test/user.js index e44c1c7291..be2af0a2d7 100644 --- a/test/user.js +++ b/test/user.js @@ -1891,10 +1891,8 @@ describe('User', function () { describe('user jobs', function () { it('should start user jobs', function (done) { - User.startJobs(function (err) { - assert.ifError(err); - done(); - }); + User.startJobs(); + done(); }); it('should stop user jobs', function (done) {