diff --git a/public/src/app.js b/public/src/app.js index ea567e239e..d7097f91e7 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -364,7 +364,7 @@ app.uid = null; } }; - function exposeConfigToTemplates() { + app.exposeConfigToTemplates = function() { $(document).ready(function() { templates.setGlobal('loggedIn', config.loggedIn); templates.setGlobal('relative_path', RELATIVE_PATH); @@ -599,7 +599,7 @@ app.uid = null; showWelcomeMessage = window.location.href.indexOf('loggedin') !== -1; - exposeConfigToTemplates(); + app.exposeConfigToTemplates(); socketIOConnect(); diff --git a/public/src/client/account/settings.js b/public/src/client/account/settings.js index 3dde90dd98..0b4bbbd00f 100644 --- a/public/src/client/account/settings.js +++ b/public/src/client/account/settings.js @@ -42,6 +42,7 @@ define('forum/account/settings', ['forum/account/header'], function(header) { config[key] = newSettings[key]; } } + app.exposeConfigToTemplates(); if (parseInt(app.uid, 10) === parseInt(ajaxify.variables.get('theirid'), 10)) { ajaxify.refresh(); diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js index 0ef47b3fd8..b667176ae9 100644 --- a/public/src/client/groups/details.js +++ b/public/src/client/groups/details.js @@ -1,5 +1,5 @@ "use strict"; -/* globals define, socket, ajaxify, app, bootbox, RELATIVE_PATH */ +/* globals define, socket, ajaxify, app, bootbox, RELATIVE_PATH, utils */ define('forum/groups/details', ['iconSelect', 'vendor/colorpicker/colorpicker', 'vendor/jquery/draggable-background/backgroundDraggable'], function(iconSelect) { var Details = { @@ -122,7 +122,7 @@ define('forum/groups/details', ['iconSelect', 'vendor/colorpicker/colorpicker', if (settings.name) { var pathname = window.location.pathname; pathname = pathname.substr(1, pathname.lastIndexOf('/')); - ajaxify.go(pathname + encodeURIComponent(settings.name)); + ajaxify.go(pathname + utils.slugify(settings.name)); } else { ajaxify.refresh(); } @@ -134,7 +134,7 @@ define('forum/groups/details', ['iconSelect', 'vendor/colorpicker/colorpicker', }; Details.deleteGroup = function() { - bootbox.confirm('Are you sure you want to delete the group: ' + ajaxify.variables.get('group_name'), function(confirm) { + bootbox.confirm('Are you sure you want to delete the group: ' + utils.escapeHTML(ajaxify.variables.get('group_name')), function(confirm) { if (confirm) { bootbox.prompt('Please enter the name of this group in order to delete it:', function(response) { if (response === ajaxify.variables.get('group_name')) { @@ -142,7 +142,7 @@ define('forum/groups/details', ['iconSelect', 'vendor/colorpicker/colorpicker', groupName: ajaxify.variables.get('group_name') }, function(err) { if (!err) { - app.alertSuccess('[[groups:event.deleted, ' + ajaxify.variables.get('group_name') + ']]'); + app.alertSuccess('[[groups:event.deleted, ' + utils.escapeHTML(ajaxify.variables.get('group_name')) + ']]'); ajaxify.go('groups'); } else { app.alertError(err.message); diff --git a/public/src/client/groups/list.js b/public/src/client/groups/list.js index d4a92ac9aa..d11eb7c8e6 100644 --- a/public/src/client/groups/list.js +++ b/public/src/client/groups/list.js @@ -1,5 +1,5 @@ "use strict"; -/* globals app, define, ajaxify, socket, bootbox */ +/* globals app, define, ajaxify, socket, bootbox, utils */ define('forum/groups/list', function() { var Groups = {}; @@ -10,7 +10,7 @@ define('forum/groups/list', function() { groupsEl.on('click', '.list-cover', function() { var groupName = $(this).parents('[data-group]').attr('data-group'); - ajaxify.go('groups/' + encodeURIComponent(groupName)); + ajaxify.go('groups/' + utils.slugify(groupName)); }); // Group creation @@ -21,7 +21,7 @@ define('forum/groups/list', function() { name: name }, function(err) { if (!err) { - ajaxify.go('groups/' + name); + ajaxify.go('groups/' + utils.slugify(name)); } else { app.alertError(err.message); } diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index 2f5a617572..5e6df47e8b 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -39,15 +39,15 @@ define('forum/topic/postTools', ['composer', 'share', 'navigator'], function(com function addVoteHandler() { $('#post-container').on('mouseenter', '.post-row .votes', function() { - loadDataAndCreateTooltip($(this), 'posts.getUpvoters'); + loadDataAndCreateTooltip($(this)); }); } - function loadDataAndCreateTooltip(el, method) { + function loadDataAndCreateTooltip(el) { var pid = el.parents('.post-row').attr('data-pid'); - socket.emit(method, pid, function(err, data) { - if (!err) { - createTooltip(el, data); + socket.emit('posts.getUpvoters', [pid], function(err, data) { + if (!err && data.length) { + createTooltip(el, data[0]); } }); } diff --git a/public/src/modules/notifications.js b/public/src/modules/notifications.js index 46e6833806..319d88041f 100644 --- a/public/src/modules/notifications.js +++ b/public/src/modules/notifications.js @@ -25,11 +25,14 @@ define('notifications', ['sounds'], function(sound) { image = ''; } - return '
  • ' + image + '' + utils.relativeTime(notification.datetime, true) + '' + notification.bodyShort + '
  • '; + return '
  • ' + image + '' + $.timeago(new Date(parseInt(notification.datetime, 10))) + '' + notification.bodyShort + '
  • '; } var x, html = ''; + // Switch to shorthand + translator.toggleTimeagoShorthand(); + if (!err && (data.read.length + data.unread.length) > 0) { var image = ''; for (x = 0; x < data.unread.length; x++) { @@ -43,6 +46,9 @@ define('notifications', ['sounds'], function(sound) { html += '
  • [[notifications:no_notifs]]
  • '; } + // Switch back to original timeago strings + translator.toggleTimeagoShorthand(); + html += ''; notifList.translateHtml(html); diff --git a/public/src/translator.js b/public/src/translator.js index 07794f360c..5b052af216 100644 --- a/public/src/translator.js +++ b/public/src/translator.js @@ -85,6 +85,34 @@ } }; + translator.toggleTimeagoShorthand = function() { + if (!translator.timeagoStrings) { + translator.timeagoStrings = $.extend({}, jQuery.timeago.settings.strings); + jQuery.timeago.settings.strings = { + prefixAgo: null, + prefixFromNow: null, + suffixAgo: "", + suffixFromNow: "", + seconds: "1m", + minute: "1m", + minutes: "%dm", + hour: "1h", + hours: "%dh", + day: "1d", + days: "%dd", + month: "1mo", + months: "%dmo", + year: "1yr", + years: "%dyr", + wordSeparator: " ", + numbers: [] + }; + } else { + jQuery.timeago.settings.strings = $.extend({}, translator.timeagoStrings); + delete translator.timeagoStrings; + } + }; + translator.translate = function (text, language, callback) { if (typeof language === 'function') { callback = language; diff --git a/public/src/utils.js b/public/src/utils.js index a3f3be9648..b9658593cc 100644 --- a/public/src/utils.js +++ b/public/src/utils.js @@ -63,44 +63,6 @@ }); }, - relativeTime: function(timestamp, min) { - var now = +new Date(), - difference = now - Math.floor(parseFloat(timestamp)); - - if(difference < 0) { - difference = 0; - } - - difference = Math.floor(difference / 1000); - - if (difference < 60) { - return difference + (min ? 's' : ' second') + (difference !== 1 && !min ? 's' : ''); - } - - difference = Math.floor(difference / 60); - if (difference < 60) { - return difference + (min ? 'm' : ' minute') + (difference !== 1 && !min ? 's' : ''); - } - - difference = Math.floor(difference / 60); - if (difference < 24) { - return difference + (min ? 'h' : ' hour') + (difference !== 1 && !min ? 's' : ''); - } - - difference = Math.floor(difference / 24); - if (difference < 30) { - return difference + (min ? 'd' : ' day') + (difference !== 1 && !min ? 's' : ''); - } - - difference = Math.floor(difference / 30); - if (difference < 12) { - return difference + (min ? 'mon' : ' month') + (difference !== 1 && !min ? 's' : ''); - } - - difference = Math.floor(difference / 12); - return difference + (min ? 'y' : ' year') + (difference !== 1 && !min ? 's' : ''); - }, - invalidUnicodeChars: XRegExp('[^\\p{L}\\s\\d\\-_]', 'g'), invalidLatinChars: /[^\w\s\d\-_]/g, trimRegex: /^\s+|\s+$/g, @@ -119,7 +81,7 @@ str = XRegExp.replace(str, utils.invalidUnicodeChars, '-'); } str = str.toLocaleLowerCase(); - str = str.replace(utils.collapseWhitespace, '-') + str = str.replace(utils.collapseWhitespace, '-'); str = str.replace(utils.collapseDash, '-'); str = str.replace(utils.trimTrailingDash, ''); str = str.replace(utils.trimLeadingDash, ''); @@ -193,7 +155,7 @@ return function (path) { var extension = utils.fileExtension(path); return map[extension] || '*'; - } + }; })(), isRelativeUrl: function(url) { @@ -248,6 +210,10 @@ return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); }, + escapeHTML: function(raw) { + return raw.replace(/&/gm,"&").replace(//gm,">"); + }, + isAndroidBrowser: function() { // http://stackoverflow.com/questions/9286355/how-to-detect-only-the-native-android-browser var nua = navigator.userAgent; @@ -289,8 +255,9 @@ key = decodeURI(val[0]), value = options.skipToType[key] ? decodeURI(val[1]) : utils.toType(decodeURI(val[1])); - if (key) + if (key) { hash[key] = value; + } }); return hash; }, @@ -331,12 +298,15 @@ return str; } else { var nb = parseFloat(str); - if (!isNaN(nb) && isFinite(str)) + if (!isNaN(nb) && isFinite(str)) { return nb; - if (str === 'false') + } + if (str === 'false') { return false; - if (str === 'true') + } + if (str === 'true') { return true; + } try { str = JSON.parse(str); @@ -352,21 +322,25 @@ // get example: utils.props(A, 'a.b.c.foo.bar') // returns undefined without throwing a TypeError // credits to github.com/gkindel props: function(obj, props, value) { - if(obj === undefined) + if(obj === undefined) { obj = window; - if(props == null) + } + if(props == null) { return undefined; + } var i = props.indexOf('.'); if( i == -1 ) { - if(value !== undefined) + if(value !== undefined) { obj[props] = value; + } return obj[props]; } var prop = props.slice(0, i), newProps = props.slice(i + 1); - if(props !== undefined && !(obj[prop] instanceof Object) ) + if(props !== undefined && !(obj[prop] instanceof Object) ) { obj[prop] = {}; + } return utils.props(obj[prop], newProps, value); } @@ -374,10 +348,12 @@ if (typeof String.prototype.startsWith != 'function') { String.prototype.startsWith = function (prefix){ - if (this.length < prefix.length) + if (this.length < prefix.length) { return false; - for (var i = prefix.length - 1; (i >= 0) && (this[i] === prefix[i]); --i) + } + for (var i = prefix.length - 1; (i >= 0) && (this[i] === prefix[i]); --i) { continue; + } return i < 0; }; } diff --git a/src/categories/delete.js b/src/categories/delete.js index d01dd13317..0ebac949d7 100644 --- a/src/categories/delete.js +++ b/src/categories/delete.js @@ -31,6 +31,7 @@ module.exports = function(Categories) { 'cid:' + cid + ':tids', 'cid:' + cid + ':tids:posts', 'cid:' + cid + ':pids', + 'cid:' + cid + ':read_by_uid', 'category:' + cid ], next); } diff --git a/src/categories/update.js b/src/categories/update.js index e103d3e5dc..f68a7aed20 100644 --- a/src/categories/update.js +++ b/src/categories/update.js @@ -11,12 +11,18 @@ module.exports = function(Categories) { Categories.update = function(modified, callback) { function updateCategory(cid, next) { - var category = modified[cid]; - var fields = Object.keys(category); + Categories.exists(cid, function(err, exists) { + if (err || !exists) { + return next(err); + } - async.each(fields, function(key, next) { - updateCategoryField(cid, key, category[key], next); - }, next); + var category = modified[cid]; + var fields = Object.keys(category); + + async.each(fields, function(key, next) { + updateCategoryField(cid, key, category[key], next); + }, next); + }); } var cids = Object.keys(modified); diff --git a/src/controllers/groups.js b/src/controllers/groups.js index 2d2ce8f050..d6307b8daf 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -26,7 +26,7 @@ groupsController.details = function(req, res, next) { async.parallel({ group: function(next) { - groups.get(req.params.name, { + groups.getByGroupslug(req.params.slug, { expand: true, uid: uid }, next); diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js index 6db3b5a4a9..f40a756600 100644 --- a/src/database/mongo/hash.js +++ b/src/database/mongo/hash.js @@ -174,6 +174,31 @@ module.exports = function(db, module) { }); }; + module.isObjectFields = function(key, fields, callback) { + if (!key) { + return callback(); + } + + var data = {}; + fields.forEach(function(field) { + field = helpers.fieldToString(field); + data[field] = ''; + }); + + db.collection('objects').findOne({_key: key}, {fields: data}, function(err, item) { + if (err) { + return callback(err); + } + var results = []; + + fields.forEach(function(field, index) { + results[index] = !!item && item[field] !== undefined && item[field] !== null; + }); + + callback(null, results); + }); + }; + module.deleteObjectField = function(key, field, callback) { callback = callback || helpers.noop; if (!key || !field) { diff --git a/src/database/redis/hash.js b/src/database/redis/hash.js index ddc21b893c..ee60ba98a2 100644 --- a/src/database/redis/hash.js +++ b/src/database/redis/hash.js @@ -88,7 +88,26 @@ module.exports = function(redisClient, module) { }); }; + module.isObjectFields = function(key, fields, callback) { + var multi = redisClient.multi(); + for (var i=0; i 0) { + if (!err && groupObj && groupObj.memberCount > 0) { winston.info('Administrator found, skipping Admin setup'); next(); } else { diff --git a/src/meta/title.js b/src/meta/title.js index a476cb61a3..11af43ae9b 100644 --- a/src/meta/title.js +++ b/src/meta/title.js @@ -26,7 +26,6 @@ module.exports = function(Meta) { } Meta.title.parseFragment(uri, language, locals, function(err, title) { - if (err) { title = fallbackTitle; } else { @@ -41,7 +40,6 @@ module.exports = function(Meta) { }; Meta.title.parseFragment = function (urlFragment, language, locals, callback) { - urlFragment = validator.escape(urlFragment); var translated = ['', 'recent', 'unread', 'users', 'notifications']; if (translated.indexOf(urlFragment) !== -1) { if (!urlFragment.length) { @@ -75,7 +73,7 @@ module.exports = function(Meta) { return callback(err); } - if (locals.notFound) { + if (!username) { username = '[[error:no-user]]'; } diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index 334e6d0b5a..2d5faace76 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -450,7 +450,12 @@ middleware.maintenanceMode = function(req, res, next) { '/login', '/stylesheet.css', '/nodebb.min.js', - '/vendor/fontawesome/fonts/fontawesome-webfont.woff' + '/vendor/fontawesome/fonts/fontawesome-webfont.woff', + '/src/modules/[\\w]+\.js', + '/api/get_templates_listing', + '/api/login', + '/api/?', + '/language/.+' ], render = function() { res.status(503); diff --git a/src/routes/index.js b/src/routes/index.js index c189404e5f..b4427f90f1 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -93,7 +93,7 @@ function groupRoutes(app, middleware, controllers) { var middlewares = [middleware.checkGlobalPrivacySettings]; setupPageRoute(app, '/groups', middleware, middlewares, controllers.groups.list); - setupPageRoute(app, '/groups/:name', middleware, middlewares, controllers.groups.details); + setupPageRoute(app, '/groups/:slug', middleware, middlewares, controllers.groups.details); } function setupPageRoute(router, name, middleware, middlewares, controller) { diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 08dab140e6..171a80d38f 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -124,11 +124,6 @@ function onMessage(socket, payload) { return winston.warn('[socket.io] Empty method name'); } - if (ratelimit.isFlooding(socket)) { - winston.warn('[socket.io] Too many emits! Disconnecting uid : ' + socket.uid + '. Message : ' + eventName); - return socket.disconnect(); - } - var parts = eventName.toString().split('.'), namespace = parts[0], methodToCall = parts.reduce(function(prev, cur) { @@ -146,6 +141,17 @@ function onMessage(socket, payload) { return; } + socket.previousEvents = socket.previousEvents || []; + socket.previousEvents.push(eventName); + if (socket.previousEvents.length > 20) { + socket.previousEvents.shift(); + } + + if (ratelimit.isFlooding(socket)) { + winston.warn('[socket.io] Too many emits! Disconnecting uid : ' + socket.uid + '. Events : ' + socket.previousEvents); + return socket.disconnect(); + } + if (Namespaces[namespace].before) { Namespaces[namespace].before(socket, eventName, function() { callMethod(methodToCall, socket, params, callback); diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 97b8e7ea74..4a8e5f19bd 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -339,8 +339,11 @@ SocketPosts.getPrivileges = function(socket, pids, callback) { }); }; -SocketPosts.getUpvoters = function(socket, pid, callback) { - favourites.getUpvotedUidsByPids([pid], function(err, data) { +SocketPosts.getUpvoters = function(socket, pids, callback) { + if (!Array.isArray(pids)) { + return callback(new Error('[[error:invalid-data]]')); + } + favourites.getUpvotedUidsByPids(pids, function(err, data) { if (err || !Array.isArray(data) || !data.length) { return callback(err, []); } diff --git a/src/upgrade.js b/src/upgrade.js index 35f3acaa87..b28158d826 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -21,7 +21,7 @@ var db = require('./database'), schemaDate, thisSchemaDate, // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema - latestSchema = Date.UTC(2015, 0, 15); + latestSchema = Date.UTC(2015, 0, 19); Upgrade.check = function(callback) { db.get('schemaDate', function(err, value) { @@ -72,6 +72,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 9, 31); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/10/31] Applying newbiePostDelay values'); async.series([ @@ -93,6 +94,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 10, 6, 18, 30); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/11/6] Updating topic authorship sorted set'); async.waterfall([ @@ -138,6 +140,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 10, 7); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/11/7] Renaming sorted set names'); async.waterfall([ @@ -199,6 +202,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 10, 11); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/11/11] Upgrading permissions'); async.waterfall([ @@ -272,6 +276,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 10, 17, 13); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/11/17] Updating user email digest settings'); async.waterfall([ @@ -306,6 +311,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 10, 29, 22); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/11/29] Updating config.json to new format'); var configPath = path.join(__dirname, '../config.json'); @@ -358,6 +364,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 11, 2); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/12/2] Removing register user fields'); db.getSortedSetRange('users:joindate', 0, -1, function(err, uids) { @@ -393,6 +400,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 11, 12); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/12/12] Updating teasers'); db.getSortedSetRange('topics:tid', 0, -1, function(err, tids) { @@ -419,6 +427,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2014, 11, 20); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2014/12/20] Updating digest settings'); async.waterfall([ @@ -455,6 +464,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2015, 0, 8); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2015/01/08] Updating category topics sorted sets'); db.getSortedSetRange('topics:tid', 0, -1, function(err, tids) { @@ -494,6 +504,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2015, 0, 9); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2015/01/09] Creating fullname:uid hash'); db.getSortedSetRange('users:joindate', 0, -1, function(err, uids) { @@ -529,6 +540,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2015, 0, 13); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2015/01/13] Creating uid:followed_tids sorted set'); db.getSortedSetRange('topics:tid', 0, -1, function(err, tids) { @@ -570,6 +582,7 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2015, 0, 14); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2015/01/14] Upgrading follow sets to sorted sets'); db.getSortedSetRange('users:joindate', 0, -1, function(err, uids) { @@ -634,11 +647,12 @@ Upgrade.upgrade = function(callback) { function(next) { thisSchemaDate = Date.UTC(2015, 0, 15); if (schemaDate < thisSchemaDate) { + updatesMade = true; winston.info('[2015/01/15] Creating topiccount for users'); db.getSortedSetRange('users:joindate', 0, -1, function(err, uids) { if (err) { - winston.error('[2014/01/15] Error encountered while Creating topiccount for users'); + winston.error('[2015/01/15] Error encountered while Creating topiccount for users'); return next(err); } @@ -668,6 +682,34 @@ Upgrade.upgrade = function(callback) { next(); } }, + function(next) { + thisSchemaDate = Date.UTC(2015, 0, 19); + if (schemaDate < thisSchemaDate) { + updatesMade = true; + winston.info('[2015/01/19] Generating group slugs'); + + Groups.list({}, function(err, groups) { + var tasks = []; + groups.forEach(function(groupObj) { + tasks.push(async.apply(db.setObjectField, 'group:' + groupObj.name, 'slug', Utils.slugify(groupObj.name))); + tasks.push(async.apply(db.setObjectField, 'groupslug:groupname', Utils.slugify(groupObj.name), groupObj.name)); + }); + + async.parallel(tasks, function(err) { + if (err) { + winston.error('[2015/01/19] Error encountered while Generating group slugs'); + return next(err); + } + + winston.info('[2015/01/19] Generating group slugs done'); + Upgrade.update(thisSchemaDate, next); + }); + }); + } else { + winston.info('[2015/01/19] Generating group slugs skipped'); + next(); + } + } // Add new schema updates here // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 22!!! diff --git a/src/user/delete.js b/src/user/delete.js index c115c727f1..b13fa1aa97 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -79,11 +79,14 @@ module.exports = function(User) { 'uid:' + uid + ':favourites', 'uid:' + uid + ':followed_tids', 'user:' + uid + ':settings', 'uid:' + uid + ':topics', 'uid:' + uid + ':posts', 'uid:' + uid + ':chats', 'uid:' + uid + ':chats:unread', - 'uid:' + uid + ':ip', 'uid:' + uid + ':upvote', 'uid:' + uid + ':downvote', + 'uid:' + uid + ':upvote', 'uid:' + uid + ':downvote', 'uid:' + uid + ':ignored:cids' ]; db.deleteAll(keys, next); }, + function(next) { + deleteUserIps(uids, next); + }, function(next) { deleteUserFromFollowers(uid, next); }, @@ -110,6 +113,23 @@ module.exports = function(User) { }); }; + function deleteUserIps(uid, callback) { + db.getSortedSetRange('uid:' + uid + ':ip', 0, -1, function(err, ips) { + if (err) { + return callback(err); + } + + async.each(ips, function(ip, next) { + db.sortedSetRemove('ip:' + ip + ':uid', uid, next); + }, function(err) { + if (err) { + return callback(err); + } + db.delete('uid:' + uid + ':ip', callback); + }); + }) + } + function deleteUserFromFollowers(uid, callback) { db.getSetMembers('followers:' + uid, function(err, uids) { if (err) { diff --git a/src/user/profile.js b/src/user/profile.js index b901bdb066..8c44b86a6f 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -57,6 +57,9 @@ module.exports = function(User) { } function isUsernameAvailable(next) { + if (!data.username) { + return next(); + } User.getUserFields(uid, ['username', 'userslug'], function(err, userData) { var userslug = utils.slugify(data.username); @@ -96,7 +99,7 @@ module.exports = function(User) { if (err) { return callback(err); } - + plugins.fireHook('action:user.updateProfile', {data: data, uid: uid}); User.getUserFields(uid, ['email', 'userslug', 'picture', 'gravatarpicture'], callback); }); }); @@ -177,6 +180,9 @@ module.exports = function(User) { } function updateUsername(uid, newUsername, callback) { + if (!newUsername) { + return callback(); + } User.getUserFields(uid, ['username', 'userslug'], function(err, userData) { function update(field, object, value, callback) { async.parallel([ diff --git a/src/user/search.js b/src/user/search.js index 1ea83a863f..a95b64bfaa 100644 --- a/src/user/search.js +++ b/src/user/search.js @@ -18,7 +18,7 @@ module.exports = function(User) { return callback(null, {timing: 0, users: [], matchCount: 0, pages: []}); } - if (searchBy === 'ip') { + if (searchBy.indexOf('ip') !== -1) { return searchByIP(query, callback); } diff --git a/tests/database/hash.js b/tests/database/hash.js index c67b57fd64..5d1d002c3b 100644 --- a/tests/database/hash.js +++ b/tests/database/hash.js @@ -222,7 +222,7 @@ describe('Hash methods', function() { }); it('should return false if field does not exist', function(done) { - db.isObjectField('testObject1', 'field1', function(err, value) { + db.isObjectField('hashTestObject', 'field1', function(err, value) { assert.equal(err, null); assert.equal(arguments.length, 2); assert.equal(value, false); @@ -240,6 +240,27 @@ describe('Hash methods', function() { }); }); + + describe('isObjectFields()', function() { + it('should return an array of false if object does not exist', function(done) { + db.isObjectFields('doesnotexist', ['field1', 'field2'], function(err, values) { + assert.equal(err, null); + assert.equal(arguments.length, 2); + assert.deepEqual(values, [false, false]); + done(); + }); + }); + + it('should return false if field does not exist', function(done) { + db.isObjectFields('hashTestObject', ['name', 'age', 'field1'], function(err, values) { + assert.equal(err, null); + assert.equal(arguments.length, 2); + assert.deepEqual(values, [true, true, false]); + done(); + }); + }); + }); + describe('deleteObjectField()', function() { before(function(done) { db.setObject('testObject10', {foo: 'bar', delete: 'this'}, done); diff --git a/tests/groups.js b/tests/groups.js index 3d24c6f5ae..47da1b8bbb 100644 --- a/tests/groups.js +++ b/tests/groups.js @@ -212,20 +212,36 @@ describe('Groups', function() { }); }); }); + + it('should rename a group if the name was updated', function(done) { + Groups.update('foo', { + name: 'foobar?' + }, function(err) { + if (err) return done(err); + + Groups.get('foobar?', {}, function(err, groupObj) { + if (err) return done(err); + + assert.strictEqual('foobar?', groupObj.name); + assert.strictEqual('foobar', groupObj.slug); + + done(); + }); + }); + }); }); describe('.destroy()', function() { before(function(done) { - Groups.join('foo', 1, done); + Groups.join('foobar?', 1, done); }); it('should destroy a group', function(done) { - Groups.destroy('foo', function(err) { + Groups.destroy('foobar?', function(err) { if (err) return done(err); - Groups.get('foo', {}, function(err, groupObj) { - if (err) return done(err); - assert.strictEqual(undefined, groupObj); + Groups.get('foobar?', {}, function(err) { + assert(err, 'Group still exists!'); done(); });