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 += '[[notifications:see_all]]';
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();
});