diff --git a/public/src/forum/accountedit.js b/public/src/forum/accountedit.js
index 207785e846..de9e3708b7 100644
--- a/public/src/forum/accountedit.js
+++ b/public/src/forum/accountedit.js
@@ -29,8 +29,8 @@ define(['forum/accountheader', 'uploader'], function(header, uploader) {
return app.alertError(err.message);
}
- if (!data || !data.success) {
- return app.alertError('There was an error updating your profile! ' + err.message);
+ if (!data) {
+ return app.alertError('There was an error updating your profile!');
}
app.alertSuccess('Your profile has been updated successfully!');
diff --git a/public/src/forum/register.js b/public/src/forum/register.js
index 674f53af6e..6d189cdad3 100644
--- a/public/src/forum/register.js
+++ b/public/src/forum/register.js
@@ -1,151 +1,162 @@
-define(function() {
- var Register = {};
+'use strict';
- Register.init = function() {
- var username = $('#username'),
- password = $('#password'),
- password_confirm = $('#password-confirm'),
- register = $('#register'),
- emailEl = $('#email'),
- username_notify = $('#username-notify'),
- email_notify = $('#email-notify'),
- password_notify = $('#password-notify'),
- password_confirm_notify = $('#password-confirm-notify'),
- agreeTerms = $('#agree-terms'),
- validationError = false,
- successIcon = '';
+/* globals define, app, utils, socket, config */
- $('#referrer').val(app.previousUrl);
- function showError(element, msg) {
- element.html(msg);
- element.parent()
- .removeClass('alert-success')
- .addClass('alert-danger');
- element.show();
+define(function() {
+ var Register = {},
+ validationError = false,
+ successIcon = '';
+
+ function showError(element, msg) {
+ element.html(msg);
+ element.parent()
+ .removeClass('alert-success')
+ .addClass('alert-danger');
+ element.show();
+ validationError = true;
+ }
+
+ function showSuccess(element, msg) {
+ element.html(msg);
+ element.parent()
+ .removeClass('alert-danger')
+ .addClass('alert-success');
+ element.show();
+ }
+
+ function validateEmail(email) {
+ var email_notify = $('#email-notify');
+
+ if (!email) {
validationError = true;
+ return;
}
- function showSuccess(element, msg) {
- element.html(msg);
- element.parent()
- .removeClass('alert-danger')
- .addClass('alert-success');
- element.show();
+ if (!utils.isEmailValid(email)) {
+ showError(email_notify, 'Invalid email address.');
+ return;
}
- function validateEmail() {
- if (!emailEl.val()) {
- validationError = true;
- return;
+ socket.emit('user.emailExists', {
+ email: email
+ }, function(err, exists) {
+ if(err) {
+ return app.alertError(err.message);
}
- if (!utils.isEmailValid(emailEl.val())) {
- showError(email_notify, 'Invalid email address.');
+ if (exists) {
+ showError(email_notify, 'Email address already taken!');
} else {
- socket.emit('user.emailExists', {
- email: emailEl.val()
- }, function(err, exists) {
- if(err) {
- return app.alertError(err.message);
- }
-
- if (exists) {
- showError(email_notify, 'Email address already taken!');
- } else {
- showSuccess(email_notify, successIcon);
- }
- });
+ showSuccess(email_notify, successIcon);
}
- }
-
- emailEl.on('blur', function() {
- validateEmail();
});
+ }
- function validateUsername() {
- if (!username.val()) {
- validationError = true;
- return;
- }
+ function validateUsername(username) {
+ var username_notify = $('#username-notify');
- if (username.val().length < config.minimumUsernameLength) {
- showError(username_notify, 'Username too short!');
- } else if (username.val().length > config.maximumUsernameLength) {
- showError(username_notify, 'Username too long!');
- } else if (!utils.isUserNameValid(username.val()) || !utils.slugify(username.val())) {
- showError(username_notify, 'Invalid username!');
- } else {
- socket.emit('user.exists', {
- username: username.val()
- }, function(err, exists) {
- if(err) {
- return app.alertError(err.message);
- }
-
- if (exists) {
- showError(username_notify, 'Username already taken!');
- } else {
- showSuccess(username_notify, successIcon);
- }
- });
- }
+ if (!username) {
+ validationError = true;
+ return;
}
- username.on('keyup', function() {
- $('#yourUsername').html(this.value.length > 0 ? this.value : 'username');
- });
+ if (username.length < config.minimumUsernameLength) {
+ showError(username_notify, 'Username too short!');
+ } else if (username.length > config.maximumUsernameLength) {
+ showError(username_notify, 'Username too long!');
+ } else if (!utils.isUserNameValid(username) || !utils.slugify(username)) {
+ showError(username_notify, 'Invalid username!');
+ } else {
+ socket.emit('user.exists', {
+ username: username
+ }, function(err, exists) {
+ if(err) {
+ return app.alertError(err.message);
+ }
- username.on('blur', function() {
- validateUsername();
- });
+ if (exists) {
+ showError(username_notify, 'Username already taken!');
+ } else {
+ showSuccess(username_notify, successIcon);
+ }
+ });
+ }
+ }
- function validatePassword() {
- if (!password.val()) {
- validationError = true;
- return;
- }
+ function validatePassword(password, password_confirm) {
+ if (!password) {
+ validationError = true;
+ return;
+ }
+ var password_notify = $('#password-notify'),
+ password_confirm_notify = $('#password-confirm-notify');
+
+ if (password.length < config.minimumPasswordLength) {
+ showError(password_notify, 'Password too short!');
+ } else if (!utils.isPasswordValid(password)) {
+ showError(password_notify, 'Invalid password!');
+ } else {
+ showSuccess(password_notify, successIcon);
+ }
- if (password.val().length < config.minimumPasswordLength) {
- showError(password_notify, 'Password too short!');
- } else if (!utils.isPasswordValid(password.val())) {
- showError(password_notify, 'Invalid password!');
- } else {
- showSuccess(password_notify, successIcon);
- }
+ if (password !== password_confirm && password_confirm !== '') {
+ showError(password_confirm_notify, 'Passwords must match!');
+ }
+ }
- if (password.val() !== password_confirm.val() && password_confirm.val() !== '') {
- showError(password_confirm_notify, 'Passwords must match!');
- }
+ function validatePasswordConfirm(password, password_confirm) {
+ var password_notify = $('#password-notify'),
+ password_confirm_notify = $('#password-confirm-notify');
+
+ if (!password || password_notify.hasClass('alert-error')) {
+ return;
}
- $(password).on('blur', function() {
- validatePassword();
+ if (password !== password_confirm) {
+ showError(password_confirm_notify, 'Passwords must match!');
+ } else {
+ showSuccess(password_confirm_notify, successIcon);
+ }
+ }
+
+ Register.init = function() {
+ var email = $('#email'),
+ username = $('#username'),
+ password = $('#password'),
+ password_confirm = $('#password-confirm'),
+ register = $('#register'),
+ agreeTerms = $('#agree-terms');
+
+ $('#referrer').val(app.previousUrl);
+
+ email.on('blur', function() {
+ validateEmail(email.val());
});
- function validatePasswordConfirm() {
- if (!password.val() || password_notify.hasClass('alert-error')) {
- return;
- }
+ username.on('keyup', function() {
+ $('#yourUsername').html(this.value.length > 0 ? this.value : 'username');
+ });
- if (password.val() !== password_confirm.val()) {
- showError(password_confirm_notify, 'Passwords must match!');
- } else {
- showSuccess(password_confirm_notify, successIcon);
- }
- }
+ username.on('blur', function() {
+ validateUsername(username.val());
+ });
+
+ password.on('blur', function() {
+ validatePassword(password.val(), password_confirm.val());
+ });
- $(password_confirm).on('blur', function() {
- validatePasswordConfirm();
+ password_confirm.on('blur', function() {
+ validatePasswordConfirm(password.val(), password_confirm.val());
});
function validateForm() {
validationError = false;
- validateEmail();
- validateUsername();
- validatePassword();
- validatePasswordConfirm();
+ validateEmail(email.val());
+ validateUsername(username.val());
+ validatePassword(password.val(), password_confirm.val());
+ validatePasswordConfirm(password.val(), password_confirm.val());
return validationError;
}
diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js
index 01538a6275..a10006a123 100644
--- a/public/src/modules/composer.js
+++ b/public/src/modules/composer.js
@@ -110,7 +110,6 @@ define(['taskbar'], function(taskbar) {
}
function removeDraft(save_id) {
- console.log('removing draft');
return localStorage.removeItem(save_id);
}
@@ -273,6 +272,10 @@ define(['taskbar'], function(taskbar) {
});
}
+ function escapeRegExp(text) {
+ return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+ };
+
function uploadContentFiles(params) {
var files = params.files,
post_uuid = params.post_uuid,
@@ -299,10 +302,15 @@ define(['taskbar'], function(taskbar) {
textarea.val(text);
uploadForm.off('submit').submit(function() {
+ function updateTextArea(filename, text) {
+ var current = textarea.val();
+ var re = new RegExp(escapeRegExp(filename) + "]\\([^)]+\\)", 'g');
+ textarea.val(current.replace(re, filename + '](' + text + ')'));
+ }
$(this).find('#postUploadCsrf').val($('#csrf_token').val());
- if(formData) {
+ if (formData) {
formData.append('_csrf', $('#csrf_token').val());
}
@@ -317,15 +325,14 @@ define(['taskbar'], function(taskbar) {
xhr = maybeParse(xhr);
app.alertError('Error uploading file!\nStatus : ' + xhr.status + '\nMessage : ' + xhr.responseText);
- if (typeof callback == 'function')
+ if (typeof callback === 'function') {
callback(xhr);
+ }
},
uploadProgress: function(event, position, total, percent) {
- var current = textarea.val();
for(var i=0; i < files.length; ++i) {
- var re = new RegExp(files[i].name + "]\\([^)]+\\)", 'g');
- textarea.val(current.replace(re, files[i].name+'](uploading ' + percent + '%)'));
+ updateTextArea(files[i].name, 'uploading' + percent + '%');
}
},
@@ -334,15 +341,14 @@ define(['taskbar'], function(taskbar) {
if(uploads && uploads.length) {
for(var i=0; i meta.config.maximumSignatureLength) {
- next(new Error('Signature can\'t be longer than ' + meta.config.maximumSignatureLength + ' characters!'), false);
- } else {
- next(null, true);
- }
- }
-
- function isEmailAvailable(next) {
- if (!data.email) {
- return next(null, true);
- }
-
- User.getUserField(uid, 'email', function(err, email) {
- if(email === data.email) {
- return next(null, true);
- }
-
- User.email.available(data.email, function(err, available) {
- if (err) {
- return next(err, null);
- }
-
- if (!available) {
- next(new Error('Email not available!'), false);
- } else {
- next(null, true);
- }
- });
- });
- }
-
- 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) {
- return next(new Error('Invalid Username!'), false);
- }
-
- User.exists(userslug, function(err, exists) {
- if(err) {
- return next(err);
- }
-
- if(exists) {
- next(new Error('Username not available!'), false);
- } else {
- next(null, true);
- }
- });
- });
- }
-
- async.series([isSignatureValid, isEmailAvailable, isUsernameAvailable], function(err, results) {
- if (err) {
- return callback(err, returnData);
- }
-
- async.each(fields, updateField, function(err) {
- if (err) {
- return callback(err, returnData);
- }
-
- returnData.success = true;
- callback(null, returnData);
- });
- });
-
- function updateField(field, next) {
- if (!(data[field] !== undefined && typeof data[field] === 'string')) {
- return next();
- }
-
- data[field] = data[field].trim();
- 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);
-
- 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;
-
- events.logEmailChange(uid, userData.email, data.email);
- next();
- });
- return;
- } else if (field === 'username') {
-
- User.getUserFields(uid, ['username', 'userslug'], function(err, userData) {
- var userslug = utils.slugify(data.username);
-
- 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);
- }
-
- if(userslug !== userData.userslug) {
- User.setUserField(uid, 'userslug', userslug);
- db.deleteObjectField('userslug:uid', userData.userslug);
- db.setObjectField('userslug:uid', userslug, uid);
- returnData.userslug = userslug;
- }
-
- next();
- });
-
- 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) {
@@ -454,67 +255,6 @@ var bcrypt = require('bcryptjs'),
});
};
- User.changePassword = function(uid, data, callback) {
- 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)) {
- return callback(new Error('Invalid password!'));
- }
-
- if(parseInt(uid, 10) !== parseInt(data.uid, 10)) {
- User.isAdministrator(uid, function(err, isAdmin) {
- if(err || !isAdmin) {
- return callback(err || new Error('not-allowed'));
- }
-
- hashAndSetPassword(callback);
- });
- } else {
- User.getUserField(uid, 'password', function(err, currentPassword) {
- if(err) {
- return callback(err);
- }
-
- 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!'));
- }
-
- hashAndSetPassword(callback);
- });
- } else {
- // No password in account (probably SSO login)
- hashAndSetPassword(callback);
- }
- });
- }
- };
-
User.setUserField = function(uid, field, value, callback) {
db.setObjectField('user:' + uid, field, value, callback);
};
@@ -532,52 +272,43 @@ var bcrypt = require('bcryptjs'),
};
User.getUsers = function(set, start, stop, callback) {
-
- db.getSortedSetRevRange(set, start, stop, function(err, uids) {
- if (err) {
- return callback(err);
+ function loadUserInfo(user, callback) {
+ if (!user) {
+ return callback(null, user);
}
- User.getUsersData(uids, function(err, users) {
- if (err) {
- return callback(err);
- }
-
- async.map(users, function(user, next) {
- if (!user) {
- return next(null, user);
+ async.waterfall([
+ function(next) {
+ User.isAdministrator(user.uid, next);
+ },
+ function(isAdmin, next) {
+ user.status = !user.status ? 'online' : '';
+ user.administrator = isAdmin ? '1':'0';
+ if (set === 'users:online') {
+ return callback(null, user);
}
-
- if (!user.status) {
- user.status = 'online';
+ db.sortedSetScore('users:online', user.uid, next);
+ },
+ function(score, next) {
+ if (!score) {
+ user.status = 'offline';
}
+ next(null, user);
+ }
+ ], callback);
+ }
- 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);
- });
- });
+ async.waterfall([
+ function(next) {
+ db.getSortedSetRevRange(set, start, stop, next);
+ },
+ function(uids, next) {
+ User.getUsersData(uids, next);
+ },
+ function(users, next) {
+ async.map(users, loadUserInfo, next);
+ }
+ ], callback);
};
User.createGravatarURLFromEmail = function(email) {
@@ -597,74 +328,14 @@ var bcrypt = require('bcryptjs'),
User.hashPassword = function(password, callback) {
if (!password) {
- callback(password);
- return;
+ return callback(password);
}
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) {
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);
- });
- });
- };
-
- User.search = function(query, callback) {
- if (!query || query.length === 0) {
- return callback(null, {timing:0, users:[]});
- }
- var start = process.hrtime();
-
- db.getObject('username:uid', function(err, usernamesHash) {
- if (err) {
- return callback(null, {timing: 0, users:[]});
- }
-
- query = query.toLowerCase();
-
- var usernames = Object.keys(usernamesHash),
- uids = [];
-
- uids = usernames.filter(function(username) {
- return username.toLowerCase().indexOf(query) === 0;
- })
- .slice(0, 10)
- .sort(function(a, b) {
- return a > b;
- })
- .map(function(username) {
- return usernamesHash[username];
- });
-
- 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});
- });
+ bcrypt.hash(password, salt, callback);
});
};
@@ -688,78 +359,8 @@ var bcrypt = require('bcryptjs'),
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);
- };
-
- 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) {
- 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);
+ callback(err, Array.isArray(pids) ? pids : []);
});
- }
-
- 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);
- };
-
-
-
-
- User.isFollowing = function(uid, theirid, callback) {
- db.isSetMember('following:' + uid, theirid, callback);
};
User.exists = function(userslug, callback) {
@@ -770,13 +371,7 @@ var bcrypt = require('bcryptjs'),
User.count = function(callback) {
db.getObjectField('global', 'userCount', function(err, count) {
- if(err) {
- return callback(err);
- }
-
- callback(null, {
- count: count ? count : 0
- });
+ callback(err, count ? count : 0);
});
};
@@ -825,21 +420,5 @@ var bcrypt = require('bcryptjs'),
groups.isMemberByGroupName(uid, 'administrators', callback);
};
- User.logIP = function(uid, ip) {
- db.sortedSetAdd('uid:' + uid + ':ip', +new Date(), ip || 'Unknown');
- };
-
- 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};
- }));
- });
- };
-
}(exports));
diff --git a/src/user/admin.js b/src/user/admin.js
new file mode 100644
index 0000000000..ece5b56cf3
--- /dev/null
+++ b/src/user/admin.js
@@ -0,0 +1,54 @@
+
+'use strict';
+
+var async = require('async'),
+ db = require('./../database');
+
+module.exports = function(User) {
+
+ User.logIP = function(uid, ip) {
+ db.sortedSetAdd('uid:' + uid + ':ip', Date.now(), ip || 'Unknown');
+ };
+
+ 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.getUsersCSV = function(callback) {
+ var csvContent = '';
+
+ async.waterfall([
+ function(next) {
+ db.getObjectValues('username:uid', next);
+ },
+ function(uids, next) {
+ User.getMultipleUserFields(uids, ['uid', 'email', 'username'], next);
+ },
+ function(usersData, next) {
+ usersData.forEach(function(user, index) {
+ if (user) {
+ csvContent += user.email + ',' + user.username + ',' + user.uid + '\n';
+ }
+ });
+
+ next(null, csvContent);
+ }
+ ], callback);
+ };
+
+ User.ban = function(uid, callback) {
+ User.setUserField(uid, 'banned', 1, callback);
+ };
+
+ User.unban = function(uid, callback) {
+ User.setUserField(uid, 'banned', 0, callback);
+ };
+};
\ No newline at end of file
diff --git a/src/useremail.js b/src/user/email.js
similarity index 89%
rename from src/useremail.js
rename to src/user/email.js
index 603dc7eee0..74aa01db17 100644
--- a/src/useremail.js
+++ b/src/user/email.js
@@ -5,12 +5,12 @@ var async = require('async'),
nconf = require('nconf'),
winston = require('winston'),
- user = require('./user'),
- utils = require('./../public/src/utils'),
- plugins = require('./plugins'),
- db = require('./database'),
- meta = require('./meta'),
- emailer = require('./emailer');
+ user = require('./../user'),
+ utils = require('./../../public/src/utils'),
+ plugins = require('./../plugins'),
+ db = require('./../database'),
+ meta = require('./../meta'),
+ emailer = require('./../emailer');
(function(UserEmail) {
diff --git a/src/user/follow.js b/src/user/follow.js
new file mode 100644
index 0000000000..4a354521fc
--- /dev/null
+++ b/src/user/follow.js
@@ -0,0 +1,68 @@
+
+'use strict';
+
+var async = require('async'),
+ db = require('./../database');
+
+module.exports = function(User) {
+
+ User.follow = function(uid, followuid, callback) {
+ toggleFollow('follow', uid, followuid, callback);
+ };
+
+ 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) {
+ 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);
+ };
+
+ User.isFollowing = function(uid, theirid, callback) {
+ db.isSetMember('following:' + uid, theirid, callback);
+ };
+
+};
\ No newline at end of file
diff --git a/src/usernotifications.js b/src/user/notifications.js
similarity index 94%
rename from src/usernotifications.js
rename to src/user/notifications.js
index 8228074b12..088caa5760 100644
--- a/src/usernotifications.js
+++ b/src/user/notifications.js
@@ -5,12 +5,11 @@ var async = require('async'),
nconf = require('nconf'),
winston = require('winston'),
- user = require('./user'),
- utils = require('./../public/src/utils'),
- db = require('./database'),
- notifications = require('./notifications'),
- topics = require('./topics'),
- websockets = require('./socket.io');
+ user = require('./../user'),
+ utils = require('./../../public/src/utils'),
+ db = require('./../database'),
+ notifications = require('./../notifications'),
+ topics = require('./../topics');
(function(UserNotifications) {
@@ -161,7 +160,7 @@ var async = require('async'),
};
UserNotifications.pushCount = function(uid) {
-
+ var websockets = require('./../socket.io');
UserNotifications.getUnreadCount(uid, function(err, count) {
if (err) {
return winston.warn('[User.pushNotifCount] Count not retrieve unread notifications count to push to uid ' + uid + '\'s client(s)');
diff --git a/src/user/profile.js b/src/user/profile.js
new file mode 100644
index 0000000000..e1fc6284d4
--- /dev/null
+++ b/src/user/profile.js
@@ -0,0 +1,256 @@
+
+'use strict';
+
+var bcrypt = require('bcryptjs'),
+ async = require('async'),
+ validator = require('validator'),
+ S = require('string'),
+
+ utils = require('./../../public/src/utils'),
+ meta = require('./../meta'),
+ events = require('./../events'),
+ db = require('./../database');
+
+module.exports = function(User) {
+
+ User.updateProfile = function(uid, data, callback) {
+ var fields = ['username', 'email', 'fullname', 'website', 'location', 'birthday', 'signature'];
+
+ function isSignatureValid(next) {
+ if (data.signature !== undefined && data.signature.length > meta.config.maximumSignatureLength) {
+ next(new Error('Signature can\'t be longer than ' + meta.config.maximumSignatureLength + ' characters!'));
+ } else {
+ next();
+ }
+ }
+
+ function isEmailAvailable(next) {
+ if (!data.email) {
+ return next();
+ }
+
+ User.getUserField(uid, 'email', function(err, email) {
+ if(email === data.email) {
+ return next();
+ }
+
+ User.email.available(data.email, function(err, available) {
+ if (err) {
+ return next(err);
+ }
+
+ next(!available ? new Error('Email not available!') : null);
+
+ });
+ });
+ }
+
+ function isUsernameAvailable(next) {
+ User.getUserFields(uid, ['username', 'userslug'], function(err, userData) {
+
+ var userslug = utils.slugify(data.username);
+
+ if(userslug === userData.userslug) {
+ return next();
+ }
+
+ if(!utils.isUserNameValid(data.username) || !userslug) {
+ return next(new Error('Invalid Username!'));
+ }
+
+ User.exists(userslug, function(err, exists) {
+ if(err) {
+ return next(err);
+ }
+
+ next(exists ? new Error('Username not available!') : null);
+ });
+ });
+ }
+
+ async.series([isSignatureValid, isEmailAvailable, isUsernameAvailable], function(err, results) {
+ if (err) {
+ return callback(err);
+ }
+
+ async.each(fields, updateField, function(err) {
+ if (err) {
+ return callback(err);
+ }
+
+ User.getUserFields(uid, ['userslug', 'picture', 'gravatarpicture'], callback);
+ });
+ });
+
+ function updateField(field, next) {
+ if (!(data[field] !== undefined && typeof data[field] === 'string')) {
+ return next();
+ }
+
+ data[field] = data[field].trim();
+ data[field] = validator.escape(data[field]);
+
+ if (field === 'email') {
+ return updateEmail(uid, data.email, next);
+ } else if (field === 'username') {
+ return updateUsername(uid, data.username, next);
+ } 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();
+ }
+ };
+
+ function updateEmail(uid, newEmail, callback) {
+ User.getUserFields(uid, ['email', 'picture', 'uploadedpicture'], function(err, userData) {
+ if (err) {
+ return callback(err);
+ }
+
+ if(userData.email === newEmail) {
+ return callback();
+ }
+
+ db.deleteObjectField('email:uid', userData.email, function(err) {
+ if (err) {
+ return callback(err);
+ }
+
+ events.logEmailChange(uid, userData.email, newEmail);
+
+ var gravatarpicture = User.createGravatarURLFromEmail(newEmail);
+ async.parallel([
+ function(next) {
+ User.setUserField(uid, 'gravatarpicture', gravatarpicture, next);
+ },
+ function(next) {
+ db.setObjectField('email:uid', newEmail, uid, next);
+ },
+ function(next) {
+ User.setUserField(uid, 'email', newEmail, next);
+ },
+ function(next) {
+ if (userData.picture !== userData.uploadedpicture) {
+ User.setUserField(uid, 'picture', gravatarpicture, next);
+ }
+ },
+ ], callback);
+ });
+ });
+ }
+
+ function updateUsername(uid, newUsername, callback) {
+ User.getUserFields(uid, ['username', 'userslug'], function(err, userData) {
+ function update(field, object, value, callback) {
+ async.parallel([
+ function(next) {
+ User.setUserField(uid, field, value, next);
+ },
+ function(next) {
+ db.setObjectField(object, value, uid, next);
+ }
+ ], callback);
+ }
+
+ if (err) {
+ return callback(err);
+ }
+ var userslug = utils.slugify(newUsername);
+
+ async.parallel([
+ function(next) {
+ if (newUsername === userData.username) {
+ return next();
+ }
+
+ db.deleteObjectField('username:uid', userData.username, function(err) {
+ if (err) {
+ return next(err);
+ }
+ events.logUsernameChange(uid, userData.username, newUsername);
+ update('username', 'username:uid', newUsername, next);
+ });
+ },
+ function(next) {
+ if (userslug === userData.userslug) {
+ return next();
+ }
+
+ db.deleteObjectField('userslug:uid', userData.userslug, function(err) {
+ if (err) {
+ return next(err);
+ }
+ update('userslug', 'userslug:uid', userslug, next);
+ });
+ }
+ ], callback);
+ });
+ }
+
+ User.changePassword = function(uid, data, callback) {
+ 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)) {
+ return callback(new Error('Invalid password!'));
+ }
+
+ if(parseInt(uid, 10) !== parseInt(data.uid, 10)) {
+ User.isAdministrator(uid, function(err, isAdmin) {
+ if(err || !isAdmin) {
+ return callback(err || new Error('not-allowed'));
+ }
+
+ hashAndSetPassword(callback);
+ });
+ } else {
+ User.getUserField(uid, 'password', function(err, currentPassword) {
+ if(err) {
+ return callback(err);
+ }
+
+ if (!currentPassword) {
+ return hashAndSetPassword(callback);
+ }
+
+ bcrypt.compare(data.currentPassword, currentPassword, function(err, res) {
+ if (err || !res) {
+ return callback(err || new Error('Your current password is not correct!'));
+ }
+ hashAndSetPassword(callback);
+ });
+ });
+ }
+ };
+
+};
\ No newline at end of file
diff --git a/src/userreset.js b/src/user/reset.js
similarity index 90%
rename from src/userreset.js
rename to src/user/reset.js
index 9561a1b48e..330f49c843 100644
--- a/src/userreset.js
+++ b/src/user/reset.js
@@ -4,13 +4,13 @@
var async = require('async'),
nconf = require('nconf'),
- user = require('./user'),
- utils = require('./../public/src/utils'),
+ user = require('./../user'),
+ utils = require('./../../public/src/utils'),
- db = require('./database'),
- meta = require('./meta'),
- events = require('./events'),
- emailer = require('./emailer');
+ db = require('./../database'),
+ meta = require('./../meta'),
+ events = require('./../events'),
+ emailer = require('./../emailer');
(function(UserReset) {
diff --git a/src/user/search.js b/src/user/search.js
new file mode 100644
index 0000000000..587f175352
--- /dev/null
+++ b/src/user/search.js
@@ -0,0 +1,45 @@
+
+'use strict';
+
+var db = require('./../database');
+
+module.exports = function(User) {
+ User.search = function(query, callback) {
+ if (!query || query.length === 0) {
+ return callback(null, {timing:0, users:[]});
+ }
+ var start = process.hrtime();
+
+ db.getObject('username:uid', function(err, usernamesHash) {
+ if (err) {
+ return callback(null, {timing: 0, users:[]});
+ }
+
+ query = query.toLowerCase();
+
+ var usernames = Object.keys(usernamesHash),
+ uids = [];
+
+ uids = usernames.filter(function(username) {
+ return username.toLowerCase().indexOf(query) === 0;
+ })
+ .slice(0, 10)
+ .sort(function(a, b) {
+ return a > b;
+ })
+ .map(function(username) {
+ return usernamesHash[username];
+ });
+
+ 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});
+ });
+ });
+ };
+};
+
diff --git a/src/user/settings.js b/src/user/settings.js
new file mode 100644
index 0000000000..af1bf9e81b
--- /dev/null
+++ b/src/user/settings.js
@@ -0,0 +1,54 @@
+
+'use strict';
+
+var meta = require('./../meta'),
+ db = require('./../database');
+
+module.exports = function(User) {
+
+ User.getSettings = function(uid, callback) {
+ function sendDefaultSettings() {
+ callback(null, {
+ showemail: false,
+ 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) {
+ settings = {};
+ }
+
+ settings.showemail = settings.showemail ? parseInt(settings.showemail, 10) !== 0 : false;
+ 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);
+ });
+ };
+
+ User.saveSettings = function(uid, data, callback) {
+
+ 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);
+ };
+};
\ No newline at end of file