Merge commit 'aca0a893e7ab52003e016eec382ea3f0d3610ba7' into weekly

This commit is contained in:
NodeBB Misty
2016-10-03 16:00:19 -04:00
33 changed files with 405 additions and 398 deletions

View File

@@ -5,13 +5,15 @@ before_install:
- "echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list"
- "sudo apt-get update"
- "sudo apt-get install mongodb-org-server"
- npm i --production
- node app --setup="{\"url\":\"http://127.0.0.1:4567/\",\"secret\":\"abcdef\",\"database\":\"mongo\",\"mongo:host\":\"127.0.0.1\",\"mongo:port\":27017,\"mongo:username\":\"\",\"mongo:password\":\"\",\"mongo:database\":0,\"redis:host\":\"127.0.0.1\",\"redis:port\":6379,\"redis:password\":\"\",\"redis:database\":0,\"admin:username\":\"admin\",\"admin:email\":\"test@example.org\",\"admin:password\":\"abcdef\",\"admin:password:confirm\":\"abcdef\"}" --ci="{\"host\":\"127.0.0.1\",\"port\":27017,\"database\":0}"
before_script:
- "npm i --production"
- sh -c "if [ '$DB' = 'mongodb' ]; then node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567/\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"mongo\\\",\\\"mongo:host\\\":\\\"127.0.0.1\\\",\\\"mongo:port\\\":27017,\\\"mongo:username\\\":\\\"\\\",\\\"mongo:password\\\":\\\"\\\",\\\"mongo:database\\\":0,\\\"redis:host\\\":\\\"127.0.0.1\\\",\\\"redis:port\\\":6379,\\\"redis:password\\\":\\\"\\\",\\\"redis:database\\\":0,\\\"admin:username\\\":\\\"admin\\\",\\\"admin:email\\\":\\\"test@example.org\\\",\\\"admin:password\\\":\\\"abcdef\\\",\\\"admin:password:confirm\\\":\\\"abcdef\\\"}\" --ci=\"{\\\"host\\\":\\\"127.0.0.1\\\",\\\"port\\\":27017,\\\"database\\\":0}\"; fi"
- sh -c "if [ '$DB' = 'redis' ]; then node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567/\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"redis\\\",\\\"mongo:host\\\":\\\"127.0.0.1\\\",\\\"mongo:port\\\":27017,\\\"mongo:username\\\":\\\"\\\",\\\"mongo:password\\\":\\\"\\\",\\\"mongo:database\\\":0,\\\"redis:host\\\":\\\"127.0.0.1\\\",\\\"redis:port\\\":6379,\\\"redis:password\\\":\\\"\\\",\\\"redis:database\\\":0,\\\"admin:username\\\":\\\"admin\\\",\\\"admin:email\\\":\\\"test@example.org\\\",\\\"admin:password\\\":\\\"abcdef\\\",\\\"admin:password:confirm\\\":\\\"abcdef\\\"}\" --ci=\"{\\\"host\\\":\\\"127.0.0.1\\\",\\\"port\\\":6379,\\\"database\\\":0}\"; fi"
before_script:
- "until nc -z localhost 27017; do echo Waiting for MongoDB; sleep 1; done"
language: node_js
env:
- CXX=g++-4.8
- CXX=g++-4.8 DB=mongodb
- CXX=g++-4.8 DB=redis
addons:
apt:
sources:

View File

