mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
feat: #7743, user/digest, user/email, user/follow
This commit is contained in:
@@ -14,182 +14,122 @@ var utils = require('../utils');
|
|||||||
|
|
||||||
var Digest = module.exports;
|
var Digest = module.exports;
|
||||||
|
|
||||||
Digest.execute = function (payload, callback) {
|
Digest.execute = async function (payload) {
|
||||||
callback = callback || function () {};
|
const digestsDisabled = meta.config.disableEmailSubscriptions === 1;
|
||||||
|
|
||||||
var digestsDisabled = meta.config.disableEmailSubscriptions === 1;
|
|
||||||
if (digestsDisabled) {
|
if (digestsDisabled) {
|
||||||
winston.info('[user/jobs] Did not send digests (' + payload.interval + ') because subscription system is disabled.');
|
winston.info('[user/jobs] Did not send digests (' + payload.interval + ') because subscription system is disabled.');
|
||||||
return callback();
|
return;
|
||||||
}
|
}
|
||||||
|
let subscribers = payload.subscribers;
|
||||||
|
if (!subscribers) {
|
||||||
|
subscribers = await Digest.getSubscribers(payload.interval);
|
||||||
|
}
|
||||||
|
if (!subscribers.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const count = await Digest.send({
|
||||||
|
interval: payload.interval,
|
||||||
|
subscribers: subscribers,
|
||||||
|
});
|
||||||
|
winston.info('[user/jobs] Digest (' + payload.interval + ') scheduling completed. ' + count + ' email(s) sent.');
|
||||||
|
} catch (err) {
|
||||||
|
winston.error('[user/jobs] Could not send digests (' + payload.interval + ')', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
async.waterfall([
|
Digest.getSubscribers = async function (interval) {
|
||||||
function (next) {
|
var subscribers = [];
|
||||||
if (payload.subscribers) {
|
|
||||||
setImmediate(next, undefined, payload.subscribers);
|
await batch.processSortedSet('users:joindate', async function (uids) {
|
||||||
} else {
|
const settings = await user.getMultipleUserSettings(uids);
|
||||||
Digest.getSubscribers(payload.interval, next);
|
let subUids = [];
|
||||||
}
|
settings.forEach(function (hash) {
|
||||||
},
|
if (hash.dailyDigestFreq === interval) {
|
||||||
function (subscribers, next) {
|
subUids.push(hash.uid);
|
||||||
if (!subscribers.length) {
|
|
||||||
return callback();
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
subUids = await user.bans.filterBanned(subUids);
|
||||||
|
subscribers = subscribers.concat(subUids);
|
||||||
|
}, { interval: 1000 });
|
||||||
|
|
||||||
var data = {
|
const results = await plugins.fireHook('filter:digest.subscribers', {
|
||||||
interval: payload.interval,
|
interval: interval,
|
||||||
subscribers: subscribers,
|
subscribers: subscribers,
|
||||||
};
|
|
||||||
|
|
||||||
Digest.send(data, next);
|
|
||||||
},
|
|
||||||
], function (err, count) {
|
|
||||||
if (err) {
|
|
||||||
winston.error('[user/jobs] Could not send digests (' + payload.interval + ')', err);
|
|
||||||
} else {
|
|
||||||
winston.info('[user/jobs] Digest (' + payload.interval + ') scheduling completed. ' + count + ' email(s) sent.');
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(err);
|
|
||||||
});
|
});
|
||||||
|
return results.subscribers;
|
||||||
};
|
};
|
||||||
|
|
||||||
Digest.getSubscribers = function (interval, callback) {
|
Digest.send = async function (data) {
|
||||||
async.waterfall([
|
|
||||||
function (next) {
|
|
||||||
var subs = [];
|
|
||||||
|
|
||||||
batch.processSortedSet('users:joindate', function (uids, next) {
|
|
||||||
async.waterfall([
|
|
||||||
function (next) {
|
|
||||||
user.getMultipleUserSettings(uids, next);
|
|
||||||
},
|
|
||||||
function (settings, next) {
|
|
||||||
const subUids = [];
|
|
||||||
settings.forEach(function (hash) {
|
|
||||||
if (hash.dailyDigestFreq === interval) {
|
|
||||||
subUids.push(hash.uid);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
user.bans.filterBanned(subUids, next);
|
|
||||||
},
|
|
||||||
function (uids, next) {
|
|
||||||
subs = subs.concat(uids);
|
|
||||||
next();
|
|
||||||
},
|
|
||||||
], next);
|
|
||||||
}, { interval: 1000 }, function (err) {
|
|
||||||
next(err, subs);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function (subscribers, next) {
|
|
||||||
plugins.fireHook('filter:digest.subscribers', {
|
|
||||||
interval: interval,
|
|
||||||
subscribers: subscribers,
|
|
||||||
}, next);
|
|
||||||
},
|
|
||||||
function (results, next) {
|
|
||||||
next(null, results.subscribers);
|
|
||||||
},
|
|
||||||
], callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
Digest.send = function (data, callback) {
|
|
||||||
var emailsSent = 0;
|
var emailsSent = 0;
|
||||||
if (!data || !data.subscribers || !data.subscribers.length) {
|
if (!data || !data.subscribers || !data.subscribers.length) {
|
||||||
return callback(null, emailsSent);
|
return emailsSent;
|
||||||
}
|
}
|
||||||
var now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
async.waterfall([
|
const users = await user.getUsersFields(data.subscribers, ['uid', 'username', 'userslug', 'lastonline']);
|
||||||
function (next) {
|
|
||||||
user.getUsersFields(data.subscribers, ['uid', 'username', 'userslug', 'lastonline'], next);
|
|
||||||
},
|
|
||||||
function (users, next) {
|
|
||||||
async.eachLimit(users, 100, function (userObj, next) {
|
|
||||||
async.waterfall([
|
|
||||||
function (next) {
|
|
||||||
async.parallel({
|
|
||||||
notifications: async.apply(user.notifications.getDailyUnread, userObj.uid),
|
|
||||||
topics: async.apply(getTermTopics, data.interval, userObj.uid, 0, 9),
|
|
||||||
}, next);
|
|
||||||
},
|
|
||||||
function (data, next) {
|
|
||||||
var notifications = data.notifications.filter(Boolean);
|
|
||||||
|
|
||||||
// If there are no notifications and no new topics, don't bother sending a digest
|
async.eachLimit(users, 100, async function (userObj) {
|
||||||
if (!notifications.length && !data.topics.length) {
|
let [notifications, topics] = await Promise.all([
|
||||||
return next();
|
user.notifications.getDailyUnread(userObj.uid),
|
||||||
}
|
getTermTopics(data.interval, userObj.uid, 0, 9),
|
||||||
|
]);
|
||||||
|
notifications = notifications.filter(Boolean);
|
||||||
|
// If there are no notifications and no new topics, don't bother sending a digest
|
||||||
|
if (!notifications.length && !topics.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
notifications.forEach(function (notification) {
|
notifications.forEach(function (notification) {
|
||||||
if (notification.image && !notification.image.startsWith('http')) {
|
if (notification.image && !notification.image.startsWith('http')) {
|
||||||
notification.image = nconf.get('url') + notification.image;
|
notification.image = nconf.get('url') + notification.image;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fix relative paths in topic data
|
// Fix relative paths in topic data
|
||||||
data.topics = data.topics.map(function (topicObj) {
|
topics = topics.map(function (topicObj) {
|
||||||
var user = topicObj.hasOwnProperty('teaser') && topicObj.teaser !== undefined ? topicObj.teaser.user : topicObj.user;
|
const user = topicObj.hasOwnProperty('teaser') && topicObj.teaser !== undefined ? topicObj.teaser.user : topicObj.user;
|
||||||
if (user && user.picture && utils.isRelativeUrl(user.picture)) {
|
if (user && user.picture && utils.isRelativeUrl(user.picture)) {
|
||||||
user.picture = nconf.get('base_url') + user.picture;
|
user.picture = nconf.get('base_url') + user.picture;
|
||||||
}
|
}
|
||||||
|
return topicObj;
|
||||||
return topicObj;
|
});
|
||||||
});
|
emailsSent += 1;
|
||||||
emailsSent += 1;
|
emailer.send('digest', userObj.uid, {
|
||||||
emailer.send('digest', userObj.uid, {
|
subject: '[[email:digest.subject, ' + (now.getFullYear() + '/' + (now.getMonth() + 1) + '/' + now.getDate()) + ']]',
|
||||||
subject: '[[email:digest.subject, ' + (now.getFullYear() + '/' + (now.getMonth() + 1) + '/' + now.getDate()) + ']]',
|
username: userObj.username,
|
||||||
username: userObj.username,
|
userslug: userObj.userslug,
|
||||||
userslug: userObj.userslug,
|
notifications: notifications,
|
||||||
notifications: notifications,
|
recent: data.topics,
|
||||||
recent: data.topics,
|
interval: data.interval,
|
||||||
interval: data.interval,
|
showUnsubscribe: true,
|
||||||
showUnsubscribe: true,
|
}, function (err) {
|
||||||
}, function (err) {
|
if (err) {
|
||||||
if (err) {
|
winston.error('[user/jobs] Could not send digest email', err);
|
||||||
winston.error('[user/jobs] Could not send digest email', err);
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
next();
|
|
||||||
},
|
|
||||||
], next);
|
|
||||||
}, next);
|
|
||||||
},
|
|
||||||
], function (err) {
|
|
||||||
callback(err, emailsSent);
|
|
||||||
});
|
});
|
||||||
|
return emailsSent;
|
||||||
function getTermTopics(term, uid, start, stop, callback) {
|
|
||||||
const options = {
|
|
||||||
uid: uid,
|
|
||||||
start: start,
|
|
||||||
stop: stop,
|
|
||||||
term: term,
|
|
||||||
sort: 'posts',
|
|
||||||
teaserPost: 'last-post',
|
|
||||||
};
|
|
||||||
|
|
||||||
async.waterfall([
|
|
||||||
function (next) {
|
|
||||||
topics.getSortedTopics(options, next);
|
|
||||||
},
|
|
||||||
function (data, next) {
|
|
||||||
if (!data.topics.length) {
|
|
||||||
topics.getLatestTopics(options, next);
|
|
||||||
} else {
|
|
||||||
next(null, data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(data, next) => {
|
|
||||||
data.topics.forEach(function (topicObj) {
|
|
||||||
if (topicObj && topicObj.teaser && topicObj.teaser.content && topicObj.teaser.content.length > 255) {
|
|
||||||
topicObj.teaser.content = topicObj.teaser.content.slice(0, 255) + '...';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
next(null, data.topics);
|
|
||||||
},
|
|
||||||
], callback);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function getTermTopics(term, uid, start, stop) {
|
||||||
|
const options = {
|
||||||
|
uid: uid,
|
||||||
|
start: start,
|
||||||
|
stop: stop,
|
||||||
|
term: term,
|
||||||
|
sort: 'posts',
|
||||||
|
teaserPost: 'last-post',
|
||||||
|
};
|
||||||
|
let data = await topics.getSortedTopics(options);
|
||||||
|
if (!data.topics.length) {
|
||||||
|
data = await topics.getLatestTopics(options);
|
||||||
|
}
|
||||||
|
data.topics.forEach(function (topicObj) {
|
||||||
|
if (topicObj && topicObj.teaser && topicObj.teaser.content && topicObj.teaser.content.length > 255) {
|
||||||
|
topicObj.teaser.content = topicObj.teaser.content.slice(0, 255) + '...';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data.topics;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var async = require('async');
|
|
||||||
var nconf = require('nconf');
|
var nconf = require('nconf');
|
||||||
|
|
||||||
var user = require('../user');
|
var user = require('../user');
|
||||||
@@ -13,30 +12,24 @@ var emailer = require('../emailer');
|
|||||||
|
|
||||||
var UserEmail = module.exports;
|
var UserEmail = module.exports;
|
||||||
|
|
||||||
UserEmail.exists = function (email, callback) {
|
UserEmail.exists = async function (email) {
|
||||||
user.getUidByEmail(email.toLowerCase(), function (err, exists) {
|
const uid = await user.getUidByEmail(email.toLowerCase());
|
||||||
callback(err, !!exists);
|
return !!uid;
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
UserEmail.available = function (email, callback) {
|
UserEmail.available = async function (email) {
|
||||||
db.isSortedSetMember('email:uid', email.toLowerCase(), function (err, exists) {
|
const exists = await db.isSortedSetMember('email:uid', email.toLowerCase());
|
||||||
callback(err, !exists);
|
return !exists;
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
UserEmail.sendValidationEmail = function (uid, options, callback) {
|
UserEmail.sendValidationEmail = async function (uid, options) {
|
||||||
/*
|
/*
|
||||||
* Options:
|
* Options:
|
||||||
* - email, overrides email retrieval
|
* - email, overrides email retrieval
|
||||||
* - force, sends email even if it is too soon to send another
|
* - force, sends email even if it is too soon to send another
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Handling for 2 arguments
|
options = options || {};
|
||||||
if (arguments.length === 2 && typeof options === 'function') {
|
|
||||||
callback = options;
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback behaviour (email passed in as second argument)
|
// Fallback behaviour (email passed in as second argument)
|
||||||
if (typeof options === 'string') {
|
if (typeof options === 'string') {
|
||||||
@@ -45,117 +38,66 @@ UserEmail.sendValidationEmail = function (uid, options, callback) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
callback = callback || function () {};
|
let confirm_code = utils.generateUUID();
|
||||||
var confirm_code = utils.generateUUID();
|
const confirm_link = nconf.get('url') + '/confirm/' + confirm_code;
|
||||||
var confirm_link = nconf.get('url') + '/confirm/' + confirm_code;
|
|
||||||
|
|
||||||
var emailInterval = meta.config.emailConfirmInterval;
|
const emailInterval = meta.config.emailConfirmInterval;
|
||||||
|
|
||||||
async.waterfall([
|
// If no email passed in (default), retrieve email from uid
|
||||||
function (next) {
|
if (!options.email || !options.email.length) {
|
||||||
// If no email passed in (default), retrieve email from uid
|
options.email = await user.getUserField(uid, 'email');
|
||||||
if (options.email && options.email.length) {
|
}
|
||||||
return setImmediate(next, null, options.email);
|
if (!options.email) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
let sent = false;
|
||||||
|
if (!options.force) {
|
||||||
|
sent = await db.get('uid:' + uid + ':confirm:email:sent');
|
||||||
|
}
|
||||||
|
if (sent) {
|
||||||
|
throw new Error('[[error:confirm-email-already-sent, ' + emailInterval + ']]');
|
||||||
|
}
|
||||||
|
await db.set('uid:' + uid + ':confirm:email:sent', 1);
|
||||||
|
await db.pexpireAt('uid:' + uid + ':confirm:email:sent', Date.now() + (emailInterval * 60 * 1000));
|
||||||
|
confirm_code = await plugins.fireHook('filter:user.verify.code', confirm_code);
|
||||||
|
|
||||||
user.getUserField(uid, 'email', next);
|
await db.setObject('confirm:' + confirm_code, {
|
||||||
},
|
email: options.email.toLowerCase(),
|
||||||
function (email, next) {
|
uid: uid,
|
||||||
options.email = email;
|
|
||||||
if (!options.email) {
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.force) {
|
|
||||||
return setImmediate(next, null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
db.get('uid:' + uid + ':confirm:email:sent', next);
|
|
||||||
},
|
|
||||||
function (sent, next) {
|
|
||||||
if (sent) {
|
|
||||||
return next(new Error('[[error:confirm-email-already-sent, ' + emailInterval + ']]'));
|
|
||||||
}
|
|
||||||
db.set('uid:' + uid + ':confirm:email:sent', 1, next);
|
|
||||||
},
|
|
||||||
function (next) {
|
|
||||||
db.pexpireAt('uid:' + uid + ':confirm:email:sent', Date.now() + (emailInterval * 60 * 1000), next);
|
|
||||||
},
|
|
||||||
function (next) {
|
|
||||||
plugins.fireHook('filter:user.verify.code', confirm_code, next);
|
|
||||||
},
|
|
||||||
function (_confirm_code, next) {
|
|
||||||
confirm_code = _confirm_code;
|
|
||||||
db.setObject('confirm:' + confirm_code, {
|
|
||||||
email: options.email.toLowerCase(),
|
|
||||||
uid: uid,
|
|
||||||
}, next);
|
|
||||||
},
|
|
||||||
function (next) {
|
|
||||||
db.expireAt('confirm:' + confirm_code, Math.floor((Date.now() / 1000) + (60 * 60 * 24)), next);
|
|
||||||
},
|
|
||||||
function (next) {
|
|
||||||
user.getUserField(uid, 'username', next);
|
|
||||||
},
|
|
||||||
function (username, next) {
|
|
||||||
var title = meta.config.title || meta.config.browserTitle || 'NodeBB';
|
|
||||||
var template = options.template || 'welcome';
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
username: username,
|
|
||||||
confirm_link: confirm_link,
|
|
||||||
confirm_code: confirm_code,
|
|
||||||
|
|
||||||
subject: options.subject || '[[email:welcome-to, ' + title + ']]',
|
|
||||||
template: template,
|
|
||||||
uid: uid,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (plugins.hasListeners('action:user.verify')) {
|
|
||||||
plugins.fireHook('action:user.verify', { uid: uid, data: data });
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
emailer.send(template, uid, data, next);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function (next) {
|
|
||||||
next(null, confirm_code);
|
|
||||||
},
|
|
||||||
], callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
UserEmail.confirm = function (code, callback) {
|
|
||||||
var confirmObj;
|
|
||||||
async.waterfall([
|
|
||||||
function (next) {
|
|
||||||
db.getObject('confirm:' + code, next);
|
|
||||||
},
|
|
||||||
function (_confirmObj, next) {
|
|
||||||
confirmObj = _confirmObj;
|
|
||||||
if (!confirmObj || !confirmObj.uid || !confirmObj.email) {
|
|
||||||
return next(new Error('[[error:invalid-data]]'));
|
|
||||||
}
|
|
||||||
|
|
||||||
user.getUserField(confirmObj.uid, 'email', next);
|
|
||||||
},
|
|
||||||
function (currentEmail, next) {
|
|
||||||
if (!currentEmail || currentEmail.toLowerCase() !== confirmObj.email) {
|
|
||||||
return next(new Error('[[error:invalid-email]]'));
|
|
||||||
}
|
|
||||||
|
|
||||||
async.series([
|
|
||||||
async.apply(user.setUserField, confirmObj.uid, 'email:confirmed', 1),
|
|
||||||
async.apply(db.delete, 'confirm:' + code),
|
|
||||||
async.apply(db.delete, 'uid:' + confirmObj.uid + ':confirm:email:sent'),
|
|
||||||
function (next) {
|
|
||||||
db.sortedSetRemove('users:notvalidated', confirmObj.uid, next);
|
|
||||||
},
|
|
||||||
function (next) {
|
|
||||||
plugins.fireHook('action:user.email.confirmed', { uid: confirmObj.uid, email: confirmObj.email }, next);
|
|
||||||
},
|
|
||||||
], next);
|
|
||||||
},
|
|
||||||
], function (err) {
|
|
||||||
callback(err);
|
|
||||||
});
|
});
|
||||||
|
await db.expireAt('confirm:' + confirm_code, Math.floor((Date.now() / 1000) + (60 * 60 * 24)));
|
||||||
|
const username = await user.getUserField(uid, 'username');
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
username: username,
|
||||||
|
confirm_link: confirm_link,
|
||||||
|
confirm_code: confirm_code,
|
||||||
|
|
||||||
|
subject: options.subject || '[[email:welcome-to, ' + (meta.config.title || meta.config.browserTitle || 'NodeBB') + ']]',
|
||||||
|
template: options.template || 'welcome',
|
||||||
|
uid: uid,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (plugins.hasListeners('action:user.verify')) {
|
||||||
|
plugins.fireHook('action:user.verify', { uid: uid, data: data });
|
||||||
|
} else {
|
||||||
|
await emailer.send(data.template, uid, data);
|
||||||
|
}
|
||||||
|
return confirm_code;
|
||||||
|
};
|
||||||
|
|
||||||
|
UserEmail.confirm = async function (code) {
|
||||||
|
const confirmObj = await db.getObject('confirm:' + code);
|
||||||
|
if (!confirmObj || !confirmObj.uid || !confirmObj.email) {
|
||||||
|
throw new Error('[[error:invalid-data]]');
|
||||||
|
}
|
||||||
|
const currentEmail = await user.getUserField(confirmObj.uid, 'email');
|
||||||
|
if (!currentEmail || currentEmail.toLowerCase() !== confirmObj.email) {
|
||||||
|
throw new Error('[[error:invalid-email]]');
|
||||||
|
}
|
||||||
|
await user.setUserField(confirmObj.uid, 'email:confirmed', 1);
|
||||||
|
await db.delete('confirm:' + code);
|
||||||
|
await db.delete('uid:' + confirmObj.uid + ':confirm:email:sent');
|
||||||
|
await db.sortedSetRemove('users:notvalidated', confirmObj.uid);
|
||||||
|
await plugins.fireHook('action:user.email.confirmed', { uid: confirmObj.uid, email: confirmObj.email });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,103 +1,85 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var async = require('async');
|
|
||||||
var plugins = require('../plugins');
|
var plugins = require('../plugins');
|
||||||
var db = require('../database');
|
var db = require('../database');
|
||||||
|
|
||||||
module.exports = function (User) {
|
module.exports = function (User) {
|
||||||
User.follow = function (uid, followuid, callback) {
|
User.follow = async function (uid, followuid) {
|
||||||
toggleFollow('follow', uid, followuid, callback);
|
await toggleFollow('follow', uid, followuid);
|
||||||
};
|
};
|
||||||
|
|
||||||
User.unfollow = function (uid, unfollowuid, callback) {
|
User.unfollow = async function (uid, unfollowuid) {
|
||||||
toggleFollow('unfollow', uid, unfollowuid, callback);
|
await toggleFollow('unfollow', uid, unfollowuid);
|
||||||
};
|
};
|
||||||
|
|
||||||
function toggleFollow(type, uid, theiruid, callback) {
|
async function toggleFollow(type, uid, theiruid) {
|
||||||
if (parseInt(uid, 10) <= 0 || parseInt(theiruid, 10) <= 0) {
|
if (parseInt(uid, 10) <= 0 || parseInt(theiruid, 10) <= 0) {
|
||||||
return callback(new Error('[[error:invalid-uid]]'));
|
throw new Error('[[error:invalid-uid]]');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parseInt(uid, 10) === parseInt(theiruid, 10)) {
|
if (parseInt(uid, 10) === parseInt(theiruid, 10)) {
|
||||||
return callback(new Error('[[error:you-cant-follow-yourself]]'));
|
throw new Error('[[error:you-cant-follow-yourself]]');
|
||||||
|
}
|
||||||
|
const exists = await User.exists(theiruid);
|
||||||
|
if (!exists) {
|
||||||
|
throw new Error('[[error:no-user]]');
|
||||||
|
}
|
||||||
|
const isFollowing = await User.isFollowing(uid, theiruid);
|
||||||
|
if (type === 'follow') {
|
||||||
|
if (isFollowing) {
|
||||||
|
throw new Error('[[error:already-following]]');
|
||||||
|
}
|
||||||
|
const now = Date.now();
|
||||||
|
await Promise.all([
|
||||||
|
db.sortedSetAddBulk([
|
||||||
|
['following:' + uid, now, theiruid],
|
||||||
|
['followers:' + theiruid, now, uid],
|
||||||
|
]),
|
||||||
|
User.incrementUserFieldBy(uid, 'followingCount', 1),
|
||||||
|
User.incrementUserFieldBy(theiruid, 'followerCount', 1),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
if (!isFollowing) {
|
||||||
|
throw new Error('[[error:not-following]]');
|
||||||
|
}
|
||||||
|
await Promise.all([
|
||||||
|
db.sortedSetRemoveBulk([
|
||||||
|
['following:' + uid, theiruid],
|
||||||
|
['followers:' + theiruid, uid],
|
||||||
|
]),
|
||||||
|
User.decrementUserFieldBy(uid, 'followingCount', 1),
|
||||||
|
User.decrementUserFieldBy(theiruid, 'followerCount', 1),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async.waterfall([
|
|
||||||
function (next) {
|
|
||||||
User.exists(theiruid, next);
|
|
||||||
},
|
|
||||||
function (exists, next) {
|
|
||||||
if (!exists) {
|
|
||||||
return next(new Error('[[error:no-user]]'));
|
|
||||||
}
|
|
||||||
User.isFollowing(uid, theiruid, next);
|
|
||||||
},
|
|
||||||
function (isFollowing, next) {
|
|
||||||
if (type === 'follow') {
|
|
||||||
if (isFollowing) {
|
|
||||||
return next(new Error('[[error:already-following]]'));
|
|
||||||
}
|
|
||||||
var now = Date.now();
|
|
||||||
async.parallel([
|
|
||||||
async.apply(db.sortedSetAddBulk, [
|
|
||||||
['following:' + uid, now, theiruid],
|
|
||||||
['followers:' + theiruid, now, uid],
|
|
||||||
]),
|
|
||||||
async.apply(User.incrementUserFieldBy, uid, 'followingCount', 1),
|
|
||||||
async.apply(User.incrementUserFieldBy, theiruid, 'followerCount', 1),
|
|
||||||
], next);
|
|
||||||
} else {
|
|
||||||
if (!isFollowing) {
|
|
||||||
return next(new Error('[[error:not-following]]'));
|
|
||||||
}
|
|
||||||
async.parallel([
|
|
||||||
async.apply(db.sortedSetRemove, 'following:' + uid, theiruid),
|
|
||||||
async.apply(db.sortedSetRemove, 'followers:' + theiruid, uid),
|
|
||||||
async.apply(User.decrementUserFieldBy, uid, 'followingCount', 1),
|
|
||||||
async.apply(User.decrementUserFieldBy, theiruid, 'followerCount', 1),
|
|
||||||
], next);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
], function (err) {
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
User.getFollowing = function (uid, start, stop, callback) {
|
User.getFollowing = async function (uid, start, stop) {
|
||||||
getFollow(uid, 'following', start, stop, callback);
|
return await getFollow(uid, 'following', start, stop);
|
||||||
};
|
};
|
||||||
|
|
||||||
User.getFollowers = function (uid, start, stop, callback) {
|
User.getFollowers = async function (uid, start, stop) {
|
||||||
getFollow(uid, 'followers', start, stop, callback);
|
return await getFollow(uid, 'followers', start, stop);
|
||||||
};
|
};
|
||||||
|
|
||||||
function getFollow(uid, type, start, stop, callback) {
|
async function getFollow(uid, type, start, stop) {
|
||||||
if (parseInt(uid, 10) <= 0) {
|
if (parseInt(uid, 10) <= 0) {
|
||||||
return setImmediate(callback, null, []);
|
return [];
|
||||||
}
|
}
|
||||||
async.waterfall([
|
const uids = await db.getSortedSetRevRange(type + ':' + uid, start, stop);
|
||||||
function (next) {
|
const data = await plugins.fireHook('filter:user.' + type, {
|
||||||
db.getSortedSetRevRange(type + ':' + uid, start, stop, next);
|
uids: uids,
|
||||||
},
|
uid: uid,
|
||||||
function (uids, next) {
|
start: start,
|
||||||
plugins.fireHook('filter:user.' + type, {
|
stop: stop,
|
||||||
uids: uids,
|
});
|
||||||
uid: uid,
|
return await User.getUsers(data.uids, uid);
|
||||||
start: start,
|
|
||||||
stop: stop,
|
|
||||||
}, next);
|
|
||||||
},
|
|
||||||
function (data, next) {
|
|
||||||
User.getUsers(data.uids, uid, next);
|
|
||||||
},
|
|
||||||
], callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
User.isFollowing = function (uid, theirid, callback) {
|
User.isFollowing = async function (uid, theirid) {
|
||||||
if (parseInt(uid, 10) <= 0 || parseInt(theirid, 10) <= 0) {
|
if (parseInt(uid, 10) <= 0 || parseInt(theirid, 10) <= 0) {
|
||||||
return setImmediate(callback, null, false);
|
return false;
|
||||||
}
|
}
|
||||||
db.isSortedSetMember('following:' + uid, theirid, callback);
|
return await db.isSortedSetMember('following:' + uid, theirid);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user