You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nodebb/src/user.js

1145 lines
28 KiB
JavaScript

11 years ago
'use strict';
var bcrypt = require('bcryptjs'),
async = require('async'),
nconf = require('nconf'),
winston = require('winston'),
gravatar = require('gravatar'),
11 years ago
validator = require('validator'),
S = require('string'),
11 years ago
utils = require('./../public/src/utils'),
plugins = require('./plugins'),
11 years ago
db = require('./database'),
meta = require('./meta'),
11 years ago
groups = require('./groups'),
notifications = require('./notifications'),
topics = require('./topics'),
11 years ago
events = require('./events'),
Emailer = require('./emailer');
11 years ago
(function(User) {
11 years ago
User.create = function(userData, callback) {
userData = userData || {};
userData.userslug = utils.slugify(userData.username);
userData.username = userData.username.trim();
if (userData.email !== undefined) {
userData.email = userData.email.trim();
11 years ago
userData.email = validator.escape(userData.email);
}
async.parallel([
function(next) {
if (userData.email) {
next(!utils.isEmailValid(userData.email) ? new Error('Invalid Email!') : null);
} else {
next();
}
},
function(next) {
next((!utils.isUserNameValid(userData.username) || !userData.userslug) ? new Error('Invalid Username!') : null);
},
function(next) {
if (userData.password) {
next(!utils.isPasswordValid(userData.password) ? new Error('Invalid Password!') : null);
} else {
next();
}
},
function(next) {
User.exists(userData.userslug, function(err, exists) {
11 years ago
if (err) {
return next(err);
}
next(exists ? new Error('Username taken!') : null);
});
},
function(next) {
if (userData.email) {
User.email.available(userData.email, function(err, available) {
if (err) {
12 years ago
return next(err);
}
12 years ago
next(!available ? new Error('Email taken!') : null);
});
} else {
next();
}
},
function(next) {
plugins.fireHook('filter:user.create', userData, function(err, filteredUserData){
next(err, utils.merge(userData, filteredUserData));
});
}
], function(err, results) {
if (err) {
11 years ago
return callback(err);
}
11 years ago
userData = results[results.length - 1];
11 years ago
db.incrObjectField('global', 'nextUid', function(err, uid) {
11 years ago
if(err) {
return callback(err);
}
var gravatar = User.createGravatarURLFromEmail(userData.email);
var timestamp = Date.now();
var password = userData.password;
userData = {
'uid': uid,
'username': userData.username,
'userslug': userData.userslug,
'fullname': '',
'location': '',
'birthday': '',
'website': '',
'email': userData.email || '',
'signature': '',
'joindate': timestamp,
'picture': gravatar,
'gravatarpicture': gravatar,
'uploadedpicture': '',
'profileviews': 0,
'reputation': 0,
'postcount': 0,
'lastposttime': 0,
'banned': 0,
'status': 'online'
};
db.setObject('user:' + uid, userData, function(err) {
if(err) {
return callback(err);
}
db.setObjectField('username:uid', userData.username, uid);
db.setObjectField('userslug:uid', userData.userslug, uid);
if (userData.email !== undefined) {
db.setObjectField('email:uid', userData.email, uid);
if (parseInt(uid, 10) !== 1) {
User.email.verify(uid, userData.email);
}
}
plugins.fireHook('action:user.create', userData);
db.incrObjectField('global', 'userCount');
db.sortedSetAdd('users:joindate', timestamp, uid);
db.sortedSetAdd('users:postcount', 0, uid);
db.sortedSetAdd('users:reputation', 0, uid);
groups.joinByGroupName('registered-users', uid);
if (password) {
User.hashPassword(password, function(err, hash) {
if(err) {
return callback(err);
}
11 years ago
User.setUserField(uid, 'password', hash);
callback(null, uid);
});
} else {
callback(null, uid);
}
});
});
});
};
User.ban = function(uid, callback) {
User.setUserField(uid, 'banned', 1, callback);
};
User.unban = function(uid, callback) {
User.setUserField(uid, 'banned', 0, callback);
};
User.getUserField = function(uid, field, callback) {
11 years ago
db.getObjectField('user:' + uid, field, callback);
};
User.getUserFields = function(uid, fields, callback) {
11 years ago
db.getObjectFields('user:' + uid, fields, callback);
};
User.getMultipleUserFields = function(uids, fields, callback) {
if (!Array.isArray(uids) || !uids.length) {
return callback(null, []);
}
var keys = uids.map(function(uid) {
return 'user:' + uid;
});
db.getObjectsFields(keys, fields, callback);
};
User.getUserData = function(uid, callback) {
User.getUsersData([uid], function(err, users) {
callback(err, users ? users[0] : null);
});
};
User.getUsersData = function(uids, callback) {
if (!Array.isArray(uids) || !uids.length) {
return callback(null, []);
}
var keys = uids.map(function(uid) {
return 'user:' + uid;
});
db.getObjects(keys, function(err, users) {
if (err) {
11 years ago
return callback(err);
}
users.forEach(function(user) {
if (user) {
if (user.password) {
user.password = null;
user.hasPassword = true;
} else {
user.hasPassword = false;
}
}
});
callback(null, users);
});
};
User.getSettings = function(uid, callback) {
function sendDefaultSettings() {
callback(null, {
showemail: false,
11 years ago
usePagination: parseInt(meta.config.usePagination, 10) === 1,
topicsPerPage: parseInt(meta.config.topicsPerPage, 10) || 20,
postsPerPage: parseInt(meta.config.postsPerPage, 10) || 10
});
}
if(!parseInt(uid, 10)) {
return sendDefaultSettings();
}
db.getObject('user:' + uid + ':settings', function(err, settings) {
if(err) {
return callback(err);
}
if(!settings) {
11 years ago
settings = {};
}
settings.showemail = settings.showemail ? parseInt(settings.showemail, 10) !== 0 : false;
11 years ago
settings.usePagination = settings.usePagination ? parseInt(settings.usePagination, 10) === 1 : parseInt(meta.config.usePagination, 10) === 1;
settings.topicsPerPage = settings.topicsPerPage ? parseInt(settings.topicsPerPage, 10) : parseInt(meta.config.topicsPerPage, 10) || 20;
settings.postsPerPage = settings.postsPerPage ? parseInt(settings.postsPerPage, 10) : parseInt(meta.config.postsPerPage, 10) || 10;
callback(null, settings);
});
11 years ago
};
User.saveSettings = function(uid, data, callback) {
11 years ago
if(!data.topicsPerPage || !data.postsPerPage || parseInt(data.topicsPerPage, 10) <= 0 || parseInt(data.postsPerPage, 10) <= 0) {
return callback(new Error('Invalid pagination value!'));
}
db.setObject('user:' + uid + ':settings', {
showemail: data.showemail,
usePagination: data.usePagination,
topicsPerPage: data.topicsPerPage,
postsPerPage: data.postsPerPage
}, callback);
11 years ago
};
User.updateLastOnlineTime = function(uid, callback) {
User.getUserField(uid, 'status', function(err, status) {
function cb(err) {
if(typeof callback === 'function') {
callback(err);
}
}
if(err || status === 'offline') {
return cb(err);
}
User.setUserField(uid, 'lastonline', Date.now(), cb);
});
};
User.updateProfile = function(uid, data, callback) {
11 years ago
var fields = ['username', 'email', 'fullname', 'website', 'location', 'birthday', 'signature'];
var returnData = {
success: false
};
12 years ago
function isSignatureValid(next) {
11 years ago
if (data.signature !== undefined && data.signature.length > meta.config.maximumSignatureLength) {
11 years ago
next(new Error('Signature can\'t be longer than ' + meta.config.maximumSignatureLength + ' characters!'), false);
12 years ago
} else {
next(null, true);
}
12 years ago
}
12 years ago
function isEmailAvailable(next) {
if (!data.email) {
return next(null, true);
12 years ago
}
12 years ago
User.getUserField(uid, 'email', function(err, email) {
11 years ago
if(email === data.email) {
return next(null, true);
}
11 years ago
User.email.available(data.email, function(err, available) {
11 years ago
if (err) {
return next(err, null);
}
if (!available) {
11 years ago
next(new Error('Email not available!'), false);
11 years ago
} else {
next(null, true);
}
});
});
12 years ago
}
11 years ago
function isUsernameAvailable(next) {
User.getUserFields(uid, ['username', 'userslug'], function(err, userData) {
var userslug = utils.slugify(data.username);
if(userslug === userData.userslug) {
return next(null, true);
}
if(!utils.isUserNameValid(data.username) || !userslug) {
11 years ago
return next(new Error('Invalid Username!'), false);
11 years ago
}
11 years ago
User.exists(userslug, function(err, exists) {
if(err) {
return next(err);
}
11 years ago
if(exists) {
11 years ago
next(new Error('Username not available!'), false);
} else {
11 years ago
next(null, true);
}
});
11 years ago
});
}
async.series([isSignatureValid, isEmailAvailable, isUsernameAvailable], function(err, results) {
if (err) {
return callback(err, returnData);
12 years ago
}
11 years ago
async.each(fields, updateField, function(err) {
if (err) {
return callback(err, returnData);
}
returnData.success = true;
callback(null, returnData);
});
12 years ago
});
function updateField(field, next) {
if (!(data[field] !== undefined && typeof data[field] === 'string')) {
return next();
}
data[field] = data[field].trim();
11 years ago
data[field] = validator.escape(data[field]);
if (field === 'email') {
User.getUserFields(uid, ['email', 'picture', 'uploadedpicture'], function(err, userData) {
if (err) {
return next(err);
}
if(userData.email === data.email) {
return next();
}
var gravatarpicture = User.createGravatarURLFromEmail(data.email);
User.setUserField(uid, 'gravatarpicture', gravatarpicture);
11 years ago
db.deleteObjectField('email:uid', userData.email);
db.setObjectField('email:uid', data.email, uid);
User.setUserField(uid, 'email', data.email);
if (userData.picture !== userData.uploadedpicture) {
returnData.picture = gravatarpicture;
User.setUserField(uid, 'picture', gravatarpicture);
}
returnData.gravatarpicture = gravatarpicture;
11 years ago
events.logEmailChange(uid, userData.email, data.email);
next();
});
return;
} else if (field === 'username') {
11 years ago
User.getUserFields(uid, ['username', 'userslug'], function(err, userData) {
var userslug = utils.slugify(data.username);
11 years ago
if(data.username !== userData.username) {
User.setUserField(uid, 'username', data.username);
db.deleteObjectField('username:uid', userData.username);
db.setObjectField('username:uid', data.username, uid);
events.logUsernameChange(uid, userData.username, data.username);
}
11 years ago
if(userslug !== userData.userslug) {
User.setUserField(uid, 'userslug', userslug);
db.deleteObjectField('userslug:uid', userData.userslug);
db.setObjectField('userslug:uid', userslug, uid);
returnData.userslug = userslug;
11 years ago
}
next();
});
12 years ago
return;
} else if (field === 'signature') {
data[field] = S(data[field]).stripTags().s;
} else if (field === 'website') {
if(data[field].substr(0, 7) !== 'http://' && data[field].substr(0, 8) !== 'https://') {
data[field] = 'http://' + data[field];
}
}
User.setUserField(uid, field, data[field]);
next();
}
};
User.isReadyToPost = function(uid, callback) {
User.getUserField(uid, 'lastposttime', function(err, lastposttime) {
if(err) {
return callback(err);
}
if(!lastposttime) {
lastposttime = 0;
}
if (Date.now() - parseInt(lastposttime, 10) < parseInt(meta.config.postDelay, 10) * 1000) {
return callback(new Error('too-many-posts'));
}
callback();
});
11 years ago
};
User.changePassword = function(uid, data, callback) {
11 years ago
if(!data || !data.uid) {
return callback(new Error('invalid-uid'));
}
function hashAndSetPassword(callback) {
User.hashPassword(data.newPassword, function(err, hash) {
if(err) {
return callback(err);
}
User.setUserField(data.uid, 'password', hash, function(err) {
if(err) {
return callback(err);
}
if(parseInt(uid, 10) === parseInt(data.uid, 10)) {
events.logPasswordChange(data.uid);
} else {
events.logAdminChangeUserPassword(uid, data.uid);
}
callback();
});
});
}
if (!utils.isPasswordValid(data.newPassword)) {
11 years ago
return callback(new Error('Invalid password!'));
}
11 years ago
if(parseInt(uid, 10) !== parseInt(data.uid, 10)) {
User.isAdministrator(uid, function(err, isAdmin) {
if(err || !isAdmin) {
return callback(err || new Error('not-allowed'));
}
11 years ago
hashAndSetPassword(callback);
});
} else {
User.getUserField(uid, 'password', function(err, currentPassword) {
if(err) {
return callback(err);
}
11 years ago
if (currentPassword !== null) {
bcrypt.compare(data.currentPassword, currentPassword, function(err, res) {
if (err || !res) {
return callback(err || new Error('Your current password is not correct!'));
}
11 years ago
hashAndSetPassword(callback);
});
} else {
// No password in account (probably SSO login)
11 years ago
hashAndSetPassword(callback);
}
});
11 years ago
}
};
User.setUserField = function(uid, field, value, callback) {
11 years ago
db.setObjectField('user:' + uid, field, value, callback);
};
11 years ago
User.setUserFields = function(uid, data, callback) {
11 years ago
db.setObject('user:' + uid, data, callback);
};
12 years ago
User.incrementUserFieldBy = function(uid, field, value, callback) {
11 years ago
db.incrObjectFieldBy('user:' + uid, field, value, callback);
};
User.decrementUserFieldBy = function(uid, field, value, callback) {
11 years ago
db.incrObjectFieldBy('user:' + uid, field, -value, callback);
};
User.getUsers = function(set, start, stop, callback) {
11 years ago
db.getSortedSetRevRange(set, start, stop, function(err, uids) {
if (err) {
return callback(err);
}
User.getUsersData(uids, function(err, users) {
if (err) {
return callback(err);
}
async.map(users, function(user, next) {
if (!user) {
return next(null, user);
}
if (!user.status) {
user.status = 'online';
}
User.isAdministrator(user.uid, function(err, isAdmin) {
if (err) {
return next(err);
}
user.administrator = isAdmin ? '1':'0';
if(set === 'users:online') {
return next(null, user);
}
db.sortedSetScore('users:online', user.uid, function(err, score) {
if (err) {
return next(err);
}
if(!score) {
user.status = 'offline';
}
next(null, user);
});
});
}, callback);
});
});
};
User.createGravatarURLFromEmail = function(email) {
12 years ago
var options = {
size: '128',
default: 'identicon',
rating: 'pg'
11 years ago
};
if (!email) {
12 years ago
email = '';
options.forcedefault = 'y';
}
12 years ago
11 years ago
return gravatar.url(email, options, true);
};
User.hashPassword = function(password, callback) {
if (!password) {
callback(password);
return;
}
bcrypt.genSalt(nconf.get('bcrypt_rounds'), function(err, salt) {
bcrypt.hash(password, salt, callback);
});
};
User.getUsersCSV = function(callback) {
var csvContent = '';
db.getObjectValues('username:uid', function(err, uids) {
if (err) {
11 years ago
return callback(err);
}
User.getMultipleUserFields(uids, ['email', 'username'], function(err, usersData) {
if (err) {
return callback(err);
}
usersData.forEach(function(user, index) {
if (user) {
csvContent += user.email + ',' + user.username + ',' + uids[index] + '\n';
}
});
callback(null, csvContent);
});
});
11 years ago
};
11 years ago
User.search = function(query, callback) {
if (!query || query.length === 0) {
return callback(null, {timing:0, users:[]});
}
var start = process.hrtime();
11 years ago
11 years ago
db.getObject('username:uid', function(err, usernamesHash) {
if (err) {
return callback(null, {timing: 0, users:[]});
}
query = query.toLowerCase();
11 years ago
var usernames = Object.keys(usernamesHash),
uids = [];
11 years ago
uids = usernames.filter(function(username) {
return username.toLowerCase().indexOf(query) === 0;
})
.slice(0, 10)
.sort(function(a, b) {
11 years ago
return a > b;
})
.map(function(username) {
return usernamesHash[username];
});
11 years ago
User.getUsersData(uids, function(err, userdata) {
if (err) {
return callback(err);
}
var diff = process.hrtime(start);
var timing = (diff[0] * 1e3 + diff[1] / 1e6).toFixed(1);
callback(null, {timing: timing, users: userdata});
});
});
};
User.onNewPostMade = function(uid, tid, pid, timestamp) {
User.addPostIdToUser(uid, pid, timestamp);
User.incrementUserFieldBy(uid, 'postcount', 1, function(err, newpostcount) {
11 years ago
db.sortedSetAdd('users:postcount', newpostcount, uid);
});
User.setUserField(uid, 'lastposttime', timestamp);
};
User.addPostIdToUser = function(uid, pid, timestamp) {
db.sortedSetAdd('uid:' + uid + ':posts', timestamp, pid);
};
User.addTopicIdToUser = function(uid, tid, timestamp) {
db.sortedSetAdd('uid:' + uid + ':topics', timestamp, tid);
};
11 years ago
User.getPostIds = function(uid, start, stop, callback) {
db.getSortedSetRevRange('uid:' + uid + ':posts', start, stop, function(err, pids) {
if(err) {
return callback(err);
}
if (pids && pids.length) {
callback(null, pids);
} else {
callback(null, []);
}
});
};
User.follow = function(uid, followuid, callback) {
toggleFollow('follow', uid, followuid, callback);
};
11 years ago
User.unfollow = function(uid, unfollowuid, callback) {
toggleFollow('unfollow', uid, unfollowuid, callback);
};
function toggleFollow(type, uid, theiruid, callback) {
var command = type === 'follow' ? 'setAdd' : 'setRemove';
db[command]('following:' + uid, theiruid, function(err) {
11 years ago
if(err) {
return callback(err);
}
db[command]('followers:' + theiruid, uid, callback);
});
}
User.getFollowing = function(uid, callback) {
getFollow('following:' + uid, callback);
};
User.getFollowers = function(uid, callback) {
getFollow('followers:' + uid, callback);
};
function getFollow(set, callback) {
db.getSetMembers(set, function(err, uids) {
if(err) {
return callback(err);
}
User.getUsersData(uids, callback);
});
}
User.getFollowingCount = function(uid, callback) {
db.setCount('following:' + uid, callback);
};
User.getFollowerCount = function(uid, callback) {
db.setCount('followers:' + uid, callback);
};
User.getFollowStats = function (uid, callback) {
async.parallel({
followingCount: function(next) {
User.getFollowingCount(uid, next);
},
followerCount : function(next) {
User.getFollowerCount(uid, next);
}
}, callback);
11 years ago
};
User.sendPostNotificationToFollowers = function(uid, tid, pid) {
12 years ago
User.getUserField(uid, 'username', function(err, username) {
11 years ago
db.getSetMembers('followers:' + uid, function(err, followers) {
if (followers && followers.length) {
topics.getTopicField(tid, 'slug', function(err, slug) {
var message = '<strong>' + username + '</strong> made a new post';
notifications.create({
text: message,
path: nconf.get('relative_path') + '/topic/' + slug + '#' + pid,
uniqueId: 'topic:' + tid,
from: uid
}, function(nid) {
notifications.push(nid, followers);
});
});
}
});
});
};
User.isFollowing = function(uid, theirid, callback) {
db.isSetMember('following:' + uid, theirid, callback);
};
User.exists = function(userslug, callback) {
User.getUidByUserslug(userslug, function(err, exists) {
11 years ago
callback(err, !! exists);
});
};
User.count = function(callback) {
db.getObjectField('global', 'userCount', function(err, count) {
11 years ago
if(err) {
11 years ago
return callback(err);
11 years ago
}
11 years ago
callback(null, {
count: count ? count : 0
});
});
};
User.getUidByUsername = function(username, callback) {
11 years ago
db.getObjectField('username:uid', username, callback);
};
User.getUidByUserslug = function(userslug, callback) {
11 years ago
db.getObjectField('userslug:uid', userslug, callback);
};
User.getUsernamesByUids = function(uids, callback) {
User.getMultipleUserFields(uids, ['username'], function(err, users) {
if (err) {
return callback(err);
}
users = users.map(function(user) {
return user.username;
});
callback(null, users);
});
};
User.getUsernameByUserslug = function(slug, callback) {
async.waterfall([
function(next) {
User.getUidByUserslug(slug, next);
},
function(uid, next) {
User.getUserField(uid, 'username', next);
}
], callback);
};
User.getUidByEmail = function(email, callback) {
db.getObjectField('email:uid', email, callback);
};
User.isModerator = function(uid, cid, callback) {
11 years ago
groups.isMemberByGroupName(uid, 'cid:' + cid + ':privileges:mod', callback);
};
User.isAdministrator = function(uid, callback) {
11 years ago
groups.isMemberByGroupName(uid, 'administrators', callback);
};
User.reset = {
validate: function(socket, code, callback) {
11 years ago
db.getObjectField('reset:uid', code, function(err, uid) {
if (err) {
11 years ago
return callback(err);
}
if (uid !== null) {
11 years ago
db.getObjectField('reset:expiry', code, function(err, expiry) {
if (err) {
11 years ago
return callback(err);
}
11 years ago
if (parseInt(expiry, 10) >= Date.now() / 1000) {
11 years ago
callback(null, true);
} else {
// Expired, delete from db
11 years ago
db.deleteObjectField('reset:uid', code);
db.deleteObjectField('reset:expiry', code);
11 years ago
callback(null, false);
}
});
} else {
11 years ago
callback(null, false);
}
});
},
11 years ago
send: function(socket, email, callback) {
11 years ago
User.getUidByEmail(email, function(err, uid) {
11 years ago
if(err) {
return callback(err);
}
11 years ago
if(!uid) {
return callback(new Error('invalid-email'));
}
11 years ago
// Generate a new reset code
var reset_code = utils.generateUUID();
db.setObjectField('reset:uid', reset_code, uid);
11 years ago
db.setObjectField('reset:expiry', reset_code, (60 * 60) + Math.floor(Date.now() / 1000));
11 years ago
var reset_link = nconf.get('url') + '/reset/' + reset_code;
11 years ago
Emailer.send('reset', uid, {
11 years ago
'site_title': (meta.config.title || 'NodeBB'),
11 years ago
'reset_link': reset_link,
11 years ago
subject: 'Password Reset Requested - ' + (meta.config.title || 'NodeBB') + '!',
11 years ago
template: 'reset',
uid: uid
});
callback(null);
});
},
11 years ago
commit: function(socket, code, password, callback) {
this.validate(socket, code, function(err, validated) {
if(err) {
return callback(err);
}
if (validated) {
11 years ago
db.getObjectField('reset:uid', code, function(err, uid) {
if (err) {
11 years ago
return callback(err);
}
User.hashPassword(password, function(err, hash) {
12 years ago
User.setUserField(uid, 'password', hash);
11 years ago
events.logPasswordReset(uid);
12 years ago
});
11 years ago
db.deleteObjectField('reset:uid', code);
db.deleteObjectField('reset:expiry', code);
11 years ago
callback(null);
});
}
});
}
};
User.pushNotifCount = function(uid) {
var websockets = require('./socket.io');
User.notifications.getUnreadCount(uid, function(err, count) {
if (!err) {
websockets.in('uid_' + uid).emit('event:notifications.updateCount', count);
} else {
winston.warn('[User.pushNotifCount] Count not retrieve unread notifications count to push to uid ' + uid + '\'s client(s)');
}
});
};
11 years ago
User.logIP = function(uid, ip) {
db.sortedSetAdd('uid:' + uid + ':ip', +new Date(), ip || 'Unknown');
11 years ago
};
User.getIPs = function(uid, end, callback) {
db.getSortedSetRevRange('uid:' + uid + ':ip', 0, end, function(err, ips) {
if(err) {
return callback(err);
}
callback(null, ips.map(function(ip) {
return {ip:ip};
}));
});
};
User.email = {
verify: function(uid, email) {
if (!plugins.hasListeners('action:email.send')) {
return;
}
var confirm_code = utils.generateUUID(),
11 years ago
confirm_link = nconf.get('url') + '/confirm/' + confirm_code;
async.series([
function(next) {
db.setObject('confirm:' + confirm_code, {
email: email,
uid: uid
}, next);
},
function(next) {
db.expireAt('confirm:' + confirm_code, Math.floor(Date.now() / 1000 + 60 * 60 * 2), next);
}
], function(err) {
// Send intro email w/ confirm code
User.getUserField(uid, 'username', function(err, username) {
Emailer.send('welcome', uid, {
11 years ago
'site_title': (meta.config.title || 'NodeBB'),
username: username,
'confirm_link': confirm_link,
11 years ago
subject: 'Welcome to ' + (meta.config.title || 'NodeBB') + '!',
template: 'welcome',
uid: uid
});
});
});
},
11 years ago
exists: function(email, callback) {
11 years ago
User.getUidByEmail(email, function(err, exists) {
11 years ago
callback(err, !!exists);
});
},
confirm: function(code, callback) {
db.getObject('confirm:' + code, function(err, confirmObj) {
if (err) {
return callback({
status:'error'
});
}
if (confirmObj && confirmObj.uid && confirmObj.email) {
db.setObjectField('email:confirmed', confirmObj.email, '1', function() {
callback({
status: 'ok'
});
});
} else {
callback({
status: 'not_ok'
});
}
});
},
available: function(email, callback) {
db.isObjectField('email:uid', email, function(err, exists) {
callback(err, !exists);
});
}
};
User.notifications = {
get: function(uid, callback) {
11 years ago
function getNotifications(set, start, stop, iterator, done) {
db.getSortedSetRevRange(set, start, stop, function(err, nids) {
if(err) {
return done(err);
}
if(!nids || nids.length === 0) {
return done(null, []);
}
if (nids.length > maxNotifs) {
nids.length = maxNotifs;
}
async.map(nids, function(nid, next) {
notifications.get(nid, uid, function(notif_data) {
11 years ago
if(typeof iterator === 'function') {
iterator(notif_data);
11 years ago
}
next(null, notif_data);
});
}, done);
});
}
var maxNotifs = 15;
async.parallel({
unread: function(next) {
getNotifications('uid:' + uid + ':notifications:unread', 0, 9, function(notif_data) {
11 years ago
if (notif_data) {
notif_data.readClass = !notif_data.read ? 'label-warning' : '';
}
11 years ago
}, next);
},
read: function(next) {
getNotifications('uid:' + uid + ':notifications:read', 0, 9, null, next);
}
}, function(err, notifications) {
if(err) {
11 years ago
return callback(err);
}
11 years ago
// Remove empties
notifications.read = notifications.read.filter(function(notifObj) {
return notifObj;
});
notifications.unread = notifications.unread.filter(function(notifObj) {
return notifObj;
});
// Limit the number of notifications to `maxNotifs`, prioritising unread notifications
if (notifications.read.length + notifications.unread.length > maxNotifs) {
notifications.read.length = maxNotifs - notifications.unread.length;
}
callback(null, notifications);
});
},
getAll: function(uid, limit, before, callback) {
var now = new Date();
if (!limit || parseInt(limit, 10) <= 0) {
limit = 25;
}
if (before) {
before = new Date(parseInt(before, 10));
}
var args1 = ['uid:' + uid + ':notifications:read', before ? before.getTime(): now.getTime(), -Infinity, 'LIMIT', 0, limit];
var args2 = ['uid:' + uid + ':notifications:unread', before ? before.getTime(): now.getTime(), -Infinity, 'LIMIT', 0, limit];
db.getSortedSetRevRangeByScore(args1, function(err, results1) {
db.getSortedSetRevRangeByScore(args2, function(err, results2) {
var nids = results1.concat(results2);
async.map(nids, function(nid, next) {
notifications.get(nid, uid, function(notif_data) {
next(null, notif_data);
});
}, function(err, notifs) {
notifs = notifs.filter(function(notif) {
return notif !== null;
}).sort(function(a, b) {
return parseInt(b.datetime, 10) - parseInt(a.datetime, 10);
}).map(function(notif) {
notif.datetimeISO = utils.toISOString(notif.datetime);
notif.readClass = !notif.read ? 'label-warning' : '';
return notif;
});
callback(err, notifs);
});
});
});
},
getUnreadCount: function(uid, callback) {
db.sortedSetCount('uid:' + uid + ':notifications:unread', -Infinity, Infinity, callback);
},
getUnreadByUniqueId: function(uid, uniqueId, callback) {
11 years ago
db.getSortedSetRange('uid:' + uid + ':notifications:unread', 0, -1, function(err, nids) {
async.filter(nids, function(nid, next) {
notifications.get(nid, uid, function(notifObj) {
if(!notifObj) {
11 years ago
return next(false);
}
11 years ago
if (notifObj.uniqueId === uniqueId) {
next(true);
} else {
next(false);
}
});
}, function(nids) {
callback(null, nids);
});
});
}
};
}(exports));