@@ -2,7 +2,7 @@
"name": "nodebb",
"license": "GPL-3.0",
"description": "NodeBB Forum",
"version": "1.1.3-auto.6",
"version": "1.2.0",
"homepage": "http://www.nodebb.org",
"repository": {
"type": "git",
@@ -61,8 +61,8 @@
"nodebb-plugin-spam-be-gone": "0.4.10",
"nodebb-rewards-essentials": "0.0.9",
"nodebb-theme-lavender": "3.0.14",
"nodebb-theme-persona": "4.1.50",
"nodebb-theme-vanilla": "5.1.33",
"nodebb-theme-persona": "4.1.56",
"nodebb-theme-vanilla": "5.1.37",
"nodebb-widget-essentials": "2.0.11",
"nodemailer": "2.0.0",
"nodemailer-sendmail-transport": "1.0.0",
@@ -81,7 +81,7 @@
"sitemap": "^1.4.0",
"socket.io": "^1.4.8",
"socket.io-client": "^1.4.0",
"socket.io-redis": "^1.0.0",
"socket.io-redis": "1.1.1",
"socketio-wildcard": "~0.3.0",
"string": "^3.0.0",
"templates.js": "0.3.4",

View File

@@ -20,6 +20,7 @@
"tags": "Tags",
"tag": "Topics tagged under \"%1\"",
"register": "Register an account",
"registration-complete": "Registration complete",
"login": "Login to your account",
"reset": "Reset your account password",
"categories": "Categories",

View File

@@ -12,8 +12,8 @@
"ignore": "관심 해제",
"watching": "Watching",
"ignoring": "Ignoring",
"watching.description": "Show topics in unread",
"ignoring.description": "Do not show topics in unread",
"watching.description": "읽지 않은 주제를 표시합니다",
"ignoring.description": "읽지 않은 주제를 표시하지 않습니다",
"watch.message": "이 카테고리에 올라오는 글을 주시하고 있습니다.",
"ignore.message": "이 카테고리에 올라오는 글을 무시합니다.",
"watched-categories": "관심 카테고리"

View File

@@ -319,7 +319,7 @@ define('admin/manage/users', ['admin/modules/selectable', 'translator'], functio
}
templates.parse('admin/manage/users', 'users', data, function(html) {
$('#users-container').html(html);
$('#users-container').html(html).find('.timeago').timeago();
$('.fa-spinner').addClass('hidden');

View File

@@ -85,13 +85,19 @@ app.cacheBuster = null;
};
app.logout = function() {
$(window).trigger('action:app.logout');
$.ajax(config.relative_path + '/logout', {
type: 'POST',
headers: {
'x-csrf-token': config.csrf_token
},
success: function() {
window.location.href = config.relative_path + '/';
var payload = {
next: config.relative_path + '/'
};
$(window).trigger('action:app.loggedOut', payload);
window.location.href = payload.next;
}
});
};
@@ -260,7 +266,7 @@ app.cacheBuster = null;
}
};
app.openChat = function (roomId) {
app.openChat = function (roomId, uid) {
if (!app.user.uid) {
return app.alertError('[[error:not-logged-in]]');
}
@@ -275,13 +281,14 @@ app.cacheBuster = null;
if (chat.modalExists(roomId)) {
loadAndCenter(chat.getModal(roomId));
} else {
socket.emit('modules.chats.loadRoom', {roomId: roomId}, function(err, roomData) {
socket.emit('modules.chats.loadRoom', {roomId: roomId, uid: uid || app.user.uid}, function(err, roomData) {
if (err) {
return app.alertError(err.message);
}
roomData.users = roomData.users.filter(function(user) {
return user && parseInt(user.uid, 10) !== parseInt(app.user.uid, 10);
});
roomData.uid = uid || app.user.uid;
chat.createModal(roomData, loadAndCenter);
});
}

View File

@@ -140,7 +140,7 @@ define('forum/account/edit', ['forum/account/header', 'uploader', 'translator',
return app.alertError(err.message);
}
updateHeader(type === 'default' ? '' : src);
updateHeader(type === 'default' ? '' : modal.find('.list-group-item.active img').attr('src'));
ajaxify.refresh();
});
}

View File

@@ -62,12 +62,12 @@ define('forum/chats', [
var roomId = ajaxify.data.roomId;
if (app.previousUrl && app.previousUrl.match(/chats/)) {
ajaxify.go('chats', function() {
app.openChat(roomId);
ajaxify.go('user/' + ajaxify.data.userslug + '/chats', function() {
app.openChat(roomId, ajaxify.data.uid);
}, true);
} else {
window.history.go(-1);
app.openChat(roomId);
app.openChat(roomId, ajaxify.data.uid);
}
$(window).one('action:chat.loaded', function() {
@@ -79,7 +79,6 @@ define('forum/chats', [
recentChats.init();
Chats.addSinceHandler(ajaxify.data.roomId, $('.expanded-chat .chat-content'), $('.expanded-chat [data-since]'));
Chats.addRenameHandler(ajaxify.data.roomId, $('[component="chat/room/name"]'));
Chats.addScrollHandler(ajaxify.data.roomId, ajaxify.data.uid, $('.chat-content'));
};
@@ -96,12 +95,14 @@ define('forum/chats', [
return;
}
loading = true;
socket.emit('modules.chats.getMessages', {roomId: roomId, uid: uid, start: $('.chat-content').children('[data-index]').first().attr('data-index')}, function(err, data) {
var start = parseInt($('.chat-content').children('[data-index]').first().attr('data-index'), 10) + 1;
socket.emit('modules.chats.getMessages', {roomId: roomId, uid: uid, start: start}, function(err, data) {
if (err) {
return app.alertError(err.message);
}
if (!data) {
return;
}
messages.parseMessage(data, function(html) {
var currentScrollTop = el.scrollTop();
var previousHeight = el[0].scrollHeight;
@@ -156,16 +157,6 @@ define('forum/chats', [
});
};
Chats.addSinceHandler = function(roomId, chatContentEl, sinceEl) {
sinceEl.on('click', function() {
var since = $(this).attr('data-since');
sinceEl.removeClass('selected');
$(this).addClass('selected');
Chats.loadChatSince(roomId, chatContentEl, since);
return false;
});
};
Chats.addRenameHandler = function(roomId, inputEl) {
var oldName = inputEl.val();
inputEl.on('blur keypress', function(ev) {
@@ -290,7 +281,7 @@ define('forum/chats', [
return app.alertError(err.message);
}
if (parseInt(roomId, 10) === ajaxify.data.roomId) {
ajaxify.go('chats');
ajaxify.go('user/' + ajaxify.data.userslug + '/chats');
} else {
el.remove();
}
@@ -301,21 +292,6 @@ define('forum/chats', [
ajaxify.go('user/' + ajaxify.data.userslug + '/chats/' + roomid);
};
Chats.loadChatSince = function(roomId, chatContentEl, since) {
if (!roomId) {
return;
}
socket.emit('modules.chats.get', {roomId: roomId, since: since}, function(err, messageData) {
if (err) {
return app.alertError(err.message);
}
chatContentEl.find('[component="chat/message"]').remove();
messages.appendChatMessage(chatContentEl, messageData);
});
};
Chats.addGlobalEventListeners = function() {
$(window).on('resize', Chats.resizeMainWindow);
$(window).on('mousemove keypress click', function() {

View File

@@ -75,7 +75,7 @@ define('forum/chats/search', ['components'], function(components) {
return app.alertError(err.message);
}
if (roomId) {
ajaxify.go('chats/' + roomId);
Chats.switchChat(roomId);
} else {
app.newChat(userObj.uid);
}
@@ -84,4 +84,4 @@ define('forum/chats/search', ['components'], function(components) {
}
return search;
});
});

View File

@@ -26,8 +26,11 @@ define('chat', [
module.loadChatsDropdown(chatsListEl);
});
chatsListEl.on('click', '[data-roomid]', function() {
var roomId = this.getAttribute('data-roomid');
chatsListEl.on('click', '[data-roomid]', function(ev) {
if ($(ev.target).parents('.user-link').length) {
return;
}
var roomId = $(this).attr('data-roomid');
if (!ajaxify.currentPage.match(/^chats\//)) {
app.openChat(roomId);
} else {
@@ -80,6 +83,7 @@ define('chat', [
return user && parseInt(user.uid, 10) !== parseInt(app.user.uid, 10);
});
roomData.silent = true;
roomData.uid = app.user.uid;
module.createModal(roomData, function(modal) {
module.toggleNew(modal.attr('UUID'), !isSelf, true);
if (!isSelf) {
@@ -150,134 +154,117 @@ define('chat', [
return $('#chat-modal-' + roomId).length !== 0;
};
function checkStatus(chatModal) {
socket.emit('user.checkStatus', chatModal.attr('touid'), function(err, status) {
if (err) {
return app.alertError(err.message);
module.createModal = function(data, callback) {
app.parseAndTranslate('chat', data, function(chatModal) {
var uuid = utils.generateUUID();
var dragged = false;
chatModal.attr('id', 'chat-modal-' + data.roomId);
chatModal.attr('roomId', data.roomId);
chatModal.attr('intervalId', 0);
chatModal.attr('UUID', uuid);
chatModal.css('position', 'fixed');
chatModal.css('zIndex', 100);
chatModal.appendTo($('body'));
chatModal.find('.timeago').timeago();
module.center(chatModal);
app.loadJQueryUI(function() {
chatModal.find('.modal-content').resizable({
handles: 'n, e, s, w, se',
minHeight: 250,
minWidth: 400
});
chatModal.find('.modal-content').on('resize', function(event, ui) {
if (ui.originalSize.height === ui.size.height) {
return;
}
chatModal.find('.chat-content').css('height', module.calculateChatListHeight(chatModal));
});
chatModal.draggable({
start:function() {
module.bringModalToTop(chatModal);
},
stop:function() {
chatModal.find('#chat-message-input').focus();
},
distance: 10,
handle: '.modal-header'
});
});
chatModal.find('#chat-close-btn').on('click', function() {
module.close(chatModal);
});
function gotoChats() {
var text = components.get('chat/input').val();
$(window).one('action:ajaxify.end', function() {
components.get('chat/input').val(text);
});
ajaxify.go('user/' + app.user.userslug + '/chats/' + chatModal.attr('roomId'));
module.close(chatModal);
}
app.updateUserStatus(chatModal.find('[component="user/status"]'), status);
});
}
chatModal.find('.modal-header').on('dblclick', gotoChats);
chatModal.find('button[data-action="maximize"]').on('click', gotoChats);
module.createModal = function(data, callback) {
templates.parse('chat', data, function(chatTpl) {
translator.translate(chatTpl, function (chatTpl) {
chatModal.on('click', function() {
module.bringModalToTop(chatModal);
var chatModal = $(chatTpl),
uuid = utils.generateUUID(),
if (dragged) {
dragged = false;
chatModal.attr('id', 'chat-modal-' + data.roomId);
chatModal.attr('roomId', data.roomId);
chatModal.attr('intervalId', 0);
chatModal.attr('UUID', uuid);
chatModal.css('position', 'fixed');
chatModal.css('zIndex', 100);
chatModal.appendTo($('body'));
module.center(chatModal);
app.loadJQueryUI(function() {
chatModal.find('.modal-content').resizable({
handles: 'n, e, s, w, se',
minHeight: 250,
minWidth: 400
});
chatModal.find('.modal-content').on('resize', function(event, ui) {
if (ui.originalSize.height === ui.size.height) {
return;
}
chatModal.find('.chat-content').css('height', module.calculateChatListHeight(chatModal));
});
chatModal.draggable({
start:function() {
module.bringModalToTop(chatModal);
},
stop:function() {
chatModal.find('#chat-message-input').focus();
},
distance: 10,
handle: '.modal-header'
});
});
chatModal.find('#chat-close-btn').on('click', function() {
module.close(chatModal);
});
function gotoChats() {
var text = components.get('chat/input').val();
$(window).one('action:ajaxify.end', function() {
components.get('chat/input').val(text);
});
ajaxify.go('user/' + app.user.userslug + '/chats/' + chatModal.attr('roomId'));
module.close(chatModal);
}
chatModal.find('.modal-header').on('dblclick', gotoChats);
chatModal.find('button[data-action="maximize"]').on('click', gotoChats);
chatModal.on('click', function() {
module.bringModalToTop(chatModal);
if (dragged) {
dragged = false;
}
});
chatModal.on('mousemove', function(e) {
if (e.which === 1) {
dragged = true;
}
});
chatModal.on('mousemove keypress click', function() {
if (newMessage) {
socket.emit('modules.chats.markRead', data.roomId);
newMessage = false;
}
});
Chats.addEditDeleteHandler(chatModal.find('[component="chat/messages"]'), data.roomId);
chatModal.find('[component="chat/controlsToggle"]').on('click', function() {
var messagesEl = chatModal.find('[component="chat/messages"]');
chatModal.find('[component="chat/controls"]').toggle();
messagesEl.css('height', module.calculateChatListHeight(chatModal));
});
Chats.addSinceHandler(chatModal.attr('roomId'), chatModal.find('.chat-content'), chatModal.find('[data-since]'));
Chats.addRenameHandler(chatModal.attr('roomId'), chatModal.find('[component="chat/room/name"]'));
Chats.addSendHandlers(chatModal.attr('roomId'), chatModal.find('#chat-message-input'), chatModal.find('#chat-message-send-btn'));
Chats.createTagsInput(chatModal.find('.users-tag-input'), data);
Chats.createAutoComplete(chatModal.find('[component="chat/input"]'));
Chats.loadChatSince(chatModal.attr('roomId'), chatModal.find('.chat-content'), 'recent');
Chats.addScrollHandler(chatModal.attr('roomId'), app.user.uid, chatModal.find('.chat-content'));
checkStatus(chatModal);
taskbar.push('chat', chatModal.attr('UUID'), {
title: data.users.length ? data.users[0].username : '',
roomId: data.roomId,
icon: 'fa-comment',
state: ''
});
$(window).trigger('action:chat.loaded', chatModal);
if (typeof callback === 'function') {
callback(chatModal);
}
});
chatModal.on('mousemove', function(e) {
if (e.which === 1) {
dragged = true;
}
});
chatModal.on('mousemove keypress click', function() {
if (newMessage) {
socket.emit('modules.chats.markRead', data.roomId);
newMessage = false;
}
});
Chats.addEditDeleteHandler(chatModal.find('[component="chat/messages"]'), data.roomId);
chatModal.find('[component="chat/controlsToggle"]').on('click', function() {
var messagesEl = chatModal.find('[component="chat/messages"]');
chatModal.find('[component="chat/controls"]').toggle();
messagesEl.css('height', module.calculateChatListHeight(chatModal));
});
Chats.addRenameHandler(chatModal.attr('roomId'), chatModal.find('[component="chat/room/name"]'));
Chats.addSendHandlers(chatModal.attr('roomId'), chatModal.find('#chat-message-input'), chatModal.find('#chat-message-send-btn'));
Chats.createTagsInput(chatModal.find('.users-tag-input'), data);
Chats.createAutoComplete(chatModal.find('[component="chat/input"]'));
Chats.addScrollHandler(chatModal.attr('roomId'), data.uid, chatModal.find('.chat-content'));
taskbar.push('chat', chatModal.attr('UUID'), {
title: data.users.length ? data.users[0].username : '',
roomId: data.roomId,
icon: 'fa-comment',
state: ''
});
$(window).trigger('action:chat.loaded', chatModal);
if (typeof callback === 'function') {
callback(chatModal);
}
});
};
@@ -339,7 +326,7 @@ define('chat', [
});
};
module.disableMobileBehaviour = function(modalEl) {
module.disableMobileBehaviour = function() {
app.toggleNavbar(true);
};
@@ -347,7 +334,6 @@ define('chat', [
var totalHeight = modalEl.find('.modal-content').outerHeight() - modalEl.find('.modal-header').outerHeight();
var padding = parseInt(modalEl.find('.modal-body').css('padding-top'), 10) + parseInt(modalEl.find('.modal-body').css('padding-bottom'), 10);
var contentMargin = parseInt(modalEl.find('.chat-content').css('margin-top'), 10) + parseInt(modalEl.find('.chat-content').css('margin-bottom'), 10);
var sinceHeight = modalEl.find('.since-bar').outerHeight(true);
var inputGroupHeight = modalEl.find('.input-group').outerHeight();
return totalHeight - padding - contentMargin - inputGroupHeight;

View File

@@ -30,10 +30,13 @@ chatsController.get = function(req, res, callback) {
if (!uid) {
return callback();
}
messaging.getRecentChats(uid, 0, 19, next);
messaging.getRecentChats(req.uid, uid, 0, 19, next);
},
function(_recentChats, next) {
recentChats = _recentChats;
if (!recentChats) {
return callback();
}
if (!req.params.roomid) {
return res.render('chats', {
rooms: recentChats.rooms,
@@ -48,15 +51,15 @@ chatsController.get = function(req, res, callback) {
messaging.isUserInRoom(req.uid, req.params.roomid, next);
},
function(inRoom, next) {
if (!inRoom && parseInt(req.uid, 10) === parseInt(uid, 10)) {
if (!inRoom) {
return callback();
}
async.parallel({
users: async.apply(messaging.getUsersInRoom, req.params.roomid, 0, -1),
messages: async.apply(messaging.getMessages, {
callerUid: req.uid,
uid: uid,
roomId: req.params.roomid,
since: 'recent',
isNew: false
}),
room: async.apply(messaging.getRoomData, req.params.roomid)
@@ -74,6 +77,7 @@ chatsController.get = function(req, res, callback) {
return user && parseInt(user.uid, 10) && parseInt(user.uid, 10) !== req.uid;
});
room.groupChat = room.hasOwnProperty('groupChat') ? room.groupChat : room.users.length > 2;
room.rooms = recentChats.rooms;
room.uid = uid;
room.userslug = req.params.userslug;

View File

@@ -87,6 +87,10 @@ helpers.getUserDataByUserSlug = function(userslug, callerUID, callback) {
userData.ips = results.ips;
}
if (!isAdmin && !isGlobalModerator) {
userData.moderationNote = undefined;
}
userData.uid = userData.uid;
userData.yourid = callerUID;
userData.theirid = userData.uid;
@@ -120,6 +124,7 @@ helpers.getUserDataByUserSlug = function(userslug, callerUID, callback) {
userData.signature = validator.escape(String(userData.signature || ''));
userData.aboutme = validator.escape(String(userData.aboutme || ''));
userData.birthday = validator.escape(String(userData.birthday || ''));
userData.moderationNote = validator.escape(String(userData.moderationNote || ''));
userData['cover:url'] = userData['cover:url'] || require('../../coverPhoto').getDefaultProfileCover(userData.uid);
userData['cover:position'] = userData['cover:position'] || '50% 50%';

View File

@@ -22,8 +22,8 @@ infoController.get = function(req, res, callback) {
async.parallel({
history: async.apply(user.getModerationHistory, userData.uid),
sessions: async.apply(user.auth.getSessions, userData.uid, req.sessionID),
usernames: async.apply(user.getUsernameHistory, userData.uid),
emails: async.apply(user.getEmailHistory, userData.uid)
usernames: async.apply(user.getHistory, 'user:' + userData.uid + ':usernames'),
emails: async.apply(user.getHistory, 'user:' + userData.uid + ':emails')
}, next);
}
], function(err, data) {

View File

@@ -56,7 +56,7 @@ flagsController.get = function(req, res, next) {
assignees: results.assignees,
analytics: results.analytics,
categories: results.categories,
byUsername: validator(String(byUsername)),
byUsername: validator.escape(String(byUsername)),
sortByCount: sortBy === 'count',
sortByTime: sortBy === 'time',
pagination: pagination.create(page, pageCount, req.query),

View File

@@ -221,6 +221,7 @@ Controllers.registerInterstitial = function(req, res, next) {
}
res.render('registerComplete', {
title: '[[pages:registration-complete]]',
errors: errors,
sections: sections
});

View File

@@ -58,6 +58,9 @@ topicsController.get = function(req, res, callback) {
if (req.params.post_index){
url += '/'+req.params.post_index;
}
if (currentPage > 1) {
url += '?page=' + currentPage;
}
return helpers.redirect(res, url);
}

View File

@@ -18,6 +18,9 @@ module.exports = function(db, module) {
value = helpers.valueToString(value);
db.collection('objects').update({_key: key, value: value}, {$set: {score: parseInt(score, 10)}}, {upsert:true, w: 1}, function(err) {
if (err && err.message.startsWith('E11000 duplicate key error')) {
return module.sortedSetAdd(key, score, value, callback);
}
callback(err);
});
};

View File

@@ -65,15 +65,18 @@
};
module.connect = function(options) {
var redis_socket_or_host = nconf.get('redis:host'),
cxn, dbIdx;
options = options || {};
var redis_socket_or_host = nconf.get('redis:host');
var cxn;
if (!redis) {
redis = require('redis');
}
options = options || {};
if (nconf.get('redis:password')) {
options.auth_pass = nconf.get('redis:password');
}
if (redis_socket_or_host && redis_socket_or_host.indexOf('/') >= 0) {
/* If redis.host contains a path name character, use the unix dom sock connection. ie, /tmp/redis.sock */
cxn = redis.createClient(nconf.get('redis:host'), options);
@@ -91,7 +94,7 @@
cxn.auth(nconf.get('redis:password'));
}
dbIdx = parseInt(nconf.get('redis:database'), 10);
var dbIdx = parseInt(nconf.get('redis:database'), 10);
if (dbIdx) {
cxn.select(dbIdx, function(error) {
if(error) {

View File

@@ -5,6 +5,12 @@ module.exports = function(redisClient, module) {
module.setAdd = function(key, value, callback) {
callback = callback || function() {};
if (!Array.isArray(value)) {
value = [value];
}
if (!value.length) {
return callback();
}
redisClient.sadd(key, value, function(err, res) {
callback(err);
});

View File

@@ -1,13 +1,13 @@
'use strict';
var async = require('async'),
db = require('./database'),
batch = require('./batch'),
user = require('./user'),
utils = require('../public/src/utils');
var async = require('async');
var validator = require('validator');
var db = require('./database');
var batch = require('./batch');
var user = require('./user');
var utils = require('../public/src/utils');
(function(events) {
events.log = function(data, callback) {
@@ -54,6 +54,11 @@ var async = require('async'),
},
function(eventsData, next) {
eventsData.forEach(function(event) {
Object.keys(event).forEach(function(key) {
if (typeof event[key] === 'string') {
event[key] = validator.escape(String(event[key] || ''));
}
});
var e = utils.merge(event);
e.eid = e.uid = e.type = e.ip = e.user = undefined;
event.jsonString = JSON.stringify(e, null, 4);
@@ -123,7 +128,7 @@ var async = require('async'),
callback = callback || function() {};
batch.processSortedSet('events:time', function(eids, next) {
events.deleteEvents(eids, callback);
events.deleteEvents(eids, next);
}, {alwaysStartAt: 0}, callback);
};

View File

@@ -189,9 +189,9 @@ module.exports = function(Groups) {
if (!checks.exists) {
return next(new Error('[[error:no-group]]'));
} else if (checks.isMember) {
return next(new Error('[[error:group-already-member]]'));
return callback();
} else if (type === 'invite' && checks.isInvited) {
return next(new Error('[[error:group-already-invited]]'));
return callback();
} else if (type === 'request' && checks.isPending) {
return next(new Error('[[error:group-already-requested]]'));
}

View File

@@ -1,18 +1,17 @@
'use strict';
var async = require('async'),
winston = require('winston'),
S = require('string'),
var async = require('async');
var winston = require('winston');
var S = require('string');
db = require('./database'),
user = require('./user'),
plugins = require('./plugins'),
meta = require('./meta'),
utils = require('../public/src/utils'),
notifications = require('./notifications'),
userNotifications = require('./user/notifications');
var db = require('./database');
var user = require('./user');
var plugins = require('./plugins');
var meta = require('./meta');
var utils = require('../public/src/utils');
var notifications = require('./notifications');
var userNotifications = require('./user/notifications');
(function(Messaging) {
@@ -23,13 +22,6 @@ var async = require('async'),
require('./messaging/unread')(Messaging);
require('./messaging/notifications')(Messaging);
var terms = {
day: 86400000,
week: 604800000,
month: 2592000000,
threemonths: 7776000000
};
Messaging.getMessageField = function(mid, field, callback) {
Messaging.getMessageFields(mid, [field], function(err, fields) {
callback(err, fields ? fields[field] : null);
@@ -51,46 +43,42 @@ var async = require('async'),
Messaging.getMessages = function(params, callback) {
var uid = params.uid;
var roomId = params.roomId;
var since = params.since;
var isNew = params.isNew;
var isNew = params.isNew || false;
var start = params.hasOwnProperty('start') ? params.start : 0;
var count = params.count || 250;
var stop = parseInt(start, 10) + ((params.count || 50) - 1);
var markRead = params.markRead || true;
var min = params.count ? 0 : Date.now() - (terms[since] || terms.day);
if (since === 'recent') {
count = 50;
min = 0;
}
db.getSortedSetRevRangeByScore('uid:' + uid + ':chat:room:' + roomId + ':mids', start, count, '+inf', min, function(err, mids) {
if (err) {
return callback(err);
}
if (!Array.isArray(mids) || !mids.length) {
return callback(null, []);
}
var indices = {};
mids.forEach(function(mid, index) {
indices[mid] = start + index;
});
mids.reverse();
Messaging.getMessagesData(mids, uid, roomId, isNew, function(err, messageData) {
if (err) {
return callback(err);
var indices = {};
async.waterfall([
function(next) {
canGetMessages(params.callerUid, params.uid, next);
},
function(canGet, next) {
if (!canGet) {
return callback(null, null);
}
db.getSortedSetRevRange('uid:' + uid + ':chat:room:' + roomId + ':mids', start, stop, next);
},
function(mids, next) {
if (!Array.isArray(mids) || !mids.length) {
return callback(null, []);
}
for(var i=0; i<messageData.length; i++) {
messageData[i].index = indices[messageData[i].messageId.toString()];
}
mids.forEach(function(mid, index) {
indices[mid] = start + index;
});
callback(null, messageData);
});
});
mids.reverse();
Messaging.getMessagesData(mids, uid, roomId, isNew, next);
},
function(messageData, next) {
messageData.forEach(function(messageData) {
messageData.index = indices[messageData.messageId.toString()];
});
next(null, messageData);
}
], callback);
if (markRead) {
notifications.markRead('chat_' + roomId + '_' + uid, uid, function(err) {
@@ -103,6 +91,16 @@ var async = require('async'),
}
};
function canGetMessages(callerUid, uid, callback) {
plugins.fireHook('filter:messaging.canGetMessages', {
callerUid: callerUid,
uid: uid,
canGet: parseInt(callerUid, 10) === parseInt(uid, 10)
}, function(err, data) {
callback(err, data ? data.canGet : false);
});
}
Messaging.getMessagesData = function(mids, uid, roomId, isNew, callback) {
var keys = mids.map(function(mid) {
@@ -250,44 +248,49 @@ var async = require('async'),
};
Messaging.getRecentChats = function(uid, start, stop, callback) {
db.getSortedSetRevRange('uid:' + uid + ':chat:rooms', start, stop, function(err, roomIds) {
if (err) {
return callback(err);
}
async.parallel({
roomData: function(next) {
Messaging.getRoomsData(roomIds, next);
},
unread: function(next) {
db.isSortedSetMembers('uid:' + uid + ':chat:rooms:unread', roomIds, next);
},
users: function(next) {
async.map(roomIds, function(roomId, next) {
db.getSortedSetRevRange('chat:room:' + roomId + ':uids', 0, 3, function(err, uids) {
if (err) {
return next(err);
}
uids = uids.filter(function(value) {
return value && parseInt(value, 10) !== parseInt(uid, 10);
Messaging.getRecentChats = function(callerUid, uid, start, stop, callback) {
async.waterfall([
function(next) {
canGetRecentChats(callerUid, uid, next);
},
function(canGet, next) {
if (!canGet) {
return callback(null, null);
}
db.getSortedSetRevRange('uid:' + uid + ':chat:rooms', start, stop, next);
},
function(roomIds, next) {
async.parallel({
roomData: function(next) {
Messaging.getRoomsData(roomIds, next);
},
unread: function(next) {
db.isSortedSetMembers('uid:' + uid + ':chat:rooms:unread', roomIds, next);
},
users: function(next) {
async.map(roomIds, function(roomId, next) {
db.getSortedSetRevRange('chat:room:' + roomId + ':uids', 0, 9, function(err, uids) {
if (err) {
return next(err);
}
uids = uids.filter(function(value) {
return value && parseInt(value, 10) !== parseInt(uid, 10);
});
user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status', 'lastonline'] , next);
});
user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status', 'lastonline'] , next);
});
}, next);
},
teasers: function(next) {
async.map(roomIds, function(roomId, next) {
Messaging.getTeaser(uid, roomId, next);
}, next);
}
}, function(err, results) {
if (err) {
return callback(err);
}
}, next);
},
teasers: function(next) {
async.map(roomIds, function(roomId, next) {
Messaging.getTeaser(uid, roomId, next);
}, next);
}
}, next);
},
function(results, next) {
results.roomData.forEach(function(room, index) {
room.users = results.users[index];
room.groupChat = room.hasOwnProperty('groupChat') ? room.groupChat : room.users.length > 2;
room.unread = results.unread[index];
room.teaser = results.teasers[index];
@@ -306,12 +309,23 @@ var async = require('async'),
}).join(', ');
});
callback(null, {rooms: results.roomData, nextStart: stop + 1});
});
});
next(null, {rooms: results.roomData, nextStart: stop + 1});
}
], callback);
};
function canGetRecentChats(callerUid, uid, callback) {
plugins.fireHook('filter:messaging.canGetRecentChats', {
callerUid: callerUid,
uid: uid,
canGet: parseInt(callerUid, 10) === parseInt(uid, 10)
}, function(err, data) {
callback(err, data ? data.canGet : false);
});
}
Messaging.getTeaser = function (uid, roomId, callback) {
var teaser;
async.waterfall([
function (next) {
db.getSortedSetRevRange('uid:' + uid + ':chat:room:' + roomId + ':mids', 0, 0, next);
@@ -320,14 +334,22 @@ var async = require('async'),
if (!mids || !mids.length) {
return next(null, null);
}
Messaging.getMessageFields(mids[0], ['content', 'timestamp'], next);
Messaging.getMessageFields(mids[0], ['fromuid', 'content', 'timestamp'], next);
},
function (teaser, next) {
if (teaser && teaser.content) {
function (_teaser, next) {
teaser = _teaser;
if (!teaser) {
return callback();
}
if (teaser.content) {
teaser.content = S(teaser.content).stripTags().decodeHTMLEntities().s;
teaser.timestampISO = utils.toISOString(teaser.timestamp);
}
}
teaser.timestampISO = utils.toISOString(teaser.timestamp);
user.getUserFields(teaser.fromuid, ['uid', 'username', 'userslug', 'picture', 'status', 'lastonline'] , next);
},
function(user, next) {
teaser.user = user;
next(null, teaser);
}
], callback);

View File

@@ -5,6 +5,7 @@ var validator = require('validator');
var db = require('../database');
var user = require('../user');
var plugins = require('../plugins');
module.exports = function(Messaging) {
@@ -13,10 +14,7 @@ module.exports = function(Messaging) {
if (err || !data) {
return callback(err || new Error('[[error:no-chat-room]]'));
}
data.roomName = data.roomName || '[[modules:chat.roomname, ' + roomId + ']]';
if (data.roomName) {
data.roomName = validator.escape(String(data.roomName));
}
modifyRoomData([data]);
callback(null, data);
});
};
@@ -29,16 +27,23 @@ module.exports = function(Messaging) {
if (err) {
return callback(err);
}
roomData.forEach(function(data) {
if (data) {
data.roomName = data.roomName || '[[modules:chat.roomname, ' + data.roomId + ']]';
data.roomName = validator.escape(String(data.roomName));
}
});
modifyRoomData(roomData);
callback(null, roomData);
});
};
function modifyRoomData(rooms) {
rooms.forEach(function(data) {
if (data) {
data.roomName = data.roomName || '[[modules:chat.roomname, ' + data.roomId + ']]';
data.roomName = validator.escape(String(data.roomName));
if (data.hasOwnProperty('groupChat')) {
data.groupChat = parseInt(data.groupChat, 10) === 1;
}
}
});
}
Messaging.newRoom = function(uid, toUids, callback) {
var roomId;
var now = Date.now();
@@ -70,7 +75,17 @@ module.exports = function(Messaging) {
};
Messaging.isUserInRoom = function(uid, roomId, callback) {
db.isSortedSetMember('chat:room:' + roomId + ':uids', uid, callback);
async.waterfall([
function(next) {
db.isSortedSetMember('chat:room:' + roomId + ':uids', uid, next);
},
function(inRoom, next) {
plugins.fireHook('filter:messaging.isUserInRoom', {uid: uid, roomId: roomId, inRoom: inRoom}, next);
},
function(data, next) {
next(null, data.inRoom);
}
], callback);
};
Messaging.roomExists = function(roomId, callback) {
@@ -105,6 +120,18 @@ module.exports = function(Messaging) {
return now;
});
db.sortedSetAdd('chat:room:' + roomId + ':uids', timestamps, uids, next);
},
function(next) {
async.parallel({
userCount: async.apply(db.sortedSetCard, 'chat:room:' + roomId + ':uids'),
roomData: async.apply(db.getObject, 'chat:room:' + roomId)
}, next);
},
function(results, next) {
if (!results.roomData.hasOwnProperty('groupChat') && results.userCount > 2) {
return db.setObjectField('chat:room:' + roomId, 'groupChat', 1, next);
}
next();
}
], callback);
};

View File

@@ -120,7 +120,7 @@ module.exports = function(middleware) {
results.user.isAdmin = results.isAdmin;
results.user.isGlobalMod = results.isGlobalMod;
results.user.uid = parseInt(results.user.uid, 10);
results.user.email = String(results.user.email).replace(/\\/g, '\\\\');
results.user.email = String(results.user.email).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1;
results.user.isEmailConfirmSent = !!results.isEmailConfirmSent;

View File

@@ -31,6 +31,6 @@ module.exports = function (app, middleware, controllers) {
app.delete('/api/user/:userslug/session/:uuid', [middleware.requireUser], controllers.accounts.session.revoke);
setupPageRoute(app, '/notifications', middleware, [middleware.authenticate], controllers.accounts.notifications.get);
setupPageRoute(app, '/user/:userslug/chats/:roomid?', middleware, accountMiddlewares, controllers.accounts.chats.get);
setupPageRoute(app, '/user/:userslug/chats/:roomid?', middleware, middlewares, controllers.accounts.chats.get);
setupPageRoute(app, '/chats/:roomid?', middleware, [], controllers.accounts.chats.redirectToChat);
};

View File

@@ -142,7 +142,7 @@ module.exports = function(app, middleware, hotswapIds) {
}
app.use(middleware.privateUploads);
app.use('/language/:code', middleware.processLanguages);
app.use(relativePath + '/language/:code', middleware.processLanguages);
app.use(relativePath, express.static(path.join(__dirname, '../../', 'public'), {
maxAge: app.enabled('cache') ? 5184000000 : 0
}));

View File

@@ -1,7 +1,8 @@
"use strict";
var async = require('async');
var validator = require('validator');
var db = require('../../database');
var groups = require('../../groups');
var user = require('../../user');
@@ -204,7 +205,7 @@ User.search = function(socket, data, callback) {
userData.forEach(function(user, index) {
if (user && userInfo[index]) {
user.email = userInfo[index].email || '';
user.email = validator.escape(String(userInfo[index].email || ''));
user.flags = userInfo[index].flags || 0;
}
});

View File

@@ -152,7 +152,7 @@ SocketGroups.issueMassInvite = isOwner(function(socket, data, callback) {
});
async.eachSeries(uids, function(uid, next) {
groups.invite(data.groupName, uid, callback);
groups.invite(data.groupName, uid, next);
}, callback);
});
});

View File

@@ -177,9 +177,8 @@ var ratelimit = require('../middleware/ratelimit');
if (nconf.get('redis')) {
var redisAdapter = require('socket.io-redis');
var redis = require('../database/redis');
var pub = redis.connect({return_buffers: true});
var pub = redis.connect();
var sub = redis.connect({return_buffers: true});
io.adapter(redisAdapter({pubClient: pub, subClient: sub}));
} else if (nconf.get('isCluster') === 'true') {
winston.warn('[socket.io] Clustering detected, you are advised to configure Redis as a websocket store.');

View File

@@ -12,26 +12,13 @@ var server = require('./');
var user = require('../user');
var SocketModules = {
chats: {},
sounds: {},
settings: {}
};
chats: {},
sounds: {},
settings: {}
};
/* Chat */
SocketModules.chats.get = function(socket, data, callback) {
if(!data || !data.roomId) {
return callback(new Error('[[error:invalid-data]]'));
}
Messaging.getMessages({
uid: socket.uid,
roomId: data.roomId,
since: data.since,
isNew: false
}, callback);
};
SocketModules.chats.getRaw = function(socket, data, callback) {
if (!data || !data.hasOwnProperty('mid')) {
return callback(new Error('[[error:invalid-data]]'));
@@ -68,7 +55,7 @@ SocketModules.chats.newRoom = function(socket, data, callback) {
};
SocketModules.chats.send = function(socket, data, callback) {
if (!data || !data.roomId) {
if (!data || !data.roomId || !socket.uid) {
return callback(new Error('[[error:invalid-data]]'));
}
@@ -128,11 +115,19 @@ SocketModules.chats.loadRoom = function(socket, data, callback) {
async.parallel({
roomData: async.apply(Messaging.getRoomData, data.roomId),
users: async.apply(Messaging.getUsersInRoom, data.roomId, 0, -1)
users: async.apply(Messaging.getUsersInRoom, data.roomId, 0, -1),
messages: async.apply(Messaging.getMessages, {
callerUid: socket.uid,
uid: data.uid || socket.uid,
roomId: data.roomId,
isNew: false
}),
}, next);
},
function (results, next) {
results.roomData.users = results.users;
results.roomData.messages = results.messages;
results.roomData.groupChat = results.roomData.hasOwnProperty('groupChat') ? results.roomData.groupChat : results.users.length > 2;
results.roomData.isOwner = parseInt(results.roomData.owner, 10) === socket.uid;
results.roomData.maximumUsersInChatRoom = parseInt(meta.config.maximumUsersInChatRoom, 10) || 0;
results.roomData.showUserInput = !results.roomData.maximumUsersInChatRoom || results.roomData.maximumUsersInChatRoom > 2;
@@ -231,6 +226,9 @@ SocketModules.chats.canMessage = function(socket, roomId, callback) {
};
SocketModules.chats.markRead = function(socket, roomId, callback) {
if (!socket.uid) {
return callback(new Error('[[error:invalid-data]]'));
}
async.parallel({
usersInRoom: async.apply(Messaging.getUidsInRoom, roomId, 0, -1),
markRead: async.apply(Messaging.markRead, socket.uid, roomId)
@@ -292,21 +290,12 @@ SocketModules.chats.renameRoom = function(socket, data, callback) {
};
SocketModules.chats.getRecentChats = function(socket, data, callback) {
if (!data || !utils.isNumber(data.after)) {
if (!data || !utils.isNumber(data.after) || !utils.isNumber(data.uid)) {
return callback(new Error('[[error:invalid-data]]'));
}
var start = parseInt(data.after, 10);
var stop = start + 9;
if (socket.uid === parseInt(data.uid, 10)) {
return Messaging.getRecentChats(socket.uid, start, stop, callback);
}
user.isAdminOrGlobalMod(socket.uid, function(err, isAdminOrGlobalMod) {
if (err || !isAdminOrGlobalMod) {
return callback(err || new Error('[[error:no-privileges]]'));
}
Messaging.getRecentChats(data.uid, start, stop, callback);
});
Messaging.getRecentChats(socket.uid, data.uid, start, stop, callback);
};
SocketModules.chats.hasPrivateChat = function(socket, uid, callback) {
@@ -320,22 +309,21 @@ SocketModules.chats.getMessages = function(socket, data, callback) {
if (!socket.uid || !data.uid || !data.roomId) {
return callback(new Error('[[error:invalid-data]]'));
}
var params = {
callerUid: socket.uid,
uid: data.uid,
roomId: data.roomId,
start: parseInt(data.start, 10) + 1,
start: parseInt(data.start, 10) || 0,
count: 50,
markRead: false
};
if (socket.uid === parseInt(data.uid, 10)) {
return Messaging.getMessages(params, callback);
if (data.hasOwnProperty('markRead')) {
params.markRead = data.markRead;
}
user.isAdminOrGlobalMod(socket.uid, function(err, isAdminOrGlobalMod) {
if (err || !isAdminOrGlobalMod) {
return callback(err || new Error('[[error:no-privileges]]'));
}
Messaging.getMessages(params, callback);
});
Messaging.getMessages(params, callback);
};
/* Sounds */

View File

@@ -345,7 +345,7 @@ var social = require('./social');
}
};
Topics.getTopicBookmarks = function( tid, callback ){
Topics.getTopicBookmarks = function(tid, callback) {
db.getSortedSetRangeWithScores(['tid:' + tid + ':bookmarks'], 0, -1, callback);
};
@@ -372,49 +372,26 @@ var social = require('./social');
};
});
async.map(uidData, function(data, mapCallback) {
posts.getPostIndices(forkedPosts, data.uid, function(err, indices) {
async.eachLimit(uidData, 50, function(data, next) {
posts.getPostIndices(forkedPosts, data.uid, function(err, postIndices) {
if (err) {
return callback(err);
return next(err);
}
data.postIndices = indices;
mapCallback(null, data);
});
}, function(err, results) {
if (err) {
return callback(err);
}
async.map(results, function(data, mapCallback) {
var uid = data.uid;
var bookmark = data.bookmark;
bookmark = bookmark < maxIndex ? bookmark : maxIndex;
var postIndices = data.postIndices;
for (var i = 0; i < postIndices.length && postIndices[i] < data.bookmark; ++i ){
for (var i = 0; i < postIndices.length && postIndices[i] < data.bookmark; ++i) {
--bookmark;
}
if (parseInt(bookmark, 10) !== parseInt(data.bookmark, 10)) {
mapCallback( null, { uid: uid, bookmark: bookmark } );
Topics.setUserBookmark(tid, data.uid, bookmark, next);
} else {
mapCallback( null, null );
next();
}
}, function(err, results) {
if (err) {
return callback(err);
}
async.map(results, function(ui, cb) {
if( ui && ui.bookmark) {
Topics.setUserBookmark(tid, ui.uid, ui.bookmark, cb);
} else {
return cb(null, null);
}
}, function(err) {
next(err);
});
});
});
}, next);
}
], function(err){
callback(err);

View File

@@ -61,24 +61,15 @@ module.exports = function(User) {
});
};
User.getEmailHistory = function(uid, callback) {
db.getSortedSetRevRangeWithScores('user:' + uid + ':emails', 0, -1, function(err, data) {
callback(err, data.map(function(set) {
User.getHistory = function(set, callback) {
db.getSortedSetRevRangeWithScores(set, 0, -1, function(err, data) {
if (err) {
return callback(err);
}
callback(null, data.map(function(set) {
set.timestamp = set.score;
set.timestampISO = new Date(set.score).toISOString();
set.value = set.value.split(':')[0];
delete set.score;
return set;
}));
});
};
User.getUsernameHistory = function(uid, callback) {
db.getSortedSetRevRangeWithScores('user:' + uid + ':usernames', 0, -1, function(err, data) {
callback(err, data.map(function(set) {
set.timestamp = set.score;
set.timestampISO = new Date(set.score).toISOString();
set.value = set.value.split(':')[0];
set.value = validator.escape(String(set.value.split(':')[0]));
delete set.score;
return set;
}));

View File

@@ -79,7 +79,7 @@
<a href="{config.relative_path}/user/{users.userslug}" target="_blank">{users.username} ({users.uid})</a><br/>
<!-- IF users.email -->
<small><span title="{users.email}">{users.email}</span></small>
<small><span title="{users.email}">{users.email}</span></small><br/>
<!-- ENDIF users.email -->
joined <span class="timeago" title="{users.joindateISO}"></span><br/>