Merge pull request #626 from designcreateplay/dbal

Dbal
v1.18.x
Barış Soner Uşaklı 12 years ago
commit 9f2196abfb

109
app.js

@ -60,7 +60,7 @@
nconf.file({
file: __dirname + '/config.json'
});
meta = require('./src/meta.js');
meta = require('./src/meta');
nconf.set('url', nconf.get('base_url') + (nconf.get('use_port') ? ':' + nconf.get('port') : '') + nconf.get('relative_path') + '/');
nconf.set('upload_url', nconf.get('url') + 'uploads/');
@ -73,58 +73,53 @@
winston.info('Base Configuration OK.');
}
meta.configs.init(function () {
// Initial setup for Redis & Reds
var reds = require('reds'),
RDB = require('./src/redis.js');
reds.createClient = function () {
return reds.client || (reds.client = RDB);
};
var templates = require('./public/src/templates.js'),
translator = require('./public/src/translator.js'),
webserver = require('./src/webserver.js'),
SocketIO = require('socket.io').listen(global.server, { log: false, transports: ['websocket', 'xhr-polling', 'jsonp-polling', 'flashsocket'], 'browser client minification': true}),
websockets = require('./src/websockets.js'),
posts = require('./src/posts.js'),
plugins = require('./src/plugins'), // Don't remove this - plugins initializes itself
Notifications = require('./src/notifications'),
Upgrade = require('./src/upgrade');
Upgrade.check(function(schema_ok) {
if (schema_ok || nconf.get('check-schema') === false) {
websockets.init(SocketIO);
global.templates = {};
global.translator = translator;
translator.loadServer();
var customTemplates = meta.config['theme:templates'] ? path.join(__dirname, 'node_modules', meta.config['theme:id'], meta.config['theme:templates']) : false;
// todo: replace below with read directory code, derp.
templates.init([
'header', 'footer', 'logout', 'outgoing', 'admin/header', 'admin/footer', 'admin/index',
'emails/reset', 'emails/reset_plaintext', 'emails/email_confirm', 'emails/email_confirm_plaintext',
'emails/header', 'emails/footer',
'noscript/header', 'noscript/home', 'noscript/category', 'noscript/topic'
], customTemplates);
plugins.ready(function() {
templates.ready(webserver.init);
});
Notifications.init();
} else {
winston.warn('Your NodeBB schema is out-of-date. Please run the following command to bring your dataset up to spec:');
winston.warn(' node app --upgrade');
winston.warn('To ignore this error (not recommended):');
winston.warn(' node app --no-check-schema')
process.exit();
}
require('./src/database').init(function(err) {
meta.configs.init(function () {
var templates = require('./public/src/templates'),
translator = require('./public/src/translator'),
webserver = require('./src/webserver'),
SocketIO = require('socket.io').listen(global.server, { log: false, transports: ['websocket', 'xhr-polling', 'jsonp-polling', 'flashsocket'], 'browser client minification': true}),
websockets = require('./src/websockets'),
plugins = require('./src/plugins'),
notifications = require('./src/notifications'),
upgrade = require('./src/upgrade');
upgrade.check(function(schema_ok) {
if (schema_ok || nconf.get('check-schema') === false) {
websockets.init(SocketIO);
plugins.init();
global.templates = {};
global.translator = translator;
translator.loadServer();
var customTemplates = meta.config['theme:templates'] ? path.join(__dirname, 'node_modules', meta.config['theme:id'], meta.config['theme:templates']) : false;
// todo: replace below with read directory code, derp.
templates.init([
'header', 'footer', 'logout', 'outgoing', 'admin/header', 'admin/footer', 'admin/index',
'emails/reset', 'emails/reset_plaintext', 'emails/email_confirm', 'emails/email_confirm_plaintext',
'emails/header', 'emails/footer',
'noscript/header', 'noscript/home', 'noscript/category', 'noscript/topic'
], customTemplates);
plugins.ready(function() {
templates.ready(webserver.init);
});
notifications.init();
} else {
winston.warn('Your NodeBB schema is out-of-date. Please run the following command to bring your dataset up to spec:');
winston.warn(' node app --upgrade');
winston.warn('To ignore this error (not recommended):');
winston.warn(' node app --no-check-schema')
process.exit();
}
});
});
});
} else if (nconf.get('setup') || nconf.get('install') || !fs.existsSync(__dirname + '/config.json')) {
@ -159,10 +154,12 @@
nconf.file({
file: __dirname + '/config.json'
});
meta = require('./src/meta.js');
require('./src/database').init(function(err) {
meta = require('./src/meta.js');
meta.configs.init(function () {
require('./src/upgrade').upgrade();
meta.configs.init(function () {
require('./src/upgrade').upgrade();
});
});
} else/* if (nconf.get('help') */{
winston.info('Usage: node app [options] [arguments]');

@ -0,0 +1,81 @@
/**
* Database Mock - wrapper for database.js, makes system use separate test db, instead of production
* ATTENTION: testing db is flushed before every use!
*/
(function(module) {
'use strict';
var utils = require('./../public/src/utils.js'),
path = require('path'),
nconf = require('nconf'),
winston = require('winston'),
errorText;
nconf.file({ file: path.join(__dirname, '../config.json') });
var dbType = nconf.get('database'),
testDbConfig = nconf.get('test_database'),
productionDbConfig = nconf.get(dbType);
if(!testDbConfig){
errorText = 'test_database is not defined';
winston.info(
"\n===========================================================\n"+
"Please, add parameters for test database in config.json\n"+
"For example (redis):\n"+
'"test_database": {' + '\n' +
' "host": "127.0.0.1",' + '\n' +
' "port": "6379",' + '\n' +
' "password": "",' + '\n' +
' "database": "1"' + '\n' +
'}\n'+
" or (mongo):\n" +
'"test_database": {' + '\n' +
' "host": "127.0.0.1",' + '\n' +
' "port": "27017",' + '\n' +
' "password": "",' + '\n' +
' "database": "1"' + '\n' +
'}\n'+
"==========================================================="
);
winston.error(errorText);
throw new Error(errorText);
}
if( testDbConfig.database === productionDbConfig.database &&
testDbConfig.host === productionDbConfig.host &&
testDbConfig.port === productionDbConfig.port
){
errorText = 'test_database has the same config as production db';
winston.error(errorText);
throw new Error(errorText);
}
nconf.set(dbType, testDbConfig);
db = require('../src/database');
before(function(done) {
db.init(function(err) {
//Clean up
db.flushdb(function(err) {
if(err){
winston.error(err);
throw new Error(err);
} else {
winston.info('test_database flushed');
done();
}
//TODO: data seeding, if needed at all
});
});
});
module.exports = db;
}(module));

@ -1,69 +0,0 @@
/**
* Redis Mock - wrapper for redis.js, makes system use separate test db, instead of production
* ATTENTION: testing db is flushed before every use!
*/
(function(module) {
'use strict';
var RedisDB,
redis = require('redis'),
utils = require('./../public/src/utils.js'),
path = require('path'),
nconf = require('nconf'),
winston = require('winston'),
errorText;
nconf.file({ file: path.join(__dirname, '../config.json') });
var testDbConfig = nconf.get('redis_test'),
productionDbConfig = nconf.get('redis');
if(!testDbConfig){
errorText = 'redis_test database is not defined';
winston.info(
"\n===========================================================\n"+
"Please, add parameters for test database in config.json\n"+
"For example:\n"+
'"redis_test": {' + '\n' +
' "host": "127.0.0.1",' + '\n' +
' "port": "6379",' + '\n' +
' "password": "",' + '\n' +
' "database": "1"' + '\n' +
'}\n'+
"==========================================================="
);
winston.error(errorText);
throw new Error(errorText);
}
if( testDbConfig.database === productionDbConfig.database &&
testDbConfig.host === productionDbConfig.host &&
testDbConfig.port === productionDbConfig.port
){
errorText = 'redis_test database has the same config as production db';
winston.error(errorText);
throw new Error(errorText);
}
nconf.set('redis',testDbConfig);
RedisDB = require('../src/redis.js');
//Clean up
RedisDB.send_command('flushdb', [], function(error){
if(error){
winston.error(error);
throw new Error(error);
} else {
winston.info('redis_test db flushed');
}
});
//TODO: data seeding, if needed at all
module.exports = RedisDB;
}(module));

@ -15,11 +15,13 @@
"dependencies": {
"socket.io": "~0.9.16",
"redis": "0.8.3",
"mongodb": "1.3.20",
"express": "3.2.0",
"express-namespace": "~0.1.1",
"emailjs": "0.3.4",
"cookie": "0.0.6",
"connect-redis": "1.4.5",
"connect-mongo": "0.4.0",
"passport": "0.1.17",
"passport-local": "0.1.6",
"passport-twitter": "0.1.5",

@ -32,7 +32,7 @@ define(['uploader'], function(uploader) {
break;
case 'checkbox':
fields[x].checked = app.config[key] === '1' ? true : false;
fields[x].checked = parseInt(app.config[key], 10) === 1;
break;
}
}

@ -140,10 +140,11 @@ define(function () {
jQuery('#topics-container, .category-sidebar').removeClass('hidden');
jQuery('#category-no-topics').remove();
html = $(html);
container.append(html);
$('#topics-container span.timeago').timeago();
app.makeNumbersHumanReadable($(html).find('.human-readable-number'));
app.makeNumbersHumanReadable(html.find('.human-readable-number'));
}
Category.loadMoreTopics = function(cid) {

@ -89,9 +89,10 @@ define(function() {
$('#category-no-topics').remove();
html = $(html);
container.append(html);
$('span.timeago').timeago();
app.makeNumbersHumanReadable($(html).find('.human-readable-number'));
app.makeNumbersHumanReadable(html.find('.human-readable-number'));
}
Recent.loadMoreTopics = function() {

@ -79,9 +79,10 @@ define(function() {
$('#category-no-topics').remove();
html = $(html);
container.append(html);
$('span.timeago').timeago();
app.makeNumbersHumanReadable($(html).find('.human-readable-number'));
app.makeNumbersHumanReadable(html.find('.human-readable-number'));
}
function loadMoreTopics() {

@ -1,3 +1,6 @@
<!-- IF redis -->
<h1>Redis</h1>
<hr />
<div id="admin-redis-info">
@ -21,4 +24,29 @@
<span>Keyspace Hits</span> <span class="text-right">{keyspace_hits}</span><br/>
<span>Keyspace Misses</span> <span class="text-right">{keyspace_misses}</span><br/>
</div>
<hr />
<h3>Raw Info </h3>
<div class="highlight">
<pre>{raw}</pre>
</div>
<!-- ENDIF redis -->
<!-- IF mongo -->
<h1>Mongo</h1>
<hr />
<div id="admin-redis-info">
<span>Collections</span> <span class="text-right">{collections}</span><br/>
<span>Objects</span> <span class="text-right">{objects}</span><br/>
<span>Avg. Object Size</span> <span class="text-right">{avgObjSize} kb</span><br/>
<hr/>
<span>Data Size</span> <span class="text-right">{dataSize} kb</span><br/>
<span>Storage Size</span> <span class="text-right">{storageSize} kb</span><br/>
<span>File Size</span> <span class="text-right">{fileSize} kb</span><br/>
</div>
<hr />
<h3>Raw Info </h3>
<div class="highlight">
<pre>{raw}</pre>
</div>
<!-- ENDIF mongo -->

@ -105,7 +105,7 @@
<li><a href='{relative_path}/admin/themes'><i class='fa fa-th'></i> Themes</a></li>
<li><a href='{relative_path}/admin/plugins'><i class='fa fa-code-fork'></i> Plugins</a></li>
<li><a href='{relative_path}/admin/settings'><i class='fa fa-cogs'></i> Settings</a></li>
<li><a href='{relative_path}/admin/redis'><i class='fa fa-hdd-o'></i> Redis</a></li>
<li><a href='{relative_path}/admin/database'><i class='fa fa-hdd-o'></i> Database</a></li>
<li><a href='{relative_path}/admin/logger'><i class='fa fa-th'></i> Logger</a></li>
<li><a href="{relative_path}/admin/motd"><i class="fa fa-comment"></i> MOTD</a></li>
</ul>

@ -4,7 +4,7 @@
"^admin/topics.*": "admin/topics",
"^admin/categories.*": "admin/categories",
"^admin/users.*": "admin/users",
"^admin/redis.*": "admin/redis",
"^admin/database.*": "admin/database",
"^admin/index.*": "admin/index",
"^admin/themes.*": "admin/themes",
"^admin/plugins/?$": "admin/plugins",

@ -1,4 +1,4 @@
var RDB = require('./../redis'),
var db = require('./../database'),
utils = require('./../../public/src/utils'),
categories = require('./../categories');
@ -11,12 +11,12 @@ var RDB = require('./../redis'),
var category = modified[cid];
for (var key in category) {
RDB.hset('category:' + cid, key, category[key]);
db.setObjectField('category:' + cid, key, category[key]);
if (key == 'name') {
// reset slugs if name is updated
var slug = cid + '/' + utils.slugify(category[key]);
RDB.hset('category:' + cid, 'slug', slug);
db.setObjectField('category:' + cid, 'slug', slug);
}
}

@ -1,5 +1,4 @@
var RDB = require('../redis'),
utils = require('../../public/src/utils'),
var utils = require('../../public/src/utils'),
user = require('../user'),
groups = require('../groups');

@ -1,4 +1,4 @@
var RDB = require('./redis.js'),
var db = require('./database.js'),
posts = require('./posts.js'),
utils = require('./../public/src/utils.js'),
user = require('./user.js'),
@ -12,13 +12,13 @@ var RDB = require('./redis.js'),
"use strict";
Categories.create = function(data, callback) {
RDB.incr('global:next_category_id', function(err, cid) {
db.incrObjectField('global', 'nextCid', function(err, cid) {
if (err) {
return callback(err, null);
}
var slug = cid + '/' + utils.slugify(data.name);
RDB.rpush('categories:cid', cid);
db.listAppend('categories:cid', cid);
var category = {
cid: cid,
@ -33,7 +33,7 @@ var RDB = require('./redis.js'),
order: data.order
};
RDB.hmset('category:' + cid, category, function(err, data) {
db.setObject('category:' + cid, category, function(err, data) {
callback(err, category);
});
});
@ -134,15 +134,15 @@ var RDB = require('./redis.js'),
};
Categories.getTopicIds = function(cid, start, stop, callback) {
RDB.zrevrange('categories:' + cid + ':tid', start, stop, callback);
db.getSortedSetRevRange('categories:' + cid + ':tid', start, stop, callback);
};
Categories.getActiveUsers = function(cid, callback) {
RDB.smembers('cid:' + cid + ':active_users', callback);
db.getSetMembers('cid:' + cid + ':active_users', callback);
};
Categories.getAllCategories = function(current_user, callback) {
RDB.lrange('categories:cid', 0, -1, function(err, cids) {
db.getListRange('categories:cid', 0, -1, function(err, cids) {
if(err) {
return callback(err);
}
@ -155,7 +155,7 @@ var RDB = require('./redis.js'),
};
Categories.getModerators = function(cid, callback) {
RDB.smembers('cid:' + cid + ':moderators', function(err, mods) {
db.getSetMembers('cid:' + cid + ':moderators', function(err, mods) {
if (!err) {
if (mods && mods.length) {
user.getMultipleUserFields(mods, ['username'], function(err, moderators) {
@ -172,7 +172,7 @@ var RDB = require('./redis.js'),
};
Categories.isTopicsRead = function(cid, uid, callback) {
RDB.zrange('categories:' + cid + ':tid', 0, -1, function(err, tids) {
db.getSortedSetRange('categories:' + cid + ':tid', 0, -1, function(err, tids) {
topics.hasReadTopics(tids, uid, function(hasRead) {
@ -189,31 +189,34 @@ var RDB = require('./redis.js'),
};
Categories.markAsRead = function(cid, uid) {
RDB.sadd('cid:' + cid + ':read_by_uid', uid);
db.setAdd('cid:' + cid + ':read_by_uid', uid);
};
Categories.hasReadCategories = function(cids, uid, callback) {
var batch = RDB.multi();
var sets = [];
for (var i = 0, ii = cids.length; i < ii; i++) {
batch.sismember('cid:' + cids[i] + ':read_by_uid', uid);
sets.push('cid:' + cids[i] + ':read_by_uid');
}
batch.exec(function(err, hasRead) {
db.isMemberOfSets(sets, uid, function(err, hasRead) {
callback(hasRead);
});
};
Categories.hasReadCategory = function(cid, uid, callback) {
RDB.sismember('cid:' + cid + ':read_by_uid', uid, function(err, hasRead) {
RDB.handle(err);
db.isSetMember('cid:' + cid + ':read_by_uid', uid, function(err, hasRead) {
if(err) {
return callback(false);
}
callback(hasRead);
});
};
Categories.getRecentReplies = function(cid, count, callback) {
RDB.zrevrange('categories:recent_posts:cid:' + cid, 0, (count < 10) ? 10 : count, function(err, pids) {
db.getSortedSetRevRange('categories:recent_posts:cid:' + cid, 0, (count < 10) ? 10 : count, function(err, pids) {
if (err) {
winston.err(err);
@ -242,8 +245,8 @@ var RDB = require('./redis.js'),
return callback(err);
}
RDB.zrem('categories:recent_posts:cid:' + oldCid, pid);
RDB.zadd('categories:recent_posts:cid:' + cid, timestamp, pid);
db.sortedSetRemove('categories:recent_posts:cid:' + oldCid, pid);
db.sortedSetAdd('categories:recent_posts:cid:' + cid, timestamp, pid);
callback(null);
});
}
@ -283,9 +286,9 @@ var RDB = require('./redis.js'),
};
Categories.getCategoryData = function(cid, callback) {
RDB.exists('category:' + cid, function(err, exists) {
db.exists('category:' + cid, function(err, exists) {
if (exists) {
RDB.hgetall('category:' + cid, callback);
db.getObject('category:' + cid, callback);
} else {
callback(new Error('No category found!'));
}
@ -293,19 +296,19 @@ var RDB = require('./redis.js'),
};
Categories.getCategoryField = function(cid, field, callback) {
RDB.hget('category:' + cid, field, callback);
db.getObjectField('category:' + cid, field, callback);
};
Categories.getCategoryFields = function(cid, fields, callback) {
RDB.hmgetObject('category:' + cid, fields, callback);
db.getObjectFields('category:' + cid, fields, callback);
};
Categories.setCategoryField = function(cid, field, value, callback) {
RDB.hset('category:' + cid, field, value, callback);
db.setObjectField('category:' + cid, field, value, callback);
};
Categories.incrementCategoryFieldBy = function(cid, field, value, callback) {
RDB.hincrby('category:' + cid, field, value, callback);
db.incrObjectFieldBy('category:' + cid, field, value, callback);
};
Categories.getCategories = function(cids, uid, callback) {
@ -349,7 +352,7 @@ var RDB = require('./redis.js'),
Categories.isUserActiveIn = function(cid, uid, callback) {
RDB.lrange('uid:' + uid + ':posts', 0, -1, function(err, pids) {
db.getListRange('uid:' + uid + ':posts', 0, -1, function(err, pids) {
if (err) {
return callback(err, null);
}
@ -387,12 +390,13 @@ var RDB = require('./redis.js'),
};
Categories.addActiveUser = function(cid, uid) {
if(parseInt(uid, 10))
RDB.sadd('cid:' + cid + ':active_users', uid);
if(parseInt(uid, 10)) {
db.setAdd('cid:' + cid + ':active_users', uid);
}
};
Categories.removeActiveUser = function(cid, uid) {
RDB.srem('cid:' + cid + ':active_users', uid);
db.setRemove('cid:' + cid + ':active_users', uid);
};
}(exports));

@ -0,0 +1,14 @@
var nconf = require('nconf'),
databaseType = nconf.get('database'),
winston = require('winston');
if(!databaseType) {
winston.info('Database type not set! Run node app --setup');
process.exit();
}
var db = require('./database/' + databaseType);
module.exports = db;

@ -0,0 +1,737 @@
(function(module) {
'use strict';
var mongoClient = require('mongodb').MongoClient,
winston = require('winston'),
async = require('async'),
nconf = require('nconf'),
express = require('express'),
mongoStore = require('connect-mongo')(express),
mongoHost = nconf.get('mongo:host'),
db;
module.init = function(callback) {
mongoClient.connect('mongodb://'+ mongoHost + ':' + nconf.get('mongo:port') + '/' + nconf.get('mongo:database'), function(err, _db) {
if(err) {
winston.error("NodeBB could not connect to your Mongo database. Mongo returned the following error: " + err.message);
process.exit();
}
db = _db;
module.client = db;
module.sessionStore = new mongoStore({
db: db
});
if(nconf.get('mongo:password') && nconf.get('mongo:username')) {
db.authenticate(nconf.get('mongo:username'), nconf.get('mongo:password'), function (err) {
if(err) {
winston.error(err.message);
process.exit();
}
createCollections();
});
} else {
createCollections();
}
function createCollections() {
db.createCollection('objects', function(err, collection) {
if(err) {
winston.error("Error creating collection " + err.message);
return;
}
if(collection) {
collection.ensureIndex({_key :1, setName:1}, {background:true}, function(err, name){
if(err) {
winston.error("Error creating index " + err.message);
}
});
}
});
db.createCollection('search', function(err, collection) {
if(err) {
winston.error("Error creating collection " + err.message);
return;
}
if(collection) {
collection.ensureIndex({content:'text'}, {background:true}, function(err, name){
if(err) {
winston.error("Error creating index " + err.message);
}
});
}
});
callback(null);
}
});
}
//
// Exported functions
//
module.searchIndex = function(key, content, id) {
var data = {
id:id,
key:key,
content:content
};
db.collection('search').update({id:id, key:key}, {$set:data}, {upsert:true, w: 1}, function(err, result) {
if(err) {
winston.error('Error indexing ' + err.message);
}
});
}
module.search = function(key, term, callback) {
db.command({text:"search" , search: term, filter: {key:key} }, function(err, result) {
if(err) {
return callback(err);
}
if(!result) {
return callback(null, []);
}
if(result.results && result.results.length) {
var data = result.results.map(function(item) {
return item.obj.id;
});
callback(null, data);
} else {
callback(null, []);
}
});
}
module.searchRemove = function(key, id) {
db.collection('search').remove({id:id, key:key}, function(err, result) {
if(err) {
winston.error('Error removing search ' + err.message);
}
});
}
module.flushdb = function(callback) {
db.dropDatabase(function(err, result) {
if(err){
winston.error(error);
if(callback) {
return callback(err);
}
}
if(callback) {
callback(null);
}
});
}
module.getFileName = function(callback) {
throw new Error('not-implemented');
}
module.info = function(callback) {
db.stats({scale:1024}, function(err, stats) {
// TODO : if this it not deleted the templates break,
// it is a nested object inside stats
delete stats.dataFileVersion;
stats.avgObjSize = (stats.avgObjSize / 1024).toFixed(2);
stats.raw = JSON.stringify(stats, null, 4);
stats.mongo = true;
//remove this when andrew adds in undefined checking to templates
stats.redis = false;
callback(err, stats);
});
}
// key
module.exists = function(key, callback) {
db.collection('objects').findOne({$or:[{_key:key}, {setName:key}]}, function(err, item) {
callback(err, item !== undefined && item !== null);
});
}
module.delete = function(key, callback) {
db.collection('objects').remove({_key:key}, function(err, result) {
if(err) {
if(callback) {
return callback(err);
} else {
return winston.error(err.message);
}
}
if(result === 0) {
db.collection('objects').remove({setName:key}, function(err, result) {
if(callback) {
callback(err, result);
}
});
} else {
if(callback) {
callback(null, result);
}
}
});
}
module.get = function(key, callback) {
module.getObjectField(key, 'value', callback);
}
module.set = function(key, value, callback) {
var data = {value:value};
module.setObject(key, data, callback);
}
module.keys = function(key, callback) {
db.collection('objects').find( { _key: { $regex: key /*, $options: 'i'*/ } }, function(err, result) {
callback(err, result);
});
}
//hashes
function removeHiddenFields(item) {
if(item) {
if(item._id) {
delete item._id;
}
if(item._key) {
delete item._key;
}
if(item.setName) {
delete item.setName;
}
}
return item;
}
module.setObject = function(key, data, callback) {
data['_key'] = key;
db.collection('objects').update({_key:key}, {$set:data}, {upsert:true, w: 1}, function(err, result) {
if(callback) {
callback(err, result);
}
});
}
module.setObjectField = function(key, field, value, callback) {
var data = {};
// if there is a '.' in the field name it inserts subdocument in mongo, replace '.'s with \uff0E
field = field.replace(/\./g, '\uff0E');
data[field] = value;
db.collection('objects').update({_key:key}, {$set:data}, {upsert:true, w: 1}, function(err, result) {
if(callback) {
callback(err, result);
}
});
}
module.getObject = function(key, callback) {
db.collection('objects').findOne({_key:key}, function(err, item) {
removeHiddenFields(item);
callback(err, item);
});
}
module.getObjects = function(keys, callback) {
db.collection('objects').find({_key:{$in:keys}}, {_id:0}).toArray(function(err, data) {
if(err) {
return callback(err);
}
var returnData = [],
resultIndex = 0;
function findData(key) {
if(!data) {
return null;
}
for(var i=0; i<data.length; ++i) {
if(data[i]._key === key) {
var item = data.splice(i, 1);
if(item && item.length) {
return item[0];
} else {
return null;
}
}
}
return null;
}
for(var i=0; i<keys.length; ++i) {
returnData.push(findData(keys[i]));
}
callback(err, returnData);
});
}
module.getObjectField = function(key, field, callback) {
module.getObjectFields(key, [field], function(err, data) {
if(err) {
return callback(err);
}
callback(null, data[field]);
});
}
module.getObjectFields = function(key, fields, callback) {
var _fields = {};
for(var i=0; i<fields.length; ++i) {
_fields[fields[i].replace(/\./g, '\uff0E')] = 1;
}
db.collection('objects').findOne({_key:key}, _fields, function(err, item) {
if(err) {
return callback(err);
}
if(item === null) {
item = {};
}
for(var i=0; i<fields.length; ++i) {
if(item[fields[i]] === null || item[fields[i]] === undefined) {
item[fields[i]] = null;
}
}
removeHiddenFields(item);
callback(err, item);
});
}
module.getObjectValues = function(key, callback) {
module.getObject(key, function(err, data) {
if(err) {
return callback(err);
}
var values = [];
for(var key in data) {
values.push(data[key]);
}
callback(null, values);
});
}
module.isObjectField = function(key, field, callback) {
var data = {};
field = field.replace(/\./g, '\uff0E');
data[field] = '';
db.collection('objects').findOne({_key:key}, {fields:data}, function(err, item) {
if(err) {
return callback(err);
}
callback(err, !!item && item[field] !== undefined && item[field] !== null);
});
}
module.deleteObjectField = function(key, field, callback) {
var data = {};
field = field.replace(/\./g, '\uff0E');
data[field] = "";
db.collection('objects').update({_key:key}, {$unset : data}, function(err, result) {
if(callback) {
callback(err, result);
}
});
}
module.incrObjectField = function(key, field, callback) {
module.incrObjectFieldBy(key, field, 1, callback);
}
module.decrObjectField = function(key, field, callback) {
module.incrObjectFieldBy(key, field, -1, callback);
}
module.incrObjectFieldBy = function(key, field, value, callback) {
var data = {};
field = field.replace(/\./g, '\uff0E');
data[field] = value;
db.collection('objects').update({_key:key}, {$inc : data}, {upsert:true}, function(err, result) {
module.getObjectField(key, field, function(err, value) {
if(callback) {
callback(err, value);
}
});
});
}
// sets
module.setAdd = function(key, value, callback) {
if(value !== null && value !== undefined) {
value = value.toString();
}
db.collection('objects').update({_key:key}, {$addToSet: { members: value }}, {upsert:true, w: 1}, function(err, result) {
if(callback) {
callback(err, result);
}
});
}
module.setRemove = function(key, value, callback) {
if(value !== null && value !== undefined) {
value = value.toString();
}
db.collection('objects').update({_key:key, members: value}, {$pull : {members: value}}, function(err, result) {
if(callback) {
callback(err, result);
}
});
}
module.isSetMember = function(key, value, callback) {
if(value !== null && value !== undefined) {
value = value.toString();
}
db.collection('objects').findOne({_key:key, members: value}, function(err, item) {
callback(err, item !== null && item !== undefined);
});
}
module.isMemberOfSets = function(sets, value, callback) {
function iterator(set, next) {
module.isSetMember(set, value, function(err, result) {
if(err) {
return next(err);
}
next(null, result?1:0);
});
}
async.map(sets, iterator, function(err, result) {
callback(err, result);
});
}
module.getSetMembers = function(key, callback) {
db.collection('objects').findOne({_key:key}, {members:1}, function(err, data) {
if(err) {
return callback(err);
}
if(!data) {
callback(null, []);
} else {
callback(null, data.members);
}
});
}
module.setCount = function(key, callback) {
db.collection('objects').findOne({_key:key}, function(err, data) {
if(err) {
return callback(err);
}
if(!data) {
return callback(null, 0);
}
callback(null, data.members.length);
});
}
module.setRemoveRandom = function(key, callback) {
db.collection('objects').findOne({_key:key}, function(err, data) {
if(err) {
if(callback) {
return callback(err);
} else {
return winston.error(err.message);
}
}
if(!data) {
if(callback) {
callback(null, 0);
}
} else {
var randomIndex = Math.floor(Math.random() * data.members.length);
var value = data.members[randomIndex];
module.setRemove(data._key, value, function(err, result) {
if(callback) {
callback(err, value);
}
});
}
});
}
// sorted sets
module.sortedSetAdd = function(key, score, value, callback) {
if(value !== null && value !== undefined) {
value = value.toString();
}
var data = {
score:score,
value:value
};
data.setName = key;
module.setObject(key + ':' + value, data, callback);
}
module.sortedSetRemove = function(key, value, callback) {
if(value !== null && value !== undefined) {
value = value.toString();
}
db.collection('objects').remove({setName:key, value:value}, function(err, result) {
if(callback) {
callback(err, result);
}
});
}
function getSortedSetRange(key, start, stop, sort, callback) {
db.collection('objects').find({setName:key}, {fields:{value:1}})
.limit(stop - start + 1)
.skip(start)
.sort({score: sort})
.toArray(function(err, data) {
if(err) {
return callback(err);
}
// maybe this can be done with mongo?
data = data.map(function(item) {
return item.value;
});
callback(err, data);
});
}
module.getSortedSetRange = function(key, start, stop, callback) {
getSortedSetRange(key, start, stop, 1, callback);
}
module.getSortedSetRevRange = function(key, start, stop, callback) {
getSortedSetRange(key, start, stop, -1, callback);
}
module.getSortedSetRevRangeByScore = function(args, callback) {
//var args = ['topics:recent', '+inf', timestamp - since, 'LIMIT', start, end - start + 1];
var key = args[0],
max = (args[1] === '+inf')?Number.MAX_VALUE:args[1],
min = args[2],
start = args[4],
stop = args[5];
db.collection('objects').find({setName:key, score: {$gt:min, $lt:max}}, {fields:{value:1}})
.limit(stop - start + 1)
.skip(start)
.sort({score: -1})
.toArray(function(err, data) {
if(err) {
return callback(err);
}
// maybe this can be done with mongo?
data = data.map(function(item) {
return item.value;
});
callback(err, data);
});
}
module.sortedSetCount = function(key, min, max, callback) {
db.collection('objects').count({setName:key, score: {$gt:min, $lt:max}}, function(err, count) {
if(err) {
return callback(err);
}
if(!count) {
return callback(null, 0);
}
callback(null,count);
});
}
module.sortedSetRank = function(key, value, callback) {
if(value !== null && value !== undefined) {
value = value.toString();
}
module.getSortedSetRange(key, 0, -1, function(err, result) {
if(err) {
return callback(err);
}
var rank = result.indexOf(value);
if(rank === -1) {
return callback(null, null);
}
callback(null, rank);
});
}
module.sortedSetScore = function(key, value, callback) {
if(value !== null && value !== undefined) {
value = value.toString();
}
db.collection('objects').findOne({setName:key, value: value}, {fields:{score:1}}, function(err, result) {
if(err) {
return callback(err);
}
if(result) {
return callback(null, result.score);
}
callback(err, null);
});
}
module.sortedSetsScore = function(keys, value, callback) {
if(value !== null && value !== undefined) {
value = value.toString();
}
db.collection('objects').find({setName:{$in:keys}, value: value}).toArray(function(err, result) {
if(err) {
return callback(err);
}
var returnData = [],
resultIndex = 0;
for(var i=0; i<keys.length; ++i) {
if(result && resultIndex < result.length && keys[i] === result[resultIndex].setName) {
returnData.push(result[resultIndex].score);
++resultIndex;
} else {
returnData.push(null);
}
}
callback(null, returnData);
});
}
// lists
module.listPrepend = function(key, value, callback) {
if(value !== null && value !== undefined) {
value = value.toString();
}
module.isObjectField(key, 'array', function(err, exists) {
if(err) {
if(callback) {
return callback(err);
} else {
return winston.error(err.message);
}
}
if(exists) {
db.collection('objects').update({_key:key}, {'$set': {'array.-1': value}}, {upsert:true, w:1 }, function(err, result) {
if(callback) {
callback(err, result);
}
});
} else {
module.listAppend(key, value, callback);
}
})
}
module.listAppend = function(key, value, callback) {
if(value !== null && value !== undefined) {
value = value.toString();
}
db.collection('objects').update({ _key: key }, { $push: { array: value } }, {upsert:true, w:1}, function(err, result) {
if(callback) {
callback(err, result);
}
});
}
module.listRemoveLast = function(key, callback) {
module.getListRange(key, -1, 0, function(err, value) {
if(err) {
return callback(err);
}
db.collection('objects').update({_key: key }, { $pop: { array: 1 } }, function(err, result) {
if(err) {
if(callback) {
return callback(err);
}
return;
}
if(value && value.length) {
if(callback) {
callback(err, value[0]);
}
} else {
if(callback) {
callback(err, null);
}
}
});
});
}
module.getListRange = function(key, start, stop, callback) {
if(stop === -1) {
// mongo doesnt allow -1 as the count argument in slice
// pass in a large value to retrieve the whole array
stop = Math.pow(2, 31) - 2;
}
db.collection('objects').findOne({_key:key}, { array: { $slice: [start, stop - start + 1] }}, function(err, data) {
if(err) {
return callback(err);
}
if(data && data.array) {
callback(null, data.array);
} else {
callback(null, []);
}
});
}
}(exports));

@ -0,0 +1,393 @@
(function(module) {
'use strict';
var redisClient,
redis = require('redis'),
winston = require('winston'),
nconf = require('nconf'),
express = require('express'),
connectRedis = require('connect-redis')(express),
reds = require('reds'),
redis_socket_or_host = nconf.get('redis:host'),
utils = require('./../../public/src/utils.js');
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 */
redisClient = redis.createClient(nconf.get('redis:host'));
} else {
/* Else, connect over tcp/ip */
redisClient = redis.createClient(nconf.get('redis:port'), nconf.get('redis:host'));
}
module.client = redisClient;
module.sessionStore = new connectRedis({
client: redisClient,
ttl: 60 * 60 * 24 * 30
});
reds.createClient = function () {
return reds.client || (reds.client = redisClient);
};
var userSearch = reds.createSearch('nodebbusersearch'),
postSearch = reds.createSearch('nodebbpostsearch'),
topicSearch = reds.createSearch('nodebbtopicsearch');
if (nconf.get('redis:password')) {
redisClient.auth(nconf.get('redis:password'));
}
var db = parseInt(nconf.get('redis:database'), 10);
if (db){
redisClient.select(db, function(error) {
if(error) {
winston.error("NodeBB could not connect to your Redis database. Redis returned the following error: " + error.message);
process.exit();
}
});
}
module.init = function(callback) {
callback(null);
}
/*
* A possibly more efficient way of doing multiple sismember calls
*/
function sismembers(key, needles, callback) {
var tempkey = key + ':temp:' + utils.generateUUID();
redisClient.sadd(tempkey, needles, function() {
redisClient.sinter(key, tempkey, function(err, data) {
redisClient.del(tempkey);
callback(err, data);
});
});
};
//
// Exported functions
//
module.searchIndex = function(key, content, id) {
if(key === 'post') {
postSearch.index(content, id);
} else if(key === 'topic') {
topicSearch.index(content, id);
} else if(key === 'user') {
userSearch.index(content, id);
}
}
module.search = function(key, term, callback) {
function search(searchObj, callback) {
searchObj
.query(term).type('or')
.end(callback);
}
if(key === 'post') {
search(postSearch, callback);
} else if(key === 'topic') {
search(topicSearch, callback);
} else if(key === 'user') {
search(userSearch, callback);
}
}
module.searchRemove = function(key, id) {
if(key === 'post') {
postSearch.remove(id);
} else if(key === 'topic') {
topicSearch.remove(id);
} else if(key === 'user') {
userSearch.remove(id);
}
}
module.flushdb = function(callback) {
redisClient.send_command('flushdb', [], function(err) {
if(err){
winston.error(error);
return callback(err);
}
callback(null);
});
}
module.getFileName = function(callback) {
var multi = redisClient.multi();
multi.config('get', 'dir');
multi.config('get', 'dbfilename');
multi.exec(function (err, results) {
if (err) {
return callback(err);
}
results = results.reduce(function (memo, config) {
memo[config[0]] = config[1];
return memo;
}, {});
var dbFile = path.join(results.dir, results.dbfilename);
callback(null, dbFile);
});
}
module.info = function(callback) {
redisClient.info(function (err, data) {
if(err) {
return callback(err);
}
data = data.split("\r\n");
var redisData = {};
for (var i in data) {
if (data[i].indexOf(':') == -1 || !data[i])
continue;
try {
data[i] = data[i].replace(/:/, "\":\"");
var json = "{\"" + data[i] + "\"}";
var jsonObject = JSON.parse(json);
for (var key in jsonObject) {
redisData[key] = jsonObject[key];
}
} catch (err) {
winston.warn('can\'t parse redis status variable, ignoring', i, data[i], err);
}
}
redisData.raw = JSON.stringify(redisData, null, 4);
redisData.redis = true;
//remove this when andrew adds in undefined checking to templates
redisData.mongo = false;
callback(null, redisData);
});
}
// key
module.exists = function(key, callback) {
redisClient.exists(key, function(err, exists) {
callback(err, exists === 1);
});
}
module.delete = function(key, callback) {
redisClient.del(key, callback);
}
module.get = function(key, callback) {
redisClient.get(key, callback);
}
module.set = function(key, value, callback) {
redisClient.set(key, value, callback);
}
module.keys = function(key, callback) {
redisClient.keys(key, callback);
}
//hashes
module.setObject = function(key, data, callback) {
// TODO: this crashes if callback isnt supplied -baris
redisClient.hmset(key, data, function(err, res) {
if(callback) {
callback(err, res);
}
});
}
module.setObjectField = function(key, field, value, callback) {
redisClient.hset(key, field, value, callback);
}
module.getObject = function(key, callback) {
redisClient.hgetall(key, callback);
}
module.getObjects = function(keys, callback) {
var multi = redisClient.multi();
for(var x=0; x<keys.length; ++x) {
multi.hgetall(keys[x]);
}
multi.exec(function (err, replies) {
callback(err, replies);
});
}
module.getObjectField = function(key, field, callback) {
module.getObjectFields(key, [field], function(err, data) {
if(err) {
return callback(err);
}
callback(null, data[field]);
});
}
module.getObjectFields = function(key, fields, callback) {
redisClient.hmget(key, fields, function(err, data) {
if(err) {
return callback(err, null);
}
var returnData = {};
for (var i = 0, ii = fields.length; i < ii; ++i) {
returnData[fields[i]] = data[i];
}
callback(null, returnData);
});
}
module.getObjectValues = function(key, callback) {
redisClient.hvals(key, callback);
}
module.isObjectField = function(key, field, callback) {
redisClient.hexists(key, field, function(err, exists) {
callback(err, exists === 1);
});
}
module.deleteObjectField = function(key, field, callback) {
redisClient.hdel(key, field, callback);
}
module.incrObjectField = function(key, field, callback) {
redisClient.hincrby(key, field, 1, callback);
}
module.decrObjectField = function(key, field, callback) {
redisClient.hincrby(key, field, -1, callback);
}
module.incrObjectFieldBy = function(key, field, value, callback) {
redisClient.hincrby(key, field, value, callback);
}
// sets
module.setAdd = function(key, value, callback) {
redisClient.sadd(key, value, callback);
}
module.setRemove = function(key, value, callback) {
redisClient.srem(key, value, callback);
}
module.isSetMember = function(key, value, callback) {
redisClient.sismember(key, value, function(err, result) {
if(err) {
return callback(err);
}
callback(null, result === 1);
});
}
module.isMemberOfSets = function(sets, value, callback) {
var batch = redisClient.multi();
for (var i = 0, ii = sets.length; i < ii; i++) {
batch.sismember(sets[i], value);
}
batch.exec(callback);
}
module.getSetMembers = function(key, callback) {
redisClient.smembers(key, callback);
}
module.setCount = function(key, callback) {
redisClient.scard(key, callback);
}
module.setRemoveRandom = function(key, callback) {
redisClient.spop(key, callback);
}
// sorted sets
module.sortedSetAdd = function(key, score, value, callback) {
redisClient.zadd(key, score, value, callback);
}
module.sortedSetRemove = function(key, value, callback) {
redisClient.zrem(key, value, callback);
}
module.getSortedSetRange = function(key, start, stop, callback) {
redisClient.zrange(key, start, stop, callback);
}
module.getSortedSetRevRange = function(key, start, stop, callback) {
redisClient.zrevrange(key, start, stop, callback);
}
module.getSortedSetRevRangeByScore = function(args, callback) {
redisClient.zrevrangebyscore(args, callback);
}
module.sortedSetCount = function(key, min, max, callback) {
redisClient.zcount(key, min, max, callback);
}
module.sortedSetRank = function(key, value, callback) {
redisClient.zrank(key, value, callback);
}
module.sortedSetScore = function(key, value, callback) {
redisClient.zscore(key, value, callback);
}
module.sortedSetsScore = function(keys, value, callback) {
var multi = redisClient.multi();
for(var x=0; x<keys.length; ++x) {
multi.zscore(keys[x], value);
}
multi.exec(callback);
}
// lists
module.listPrepend = function(key, value, callback) {
redisClient.lpush(key, value, callback);
}
module.listAppend = function(key, value, callback) {
redisClient.rpush(key, value, callback);
}
module.listRemoveLast = function(key, callback) {
redisClient.rpop(key, callback);
}
module.getListRange = function(key, start, stop, callback) {
redisClient.lrange(key, start, stop, callback);
}
}(exports));

@ -1,4 +1,4 @@
var RDB = require('./redis'),
var db = require('./database'),
posts = require('./posts'),
user = require('./user'),
websockets = require('./websockets')
@ -8,6 +8,7 @@ var RDB = require('./redis'),
"use strict";
Favourites.favourite = function (pid, room_id, uid, socket) {
if (uid === 0) {
translator.mget(['topic:favourites.not_logged_in.message', 'topic:favourites.not_logged_in.title'], function(err, results) {
@ -25,15 +26,16 @@ var RDB = require('./redis'),
posts.getPostFields(pid, ['uid', 'timestamp'], function (err, postData) {
Favourites.hasFavourited(pid, uid, function (hasFavourited) {
if (hasFavourited === 0) {
RDB.sadd('pid:' + pid + ':users_favourited', uid);
RDB.zadd('uid:' + uid + ':favourites', postData.timestamp, pid);
RDB.hincrby('post:' + pid, 'reputation', 1);
if (!hasFavourited) {
db.setAdd('pid:' + pid + ':users_favourited', uid);
db.sortedSetAdd('uid:' + uid + ':favourites', postData.timestamp, pid);
db.incrObjectFieldBy('post:' + pid, 'reputation', 1);
if (uid !== postData.uid) {
user.incrementUserFieldBy(postData.uid, 'reputation', 1, function (err, newreputation) {
RDB.zadd('users:reputation', newreputation, postData.uid);
db.sortedSetAdd('users:reputation', newreputation, postData.uid);
});
}
@ -60,15 +62,15 @@ var RDB = require('./redis'),
posts.getPostField(pid, 'uid', function (err, uid_of_poster) {
Favourites.hasFavourited(pid, uid, function (hasFavourited) {
if (hasFavourited === 1) {
RDB.srem('pid:' + pid + ':users_favourited', uid);
RDB.zrem('uid:' + uid + ':favourites', pid);
if (hasFavourited) {
db.setRemove('pid:' + pid + ':users_favourited', uid);
db.sortedSetRemove('uid:' + uid + ':favourites', pid);
RDB.hincrby('post:' + pid, 'reputation', -1);
db.incrObjectFieldBy('post:' + pid, 'reputation', -1);
if (uid !== uid_of_poster) {
user.incrementUserFieldBy(uid_of_poster, 'reputation', -1, function (err, newreputation) {
RDB.zadd('users:reputation', newreputation, uid_of_poster);
db.sortedSetAdd('users:reputation', newreputation, uid_of_poster);
});
}
@ -89,8 +91,7 @@ var RDB = require('./redis'),
};
Favourites.hasFavourited = function (pid, uid, callback) {
RDB.sismember('pid:' + pid + ':users_favourited', uid, function (err, hasFavourited) {
RDB.handle(err);
db.isSetMember('pid:' + pid + ':users_favourited', uid, function (err, hasFavourited) {
callback(hasFavourited);
});

@ -1,7 +1,7 @@
(function (Feed) {
var RDB = require('./redis.js'),
posts = require('./posts.js'),
topics = require('./topics.js'),
var db = require('./database'),
posts = require('./posts'),
topics = require('./topics'),
categories = require('./categories'),
fs = require('fs'),
@ -30,7 +30,12 @@
Feed.updateTopic = function (tid, callback) {
topics.getTopicWithPosts(tid, 0, 0, -1, true, function (err, topicData) {
if (err) {
return callback(new Error('topic-invalid'));
if(callback) {
return callback(new Error('topic-invalid'));
} else {
winston.error(err.message);
return;
}
}
var feed = new rss({
@ -50,8 +55,8 @@
}
async.each(topicData.posts, function(postData, next) {
if (postData.deleted === '0') {
dateStamp = new Date(parseInt(postData.edited === '0' ? postData.timestamp : postData.edited, 10)).toUTCString();
if (parseInt(postData.deleted, 10) === 0) {
dateStamp = new Date(parseInt(parseInt(postData.edited, 10) === 0 ? postData.timestamp : postData.edited, 10)).toUTCString();
feed.item({
title: 'Reply to ' + topicData.topic_name + ' on ' + dateStamp,

@ -2,11 +2,11 @@
"use strict";
var async = require('async'),
User = require('./user'),
RDB = RDB || require('./redis');
user = require('./user'),
db = require('./database');
Groups.list = function(options, callback) {
RDB.hvals('group:gid', function (err, gids) {
db.getObjectValues('group:gid', function (err, gids) {
if (gids.length > 0) {
async.map(gids, function (gid, next) {
Groups.get(gid, {
@ -15,7 +15,7 @@
}, function (err, groups) {
// Remove deleted and hidden groups from this list
callback(err, groups.filter(function (group) {
if (group.deleted === '1' || group.hidden === '1') {
if (parseInt(group.deleted, 10) === 1 || parseInt(group.hidden, 10) === 1) {
return false;
} else {
return true;
@ -31,17 +31,17 @@
Groups.get = function(gid, options, callback) {
async.parallel({
base: function (next) {
RDB.hgetall('gid:' + gid, next);
db.getObject('gid:' + gid, next);
},
users: function (next) {
RDB.smembers('gid:' + gid + ':members', function (err, uids) {
db.getSetMembers('gid:' + gid + ':members', function (err, uids) {
if (options.expand) {
if (err) {
return next(err);
}
async.map(uids, function (uid, next) {
User.getUserData(uid, next);
user.getUserData(uid, next);
}, function (err, users) {
next(err, users);
});
@ -58,7 +58,7 @@
results.base.count = results.users.length;
results.base.members = results.users;
results.base.deletable = (results.base.gid !== '1');
results.base.deletable = parseInt(results.base.gid, 10) !== 1;
callback(err, results.base);
});
@ -75,19 +75,19 @@
};
Groups.isDeleted = function(gid, callback) {
RDB.hget('gid:' + gid, 'deleted', function(err, deleted) {
callback(err, deleted === '1');
db.getObjectField('gid:' + gid, 'deleted', function(err, deleted) {
callback(err, parseInt(deleted, 10) === 1);
});
};
Groups.getGidFromName = function(name, callback) {
RDB.hget('group:gid', name, callback);
db.getObjectField('group:gid', name, callback);
};
Groups.isMember = function(uid, gid, callback) {
Groups.isDeleted(gid, function(err, deleted) {
if (!deleted) {
RDB.sismember('gid:' + gid + ':members', uid, callback);
db.isSetMember('gid:' + gid + ':members', uid, callback);
} else {
callback(err, false);
}
@ -107,7 +107,7 @@
};
Groups.isEmpty = function(gid, callback) {
RDB.scard('gid:' + gid + ':members', function(err, numMembers) {
db.setCount('gid:' + gid + ':members', function(err, numMembers) {
callback(err, numMembers === 0);
});
};
@ -125,7 +125,7 @@
Groups.exists = function(name, callback) {
async.parallel({
exists: function(next) {
RDB.hexists('group:gid', name, next);
db.isObjectField('group:gid', name, next);
},
deleted: function(next) {
Groups.getGidFromName(name, function(err, gid) {
@ -144,19 +144,23 @@
Groups.exists(name, function (err, exists) {
if (!exists) {
RDB.incr('next_gid', function (err, gid) {
RDB.multi()
.hset('group:gid', name, gid)
.hmset('gid:' + gid, {
db.incrObjectField('global', 'nextGid', function (err, gid) {
db.setObjectField('group:gid', name, gid, function(err) {
var groupData = {
gid: gid,
name: name,
description: description,
deleted: '0',
hidden: '0'
})
.exec(function (err) {
};
db.setObject('gid:' + gid, groupData, function(err) {
Groups.get(gid, {}, callback);
});
});
});
} else {
callback(new Error('group-exists'));
@ -171,22 +175,24 @@
};
Groups.update = function(gid, values, callback) {
RDB.exists('gid:' + gid, function (err, exists) {
db.exists('gid:' + gid, function (err, exists) {
console.log('exists?', gid, exists, values);
if (!err && exists) {
RDB.hmset('gid:' + gid, values, callback);
db.setObject('gid:' + gid, values, callback);
} else {
if (callback) callback(new Error('gid-not-found'));
if (callback) {
callback(new Error('gid-not-found'));
}
}
});
};
Groups.destroy = function(gid, callback) {
RDB.hset('gid:' + gid, 'deleted', '1', callback);
db.setObjectField('gid:' + gid, 'deleted', '1', callback);
};
Groups.join = function(gid, uid, callback) {
RDB.sadd('gid:' + gid + ':members', uid, callback);
db.setAdd('gid:' + gid + ':members', uid, callback);
};
Groups.joinByGroupName = function(groupName, uid, callback) {
@ -210,7 +216,7 @@
};
Groups.leave = function(gid, uid, callback) {
RDB.srem('gid:' + gid + ':members', uid, callback);
db.setRemove('gid:' + gid + ':members', uid, callback);
};
Groups.leaveByGroupName = function(groupName, uid, callback) {
@ -225,30 +231,36 @@
Groups.prune = function(callback) {
// Actually deletes groups (with the deleted flag) from the redis database
RDB.hvals('group:gid', function (err, gids) {
var multi = RDB.multi(),
groupsDeleted = 0;
db.getObjectValues('group:gid', function (err, gids) {
var groupsDeleted = 0;
async.each(gids, function(gid, next) {
Groups.get(gid, {}, function(err, groupObj) {
if (!err && groupObj.deleted === '1') {
multi.hdel('group:gid', groupObj.name);
multi.del('gid:' + gid);
groupsDeleted++;
if(err) {
return next(err);
}
next(null);
if (parseInt(groupObj.deleted, 10) === 1) {
db.deleteObjectField('group:gid', groupObj.name, function(err) {
db.delete('gid:' + gid, function(err) {
groupsDeleted++;
next(null);
});
});
} else {
next(null);
}
});
}, function(err) {
multi.exec(function(err) {
if (!err && process.env.NODE_ENV === 'development') {
winston.info('[groups.prune] Pruned ' + groupsDeleted + ' deleted groups from Redis');
}
callback(err);
});
if (!err && process.env.NODE_ENV === 'development') {
winston.info('[groups.prune] Pruned ' + groupsDeleted + ' deleted groups from Redis');
}
callback(err);
});
});
};
}(module.exports));

@ -19,8 +19,8 @@ var async = require('async'),
name: 'port',
description: 'Port number of your NodeBB',
'default': nconf.get('port') || 4567,
pattern: /[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]/,
message: 'Please enter a value betweeen 1 and 65535'
pattern: /[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]/,
message: 'Please enter a value betweeen 1 and 65535'
}, {
name: 'use_port',
description: 'Use a port number to access NodeBB?',
@ -32,6 +32,15 @@ var async = require('async'),
description: 'Please enter a NodeBB secret',
'default': nconf.get('secret') || utils.generateUUID()
}, {
name: 'bind_address',
description: 'IP or Hostname to bind to',
'default': nconf.get('bind_address') || '0.0.0.0'
}, {
name: 'database',
description: 'Which database to use',
'default': nconf.get('database') || 'redis'
}],
redisQuestions : [{
name: 'redis:host',
description: 'Host IP or address of your Redis instance',
'default': nconf.get('redis:host') || '127.0.0.1'
@ -46,11 +55,27 @@ var async = require('async'),
name: "redis:database",
description: "Which database to use (0..n)",
'default': nconf.get('redis:database') || 0
}],
mongoQuestions : [{
name: 'mongo:host',
description: 'Host IP or address of your MongoDB instance',
'default': nconf.get('mongo:host') || '127.0.0.1'
}, {
name: 'bind_address',
description: 'IP or Hostname to bind to',
'default': nconf.get('bind_address') || '0.0.0.0'
name: 'mongo:port',
description: 'Host port of your MongoDB instance',
'default': nconf.get('mongo:port') || 27017
}, {
name: 'mongo:user',
description: 'MongoDB username'
}, {
name: 'mongo:password',
description: 'Password of your MongoDB database'
}, {
name: "mongo:database",
description: "Which database to use (0..n)",
'default': nconf.get('mongo:database') || 0
}],
setup: function (callback) {
async.series([
function(next) {
@ -74,7 +99,9 @@ var async = require('async'),
if (!setupVal['admin:email']) winston.error(' admin:email');
process.exit();
}
} else next();
} else {
next();
}
},
function (next) {
var success = function(err, config) {
@ -82,36 +109,58 @@ var async = require('async'),
return next(new Error('aborted'));
}
// Translate redis properties into redis object
config.redis = {
host: config['redis:host'],
port: config['redis:port'],
password: config['redis:password'],
database: config['redis:database']
};
delete config['redis:host'];
delete config['redis:port'];
delete config['redis:password'];
delete config['redis:database'];
// Add hardcoded values
config.bcrypt_rounds = 12;
config.upload_path = '/public/uploads';
config.use_port = (config.use_port.slice(0, 1) === 'y') ? true : false;
var urlObject = url.parse(config.base_url),
relative_path = (urlObject.pathname && urlObject.pathname.length > 1) ? urlObject.pathname : '',
host = urlObject.host,
protocol = urlObject.protocol,
server_conf = config,
client_conf = {
relative_path: relative_path
};
server_conf.base_url = protocol + '//' + host;
server_conf.relative_path = relative_path;
install.save(server_conf, client_conf, next);
function dbQuestionsSuccess(err, databaseConfig) {
if (!databaseConfig) {
return next(new Error('aborted'));
}
// Translate redis properties into redis object
if(config.database === 'redis') {
config.redis = {
host: databaseConfig['redis:host'],
port: databaseConfig['redis:port'],
password: databaseConfig['redis:password'],
database: databaseConfig['redis:database']
};
} else if (config.database === 'mongo') {
config.mongo = {
host: databaseConfig['mongo:host'],
port: databaseConfig['mongo:port'],
password: databaseConfig['mongo:password'],
database: databaseConfig['mongo:database']
};
} else {
return next(new Error('unknown database : ' + config.database));
}
config.bcrypt_rounds = 12;
config.upload_path = '/public/uploads';
config.use_port = (config.use_port.slice(0, 1) === 'y') ? true : false;
var urlObject = url.parse(config.base_url),
relative_path = (urlObject.pathname && urlObject.pathname.length > 1) ? urlObject.pathname : '',
host = urlObject.host,
protocol = urlObject.protocol,
server_conf = config,
client_conf = {
relative_path: relative_path
};
server_conf.base_url = protocol + '//' + host;
server_conf.relative_path = relative_path;
install.save(server_conf, client_conf, function(err) {
require('./database').init(next);
});
}
if(config.database === 'redis') {
prompt.get(install.redisQuestions, dbQuestionsSuccess);
} else if(config.database === 'mongo') {
prompt.get(install.mongoQuestions, dbQuestionsSuccess);
} else {
return next(new Error('unknown database : ' + config.database));
}
};
// prompt prepends "prompt: " to questions, let's clear that.
@ -119,8 +168,9 @@ var async = require('async'),
prompt.message = '';
prompt.delimiter = '';
if (!install.values) prompt.get(install.questions, success);
else {
if (!install.values) {
prompt.get(install.questions, success);
} else {
// Use provided values, fall back to defaults
var config = {},
question, x, numQ;
@ -252,9 +302,7 @@ var async = require('async'),
}, next);
},
function (next) {
// Upgrading schema
var Upgrade = require('./upgrade');
Upgrade.upgrade(next);
require('./upgrade').upgrade(next);
}
], function (err) {
if (err) {
@ -343,8 +391,9 @@ var async = require('async'),
// Add the password questions
questions = questions.concat(passwordQuestions);
if (!install.values) prompt.get(questions, success);
else {
if (!install.values) {
prompt.get(questions, success);
} else {
var results = {
username: install.values['admin:username'],
email: install.values['admin:email'],
@ -375,10 +424,10 @@ var async = require('async'),
file: path.join(__dirname, '..', 'config.json')
});
var RDB = require('./redis');
/*var RDB = require('./redis');
reds.createClient = function () {
return reds.client || (reds.client = RDB);
};
};*/
callback(err);
});

@ -1,9 +1,9 @@
var user = require('./user.js'),
var user = require('./user'),
bcrypt = require('bcrypt'),
RDB = require('./redis.js'),
db = require('./database'),
path = require('path'),
winston = require('winston'),
utils = require('./../public/src/utils.js');
utils = require('./../public/src/utils');
(function(Login) {
@ -28,7 +28,7 @@ var user = require('./user.js'),
user.getUserFields(uid, ['password', 'banned'], function(err, userData) {
if (err) return next(err);
if (userData.banned && userData.banned === '1') {
if (userData.banned && parseInt(userData.banned, 10) === 1) {
return next({
status: "error",
message: "user-banned"
@ -58,7 +58,11 @@ var user = require('./user.js'),
}
Login.loginViaTwitter = function(twid, handle, photos, callback) {
user.getUidByTwitterId(twid, function(uid) {
user.getUidByTwitterId(twid, function(err, uid) {
if(err) {
return callback(err);
}
if (uid !== null) {
// Existing User
callback(null, {
@ -67,32 +71,36 @@ var user = require('./user.js'),
} else {
// New User
user.create(handle, undefined, undefined, function(err, uid) {
if (err !== null) {
callback(err);
} else {
// Save twitter-specific information to the user
user.setUserField(uid, 'twid', twid);
RDB.hset('twid:uid', twid, uid);
// Save their photo, if present
if (photos && photos.length > 0) {
var photoUrl = photos[0].value;
photoUrl = path.dirname(photoUrl) + '/' + path.basename(photoUrl, path.extname(photoUrl)).slice(0, -6) + 'bigger' + path.extname(photoUrl);
user.setUserField(uid, 'uploadedpicture', photoUrl);
user.setUserField(uid, 'picture', photoUrl);
}
if(err) {
return callback(err);
}
callback(null, {
uid: uid
});
// Save twitter-specific information to the user
user.setUserField(uid, 'twid', twid);
db.setObjectField('twid:uid', twid, uid);
// Save their photo, if present
if (photos && photos.length > 0) {
var photoUrl = photos[0].value;
photoUrl = path.dirname(photoUrl) + '/' + path.basename(photoUrl, path.extname(photoUrl)).slice(0, -6) + 'bigger' + path.extname(photoUrl);
user.setUserField(uid, 'uploadedpicture', photoUrl);
user.setUserField(uid, 'picture', photoUrl);
}
callback(null, {
uid: uid
});
});
}
});
}
Login.loginViaGoogle = function(gplusid, handle, email, callback) {
user.getUidByGoogleId(gplusid, function(uid) {
user.getUidByGoogleId(gplusid, function(err, uid) {
if(err) {
return callback(err);
}
if (uid !== null) {
// Existing User
callback(null, {
@ -103,27 +111,39 @@ var user = require('./user.js'),
var success = function(uid) {
// Save google-specific information to the user
user.setUserField(uid, 'gplusid', gplusid);
RDB.hset('gplusid:uid', gplusid, uid);
db.setObjectField('gplusid:uid', gplusid, uid);
callback(null, {
uid: uid
});
}
user.getUidByEmail(email, function(uid) {
user.getUidByEmail(email, function(err, uid) {
if(err) {
return callback(err);
}
if (!uid) {
user.create(handle, undefined, email, function(err, uid) {
if (err !== null) {
callback(err);
} else success(uid);
if(err) {
return callback(err);
}
success(uid);
});
} else success(uid); // Existing account -- merge
} else {
success(uid); // Existing account -- merge
}
});
}
});
}
Login.loginViaFacebook = function(fbid, name, email, callback) {
user.getUidByFbid(fbid, function(uid) {
user.getUidByFbid(fbid, function(err, uid) {
if(err) {
return callback(err);
}
if (uid !== null) {
// Existing User
callback(null, {
@ -134,20 +154,28 @@ var user = require('./user.js'),
var success = function(uid) {
// Save facebook-specific information to the user
user.setUserField(uid, 'fbid', fbid);
RDB.hset('fbid:uid', fbid, uid);
db.setObjectField('fbid:uid', fbid, uid);
callback(null, {
uid: uid
});
}
user.getUidByEmail(email, function(uid) {
user.getUidByEmail(email, function(err, uid) {
if(err) {
return callback(err);
}
if (!uid) {
user.create(name, undefined, email, function(err, uid) {
if (err !== null) {
callback(err);
} else success(uid);
if(err) {
return callback(err);
}
success(uid);
});
} else success(uid); // Existing account -- merge
} else {
success(uid); // Existing account -- merge
}
});
}
});

@ -1,4 +1,4 @@
var RDB = require('./redis'),
var db = require('./database'),
async = require('async'),
user = require('./user');
@ -14,9 +14,10 @@ var RDB = require('./redis'),
Messaging.addMessage = function(fromuid, touid, content, callback) {
var uids = sortUids(fromuid, touid);
RDB.incr('global:next_message_id', function(err, mid) {
if (err)
db.incrObjectField('global', 'nextMid', function(err, mid) {
if (err) {
return callback(err, null);
}
var message = {
content: content,
@ -25,8 +26,8 @@ var RDB = require('./redis'),
touid: touid
};
RDB.hmset('message:' + mid, message);
RDB.rpush('messages:' + uids[0] + ':' + uids[1], mid);
db.setObject('message:' + mid, message);
db.listAppend('messages:' + uids[0] + ':' + uids[1], mid);
Messaging.updateChatTime(fromuid, touid);
Messaging.updateChatTime(touid, fromuid);
@ -37,9 +38,10 @@ var RDB = require('./redis'),
Messaging.getMessages = function(fromuid, touid, callback) {
var uids = sortUids(fromuid, touid);
RDB.lrange('messages:' + uids[0] + ':' + uids[1], 0, -1, function(err, mids) {
if (err)
db.getListRange('messages:' + uids[0] + ':' + uids[1], 0, -1, function(err, mids) {
if (err) {
return callback(err, null);
}
if (!mids || !mids.length) {
return callback(null, []);
@ -51,14 +53,16 @@ var RDB = require('./redis'),
var messages = [];
function getMessage(mid, next) {
RDB.hgetall('message:' + mid, function(err, message) {
if (err)
db.getObject('message:' + mid, function(err, message) {
if (err) {
return next(err);
}
if (message.fromuid === fromuid)
if (message.fromuid === fromuid) {
message.content = 'You : ' + message.content;
else
} else {
message.content = tousername + ' : ' + message.content;
}
messages.push(message);
next(null);
@ -66,8 +70,9 @@ var RDB = require('./redis'),
}
async.eachSeries(mids, getMessage, function(err) {
if (err)
if (err) {
return callback(err, null);
}
callback(null, messages);
});
@ -76,7 +81,7 @@ var RDB = require('./redis'),
}
Messaging.updateChatTime = function(uid, toUid, callback) {
RDB.zadd('uid:' + uid + ':chats', Date.now(), toUid, function(err) {
db.sortedSetAdd('uid:' + uid + ':chats', Date.now(), toUid, function(err) {
if (callback) {
callback(err);
}
@ -84,7 +89,7 @@ var RDB = require('./redis'),
};
Messaging.getRecentChats = function(uid, callback) {
RDB.zrevrange('uid:' + uid + ':chats', 0, 9, function(err, uids) {
db.getSortedSetRevRange('uid:' + uid + ':chats', 0, 9, function(err, uids) {
if (!err) {
user.getMultipleUserFields(uids, ['username', 'picture', 'uid'], callback);
} else {

@ -1,11 +1,13 @@
var utils = require('./../public/src/utils.js'),
RDB = require('./redis.js'),
plugins = require('./plugins'),
async = require('async'),
var fs = require('fs'),
path = require('path'),
fs = require('fs'),
async = require('async'),
winston = require('winston'),
nconf = require('nconf');
nconf = require('nconf'),
utils = require('./../public/src/utils'),
db = require('./database'),
plugins = require('./plugins');
(function (Meta) {
Meta.config = {};
@ -15,33 +17,34 @@ var utils = require('./../public/src/utils.js'),
delete Meta.config;
Meta.configs.list(function (err, config) {
if (!err) {
Meta.config = config;
callback();
} else {
if(err) {
winston.error(err);
return callback(err);
}
Meta.config = config;
callback();
});
},
list: function (callback) {
RDB.hgetall('config', function (err, config) {
if (!err) {
config = config || {};
config.status = 'ok';
callback(err, config);
} else {
callback(new Error('could-not-read-config'));
db.getObject('config', function (err, config) {
if(err) {
return callback(new Error('could-not-read-config'));
}
config = config || {};
config.status = 'ok';
callback(err, config);
});
},
get: function (field, callback) {
RDB.hget('config', field, callback);
db.getObjectField('config', field, callback);
},
getFields: function (fields, callback) {
RDB.hmgetObject('config', fields, callback);
db.getObjectFields('config', fields, callback);
},
set: function (field, value, callback) {
RDB.hset('config', field, value, function (err, res) {
db.setObjectField('config', field, value, function(err, res) {
if (callback) {
if(!err && Meta.config)
Meta.config[field] = value;
@ -50,7 +53,7 @@ var utils = require('./../public/src/utils.js'),
});
},
setOnEmpty: function (field, value, callback) {
this.get(field, function (err, curValue) {
Meta.configs.get(field, function (err, curValue) {
if (!curValue) {
Meta.configs.set(field, value, callback);
} else {
@ -59,7 +62,7 @@ var utils = require('./../public/src/utils.js'),
});
},
remove: function (field) {
RDB.hdel('config', field);
db.deleteObjectField('config', field);
}
};
@ -125,7 +128,7 @@ var utils = require('./../public/src/utils.js'),
themeData['theme:staticDir'] = config.staticDir ? config.staticDir : '';
themeData['theme:templates'] = config.templates ? config.templates : '';
RDB.hmset('config', themeData, next);
db.setObject('config', themeData, next);
}
], function(err) {
callback(err);
@ -134,7 +137,7 @@ var utils = require('./../public/src/utils.js'),
case 'bootswatch':
themeData['theme:src'] = data.src;
RDB.hmset('config', themeData, callback);
db.setObject('config', themeData, callback);
break;
}
}
@ -257,11 +260,16 @@ var utils = require('./../public/src/utils.js'),
}),
minified;
if (process.env.NODE_ENV === 'development') winston.info('Minifying client-side libraries');
if (process.env.NODE_ENV === 'development') {
winston.info('Minifying client-side libraries');
}
minified = uglifyjs.minify(jsPaths);
fs.writeFile(Meta.js.minFile, minified.code, function (err) {
if (!err) {
if (process.env.NODE_ENV === 'development') winston.info('Minified client-side libraries');
if (process.env.NODE_ENV === 'development') {
winston.info('Minified client-side libraries');
}
callback();
} else {
winston.error('Problem minifying client-side libraries, exiting.');
@ -273,23 +281,7 @@ var utils = require('./../public/src/utils.js'),
Meta.db = {
getFile: function (callback) {
var multi = RDB.multi();
multi.config('get', 'dir');
multi.config('get', 'dbfilename');
multi.exec(function (err, results) {
if (err) {
return callback(err);
} else {
results = results.reduce(function (memo, config) {
memo[config[0]] = config[1];
return memo;
}, {});
var dbFile = path.join(results.dir, results.dbfilename);
callback(null, dbFile);
}
});
db.getFileName(callback);
}
};
}(exports));

@ -1,9 +1,9 @@
var RDB = require('./redis'),
async = require('async'),
utils = require('../public/src/utils'),
var async = require('async'),
winston = require('winston'),
cron = require('cron').CronJob,
db = require('./database'),
utils = require('../public/src/utils'),
websockets = require('./websockets');
@ -19,28 +19,21 @@ var RDB = require('./redis'),
};
Notifications.get = function(nid, uid, callback) {
RDB.multi()
.hmget('notifications:' + nid, 'text', 'score', 'path', 'datetime', 'uniqueId')
.zrank('uid:' + uid + ':notifications:read', nid)
.exists('notifications:' + nid)
.exec(function(err, results) {
var notification = results[0],
readIdx = results[1];
if (!results[2]) {
return callback(null);
}
callback({
nid: nid,
text: notification[0],
score: notification[1],
path: notification[2],
datetime: notification[3],
uniqueId: notification[4],
read: readIdx !== null ? true : false
db.exists('notifications:' + nid, function(err, exists) {
if(!exists) {
return callback(null);
}
db.sortedSetRank('uid:' + uid + ':notifications:read', nid, function(err, rank) {
db.getObjectFields('notifications:' + nid, ['nid', 'text', 'score', 'path', 'datetime', 'uniqueId'], function(err, notification) {
notification.read = rank !== null ? true:false;
callback(notification);
});
});
});
};
Notifications.create = function(text, path, uniqueId, callback) {
@ -50,9 +43,9 @@ var RDB = require('./redis'),
* (un)read list contains the same uniqueId, it will be removed, and
* the new one put in its place.
*/
RDB.incr('notifications:next_nid', function(err, nid) {
RDB.sadd('notifications', nid);
RDB.hmset('notifications:' + nid, {
db.incrObjectField('global', 'nextNid', function(err, nid) {
db.setAdd('notifications', nid);
db.setObject('notifications:' + nid, {
text: text || '',
path: path || null,
datetime: Date.now(),
@ -66,16 +59,14 @@ var RDB = require('./redis'),
};
function destroy(nid) {
var multi = RDB.multi();
multi.del('notifications:' + nid);
multi.srem('notifications', nid);
multi.exec(function(err) {
if (err) {
winston.error('Problem deleting expired notifications. Stack follows.');
winston.error(err.stack);
}
db.delete('notifications:' + nid, function(err, result) {
db.setRemove('notifications', nid, function(err, result) {
if (err) {
winston.error('Problem deleting expired notifications. Stack follows.');
winston.error(err.stack);
}
});
});
}
@ -92,7 +83,7 @@ var RDB = require('./redis'),
if (parseInt(uids[x], 10) > 0) {
(function(uid) {
remove_by_uniqueId(notif_data.uniqueId, uid, function() {
RDB.zadd('uid:' + uid + ':notifications:unread', notif_data.datetime, nid);
db.sortedSetAdd('uid:' + uid + ':notifications:unread', notif_data.datetime, nid);
websockets.in('uid_' + uid).emit('event:new_notification');
@ -109,12 +100,12 @@ var RDB = require('./redis'),
function remove_by_uniqueId(uniqueId, uid, callback) {
async.parallel([
function(next) {
RDB.zrange('uid:' + uid + ':notifications:unread', 0, -1, function(err, nids) {
db.getSortedSetRange('uid:' + uid + ':notifications:unread', 0, -1, function(err, nids) {
if (nids && nids.length > 0) {
async.each(nids, function(nid, next) {
Notifications.get(nid, uid, function(nid_info) {
if (nid_info.uniqueId === uniqueId) {
RDB.zrem('uid:' + uid + ':notifications:unread', nid);
db.sortedSetRemove('uid:' + uid + ':notifications:unread', nid);
}
next();
@ -128,12 +119,12 @@ var RDB = require('./redis'),
});
},
function(next) {
RDB.zrange('uid:' + uid + ':notifications:read', 0, -1, function(err, nids) {
db.getSortedSetRange('uid:' + uid + ':notifications:read', 0, -1, function(err, nids) {
if (nids && nids.length > 0) {
async.each(nids, function(nid, next) {
Notifications.get(nid, uid, function(nid_info) {
if (nid_info && nid_info.uniqueId === uniqueId) {
RDB.zrem('uid:' + uid + ':notifications:read', nid);
db.sortedSetRemove('uid:' + uid + ':notifications:read', nid);
}
next();
@ -156,8 +147,8 @@ var RDB = require('./redis'),
Notifications.mark_read = function(nid, uid, callback) {
if (parseInt(uid, 10) > 0) {
Notifications.get(nid, uid, function(notif_data) {
RDB.zrem('uid:' + uid + ':notifications:unread', nid);
RDB.zadd('uid:' + uid + ':notifications:read', notif_data.datetime, nid);
db.sortedSetRemove('uid:' + uid + ':notifications:unread', nid);
db.sortedSetAdd('uid:' + uid + ':notifications:read', notif_data.datetime, nid);
if (callback) {
callback();
}
@ -184,7 +175,7 @@ var RDB = require('./redis'),
};
Notifications.mark_all_read = function(uid, callback) {
RDB.zrange('uid:' + uid + ':notifications:unread', 0, 10, function(err, nids) {
db.getSortedSetRange('uid:' + uid + ':notifications:unread', 0, 10, function(err, nids) {
if (err) {
return callback(err);
}
@ -200,6 +191,7 @@ var RDB = require('./redis'),
};
Notifications.prune = function(cutoff) {
if (process.env.NODE_ENV === 'development') {
winston.info('[notifications.prune] Removing expired notifications from the database.');
}
@ -215,12 +207,20 @@ var RDB = require('./redis'),
async.parallel({
"inboxes": function(next) {
RDB.keys('uid:*:notifications:unread', next);
db.getSortedSetRange('users:joindate', 0, -1, function(err, uids) {
if(err) {
return next(err);
}
uids = uids.map(function(uid) {
return 'uid:' + uid + ':notifications:unread';
});
next(null, uids);
});
},
"nids": function(next) {
RDB.smembers('notifications', function(err, nids) {
"expiredNids": function(next) {
db.getSetMembers('notifications', function(err, nids) {
async.filter(nids, function(nid, next) {
RDB.hget('notifications:' + nid, 'datetime', function(err, datetime) {
db.getObjectField('notifications:' + nid, 'datetime', function(err, datetime) {
if (parseInt(datetime, 10) < cutoffTime) {
next(true);
} else {
@ -233,43 +233,39 @@ var RDB = require('./redis'),
});
}
}, function(err, results) {
if (!err) {
var numInboxes = results.inboxes.length,
x;
if(err) {
if (process.env.NODE_ENV === 'development') {
winston.error('[notifications.prune] Ran into trouble pruning expired notifications. Stack trace to follow.');
winston.error(err.stack);
}
return;
}
async.eachSeries(results.nids, function(nid, next) {
var multi = RDB.multi();
async.eachSeries(results.expiredNids, function(nid, next) {
for(x=0;x<numInboxes;x++) {
multi.zscore(results.inboxes[x], nid);
db.sortedSetsScore(results.inboxes, nid, function(err, results) {
if(err) {
return next(err);
}
multi.exec(function(err, results) {
// If the notification is not present in any inbox, delete it altogether
var expired = results.every(function(present) {
if (present === null) {
return true;
}
});
if (expired) {
destroy(nid);
numPruned++;
}
// If the notification is not present in any inbox, delete it altogether
var expired = results.every(function(present) {
return present === null;
});
next();
});
}, function(err) {
if (process.env.NODE_ENV === 'development') {
winston.info('[notifications.prune] Notification pruning completed. ' + numPruned + ' expired notification' + (numPruned !== 1 ? 's' : '') + ' removed.');
if (expired) {
destroy(nid);
numPruned++;
}
next();
});
} else {
}, function(err) {
if (process.env.NODE_ENV === 'development') {
winston.error('[notifications.prune] Ran into trouble pruning expired notifications. Stack trace to follow.');
winston.error(err.stack);
winston.info('[notifications.prune] Notification pruning completed. ' + numPruned + ' expired notification' + (numPruned !== 1 ? 's' : '') + ' removed.');
}
}
});
});
};

@ -1,326 +1,354 @@
var fs = require('fs'),
path = require('path'),
RDB = require('./redis.js'),
async = require('async'),
winston = require('winston'),
eventEmitter = require('events').EventEmitter,
plugins = {
libraries: {},
loadedHooks: {},
staticDirs: {},
cssFiles: [],
db = require('./database');
// Events
readyEvent: new eventEmitter,
(function(Plugins) {
init: function() {
if (this.initialized) return;
if (global.env === 'development') winston.info('[plugins] Initializing plugins system');
Plugins.libraries = {};
Plugins.loadedHooks = {};
Plugins.staticDirs = {};
Plugins.cssFiles = [];
this.reload(function(err) {
if (err) {
if (global.env === 'development') winston.info('[plugins] NodeBB encountered a problem while loading plugins', err.message);
return;
Plugins.initialized = false;
// Events
Plugins.readyEvent = new eventEmitter;
Plugins.init = function() {
if (Plugins.initialized) {
return;
}
if (global.env === 'development') {
winston.info('[plugins] Initializing plugins system');
}
Plugins.reload(function(err) {
if (err) {
if (global.env === 'development') {
winston.info('[plugins] NodeBB encountered a problem while loading plugins', err.message);
}
return;
}
if (global.env === 'development') {
winston.info('[plugins] Plugins OK');
}
Plugins.initialized = true;
Plugins.readyEvent.emit('ready');
});
};
Plugins.ready = function(callback) {
if (!Plugins.initialized) {
Plugins.readyEvent.once('ready', callback);
} else {
callback();
}
};
Plugins.reload = function(callback) {
// Resetting all local plugin data
Plugins.loadedHooks = {};
Plugins.staticDirs = {};
Plugins.cssFiles.length = 0;
// Read the list of activated plugins and require their libraries
async.waterfall([
function(next) {
db.getSetMembers('plugins:active', next);
},
function(plugins, next) {
if (plugins && Array.isArray(plugins) && plugins.length > 0) {
async.each(plugins, function(plugin, next) {
var modulePath = path.join(__dirname, '../node_modules/', plugin);
if (fs.existsSync(modulePath)) {
Plugins.loadPlugin(modulePath, next);
} else {
if (global.env === 'development') {
winston.warn('[plugins] Plugin \'' + plugin + '\' not found');
}
next(); // Ignore this plugin silently
}
}, next);
} else next();
},
function(next) {
if (global.env === 'development') winston.info('[plugins] Sorting hooks to fire in priority sequence');
Object.keys(Plugins.loadedHooks).forEach(function(hook) {
var hooks = Plugins.loadedHooks[hook];
hooks = hooks.sort(function(a, b) {
return a.priority - b.priority;
});
});
if (global.env === 'development') winston.info('[plugins] Plugins OK');
next();
}
], callback);
};
plugins.initialized = true;
plugins.readyEvent.emit('ready');
});
},
ready: function(callback) {
if (!this.initialized) this.readyEvent.once('ready', callback);
else callback();
},
initialized: false,
reload: function(callback) {
var _self = this;
// Resetting all local plugin data
this.loadedHooks = {};
this.staticDirs = {};
this.cssFiles.length = 0;
// Read the list of activated plugins and require their libraries
async.waterfall([
Plugins.loadPlugin = function(pluginPath, callback) {
fs.readFile(path.join(pluginPath, 'plugin.json'), function(err, data) {
if (err) {
return callback(err);
}
var pluginData = JSON.parse(data),
libraryPath, staticDir;
async.parallel([
function(next) {
RDB.smembers('plugins:active', next);
},
function(plugins, next) {
if (plugins && Array.isArray(plugins) && plugins.length > 0) {
async.each(plugins, function(plugin, next) {
var modulePath = path.join(__dirname, '../node_modules/', plugin);
if (fs.existsSync(modulePath)) _self.loadPlugin(modulePath, next);
else {
if (global.env === 'development') winston.warn('[plugins] Plugin \'' + plugin + '\' not found');
next(); // Ignore this plugin silently
if (pluginData.library) {
libraryPath = path.join(pluginPath, pluginData.library);
fs.exists(libraryPath, function(exists) {
if (exists) {
if (!Plugins.libraries[pluginData.id]) {
Plugins.libraries[pluginData.id] = require(libraryPath);
}
// Register hooks for this plugin
if (pluginData.hooks && Array.isArray(pluginData.hooks) && pluginData.hooks.length > 0) {
async.each(pluginData.hooks, function(hook, next) {
Plugins.registerHook(pluginData.id, hook, next);
}, next);
} else {
next(null);
}
} else {
winston.warn('[plugins.reload] Library not found for plugin: ' + pluginData.id);
next();
}
}, next);
} else next();
});
} else {
winston.warn('[plugins.reload] Library not found for plugin: ' + pluginData.id);
next();
}
},
function(next) {
if (global.env === 'development') winston.info('[plugins] Sorting hooks to fire in priority sequence');
Object.keys(_self.loadedHooks).forEach(function(hook) {
var hooks = _self.loadedHooks[hook];
hooks = hooks.sort(function(a, b) {
return a.priority - b.priority;
// Static Directories for Plugins
if (pluginData.staticDir) {
staticDir = path.join(pluginPath, pluginData.staticDir);
fs.exists(staticDir, function(exists) {
if (exists) {
Plugins.staticDirs[pluginData.id] = staticDir;
next();
} else next();
});
});
next();
}
], callback);
},
loadPlugin: function(pluginPath, callback) {
var _self = this;
fs.readFile(path.join(pluginPath, 'plugin.json'), function(err, data) {
if (err) return callback(err);
var pluginData = JSON.parse(data),
libraryPath, staticDir;
async.parallel([
function(next) {
if (pluginData.library) {
libraryPath = path.join(pluginPath, pluginData.library);
fs.exists(libraryPath, function(exists) {
if (exists) {
if (!_self.libraries[pluginData.id]) {
_self.libraries[pluginData.id] = require(libraryPath);
}
// Register hooks for this plugin
if (pluginData.hooks && Array.isArray(pluginData.hooks) && pluginData.hooks.length > 0) {
async.each(pluginData.hooks, function(hook, next) {
_self.registerHook(pluginData.id, hook, next);
}, next);
} else {
next(null);
}
} else {
winston.warn('[plugins.reload] Library not found for plugin: ' + pluginData.id);
next();
}
});
} else {
winston.warn('[plugins.reload] Library not found for plugin: ' + pluginData.id);
next();
} else next();
},
function(next) {
// CSS Files for plugins
if (pluginData.css && pluginData.css instanceof Array) {
if (global.env === 'development') {
winston.info('[plugins] Found ' + pluginData.css.length + ' CSS file(s) for plugin ' + pluginData.id);
}
},
function(next) {
// Static Directories for Plugins
if (pluginData.staticDir) {
staticDir = path.join(pluginPath, pluginData.staticDir);
fs.exists(staticDir, function(exists) {
if (exists) {
_self.staticDirs[pluginData.id] = staticDir;
next();
} else next();
});
} else next();
},
function(next) {
// CSS Files for plugins
if (pluginData.css && pluginData.css instanceof Array) {
if (global.env === 'development') {
winston.info('[plugins] Found ' + pluginData.css.length + ' CSS file(s) for plugin ' + pluginData.id);
}
_self.cssFiles = _self.cssFiles.concat(pluginData.css.map(function(file) {
return path.join('/plugins', pluginData.id, file);
}));
Plugins.cssFiles = Plugins.cssFiles.concat(pluginData.css.map(function(file) {
return path.join('/plugins', pluginData.id, file);
}));
next();
} else next();
}
], function(err) {
if (!err) {
if (global.env === 'development') winston.info('[plugins] Loaded plugin: ' + pluginData.id);
callback();
} else callback(new Error('Could not load plugin system'))
});
});
},
registerHook: function(id, data, callback) {
/*
`data` is an object consisting of (* is required):
`data.hook`*, the name of the NodeBB hook
`data.method`*, the method called in that plugin
`data.callbacked`, whether or not the hook expects a callback (true), or a return (false). Only used for filters. (Default: false)
`data.priority`, the relative priority of the method when it is eventually called (default: 10)
*/
var _self = this;
if (data.hook && data.method) {
data.id = id;
if (!data.priority) data.priority = 10;
data.method = data.method.split('.').reduce(function(memo, prop) {
if (memo[prop]) {
return memo[prop];
next();
} else {
// Couldn't find method by path, assuming property with periods in it (evil!)
_self.libraries[data.id][data.method];
next();
}
}, _self.libraries[data.id]);
_self.loadedHooks[data.hook] = _self.loadedHooks[data.hook] || [];
_self.loadedHooks[data.hook].push(data);
if (global.env === 'development') winston.info('[plugins] Hook registered: ' + data.hook + ' will call ' + id);
callback();
} else return;
},
fireHook: function(hook, args, callback) {
var _self = this
hookList = this.loadedHooks[hook];
if (hookList && Array.isArray(hookList)) {
//if (global.env === 'development') winston.info('[plugins] Firing hook: \'' + hook + '\'');
var hookType = hook.split(':')[0];
switch (hookType) {
case 'filter':
async.reduce(hookList, args, function(value, hookObj, next) {
if (hookObj.method) {
if (hookObj.callbacked) { // If a callback is present (asynchronous method)
hookObj.method.call(_self.libraries[hookObj.id], value, next);
} else { // Synchronous method
value = hookObj.method.call(_self.libraries[hookObj.id], value);
next(null, value);
}
} else {
if (global.env === 'development') winston.info('[plugins] Expected method \'' + hookObj.method + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
}
], function(err) {
if (!err) {
if (global.env === 'development') {
winston.info('[plugins] Loaded plugin: ' + pluginData.id);
}
callback();
} else {
callback(new Error('Could not load plugin system'));
}
});
});
};
Plugins.registerHook = function(id, data, callback) {
/*
`data` is an object consisting of (* is required):
`data.hook`*, the name of the NodeBB hook
`data.method`*, the method called in that plugin
`data.callbacked`, whether or not the hook expects a callback (true), or a return (false). Only used for filters. (Default: false)
`data.priority`, the relative priority of the method when it is eventually called (default: 10)
*/
if (data.hook && data.method) {
data.id = id;
if (!data.priority) data.priority = 10;
data.method = data.method.split('.').reduce(function(memo, prop) {
if (memo[prop]) {
return memo[prop];
} else {
// Couldn't find method by path, assuming property with periods in it (evil!)
Plugins.libraries[data.id][data.method];
}
}, Plugins.libraries[data.id]);
Plugins.loadedHooks[data.hook] = Plugins.loadedHooks[data.hook] || [];
Plugins.loadedHooks[data.hook].push(data);
if (global.env === 'development') {
winston.info('[plugins] Hook registered: ' + data.hook + ' will call ' + id);
}
callback();
} else return;
};
Plugins.fireHook = function(hook, args, callback) {
hookList = Plugins.loadedHooks[hook];
if (hookList && Array.isArray(hookList)) {
//if (global.env === 'development') winston.info('[plugins] Firing hook: \'' + hook + '\'');
var hookType = hook.split(':')[0];
switch (hookType) {
case 'filter':
async.reduce(hookList, args, function(value, hookObj, next) {
if (hookObj.method) {
if (hookObj.callbacked) { // If a callback is present (asynchronous method)
hookObj.method.call(Plugins.libraries[hookObj.id], value, next);
} else { // Synchronous method
value = hookObj.method.call(Plugins.libraries[hookObj.id], value);
next(null, value);
}
}, function(err, value) {
if (err) {
if (global.env === 'development') {
winston.info('[plugins] Problem executing hook: ' + hook);
}
} else {
if (global.env === 'development') {
winston.info('[plugins] Expected method \'' + hookObj.method + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
}
next(null, value);
}
}, function(err, value) {
if (err) {
if (global.env === 'development') {
winston.info('[plugins] Problem executing hook: ' + hook);
}
}
callback.apply(plugins, arguments);
});
break;
case 'action':
async.each(hookList, function(hookObj) {
if (hookObj.method) {
hookObj.method.call(_self.libraries[hookObj.id], args);
} else {
if (global.env === 'development') {
winston.info('[plugins] Expected method \'' + hookObj.method + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
}
callback.apply(Plugins, arguments);
});
break;
case 'action':
async.each(hookList, function(hookObj) {
if (hookObj.method) {
hookObj.method.call(Plugins.libraries[hookObj.id], args);
} else {
if (global.env === 'development') {
winston.info('[plugins] Expected method \'' + hookObj.method + '\' in plugin \'' + hookObj.id + '\' not found, skipping.');
}
});
break;
default:
// Do nothing...
break;
}
} else {
// Otherwise, this hook contains no methods
var returnVal = args;
if (callback) {
callback(null, returnVal);
}
}
});
break;
default:
// Do nothing...
break;
}
} else {
// Otherwise, this hook contains no methods
var returnVal = args;
if (callback) {
callback(null, returnVal);
}
},
isActive: function(id, callback) {
RDB.sismember('plugins:active', id, callback);
},
toggleActive: function(id, callback) {
this.isActive(id, function(err, active) {
}
};
Plugins.isActive = function(id, callback) {
db.isSetMember('plugins:active', id, callback);
};
Plugins.toggleActive = function(id, callback) {
Plugins.isActive(id, function(err, active) {
if (err) {
if (global.env === 'development') winston.info('[plugins] Could not toggle active state on plugin \'' + id + '\'');
return;
}
db[(active ? 'setRemove' : 'setAdd')]('plugins:active', id, function(err, success) {
if (err) {
if (global.env === 'development') winston.info('[plugins] Could not toggle active state on plugin \'' + id + '\'');
return;
}
RDB[(active ? 'srem' : 'sadd')]('plugins:active', id, function(err, success) {
if (err) {
if (global.env === 'development') winston.info('[plugins] Could not toggle active state on plugin \'' + id + '\'');
return;
}
// Reload meta data
plugins.reload(function() {
// (De)activation Hooks
plugins.fireHook('action:plugin.' + (active ? 'de' : '') + 'activate', id);
// Reload meta data
Plugins.reload(function() {
// (De)activation Hooks
Plugins.fireHook('action:plugin.' + (active ? 'de' : '') + 'activate', id);
if (callback) {
callback({
id: id,
active: !active
});
}
});
if (callback) {
callback({
id: id,
active: !active
});
}
});
});
},
showInstalled: function(callback) {
var _self = this;
npmPluginPath = path.join(__dirname, '../node_modules');
async.waterfall([
function(next) {
fs.readdir(npmPluginPath, function(err, dirs) {
dirs = dirs.map(function(file) {
return path.join(npmPluginPath, file);
}).filter(function(file) {
var stats = fs.statSync(file);
if (stats.isDirectory() && file.substr(npmPluginPath.length + 1, 14) === 'nodebb-plugin-') return true;
else return false;
});
});
}
next(err, dirs);
Plugins.showInstalled = function(callback) {
npmPluginPath = path.join(__dirname, '../node_modules');
async.waterfall([
function(next) {
fs.readdir(npmPluginPath, function(err, dirs) {
dirs = dirs.map(function(file) {
return path.join(npmPluginPath, file);
}).filter(function(file) {
var stats = fs.statSync(file);
if (stats.isDirectory() && file.substr(npmPluginPath.length + 1, 14) === 'nodebb-plugin-') return true;
else return false;
});
},
function(files, next) {
var plugins = [];
async.each(files, function(file, next) {
var configPath;
async.waterfall([
function(next) {
fs.readFile(path.join(file, 'plugin.json'), next);
},
function(configJSON, next) {
try {
var config = JSON.parse(configJSON);
} catch (err) {
winston.warn("Plugin: " + file + " is corrupted or invalid. Please check plugin.json for errors.")
return next(err, null);
}
_self.isActive(config.id, function(err, active) {
if (err) next(new Error('no-active-state'));
delete config.library;
delete config.hooks;
config.active = active;
config.activeText = '<i class="fa fa-power-off"></i> ' + (active ? 'Dea' : 'A') + 'ctivate';
next(null, config);
});
next(err, dirs);
});
},
function(files, next) {
var plugins = [];
async.each(files, function(file, next) {
var configPath;
async.waterfall([
function(next) {
fs.readFile(path.join(file, 'plugin.json'), next);
},
function(configJSON, next) {
try {
var config = JSON.parse(configJSON);
} catch (err) {
winston.warn("Plugin: " + file + " is corrupted or invalid. Please check plugin.json for errors.")
return next(err, null);
}
], function(err, config) {
if (err) return next(); // Silently fail
plugins.push(config);
next();
});
}, function(err) {
next(null, plugins);
});
}
], function(err, plugins) {
callback(err, plugins);
});
}
}
Plugins.isActive(config.id, function(err, active) {
if (err) {
next(new Error('no-active-state'));
}
plugins.init();
delete config.library;
delete config.hooks;
config.active = active;
config.activeText = '<i class="fa fa-power-off"></i> ' + (active ? 'Dea' : 'A') + 'ctivate';
next(null, config);
});
}
], function(err, config) {
if (err) return next(); // Silently fail
module.exports = plugins;
plugins.push(config);
next();
});
}, function(err) {
next(null, plugins);
});
}
], function(err, plugins) {
callback(err, plugins);
});
}
}(exports));

@ -1,4 +1,4 @@
var RDB = require('./redis'),
var db = require('./database'),
posts = require('./posts'),
topics = require('./topics'),
threadTools = require('./threadTools'),
@ -10,16 +10,14 @@ var RDB = require('./redis'),
utils = require('../public/src/utils'),
plugins = require('./plugins'),
reds = require('reds'),
postSearch = reds.createSearch('nodebbpostsearch'),
topicSearch = reds.createSearch('nodebbtopicsearch'),
winston = require('winston'),
meta = require('./meta'),
Feed = require('./feed');
(function(PostTools) {
PostTools.isMain = function(pid, tid, callback) {
RDB.lrange('tid:' + tid + ':posts', 0, 0, function(err, pids) {
db.getListRange('tid:' + tid + ':posts', 0, 0, function(err, pids) {
if(err) {
return callback(err);
}
@ -83,8 +81,8 @@ var RDB = require('./redis'),
}
]);
postSearch.remove(pid, function() {
postSearch.index(content, pid);
db.searchRemove('post', pid, function() {
db.searchIndex('post', content, pid);
});
async.parallel([
@ -93,8 +91,8 @@ var RDB = require('./redis'),
PostTools.isMain(pid, tid, function(err, isMainPost) {
if (isMainPost) {
topics.setTopicField(tid, 'title', title);
topicSearch.remove(tid, function() {
topicSearch.index(title, tid);
db.searchRemove('topic', tid, function() {
db.searchIndex('topic', title, tid);
});
}
@ -131,14 +129,14 @@ var RDB = require('./redis'),
PostTools.delete = function(uid, pid, callback) {
var success = function() {
posts.setPostField(pid, 'deleted', 1);
RDB.decr('totalpostcount');
postSearch.remove(pid);
db.decrObjectField('global', 'postCount');
db.searchRemove('post', pid);
posts.getPostFields(pid, ['tid', 'uid'], function(err, postData) {
RDB.hincrby('topic:' + postData.tid, 'postcount', -1);
db.incrObjectFieldBy('topic:' + postData.tid, 'postcount', -1);
user.decrementUserFieldBy(postData.uid, 'postcount', 1, function(err, postcount) {
RDB.zadd('users:postcount', postcount, postData.uid);
db.sortedSetAdd('users:postcount', postcount, postData.uid);
});
// Delete the thread if it is the last undeleted post
@ -164,7 +162,7 @@ var RDB = require('./redis'),
};
posts.getPostField(pid, 'deleted', function(err, deleted) {
if(deleted === '1') {
if(parseInt(deleted, 10) === 1) {
return callback(new Error('Post already deleted!'));
}
@ -180,10 +178,10 @@ var RDB = require('./redis'),
PostTools.restore = function(uid, pid, callback) {
var success = function() {
posts.setPostField(pid, 'deleted', 0);
RDB.incr('totalpostcount');
db.incrObjectField('global', 'postCount');
posts.getPostFields(pid, ['tid', 'uid', 'content'], function(err, postData) {
RDB.hincrby('topic:' + postData.tid, 'postcount', 1);
db.incrObjectFieldBy('topic:' + postData.tid, 'postcount', 1);
user.incrementUserFieldBy(postData.uid, 'postcount', 1);
@ -195,7 +193,7 @@ var RDB = require('./redis'),
// Restore topic if it is the only post
topics.getTopicField(postData.tid, 'postcount', function(err, count) {
if (count === '1') {
if (parseInt(count, 10) === 1) {
threadTools.restore(postData.tid, uid);
}
});
@ -203,14 +201,14 @@ var RDB = require('./redis'),
Feed.updateTopic(postData.tid);
Feed.updateRecent();
postSearch.index(postData.content, pid);
db.searchIndex('post', postData.content, pid);
callback();
});
};
posts.getPostField(pid, 'deleted', function(err, deleted) {
if(deleted === '0') {
if(parseInt(deleted, 10) === 0) {
return callback(new Error('Post already restored'));
}

@ -1,4 +1,4 @@
var RDB = require('./redis'),
var db = require('./database'),
utils = require('./../public/src/utils'),
user = require('./user'),
topics = require('./topics'),
@ -12,8 +12,6 @@ var RDB = require('./redis'),
meta = require('./meta'),
async = require('async'),
reds = require('reds'),
postSearch = reds.createSearch('nodebbpostsearch'),
nconf = require('nconf'),
validator = require('validator'),
winston = require('winston');
@ -23,18 +21,17 @@ var RDB = require('./redis'),
Posts.create = function(uid, tid, content, callback) {
if (uid === null) {
callback(new Error('invalid-user'), null);
return;
return callback(new Error('invalid-user'), null);
}
topics.isLocked(tid, function(err, locked) {
if(err) {
return callback(err, null);
} else if(locked) {
callback(new Error('topic-locked'), null);
return callback(new Error('topic-locked'), null);
}
RDB.incr('global:next_post_id', function(err, pid) {
db.incrObjectField('global', 'nextPid', function(err, pid) {
if(err) {
return callback(err, null);
}
@ -57,7 +54,7 @@ var RDB = require('./redis'),
'deleted': 0
};
RDB.hmset('post:' + pid, postData);
db.setObject('post:' + pid, postData);
postData.favourited = false;
postData.display_moderator_tools = true;
@ -67,26 +64,24 @@ var RDB = require('./redis'),
topics.increasePostCount(tid);
topics.updateTimestamp(tid, timestamp);
RDB.incr('totalpostcount');
db.incrObjectField('global', 'postCount');
topics.getTopicFields(tid, ['cid', 'pinned'], function(err, topicData) {
RDB.handle(err);
var cid = topicData.cid;
feed.updateTopic(tid);
feed.updateRecent();
RDB.zadd('categories:recent_posts:cid:' + cid, timestamp, pid);
db.sortedSetAdd('categories:recent_posts:cid:' + cid, timestamp, pid);
if(topicData.pinned === '0') {
RDB.zadd('categories:' + cid + ':tid', timestamp, tid);
if(parseInt(topicData.pinned, 10) === 0) {
db.sortedSetAdd('categories:' + cid + ':tid', timestamp, tid);
}
RDB.scard('cid:' + cid + ':active_users', function(err, amount) {
db.setCount('cid:' + cid + ':active_users', function(err, amount) {
if (amount > 15) {
RDB.spop('cid:' + cid + ':active_users');
db.setRemoveRandom('cid:' + cid + ':active_users');
}
categories.addActiveUser(cid, uid);
@ -110,7 +105,7 @@ var RDB = require('./redis'),
plugins.fireHook('action:post.save', postData);
postSearch.index(content, pid);
db.searchIndex('post', content, pid);
callback(null, postData);
});
@ -158,7 +153,7 @@ var RDB = require('./redis'),
return next(err);
}
RDB.del('cid:' + cid + ':read_by_uid');
db.delete('cid:' + cid + ':read_by_uid');
next();
});
},
@ -185,8 +180,10 @@ var RDB = require('./redis'),
}
Posts.getPostsByTid = function(tid, start, end, callback) {
RDB.lrange('tid:' + tid + ':posts', start, end, function(err, pids) {
RDB.handle(err);
db.getListRange('tid:' + tid + ':posts', start, end, function(err, pids) {
if(err) {
return callback(err);
}
if (pids.length) {
plugins.fireHook('filter:post.getTopic', pids, function(err, posts) {
@ -216,7 +213,7 @@ var RDB = require('./redis'),
post.userslug = userData.userslug || '';
post.user_rep = userData.reputation || 0;
post.user_postcount = userData.postcount || 0;
post.user_banned = userData.banned === '1';
post.user_banned = parseInt(userData.banned, 10) === 1;
post.picture = userData.picture || require('gravatar').url('', {}, https = nconf.get('https'));
post.signature = signature;
@ -253,7 +250,7 @@ var RDB = require('./redis'),
async.waterfall([
function(next) {
Posts.getPostFields(pid, ['pid', 'tid', 'content', 'uid', 'timestamp', 'deleted'], function(err, postData) {
if (postData.deleted === '1') {
if (parseInt(postData.deleted, 10) === 1) {
return callback(null);
} else {
postData.relativeTime = new Date(parseInt(postData.timestamp || 0, 10)).toISOString();
@ -270,7 +267,7 @@ var RDB = require('./redis'),
topics.getTopicFields(postData.tid, ['title', 'cid', 'slug', 'deleted'], function(err, topicData) {
if (err) {
return callback(err);
} else if (topicData.deleted === '1') {
} else if (parseInt(topicData.deleted, 10) === 1) {
return callback(null);
}
categories.getCategoryFields(topicData.cid, ['name', 'icon', 'slug'], function(err, categoryData) {
@ -313,7 +310,7 @@ var RDB = require('./redis'),
};
Posts.getPostData = function(pid, callback) {
RDB.hgetall('post:' + pid, function(err, data) {
db.getObject('post:' + pid, function(err, data) {
if(err) {
return callback(err, null);
}
@ -328,7 +325,7 @@ var RDB = require('./redis'),
}
Posts.getPostFields = function(pid, fields, callback) {
RDB.hmgetObject('post:' + pid, fields, function(err, data) {
db.getObjectFields('post:' + pid, fields, function(err, data) {
if(err) {
return callback(err, null);
}
@ -358,7 +355,7 @@ var RDB = require('./redis'),
}
Posts.setPostField = function(pid, field, value, callback) {
RDB.hset('post:' + pid, field, value, callback);
db.setObjectField('post:' + pid, field, value, callback);
plugins.fireHook('action:post.setField', {
'pid': pid,
'field': field,
@ -367,28 +364,27 @@ var RDB = require('./redis'),
}
Posts.getPostsByPids = function(pids, callback) {
var posts = [],
multi = RDB.multi();
var keys = [];
for(var x=0, numPids=pids.length; x<numPids; x++) {
multi.hgetall("post:" + pids[x]);
keys.push('post:' + pids[x]);
}
multi.exec(function (err, replies) {
async.map(replies, function(postData, _callback) {
db.getObjects(keys, function(err, data) {
async.map(data, function(postData, _callback) {
if (postData) {
try {
postData.relativeTime = new Date(parseInt(postData.timestamp,10)).toISOString();
postData.relativeEditTime = postData.edited !== '0' ? (new Date(parseInt(postData.edited,10)).toISOString()) : '';
postData.relativeEditTime = parseInt(postData.edited, 10) !== 0 ? (new Date(parseInt(postData.edited, 10)).toISOString()) : '';
} catch(e) {
winston.err('invalid time value');
}
postTools.parse(postData.content, function(err, content) {
postData.content = content;
postTools.parse(postData.content, function(err, content) {
postData.content = content;
_callback(null, postData);
});
});
} else {
_callback(null);
}
@ -399,7 +395,7 @@ var RDB = require('./redis'),
return callback(err, null);
}
});
})
});
}
Posts.getCidByPid = function(pid, callback) {
@ -460,8 +456,10 @@ var RDB = require('./redis'),
Posts.getPostsByUid = function(uid, start, end, callback) {
user.getPostIds(uid, start, end, function(pids) {
if (pids && pids.length) {
plugins.fireHook('filter:post.getTopic', pids, function(err, posts) {
if (!err & 0 < posts.length) {
Posts.getPostsByPids(pids, function(err, posts) {
plugins.fireHook('action:post.gotTopic', posts);
@ -482,10 +480,10 @@ var RDB = require('./redis'),
function reIndex(pid, callback) {
Posts.getPostField(pid, 'content', function(err, content) {
postSearch.remove(pid, function() {
db.searchRemove('post', pid, function() {
if (content && content.length) {
postSearch.index(content, pid);
db.searchIndex('post', content, pid);
}
callback(null);
});
@ -502,7 +500,7 @@ var RDB = require('./redis'),
}
Posts.getFavourites = function(uid, callback) {
RDB.zrevrange('uid:' + uid + ':favourites', 0, -1, function(err, pids) {
db.getSortedSetRevRange('uid:' + uid + ':favourites', 0, -1, function(err, pids) {
if (err)
return callback(err, null);

@ -1,78 +0,0 @@
(function(module) {
'use strict';
var RedisDB,
redis = require('redis'),
utils = require('./../public/src/utils.js'),
winston = require('winston'),
nconf = require('nconf'),
redis_socket_or_host = nconf.get('redis:host');
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 */
RedisDB = redis.createClient(nconf.get('redis:host'));
} else {
/* Else, connect over tcp/ip */
RedisDB = redis.createClient(nconf.get('redis:port'), nconf.get('redis:host'));
}
if (nconf.get('redis:password')) {
RedisDB.auth(nconf.get('redis:password'));
}
var db = parseInt(nconf.get('redis:database'), 10);
if (db){
RedisDB.select(db, function(error){
if(error !== null){
winston.error("NodeBB could not connect to your Redis database. Redis returned the following error: " + error.message);
process.exit();
}
});
}
RedisDB.handle = function(error) {
if (error !== null) {
winston.err(error);
if (global.env !== 'production') {
throw new Error(error);
}
}
};
/*
* A possibly more efficient way of doing multiple sismember calls
*/
RedisDB.sismembers = function(key, needles, callback) {
var tempkey = key + ':temp:' + utils.generateUUID();
RedisDB.sadd(tempkey, needles, function() {
RedisDB.sinter(key, tempkey, function(err, data) {
RedisDB.del(tempkey);
callback(err, data);
});
});
};
/*
* gets fields of a hash as an object instead of an array
*/
RedisDB.hmgetObject = function(key, fields, callback) {
RedisDB.hmget(key, fields, function(err, data) {
if(err) {
return callback(err, null);
}
var returnData = {};
for (var i = 0, ii = fields.length; i < ii; ++i) {
returnData[fields[i]] = data[i];
}
callback(null, returnData);
});
};
module.exports = RedisDB;
}(module));

@ -3,7 +3,7 @@ var nconf = require('nconf'),
path = require('path'),
winston = require('winston'),
RDB = require('./../redis'),
db = require('./../database'),
user = require('./../user'),
groups = require('../groups'),
topics = require('./../topics'),
@ -55,7 +55,7 @@ var nconf = require('nconf'),
(function () {
var routes = [
'categories/active', 'categories/disabled', 'users', 'topics', 'settings', 'themes',
'twitter', 'facebook', 'gplus', 'redis', 'motd', 'groups', 'plugins', 'logger',
'twitter', 'facebook', 'gplus', 'database', 'motd', 'groups', 'plugins', 'logger',
'users/latest', 'users/sort-posts', 'users/sort-reputation',
'users/search'
];
@ -241,7 +241,7 @@ var nconf = require('nconf'),
app.get('/categories/active', function (req, res) {
categories.getAllCategories(0, function (err, data) {
data.categories = data.categories.filter(function (category) {
return (!category.disabled || category.disabled === "0");
return (!category.disabled || parseInt(category.disabled, 10) === 0);
});
res.json(data);
});
@ -250,7 +250,7 @@ var nconf = require('nconf'),
app.get('/categories/disabled', function (req, res) {
categories.getAllCategories(0, function (err, data) {
data.categories = data.categories.filter(function (category) {
return category.disabled === "1";
return parseInt(category.disabled, 10) === 1;
});
res.json(data);
});
@ -265,31 +265,10 @@ var nconf = require('nconf'),
});
});
app.namespace('/redis', function () {
app.namespace('/database', function () {
app.get('/', function (req, res) {
RDB.info(function (err, data) {
data = data.split("\r\n");
var finalData = {};
for (var i in data) {
if (data[i].indexOf(':') == -1 || !data[i])
continue;
try {
data[i] = data[i].replace(/:/, "\":\"");
var json = "{\"" + data[i] + "\"}";
var jsonObject = JSON.parse(json);
for (var key in jsonObject) {
finalData[key] = jsonObject[key];
}
} catch (err) {
winston.warn('can\'t parse redis status variable, ignoring', i, data[i], err);
}
}
res.json(finalData);
db.info(function (err, data) {
res.json(data);
});
});

@ -42,7 +42,7 @@ var path = require('path'),
var uid = (req.user) ? req.user.uid : 0;
categories.getAllCategories(uid, function (err, data) {
data.categories = data.categories.filter(function (category) {
return (!category.disabled || category.disabled === "0");
return (!category.disabled || parseInt(category.disabled, 10) === 0);
});
function iterator(category, callback) {
@ -54,7 +54,7 @@ var path = require('path'),
}
async.each(data.categories, iterator, function (err) {
data.motd_class = (meta.config.show_motd === '1' || meta.config.show_motd === undefined) ? '' : ' none';
data.motd_class = (parseInt(meta.config.show_motd, 10) === 1 || meta.config.show_motd === undefined) ? '' : ' none';
data.motd_class += (meta.config.motd && meta.config.motd.length > 0 ? '' : ' default');
data.motd = require('marked')(meta.config.motd || "<div class=\"pull-right btn-group\"><a target=\"_blank\" href=\"http://www.nodebb.org\" class=\"btn btn-default btn-lg\"><i class=\"fa fa-comment\"></i><span class='hidden-mobile'>&nbsp;Get NodeBB</span></a> <a target=\"_blank\" href=\"https://github.com/designcreateplay/NodeBB\" class=\"btn btn-default btn-lg\"><i class=\"fa fa-github\"></i><span class='hidden-mobile'>&nbsp;Fork us on Github</span></a> <a target=\"_blank\" href=\"https://twitter.com/dcplabs\" class=\"btn btn-default btn-lg\"><i class=\"fa fa-twitter\"></i><span class='hidden-mobile'>&nbsp;@dcplabs</span></a></div>\n\n# NodeBB <span>v" + pkg.version + "</span>\nWelcome to NodeBB, the discussion platform of the future.");
@ -117,7 +117,7 @@ var path = require('path'),
var uid = (req.user) ? req.user.uid : 0;
topics.getTopicWithPosts(req.params.id, uid, 0, 10, false, function (err, data) {
if (!err) {
if (data.deleted === '1' && data.expose_tools === 0) {
if (parseInt(data.deleted, 10) === 1 && parseInt(data.expose_tools, 10) === 0) {
return res.json(404, {});
}
res.json(data);
@ -132,10 +132,11 @@ var path = require('path'),
categoryTools.privileges(req.params.id, uid, function(err, privileges) {
if (!err && privileges.read) {
categories.getCategoryById(req.params.id, uid, function (err, data) {
if (!err && data && data.disabled === "0")
if (!err && data && parseInt(data.disabled, 10) === 0) {
res.json(data);
else
} else {
next();
}
}, req.params.id, uid);
} else {
res.send(403);
@ -225,18 +226,8 @@ var path = require('path'),
app.get('/search/:term', function (req, res, next) {
var reds = require('reds');
var postSearch = reds.createSearch('nodebbpostsearch');
var topicSearch = reds.createSearch('nodebbtopicsearch');
function search(searchObj, callback) {
searchObj
.query(query = req.params.term).type('or')
.end(callback);
}
function searchPosts(callback) {
search(postSearch, function (err, pids) {
db.search('post', req.params.term, function(err, pids) {
if (err) {
return callback(err, null);
}
@ -247,11 +238,11 @@ var path = require('path'),
}
callback(null, posts);
});
})
});
}
function searchTopics(callback) {
search(topicSearch, function (err, tids) {
db.search('topic', req.params.term, function(err, tids) {
if (err) {
return callback(err, null);
}

@ -14,8 +14,11 @@
passport.use(new passportLocal(function(user, password, next) {
login_module.loginViaLocal(user, password, function(err, login) {
if (!err) next(null, login.user);
else next(null, false, err);
if (!err) {
next(null, login.user);
} else {
next(null, false, err);
}
});
}));

@ -78,11 +78,6 @@ var DebugRoute = function(app) {
});
});
});
app.get('/test', function(req, res) {
topics.pushUnreadCount();
res.send();
});
});
};

@ -9,7 +9,7 @@ var fs = require('fs'),
postTools = require('../postTools'),
utils = require('./../../public/src/utils'),
meta = require('./../meta'),
RDB = require('./../redis'),
db = require('./../database'),
websockets = require('./../websockets');
(function (User) {
@ -318,10 +318,11 @@ var fs = require('fs'),
return next(err);
if (userData) {
if (userData.showemail && userData.showemail === "1")
if (userData.showemail && parseInt(userData.showemail, 10) === 1) {
userData.showemail = "checked";
else
} else {
userData.showemail = "";
}
res.json(userData);
} else {
res.json(404, {
@ -382,13 +383,15 @@ var fs = require('fs'),
posts.getPostsByUid(userData.theirid, 0, 9, function (posts) {
userData.posts = posts.filter(function (p) {
return p && p.deleted !== "1";
return p && parseInt(p.deleted, 10) !== 1;
});
userData.isFollowing = isFollowing;
if (!userData.profileviews)
if (!userData.profileviews) {
userData.profileviews = 1;
if (callerUID !== userData.uid)
}
if (callerUID !== userData.uid) {
user.incrementUserFieldBy(userData.uid, 'profileviews', 1);
}
postTools.parse(userData.signature, function (err, signature) {
userData.signature = signature;
@ -454,7 +457,7 @@ var fs = require('fs'),
if(websockets.isUserOnline(user.uid)) {
onlineUsers.push(user);
} else {
RDB.zrem('users:online', user.uid);
db.sortedSetRemove('users:online', user.uid);
}
callback(null);
}
@ -501,21 +504,21 @@ var fs = require('fs'),
}
function canSeeEmail() {
return callerUID == uid || (data.email && (data.showemail && data.showemail === "1"));
return callerUID == uid || (data.email && (data.showemail && parseInt(data.showemail, 10) === 1));
}
if (!canSeeEmail()) {
data.email = "";
}
if (callerUID == uid && (!data.showemail || data.showemail === "0")) {
if (callerUID == uid && (!data.showemail || parseInt(data.showemail, 10) === 0)) {
data.emailClass = "";
} else {
data.emailClass = "hide";
}
data.websiteName = data.website.replace('http://', '').replace('https://', '');
data.banned = data.banned === '1';
data.banned = parseInt(data.banned, 10) === 1;
data.uid = uid;
data.yourid = callerUID;
data.theirid = uid;

@ -45,7 +45,7 @@ var path = require('path'),
var topicUrls = [];
topics.getAllTopics(null, null, function(err, topics) {
topics.forEach(function(topic) {
if (topic.deleted !== '1') {
if (parseInt(topic.deleted, 10) !== 1) {
topicUrls.push({
url: path.join('topic', topic.slug),
changefreq: 'daily',

@ -1,4 +1,4 @@
var RDB = require('./redis'),
var db = require('./database'),
topics = require('./topics'),
categories = require('./categories'),
CategoryTools = require('./categoryTools'),
@ -8,18 +8,20 @@ var RDB = require('./redis'),
posts = require('./posts'),
meta = require('./meta'),
websockets = require('./websockets');
reds = require('reds'),
topicSearch = reds.createSearch('nodebbtopicsearch'),
winston = require('winston'),
nconf = require('nconf'),
(function(ThreadTools) {
ThreadTools.exists = function(tid, callback) {
RDB.sismember('topics:tid', tid, function(err, ismember) {
if (err) RDB.handle(err);
callback( !! ismember || false);
db.isSetMember('topics:tid', tid, function(err, ismember) {
if (err) {
callback(false);
}
callback(ismember);
});
}
@ -85,11 +87,11 @@ var RDB = require('./redis'),
ThreadTools.delete = function(tid, callback) {
topics.delete(tid);
RDB.decr('totaltopiccount');
db.decrObjectField('global', 'topicCount');
ThreadTools.lock(tid);
topicSearch.remove(tid);
db.searchRemove('topic', tid);
websockets.in('topic_' + tid).emit('event:topic_deleted', {
tid: tid,
@ -103,7 +105,7 @@ var RDB = require('./redis'),
ThreadTools.restore = function(tid, socket, callback) {
topics.restore(tid);
RDB.incr('totaltopiccount');
db.incrObjectField('global', 'topicCount');
ThreadTools.unlock(tid);
websockets.in('topic_' + tid).emit('event:topic_restored', {
@ -112,7 +114,7 @@ var RDB = require('./redis'),
});
topics.getTopicField(tid, 'title', function(err, title) {
topicSearch.index(title, tid);
db.searchIndex('topic', title, tid);
});
if(callback) {
@ -123,7 +125,7 @@ var RDB = require('./redis'),
ThreadTools.pin = function(tid, socket) {
topics.setTopicField(tid, 'pinned', 1);
topics.getTopicField(tid, 'cid', function(err, cid) {
RDB.zadd('categories:' + cid + ':tid', Math.pow(2, 53), tid);
db.sortedSetAdd('categories:' + cid + ':tid', Math.pow(2, 53), tid);
});
if (socket) {
@ -144,7 +146,7 @@ var RDB = require('./redis'),
ThreadTools.unpin = function(tid, socket) {
topics.setTopicField(tid, 'pinned', 0);
topics.getTopicFields(tid, ['cid', 'lastposttime'], function(err, topicData) {
RDB.zadd('categories:' + topicData.cid + ':tid', topicData.lastposttime, tid);
db.sortedSetAdd('categories:' + topicData.cid + ':tid', topicData.lastposttime, tid);
});
if (socket) {
websockets.in('topic_' + tid).emit('event:topic_unpinned', {
@ -165,14 +167,16 @@ var RDB = require('./redis'),
topics.getTopicFields(tid, ['cid', 'lastposttime'], function(err, topicData) {
var oldCid = topicData.cid;
var multi = RDB.multi();
multi.zrem('categories:' + oldCid + ':tid', tid);
multi.zadd('categories:' + cid + ':tid', topicData.lastposttime, tid);
db.sortedSetRemove('categories:' + oldCid + ':tid', tid, function(err, result) {
db.sortedSetAdd('categories:' + cid + ':tid', topicData.lastposttime, tid, function(err, result) {
multi.exec(function(err, result) {
if (!err && result[0] === 1 && result[1] === 1) {
if(err) {
socket.emit('api:topic.move', {
status: 'error'
});
return;
}
topics.setTopicField(tid, 'cid', cid);
@ -198,17 +202,13 @@ var RDB = require('./redis'),
websockets.in('topic_' + tid).emit('event:topic_moved', {
tid: tid
});
} else {
socket.emit('api:topic.move', {
status: 'error'
});
}
});
});
});
}
ThreadTools.isFollowing = function(tid, current_user, callback) {
RDB.sismember('tid:' + tid + ':followers', current_user, function(err, following) {
db.isSetMember('tid:' + tid + ':followers', current_user, function(err, following) {
callback(following);
});
}
@ -216,7 +216,7 @@ var RDB = require('./redis'),
ThreadTools.toggleFollow = function(tid, current_user, callback) {
ThreadTools.isFollowing(tid, current_user, function(following) {
if (!following) {
RDB.sadd('tid:' + tid + ':followers', current_user, function(err, success) {
db.setAdd('tid:' + tid + ':followers', current_user, function(err, success) {
if (callback) {
if (!err) {
callback({
@ -229,7 +229,7 @@ var RDB = require('./redis'),
}
});
} else {
RDB.srem('tid:' + tid + ':followers', current_user, function(err, success) {
db.setRemove('tid:' + tid + ':followers', current_user, function(err, success) {
if (callback) {
if (!err) {
callback({
@ -246,7 +246,7 @@ var RDB = require('./redis'),
}
ThreadTools.getFollowers = function(tid, callback) {
RDB.smembers('tid:' + tid + ':followers', function(err, followers) {
db.getSetMembers('tid:' + tid + ':followers', function(err, followers) {
callback(err, followers.map(function(follower) {
return parseInt(follower, 10);
}));
@ -274,24 +274,33 @@ var RDB = require('./redis'),
});
}
], function(err, results) {
if (!err) notifications.push(results[0], results[1]);
// Otherwise, do nothing
if (!err) {
notifications.push(results[0], results[1]);
}
});
}
ThreadTools.getLatestUndeletedPid = function(tid, callback) {
RDB.lrange('tid:' + tid + ':posts', 0, -1, function(err, pids) {
if (pids.length === 0) return callback(new Error('no-undeleted-pids-found'));
db.getListRange('tid:' + tid + ':posts', 0, -1, function(err, pids) {
if (pids.length === 0) {
return callback(new Error('no-undeleted-pids-found'));
}
pids.reverse();
async.detectSeries(pids, function(pid, next) {
posts.getPostField(pid, 'deleted', function(err, deleted) {
if (deleted === '0') next(true);
else next(false);
if (parseInt(deleted, 10) === 0) {
next(true);
} else {
next(false);
}
});
}, function(pid) {
if (pid) callback(null, pid);
else callback(new Error('no-undeleted-pids-found'));
if (pid) {
callback(null, pid);
} else {
callback(new Error('no-undeleted-pids-found'));
}
});
});
}

@ -2,10 +2,8 @@ var async = require('async'),
gravatar = require('gravatar'),
nconf = require('nconf'),
validator = require('validator'),
reds = require('reds'),
topicSearch = reds.createSearch('nodebbtopicsearch'),
RDB = require('./redis'),
db = require('./database'),
posts = require('./posts'),
utils = require('./../public/src/utils'),
user = require('./user'),
@ -60,16 +58,16 @@ var async = require('async'),
return callback(new Error('too-many-posts'), null);
}
RDB.incr('next_topic_id', function(err, tid) {
db.incrObjectField('global', 'nextTid', function(err, tid) {
if(err) {
return callback(err);
}
RDB.sadd('topics:tid', tid);
db.setAdd('topics:tid', tid);
var slug = tid + '/' + utils.slugify(title);
var timestamp = Date.now();
RDB.hmset('topic:' + tid, {
db.setObject('topic:' + tid, {
'tid': tid,
'uid': uid,
'cid': cid,
@ -84,19 +82,19 @@ var async = require('async'),
'pinned': 0
});
topicSearch.index(title, tid);
db.searchIndex('topic', title, tid);
user.addTopicIdToUser(uid, tid);
// let everyone know that there is an unread topic in this category
RDB.del('cid:' + cid + ':read_by_uid', function(err, data) {
db.delete('cid:' + cid + ':read_by_uid', function(err, data) {
Topics.markAsRead(tid, uid);
});
// in future it may be possible to add topics to several categories, so leaving the door open here.
RDB.zadd('categories:' + cid + ':tid', timestamp, tid);
RDB.hincrby('category:' + cid, 'topic_count', 1);
RDB.incr('totaltopiccount');
db.sortedSetAdd('categories:' + cid + ':tid', timestamp, tid);
db.incrObjectField('category:' + cid, 'topic_count');
db.incrObjectField('global', 'topicCount');
feed.updateCategory(cid);
@ -127,7 +125,7 @@ var async = require('async'),
};
Topics.getTopicData = function(tid, callback) {
RDB.hgetall('topic:' + tid, function(err, data) {
db.getObject('topic:' + tid, function(err, data) {
if(err) {
return callback(err, null);
}
@ -173,7 +171,7 @@ var async = require('async'),
}
postData = postData.filter(function(post) {
return parseInt(current_user, 10) !== 0 || post.deleted === "0";
return parseInt(current_user, 10) !== 0 || parseInt(post.deleted, 10) === 0;
});
function getFavouritesData(next) {
@ -209,7 +207,7 @@ var async = require('async'),
privileges = results[2];
for (var i = 0; i < postData.length; ++i) {
postData[i].favourited = fav_data[postData[i].pid] === 1;
postData[i].favourited = fav_data[postData[i].pid];
postData[i].display_moderator_tools = ((current_user != 0) && (postData[i].uid == current_user || privileges.editable));
}
@ -239,8 +237,7 @@ var async = require('async'),
since = terms[term];
var args = ['topics:recent', '+inf', timestamp - since, 'LIMIT', start, end - start + 1];
RDB.zrevrangebyscore(args, function(err, tids) {
db.getSortedSetRevRangeByScore(args, function(err, tids) {
if (err) {
return callback(err);
}
@ -275,7 +272,7 @@ var async = require('async'),
return unreadTids.length < 21 && !done;
},
function(callback) {
RDB.zrevrange('topics:recent', start, stop, function(err, tids) {
db.getSortedSetRevRange('topics:recent', start, stop, function(err, tids) {
if (err)
return callback(err);
@ -316,7 +313,7 @@ var async = require('async'),
}
async.whilst(continueCondition, function(callback) {
RDB.zrevrange('topics:recent', start, stop, function(err, tids) {
db.getSortedSetRevRange('topics:recent', start, stop, function(err, tids) {
if (err) {
return callback(err);
}
@ -489,18 +486,18 @@ var async = require('async'),
getTopicInfo(topicData, function(topicInfo) {
topicData['pin-icon'] = topicData.pinned === '1' ? 'fa-thumb-tack' : 'none';
topicData['lock-icon'] = topicData.locked === '1' ? 'fa-lock' : 'none';
topicData['deleted-class'] = topicData.deleted === '1' ? 'deleted' : '';
topicData['pin-icon'] = parseInt(topicData.pinned, 10) === 1 ? 'fa-thumb-tack' : 'none';
topicData['lock-icon'] = parseInt(topicData.locked, 10) === 1 ? 'fa-lock' : 'none';
topicData['deleted-class'] = parseInt(topicData.deleted, 10) === 1 ? 'deleted' : '';
topicData.unreplied = topicData.postcount === '1';
topicData.unreplied = parseInt(topicData.postcount, 10) === 1;
topicData.username = topicInfo.username || 'anonymous';
topicData.userslug = topicInfo.userslug || '';
topicData.picture = topicInfo.picture || gravatar.url('', {}, https = nconf.get('https'));
topicData.categoryIcon = topicInfo.categoryData.icon;
topicData.categoryName = topicInfo.categoryData.name;
topicData.categorySlug = topicInfo.categoryData.slug;
topicData.badgeclass = (topicInfo.hasread && current_user != 0) ? '' : 'badge-important';
topicData.badgeclass = (topicInfo.hasread && parseInt(current_user, 10) !== 0) ? '' : 'badge-important';
topicData.teaser_text = topicInfo.teaserInfo.text || '',
topicData.teaser_username = topicInfo.teaserInfo.username || '';
topicData.teaser_userslug = topicInfo.teaserInfo.userslug || '';
@ -577,7 +574,7 @@ var async = require('async'),
'slug': topicData.slug,
'postcount': topicData.postcount,
'viewcount': topicData.viewcount,
'unreplied': topicData.postcount > 1,
'unreplied': parseInt(topicData.postcount, 10) > 1,
'topic_id': tid,
'expose_tools': privileges.editable ? 1 : 0,
'posts': topicPosts
@ -594,7 +591,7 @@ var async = require('async'),
}
function getReadStatus(next) {
if (uid && parseInt(uid) > 0) {
if (uid && parseInt(uid, 10) > 0) {
Topics.hasReadTopic(tid, uid, function(read) {
next(null, read);
});
@ -619,8 +616,8 @@ var async = require('async'),
hasRead = results[1],
teaser = results[2];
topicData['pin-icon'] = topicData.pinned === '1' ? 'fa-thumb-tack' : 'none';
topicData['lock-icon'] = topicData.locked === '1' ? 'fa-lock' : 'none';
topicData['pin-icon'] = parseInt(topicData.pinned, 10) === 1 ? 'fa-thumb-tack' : 'none';
topicData['lock-icon'] = parseInt(topicData.locked, 10) === 1 ? 'fa-lock' : 'none';
topicData.badgeclass = hasRead ? '' : 'badge-important';
topicData.teaser_text = teaser.text || '';
@ -635,7 +632,7 @@ var async = require('async'),
}
Topics.getAllTopics = function(limit, after, callback) {
RDB.smembers('topics:tid', function(err, tids) {
db.getSetMembers('topics:tid', function(err, tids) {
if(err) {
return callback(err, null);
}
@ -681,7 +678,7 @@ var async = require('async'),
}
Topics.markAllRead = function(uid, callback) {
RDB.smembers('topics:tid', function(err, tids) {
db.getSetMembers('topics:tid', function(err, tids) {
if (err) {
return callback(err, null);
}
@ -705,12 +702,12 @@ var async = require('async'),
}
Topics.markUnRead = function(tid, callback) {
RDB.del('tid:' + tid + ':read_by_uid', callback);
db.delete('tid:' + tid + ':read_by_uid', callback);
}
Topics.markAsRead = function(tid, uid) {
RDB.sadd('tid:' + tid + ':read_by_uid', uid);
db.setAdd('tid:' + tid + ':read_by_uid', uid);
Topics.getTopicField(tid, 'cid', function(err, cid) {
@ -729,19 +726,19 @@ var async = require('async'),
}
Topics.hasReadTopics = function(tids, uid, callback) {
var batch = RDB.multi();
var sets = [];
for (var i = 0, ii = tids.length; i < ii; i++) {
batch.sismember('tid:' + tids[i] + ':read_by_uid', uid);
sets.push('tid:' + tids[i] + ':read_by_uid');
}
batch.exec(function(err, hasRead) {
db.isMemberOfSets(sets, uid, function(err, hasRead) {
callback(hasRead);
});
}
Topics.hasReadTopic = function(tid, uid, callback) {
RDB.sismember('tid:' + tid + ':read_by_uid', uid, function(err, hasRead) {
db.isSetMember('tid:' + tid + ':read_by_uid', uid, function(err, hasRead) {
if (err === null) {
callback(hasRead);
@ -757,7 +754,9 @@ var async = require('async'),
if (Array.isArray(tids)) {
async.eachSeries(tids, function(tid, next) {
Topics.getTeaser(tid, function(err, teaser_info) {
if (err) teaser_info = {};
if (err) {
teaser_info = {};
}
teasers.push(teaser_info);
next();
});
@ -821,23 +820,23 @@ var async = require('async'),
}
Topics.getTopicField = function(tid, field, callback) {
RDB.hget('topic:' + tid, field, callback);
db.getObjectField('topic:' + tid, field, callback);
}
Topics.getTopicFields = function(tid, fields, callback) {
RDB.hmgetObject('topic:' + tid, fields, callback);
db.getObjectFields('topic:' + tid, fields, callback);
}
Topics.setTopicField = function(tid, field, value, callback) {
RDB.hset('topic:' + tid, field, value, callback);
db.setObjectField('topic:' + tid, field, value, callback);
}
Topics.increasePostCount = function(tid, callback) {
RDB.hincrby('topic:' + tid, 'postcount', 1, callback);
db.incrObjectField('topic:' + tid, 'postcount', callback);
}
Topics.increaseViewCount = function(tid, callback) {
RDB.hincrby('topic:' + tid, 'viewcount', 1, callback);
db.incrObjectField('topic:' + tid, 'viewcount', callback);
}
Topics.isLocked = function(tid, callback) {
@ -845,21 +844,21 @@ var async = require('async'),
if(err) {
return callback(err, null);
}
callback(null, locked === "1");
callback(null, parseInt(locked, 10) === 1);
});
}
Topics.updateTimestamp = function(tid, timestamp) {
RDB.zadd('topics:recent', timestamp, tid);
db.sortedSetAdd('topics:recent', timestamp, tid);
Topics.setTopicField(tid, 'lastposttime', timestamp);
}
Topics.addPostToTopic = function(tid, pid) {
RDB.rpush('tid:' + tid + ':posts', pid);
db.listAppend('tid:' + tid + ':posts', pid);
}
Topics.getPids = function(tid, callback) {
RDB.lrange('tid:' + tid + ':posts', 0, -1, callback);
db.getListRange('tid:' + tid + ':posts', 0, -1, callback);
}
Topics.getUids = function(tid, callback) {
@ -886,23 +885,23 @@ var async = require('async'),
Topics.delete = function(tid) {
Topics.setTopicField(tid, 'deleted', 1);
RDB.zrem('topics:recent', tid);
db.sortedSetRemove('topics:recent', tid);
Topics.getTopicField(tid, 'cid', function(err, cid) {
feed.updateCategory(cid);
RDB.hincrby('category:' + cid, 'topic_count', -1);
db.incrObjectFieldBy('category:' + cid, 'topic_count', -1);
});
}
Topics.restore = function(tid) {
Topics.setTopicField(tid, 'deleted', 0);
Topics.getTopicField(tid, 'lastposttime', function(err, lastposttime) {
RDB.zadd('topics:recent', lastposttime, tid);
db.sortedSetAdd('topics:recent', lastposttime, tid);
});
Topics.getTopicField(tid, 'cid', function(err, cid) {
feed.updateCategory(cid);
RDB.hincrby('category:' + cid, 'topic_count', 1);
db.incrObjectFieldBy('category:' + cid, 'topic_count', 1);
});
}
@ -923,7 +922,7 @@ var async = require('async'),
}
Topics.reIndexAll = function(callback) {
RDB.smembers('topics:tid', function(err, tids) {
db.getSetMembers('topics:tid', function(err, tids) {
if (err) {
callback(err, null);
} else {

@ -1,19 +1,20 @@
"use strict";
var RDB = require('./redis.js'),
var db = require('./database'),
async = require('async'),
winston = require('winston'),
notifications = require('./notifications'),
categories = require('./categories'),
nconf = require('nconf'),
Upgrade = {},
schemaDate, thisSchemaDate;
Upgrade.check = function(callback) {
// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema
var latestSchema = new Date(2013, 10, 26).getTime();
var latestSchema = new Date(2013, 11, 2).getTime();
RDB.get('schemaDate', function(err, value) {
db.get('schemaDate', function(err, value) {
if (parseInt(value, 10) >= latestSchema) {
callback(true);
} else {
@ -23,6 +24,22 @@ Upgrade.check = function(callback) {
};
Upgrade.upgrade = function(callback) {
var databaseType = nconf.get('database');
if(databaseType === 'redis') {
Upgrade.upgradeRedis(callback);
} else if(databaseType === 'mongo') {
Upgrade.upgradeMongo(callback);
} else {
winston.error('Unknown database type. Aborting upgrade');
callback(new Error('unknown-database'));
}
};
Upgrade.upgradeRedis = function(callback) {
var RDB = db.client;
winston.info('Beginning Redis database schema update');
async.series([
@ -179,7 +196,7 @@ Upgrade.upgrade = function(callback) {
},
function(next) {
thisSchemaDate = new Date(2013, 10, 26).getTime();
if (schemaDate < thisSchemaDate || 1) {
if (schemaDate < thisSchemaDate) {
categories.getAllCategories(0, function(err, categories) {
function updateIcon(category, next) {
@ -209,7 +226,57 @@ Upgrade.upgrade = function(callback) {
winston.info('[2013/11/26] Update to Category icons skipped.');
next();
}
}
},
function(next) {
function updateKeyToHash(key, next) {
RDB.get(key, function(err, value) {
RDB.hset('global', newKeys[key], value, next);
});
}
thisSchemaDate = new Date(2013, 11, 2).getTime();
if (schemaDate < thisSchemaDate) {
var keys = [
'global:next_user_id',
'next_topic_id',
'next_gid',
'notifications:next_nid',
'global:next_category_id',
'global:next_message_id',
'global:next_post_id',
'usercount',
'totaltopiccount',
'totalpostcount'
];
var newKeys = {
'global:next_user_id':'nextUid',
'next_topic_id':'nextTid',
'next_gid':'nextGid',
'notifications:next_nid':'nextNid',
'global:next_category_id':'nextCid',
'global:next_message_id':'nextMid',
'global:next_post_id':'nextPid',
'usercount':'userCount',
'totaltopiccount':'topicCount',
'totalpostcount':'postCount'
};
async.each(keys, updateKeyToHash, function(err) {
if(err) {
return next(err);
}
winston.info('[2013/12/2] Updated global keys to hash.');
next();
});
} else {
winston.info('[2013/12/2] Update to global keys skipped');
next();
}
},
// Add new schema updates here
// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 12!!!
], function(err) {
@ -234,4 +301,41 @@ Upgrade.upgrade = function(callback) {
});
};
Upgrade.upgradeMongo = function(callback) {
var MDB = db.client;
winston.info('Beginning Mongo database schema update');
async.series([
function(next) {
db.get('schemaDate', function(err, value) {
schemaDate = value;
thisSchemaDate = new Date(2013, 11, 6).getTime();
next();
});
}
// Add new schema updates here
], function(err) {
if (!err) {
db.set('schemaDate', thisSchemaDate, function(err) {
if (!err) {
winston.info('[upgrade] Mongo schema update complete!');
if (callback) {
callback(err);
} else {
process.exit();
}
} else {
winston.error('[upgrade] Could not update NodeBB schema date!');
process.exit();
}
});
} else {
winston.error('[upgrade] Errors were encountered while updating the NodeBB schema: ' + err.message);
process.exit();
}
});
}
module.exports = Upgrade;

@ -4,16 +4,15 @@ var bcrypt = require('bcrypt'),
nconf = require('nconf'),
winston = require('winston'),
gravatar = require('gravatar'),
userSearch = require('reds').createSearch('nodebbusersearch'),
check = require('validator').check,
sanitize = require('validator').sanitize,
utils = require('./../public/src/utils'),
plugins = require('./plugins'),
RDB = require('./redis'),
db = require('./database'),
meta = require('./meta'),
emailjsServer = emailjs.server.connect(meta.config['email:smtp:host'] || '127.0.0.1'),
Groups = require('./groups'),
groups = require('./groups'),
notifications = require('./notifications'),
topics = require('./topics');
@ -65,16 +64,18 @@ var bcrypt = require('bcrypt'),
}
], function(err, results) {
if (err) {
return callback(err, null);
return callback(err);
}
RDB.incr('global:next_user_id', function(err, uid) {
RDB.handle(err);
db.incrObjectField('global', 'nextUid', function(err, uid) {
if(err) {
return callback(err);
}
var gravatar = User.createGravatarURLFromEmail(email);
var timestamp = Date.now();
RDB.hmset('user:' + uid, {
db.setObject('user:' + uid, {
'uid': uid,
'username': username,
'userslug': userslug,
@ -96,22 +97,22 @@ var bcrypt = require('bcrypt'),
'showemail': 0
});
RDB.hset('username:uid', username, uid);
RDB.hset('userslug:uid', userslug, uid);
db.setObjectField('username:uid', username, uid);
db.setObjectField('userslug:uid', userslug, uid);
if (email !== undefined) {
RDB.hset('email:uid', email, uid);
db.setObjectField('email:uid', email, uid);
User.sendConfirmationEmail(email);
}
plugins.fireHook('action:user.create', {uid: uid, username: username, email: email, picture: gravatar, timestamp: timestamp});
RDB.incr('usercount');
db.incrObjectField('global', 'userCount');
RDB.zadd('users:joindate', timestamp, uid);
RDB.zadd('users:postcount', 0, uid);
RDB.zadd('users:reputation', 0, uid);
db.sortedSetAdd('users:joindate', timestamp, uid);
db.sortedSetAdd('users:postcount', 0, uid);
db.sortedSetAdd('users:reputation', 0, uid);
userSearch.index(username, uid);
db.searchIndex('user', username, uid);
if (password !== undefined) {
User.hashPassword(password, function(err, hash) {
@ -134,11 +135,11 @@ var bcrypt = require('bcrypt'),
};
User.getUserField = function(uid, field, callback) {
RDB.hget('user:' + uid, field, callback);
db.getObjectField('user:' + uid, field, callback);
};
User.getUserFields = function(uid, fields, callback) {
RDB.hmgetObject('user:' + uid, fields, callback);
db.getObjectFields('user:' + uid, fields, callback);
};
User.getMultipleUserFields = function(uids, fields, callback) {
@ -168,7 +169,10 @@ var bcrypt = require('bcrypt'),
};
User.getUserData = function(uid, callback) {
RDB.hgetall('user:' + uid, function(err, data) {
db.getObject('user:' + uid, function(err, data) {
if(err) {
return callback(err);
}
if (data && data.password) {
delete data.password;
@ -177,12 +181,6 @@ var bcrypt = require('bcrypt'),
});
};
User.filterBannedUsers = function(users) {
return users.filter(function(user) {
return (!user.banned || user.banned === '0');
});
};
User.updateProfile = function(uid, data, callback) {
var fields = ['email', 'fullname', 'website', 'location', 'birthday', 'signature'];
@ -253,8 +251,8 @@ var bcrypt = require('bcrypt'),
return next(err);
}
RDB.hdel('email:uid', userData.email);
RDB.hset('email:uid', data.email, uid);
db.deleteObjectField('email:uid', userData.email);
db.setObjectField('email:uid', data.email, uid);
User.setUserField(uid, field, data[field]);
if (userData.picture !== userData.uploadedpicture) {
returnData.picture = gravatarpicture;
@ -282,7 +280,7 @@ var bcrypt = require('bcrypt'),
};
User.isEmailAvailable = function(email, callback) {
RDB.hexists('email:uid', email, function(err, exists) {
db.isObjectField('email:uid', email, function(err, exists) {
callback(err, !exists);
});
};
@ -316,25 +314,25 @@ var bcrypt = require('bcrypt'),
};
User.setUserField = function(uid, field, value, callback) {
RDB.hset('user:' + uid, field, value, callback);
db.setObjectField('user:' + uid, field, value, callback);
};
User.setUserFields = function(uid, data, callback) {
RDB.hmset('user:' + uid, data, callback);
db.setObject('user:' + uid, data, callback);
};
User.incrementUserFieldBy = function(uid, field, value, callback) {
RDB.hincrby('user:' + uid, field, value, callback);
db.incrObjectFieldBy('user:' + uid, field, value, callback);
};
User.decrementUserFieldBy = function(uid, field, value, callback) {
RDB.hincrby('user:' + uid, field, -value, callback);
db.incrObjectFieldBy('user:' + uid, field, -value, callback);
};
User.getUsers = function(set, start, stop, callback) {
var data = [];
RDB.zrevrange(set, start, stop, function(err, uids) {
db.getSortedSetRevRange(set, start, stop, function(err, uids) {
if (err) {
return callback(err, null);
}
@ -355,7 +353,6 @@ var bcrypt = require('bcrypt'),
callback(err, data);
});
});
};
User.createGravatarURLFromEmail = function(email) {
@ -392,8 +389,8 @@ var bcrypt = require('bcrypt'),
}
function reIndexUser(uid, username) {
userSearch.remove(uid, function() {
userSearch.index(username, uid);
db.searchRemove('user', uid, function() {
db.searchIndex('user', username, uid);
});
}
@ -409,7 +406,7 @@ var bcrypt = require('bcrypt'),
callback([]);
return;
}
userSearch.query(username).type('or').end(function(err, uids) {
db.search('user', username, function(err, uids) {
if (err) {
console.log(err);
return;
@ -428,7 +425,7 @@ var bcrypt = require('bcrypt'),
User.addPostIdToUser(uid, pid);
User.incrementUserFieldBy(uid, 'postcount', 1, function(err, newpostcount) {
RDB.zadd('users:postcount', newpostcount, uid);
db.sortedSetAdd('users:postcount', newpostcount, uid);
});
User.setUserField(uid, 'lastposttime', timestamp);
@ -437,15 +434,15 @@ var bcrypt = require('bcrypt'),
};
User.addPostIdToUser = function(uid, pid) {
RDB.lpush('uid:' + uid + ':posts', pid);
db.listPrepend('uid:' + uid + ':posts', pid);
};
User.addTopicIdToUser = function(uid, tid) {
RDB.lpush('uid:' + uid + ':topics', tid);
db.listPrepend('uid:' + uid + ':topics', tid);
};
User.getPostIds = function(uid, start, end, callback) {
RDB.lrange('uid:' + uid + ':posts', start, end, function(err, pids) {
User.getPostIds = function(uid, start, stop, callback) {
db.getListRange('uid:' + uid + ':posts', start, stop, function(err, pids) {
if (!err) {
if (pids && pids.length) {
callback(pids);
@ -459,51 +456,12 @@ var bcrypt = require('bcrypt'),
});
};
User.sendConfirmationEmail = function(email) {
if (meta.config['email:smtp:host'] && meta.config['email:smtp:port'] && meta.config['email:from']) {
var confirm_code = utils.generateUUID(),
confirm_link = nconf.get('url') + 'confirm/' + confirm_code,
confirm_email = global.templates['emails/header'] + global.templates['emails/email_confirm'].parse({
'CONFIRM_LINK': confirm_link
}) + global.templates['emails/footer'],
confirm_email_plaintext = global.templates['emails/email_confirm_plaintext'].parse({
'CONFIRM_LINK': confirm_link
});
// Email confirmation code
var expiry_time = 60 * 60 * 2, // Expire after 2 hours
email_key = 'email:' + email + ':confirm',
confirm_key = 'confirm:' + confirm_code + ':email';
RDB.set(email_key, confirm_code);
RDB.expire(email_key, expiry_time);
RDB.set(confirm_key, email);
RDB.expire(confirm_key, expiry_time);
// Send intro email w/ confirm code
var message = emailjs.message.create({
text: confirm_email_plaintext,
from: meta.config['email:from'] || 'localhost@example.org',
to: email,
subject: '[NodeBB] Registration Email Verification',
attachment: [{
data: confirm_email,
alternative: true
}]
});
emailjsServer.send(message, function(err, success) {
if (err) {
console.log(err);
}
});
}
};
User.follow = function(uid, followid, callback) {
RDB.sadd('following:' + uid, followid, function(err, data) {
db.setAdd('following:' + uid, followid, function(err, data) {
if (!err) {
RDB.sadd('followers:' + followid, uid, function(err, data) {
db.setAdd('followers:' + followid, uid, function(err, data) {
if (!err) {
callback(true);
} else {
@ -519,9 +477,9 @@ var bcrypt = require('bcrypt'),
};
User.unfollow = function(uid, unfollowid, callback) {
RDB.srem('following:' + uid, unfollowid, function(err, data) {
db.setRemove('following:' + uid, unfollowid, function(err, data) {
if (!err) {
RDB.srem('followers:' + unfollowid, uid, function(err, data) {
db.setRemove('followers:' + unfollowid, uid, function(err, data) {
callback(data);
});
} else {
@ -531,7 +489,7 @@ var bcrypt = require('bcrypt'),
};
User.getFollowing = function(uid, callback) {
RDB.smembers('following:' + uid, function(err, userIds) {
db.getSetMembers('following:' + uid, function(err, userIds) {
if (!err) {
User.getDataForUsers(userIds, callback);
} else {
@ -541,7 +499,7 @@ var bcrypt = require('bcrypt'),
};
User.getFollowers = function(uid, callback) {
RDB.smembers('followers:' + uid, function(err, userIds) {
db.getSetMembers('followers:' + uid, function(err, userIds) {
if (!err) {
User.getDataForUsers(userIds, callback);
} else {
@ -551,12 +509,12 @@ var bcrypt = require('bcrypt'),
};
User.getFollowingCount = function(uid, callback) {
RDB.smembers('following:' + uid, function(err, userIds) {
db.getSetMembers('following:' + uid, function(err, userIds) {
if (err) {
console.log(err);
} else {
userIds = userIds.filter(function(value) {
return value !== '0';
return parseInt(value, 10) !== 0;
});
callback(userIds.length);
}
@ -564,12 +522,12 @@ var bcrypt = require('bcrypt'),
};
User.getFollowerCount = function(uid, callback) {
RDB.smembers('followers:' + uid, function(err, userIds) {
db.getSetMembers('followers:' + uid, function(err, userIds) {
if(err) {
console.log(err);
} else {
userIds = userIds.filter(function(value) {
return value !== '0';
return parseInt(value, 10) !== 0;
});
callback(userIds.length);
}
@ -585,7 +543,7 @@ var bcrypt = require('bcrypt'),
}
function iterator(uid, callback) {
if(uid === "0") {
if(parseInt(uid, 10) === 0) {
return callback(null);
}
@ -603,7 +561,7 @@ var bcrypt = require('bcrypt'),
User.sendPostNotificationToFollowers = function(uid, tid, pid) {
User.getUserField(uid, 'username', function(err, username) {
RDB.smembers('followers:' + uid, function(err, followers) {
db.getSetMembers('followers:' + uid, function(err, followers) {
topics.getTopicField(tid, 'slug', function(err, slug) {
var message = '<strong>' + username + '</strong> made a new post';
@ -616,9 +574,9 @@ var bcrypt = require('bcrypt'),
};
User.isFollowing = function(uid, theirid, callback) {
RDB.sismember('following:' + uid, theirid, function(err, data) {
db.isSetMember('following:' + uid, theirid, function(err, isMember) {
if (!err) {
callback(data === 1);
callback(isMember);
} else {
console.log(err);
}
@ -632,8 +590,10 @@ var bcrypt = require('bcrypt'),
};
User.count = function(socket) {
RDB.get('usercount', function(err, count) {
RDB.handle(err);
db.getObjectField('global', 'userCount', function(err, count) {
if(err) {
return;
}
socket.emit('user.count', {
count: count ? count : 0
@ -642,11 +602,11 @@ var bcrypt = require('bcrypt'),
};
User.getUidByUsername = function(username, callback) {
RDB.hget('username:uid', username, callback);
db.getObjectField('username:uid', username, callback);
};
User.getUidByUserslug = function(userslug, callback) {
RDB.hget('userslug:uid', userslug, callback);
db.getObjectField('userslug:uid', userslug, callback);
};
User.getUsernamesByUids = function(uids, callback) {
@ -688,52 +648,54 @@ var bcrypt = require('bcrypt'),
};
User.getUidByEmail = function(email, callback) {
RDB.hget('email:uid', email, function(err, data) {
db.getObjectField('email:uid', email, function(err, data) {
if (err) {
RDB.handle(err);
return callback(err);
}
callback(data);
callback(null, data);
});
};
User.getUidByTwitterId = function(twid, callback) {
RDB.hget('twid:uid', twid, function(err, uid) {
db.getObjectField('twid:uid', twid, function(err, uid) {
if (err) {
RDB.handle(err);
return callback(err);
}
callback(uid);
callback(null, uid);
});
};
User.getUidByGoogleId = function(gplusid, callback) {
RDB.hget('gplusid:uid', gplusid, function(err, uid) {
db.getObjectField('gplusid:uid', gplusid, function(err, uid) {
if (err) {
RDB.handle(err);
return callback(err);
}
callback(uid);
callback(null, uid);
});
};
User.getUidByFbid = function(fbid, callback) {
RDB.hget('fbid:uid', fbid, function(err, uid) {
db.getObjectField('fbid:uid', fbid, function(err, uid) {
if (err) {
RDB.handle(err);
return callback(err);
}
callback(uid);
callback(null, uid);
});
};
User.isModerator = function(uid, cid, callback) {
RDB.sismember('cid:' + cid + ':moderators', uid, function(err, exists) {
RDB.handle(err);
callback(err, !! exists);
db.isSetMember('cid:' + cid + ':moderators', uid, function(err, exists) {
if(err) {
return calback(err);
}
callback(err, exists);
});
};
User.isAdministrator = function(uid, callback) {
Groups.getGidFromName('Administrators', function(err, gid) {
Groups.isMember(uid, gid, function(err, isAdmin) {
callback(err, !! isAdmin);
groups.getGidFromName('Administrators', function(err, gid) {
groups.isMember(uid, gid, function(err, isAdmin) {
callback(err, isAdmin);
});
});
};
@ -745,15 +707,15 @@ var bcrypt = require('bcrypt'),
callback = null;
}
RDB.hget('reset:uid', code, function(err, uid) {
db.getObjectField('reset:uid', code, function(err, uid) {
if (err) {
RDB.handle(err);
return callback(false);
}
if (uid !== null) {
RDB.hget('reset:expiry', code, function(err, expiry) {
db.getObjectField('reset:expiry', code, function(err, expiry) {
if (err) {
RDB.handle(err);
return callback(false);
}
if (expiry >= +Date.now() / 1000 | 0) {
@ -766,8 +728,8 @@ var bcrypt = require('bcrypt'),
}
} else {
// Expired, delete from db
RDB.hdel('reset:uid', code);
RDB.hdel('reset:expiry', code);
db.deleteObjectField('reset:uid', code);
db.deleteObjectField('reset:expiry', code);
if (!callback) {
socket.emit('user:reset.valid', {
valid: false
@ -789,12 +751,12 @@ var bcrypt = require('bcrypt'),
});
},
send: function(socket, email) {
User.getUidByEmail(email, function(uid) {
User.getUidByEmail(email, function(err, uid) {
if (uid !== null) {
// Generate a new reset code
var reset_code = utils.generateUUID();
RDB.hset('reset:uid', reset_code, uid);
RDB.hset('reset:expiry', reset_code, (60 * 60) + new Date() / 1000 | 0); // Active for one hour
db.setObjectField('reset:uid', reset_code, uid);
db.setobjectField('reset:expiry', reset_code, (60 * 60) + new Date() / 1000 | 0); // Active for one hour
var reset_link = nconf.get('url') + 'reset/' + reset_code,
reset_email = global.templates['emails/reset'].parse({
@ -842,17 +804,17 @@ var bcrypt = require('bcrypt'),
commit: function(socket, code, password) {
this.validate(socket, code, function(validated) {
if (validated) {
RDB.hget('reset:uid', code, function(err, uid) {
db.getObjectField('reset:uid', code, function(err, uid) {
if (err) {
RDB.handle(err);
return;
}
User.hashPassword(password, function(err, hash) {
User.setUserField(uid, 'password', hash);
});
RDB.hdel('reset:uid', code);
RDB.hdel('reset:expiry', code);
db.deleteObjectField('reset:uid', code);
db.deleteObjectField('reset:expiry', code);
socket.emit('user:reset.commit', {
status: 'ok'
@ -863,9 +825,48 @@ var bcrypt = require('bcrypt'),
}
};
User.sendConfirmationEmail = function(email) {
if (meta.config['email:smtp:host'] && meta.config['email:smtp:port'] && meta.config['email:from']) {
var confirm_code = utils.generateUUID(),
confirm_link = nconf.get('url') + 'confirm/' + confirm_code,
confirm_email = global.templates['emails/header'] + global.templates['emails/email_confirm'].parse({
'CONFIRM_LINK': confirm_link
}) + global.templates['emails/footer'],
confirm_email_plaintext = global.templates['emails/email_confirm_plaintext'].parse({
'CONFIRM_LINK': confirm_link
});
// Email confirmation code
var expiry_time = Date.now() / 1000 + 60 * 60 * 2;
db.setObjectField('email:confirm', email, confirm_code);
db.setObjectField('confirm:email', confirm_code, email);
db.setObjectField('confirm:email', confirm_code + ':expire', expiry_time);
// Send intro email w/ confirm code
var message = emailjs.message.create({
text: confirm_email_plaintext,
from: meta.config['email:from'] || 'localhost@example.org',
to: email,
subject: '[NodeBB] Registration Email Verification',
attachment: [{
data: confirm_email,
alternative: true
}]
});
emailjsServer.send(message, function(err, success) {
if (err) {
console.log(err);
}
});
}
};
User.email = {
exists: function(socket, email, callback) {
User.getUidByEmail(email, function(exists) {
User.getUidByEmail(email, function(err, exists) {
exists = !! exists;
if (typeof callback !== 'function') {
socket.emit('user.email.exists', {
@ -877,14 +878,30 @@ var bcrypt = require('bcrypt'),
});
},
confirm: function(code, callback) {
RDB.get('confirm:' + code + ':email', function(err, email) {
db.getObjectFields('confirm:email', [code, code + ':expire'], function(err, data) {
if (err) {
RDB.handle(err);
return callback({
status:'error'
});
}
var email = data.email;
var expiry = data[code + ':expire'];
if (parseInt(expiry, 10) >= Date.now() / 1000) {
db.deleteObjectField('confirm:email', code);
db.deleteObjectField('confirm:email', code + ':expire');
return callback({
status: 'expired'
});
}
if (email !== null) {
RDB.set('email:' + email + ':confirm', true);
RDB.del('confirm:' + code + ':email');
db.setObjectField('email:confirm', email, true);
db.deleteObjectField('confirm:email', code);
db.deleteObjectField('confirm:email', code + ':expire');
callback({
status: 'ok'
});
@ -903,7 +920,7 @@ var bcrypt = require('bcrypt'),
async.parallel({
unread: function(next) {
RDB.zrevrange('uid:' + uid + ':notifications:unread', 0, 10, function(err, nids) {
db.getSortedSetRevRange('uid:' + uid + ':notifications:unread', 0, 10, function(err, nids) {
// @todo handle err
var unread = [];
@ -919,7 +936,7 @@ var bcrypt = require('bcrypt'),
if (notif_data) {
unread.push(notif_data);
} else {
RDB.zrem('uid:' + uid + ':notifications:unread', nid);
db.sortedSetRemove('uid:' + uid + ':notifications:unread', nid);
}
next();
@ -933,7 +950,7 @@ var bcrypt = require('bcrypt'),
});
},
read: function(next) {
RDB.zrevrange('uid:' + uid + ':notifications:read', 0, 10, function(err, nids) {
db.getSortedSetRevRange('uid:' + uid + ':notifications:read', 0, 10, function(err, nids) {
// @todo handle err
var read = [];
@ -949,7 +966,7 @@ var bcrypt = require('bcrypt'),
if (notif_data) {
read.push(notif_data);
} else {
RDB.zrem('uid:' + uid + ':notifications:read', nid);
db.sortedSetRemove('uid:' + uid + ':notifications:read', nid);
}
next();
@ -981,13 +998,13 @@ var bcrypt = require('bcrypt'),
before = new Date(parseInt(before, 10));
}
RDB.multi()
.zrevrangebyscore('uid:' + uid + ':notifications:read', before ? before.getTime(): now.getTime(), -Infinity, 'LIMIT', 0, limit)
.zrevrangebyscore('uid:' + uid + ':notifications:unread', before ? before.getTime(): now.getTime(), -Infinity, 'LIMIT', 0, limit)
.exec(function(err, results) {
// Merge the read and unread notifications
var nids = results[0].concat(results[1]);
var args1 = ['uid:' + uid + ':notifications:read', before ? before.getTime(): now.getTime(), -Infinity, 'LIMIT', 0, limit];
var args2 = ['uid:' + uid + ':notifications:unread', before ? before.getTime(): now.getTime(), -Infinity, 'LIMIT', 0, limit];
db.getSortedSetRevRangeByScore(args1, function(err, results1) {
db.getSortedSetRevRangeByScore(args2, function(err, results2) {
var nids = results1.concat(results2);
async.map(nids, function(nid, next) {
notifications.get(nid, uid, function(notif_data) {
next(null, notif_data);
@ -1007,14 +1024,20 @@ var bcrypt = require('bcrypt'),
callback(err, notifs);
});
});
});
},
getUnreadCount: function(uid, callback) {
RDB.zcount('uid:' + uid + ':notifications:unread', -Infinity, Infinity, callback);
db.sortedSetCount('uid:' + uid + ':notifications:unread', -Infinity, Infinity, callback);
},
getUnreadByUniqueId: function(uid, uniqueId, callback) {
RDB.zrange('uid:' + uid + ':notifications:unread', 0, -1, function(err, nids) {
db.getSortedSetRange('uid:' + uid + ':notifications:unread', 0, -1, function(err, nids) {
async.filter(nids, function(nid, next) {
notifications.get(nid, uid, function(notifObj) {
if(!notifObj) {
next(false);
}
if (notifObj.uniqueId === uniqueId) {
next(true);
} else {

@ -5,7 +5,6 @@ var path = require('path'),
express_namespace = require('express-namespace'),
WebServer = express(),
server = require('http').createServer(WebServer),
RedisStore = require('connect-redis')(express),
nconf = require('nconf'),
winston = require('winston'),
validator = require('validator'),
@ -14,7 +13,7 @@ var path = require('path'),
pkg = require('../package.json'),
utils = require('../public/src/utils'),
RDB = require('./redis'),
db = require('./database'),
user = require('./user'),
categories = require('./categories'),
posts = require('./posts'),
@ -141,17 +140,16 @@ var path = require('path'),
}));
app.use(express.bodyParser()); // Puts POST vars in request.body
app.use(express.cookieParser()); // If you want to parse cookies (res.cookies)
app.use(express.session({
store: new RedisStore({
client: RDB,
ttl: 60 * 60 * 24 * 30
}),
store: db.sessionStore,
secret: nconf.get('secret'),
key: 'express.sid',
cookie: {
maxAge: 60 * 60 * 24 * 30 * 1000 // 30 days
}
}));
app.use(express.csrf());
// Local vars, other assorted setup
@ -173,33 +171,33 @@ var path = require('path'),
function(next) {
async.parallel([
function(next) {
// Theme configuration
RDB.hmget('config', 'theme:type', 'theme:id', 'theme:staticDir', 'theme:templates', function(err, themeData) {
var themeId = (themeData[1] || 'nodebb-theme-vanilla');
db.getObjectFields('config', ['theme:type', 'theme:id', 'theme:staticDir', 'theme:templates'], function(err, themeData) {
var themeId = (themeData['theme:id'] || 'nodebb-theme-vanilla');
// Detect if a theme has been selected, and handle appropriately
if (!themeData[0] || themeData[0] === 'local') {
if (!themeData['theme:type'] || themeData['theme:type'] === 'local') {
// Local theme
if (process.env.NODE_ENV === 'development') {
winston.info('[themes] Using theme ' + themeId);
}
// Theme's static directory
if (themeData[2]) {
app.use('/css/assets', express.static(path.join(__dirname, '../node_modules', themeData[1], themeData[2]), {
if (themeData['theme:staticDir']) {
app.use('/css/assets', express.static(path.join(__dirname, '../node_modules', themeData['theme:id'], themeData['theme:staticDir']), {
maxAge: app.enabled('cache') ? 5184000000 : 0
}));
if (process.env.NODE_ENV === 'development') {
winston.info('Static directory routed for theme: ' + themeData[1]);
winston.info('Static directory routed for theme: ' + themeData['theme:id']);
}
}
if (themeData[3]) {
app.use('/templates', express.static(path.join(__dirname, '../node_modules', themeData[1], themeData[3]), {
if (themeData['theme:templates']) {
app.use('/templates', express.static(path.join(__dirname, '../node_modules', themeData['theme:id'], themeData['theme:templates']), {
maxAge: app.enabled('cache') ? 5184000000 : 0
}));
if (process.env.NODE_ENV === 'development') {
winston.info('Custom templates directory routed for theme: ' + themeData[1]);
winston.info('Custom templates directory routed for theme: ' + themeData['theme:id']);
}
}
@ -411,7 +409,7 @@ var path = require('path'),
"categories": function (next) {
categories.getAllCategories(0, function (err, returnData) {
returnData.categories = returnData.categories.filter(function (category) {
if (category.disabled !== '1') {
if (parseInt(category.disabled, 10) !== 1) {
return true;
} else {
return false;
@ -467,7 +465,7 @@ var path = require('path'),
function (next) {
topics.getTopicWithPosts(tid, ((req.user) ? req.user.uid : 0), 0, -1, true, function (err, topicData) {
if (topicData) {
if (topicData.deleted === '1' && topicData.expose_tools === 0) {
if (parseInt(topicData.deleted, 10) === 1 && parseInt(topicData.expose_tools, 10) === 0) {
return next(new Error('Topic deleted'), null);
}
}
@ -589,7 +587,7 @@ var path = require('path'),
categories.getCategoryById(cid, 0, function (err, categoryData) {
if (categoryData) {
if (categoryData.disabled === '1') {
if (parseInt(categoryData.disabled, 10) === 1) {
return next(new Error('Category disabled'), null);
}
}

@ -9,15 +9,10 @@ var cookie = require('cookie'),
gravatar = require('gravatar'),
winston = require('winston'),
RedisStoreLib = require('connect-redis')(express),
RDB = require('./redis'),
RedisStore = new RedisStoreLib({
client: RDB,
ttl: 60 * 60 * 24 * 14
}),
db = require('./database'),
user = require('./user'),
Groups = require('./groups'),
groups = require('./groups'),
posts = require('./posts'),
favourites = require('./favourites'),
utils = require('../public/src/utils'),
@ -70,9 +65,12 @@ websockets.init = function(io) {
// Validate the session, if present
socketCookieParser(hs, {}, function(err) {
sessionID = socket.handshake.signedCookies["express.sid"];
RedisStore.get(sessionID, function(err, sessionData) {
if (!err && sessionData && sessionData.passport && sessionData.passport.user) uid = users[sessionID] = sessionData.passport.user;
else uid = users[sessionID] = 0;
db.sessionStore.get(sessionID, function(err, sessionData) {
if (!err && sessionData && sessionData.passport && sessionData.passport.user) {
uid = users[sessionID] = sessionData.passport.user;
} else {
uid = users[sessionID] = 0;
}
userSockets[uid] = userSockets[uid] || [];
userSockets[uid].push(socket);
@ -89,7 +87,7 @@ websockets.init = function(io) {
if (uid) {
RDB.zadd('users:online', Date.now(), uid, function(err, data) {
db.sortedSetAdd('users:online', Date.now(), uid, function(err, data) {
socket.join('uid_' + uid);
user.getUserField(uid, 'username', function(err, username) {
@ -117,7 +115,7 @@ websockets.init = function(io) {
delete users[sessionID];
delete userSockets[uid];
if (uid) {
RDB.zrem('users:online', uid, function(err, data) {
db.sortedSetRemove('users:online', uid, function(err, data) {
});
}
}
@ -348,7 +346,7 @@ websockets.init = function(io) {
});
socket.on('api:topics.post', function(data) {
if (uid < 1 && meta.config.allowGuestPosting === '0') {
if (uid < 1 && parseInt(meta.config.allowGuestPosting, 10) === 0) {
socket.emit('event:alert', {
title: 'Post Unsuccessful',
message: 'You don&apos;t seem to be logged in, so you cannot reply.',
@ -419,7 +417,7 @@ websockets.init = function(io) {
});
socket.on('api:posts.reply', function(data) {
if (uid < 1 && meta.config.allowGuestPosting === '0') {
if (uid < 1 && parseInt(meta.config.allowGuestPosting, 10) === 0) {
socket.emit('event:alert', {
title: 'Reply Unsuccessful',
message: 'You don&apos;t seem to be logged in, so you cannot reply.',
@ -781,7 +779,7 @@ websockets.init = function(io) {
});
socket.on('api:composer.push', function(data) {
if (uid > 0 || meta.config.allowGuestPosting === '1') {
if (parseInt(uid, 10) > 0 || parseInt(meta.config.allowGuestPosting, 10) === 1) {
if (parseInt(data.tid) > 0) {
topics.getTopicData(data.tid, function(err, topicData) {
if (data.body)
@ -1029,16 +1027,16 @@ websockets.init = function(io) {
};
if (set) {
Groups.joinByGroupName('cid:' + cid + ':privileges:' + privilege, uid, cb);
groups.joinByGroupName('cid:' + cid + ':privileges:' + privilege, uid, cb);
} else {
Groups.leaveByGroupName('cid:' + cid + ':privileges:' + privilege, uid, cb);
groups.leaveByGroupName('cid:' + cid + ':privileges:' + privilege, uid, cb);
}
});
socket.on('api:admin.categories.getPrivilegeSettings', function(cid, callback) {
async.parallel({
"+r": function(next) {
Groups.getByGroupName('cid:' + cid + ':privileges:+r', { expand: true }, function(err, groupObj) {
groups.getByGroupName('cid:' + cid + ':privileges:+r', { expand: true }, function(err, groupObj) {
if (!err) {
next.apply(this, arguments);
} else {
@ -1049,7 +1047,7 @@ websockets.init = function(io) {
});
},
"+w": function(next) {
Groups.getByGroupName('cid:' + cid + ':privileges:+w', { expand: true }, function(err, groupObj) {
groups.getByGroupName('cid:' + cid + ':privileges:+w', { expand: true }, function(err, groupObj) {
if (!err) {
next.apply(this, arguments);
} else {
@ -1090,19 +1088,19 @@ websockets.init = function(io) {
*/
socket.on('api:groups.create', function(data, callback) {
Groups.create(data.name, data.description, function(err, groupObj) {
groups.create(data.name, data.description, function(err, groupObj) {
callback(err ? err.message : null, groupObj || undefined);
});
});
socket.on('api:groups.delete', function(gid, callback) {
Groups.destroy(gid, function(err) {
groups.destroy(gid, function(err) {
callback(err ? err.message : null, err ? null : 'OK');
});
});
socket.on('api:groups.get', function(gid, callback) {
Groups.get(gid, {
groups.get(gid, {
expand: true
}, function(err, groupObj) {
callback(err ? err.message : null, groupObj || undefined);
@ -1110,15 +1108,15 @@ websockets.init = function(io) {
});
socket.on('api:groups.join', function(data, callback) {
Groups.join(data.gid, data.uid, callback);
groups.join(data.gid, data.uid, callback);
});
socket.on('api:groups.leave', function(data, callback) {
Groups.leave(data.gid, data.uid, callback);
groups.leave(data.gid, data.uid, callback);
});
socket.on('api:groups.update', function(data, callback) {
Groups.update(data.gid, data.values, function(err) {
groups.update(data.gid, data.values, function(err) {
callback(err ? err.message : null);
});
});
@ -1128,14 +1126,14 @@ websockets.init = function(io) {
function emitTopicPostStats() {
RDB.mget(['totaltopiccount', 'totalpostcount'], function(err, data) {
db.getObjectFields('global', ['topicCount', 'postCount'], function(err, data) {
if (err) {
return winston.err(err);
}
var stats = {
topics: data[0] ? data[0] : 0,
posts: data[1] ? data[1] : 0
topics: data.topicCount ? data.topicCount : 0,
posts: data.postCount ? data.postCount : 0
};
io.sockets.emit('post.stats', stats);
@ -1143,7 +1141,7 @@ websockets.init = function(io) {
}
websockets.emitUserCount = function() {
RDB.get('usercount', function(err, count) {
db.getObjectField('global', 'userCount', function(err, count) {
io.sockets.emit('user.count', {
count: count
});
@ -1154,8 +1152,10 @@ websockets.init = function(io) {
return io.sockets.in(room);
};
}
websockets.getConnectedClients = function() {
return userSockets;
}
}
})(module.exports);
})(module.exports);

@ -7,14 +7,7 @@ process.on('uncaughtException', function (err) {
});
var assert = require('assert'),
RDB = require('../mocks/redismock');
// Reds is not technically used in this test suite, but its invocation is required to stop the included
// libraries from trying to connect to the default Redis host/port
var reds = require('reds');
reds.createClient = function () {
return reds.client || (reds.client = RDB);
};
db = require('../mocks/databasemock');
var Categories = require('../src/categories');
@ -23,6 +16,7 @@ describe('Categories', function() {
describe('.create', function() {
it('should create a new category', function(done) {
Categories.create({
name: 'Test Category',
description: 'Test category created by testing script',
@ -62,9 +56,7 @@ describe('Categories', function() {
});
after(function() {
RDB.multi()
.del('category:'+categoryObj.cid)
.rpop('categories:cid')
.exec();
db.delete('category:' + categoryObj.cid);
db.listRemoveLast('categories:cid');
});
});

@ -0,0 +1,330 @@
var assert = require('assert'),
db = require('../mocks/databasemock'),
async = require('async');
describe('Test database', function() {
it('should work', function(){
assert.doesNotThrow(function(){
var db = require('../mocks/databasemock');
});
});
it('should not throw err', function(done) {
var objectKey = 'testObj';
function setObject(callback) {
db.setObject(objectKey, {name:'baris', 'lastname':'usakli', age:3}, function(err, result) {
callback(err, {'setObject':result});
});
}
function getObject(callback) {
db.getObject(objectKey, function(err, data) {
callback(err, {'getObject':data});
});
}
function getObjects(callback) {
db.getObjects(['testing1', objectKey, 'doesntexist', 'user:1'], function(err, data) {
callback(err, {'getObjects':data});
});
}
function setObjectField(callback) {
db.setObjectField(objectKey, 'reputation', 5, function(err, result) {
callback(err, {'setObjectField': result});
});
}
function getObjectField(callback) {
db.getObjectField(objectKey, 'age', function(err, age) {
callback(err, {'getObjectField' : age});
});
}
function getObjectFields(callback) {
db.getObjectFields(objectKey, ['name', 'lastname'], function(err, data) {
callback(err, {'getObjectFields':data});
});
}
function getObjectValues(callback) {
db.getObjectValues(objectKey, function(err, data) {
callback(err, {'getObjectValues':data});
});
}
function isObjectField(callback) {
db.isObjectField(objectKey, 'age', function(err, data) {
callback(err, {'isObjectField':data});
});
}
function deleteObjectField(callback) {
db.deleteObjectField(objectKey, 'reputation', function(err, data) {
callback(err, {'deleteObjectField':data});
});
}
function incrObjectFieldBy(callback) {
db.incrObjectFieldBy(objectKey, 'age', 3, function(err, data) {
callback(err, {'incrObjectFieldBy':data});
});
}
var objectTasks = [
setObject,
getObject,
deleteObjectField,
getObject,
setObjectField,
getObject,
deleteObjectField,
getObject,
getObjectField,
getObjectFields,
getObjectValues,
isObjectField,
incrObjectFieldBy,
getObject,
getObjects
];
async.series(objectTasks, function(err, results) {
assert.equal(err, null, 'error in object methods');
assert.ok(results);
done();
});
});
it('should not throw err', function(done) {
function sortedSetAdd(callback) {
db.sortedSetAdd('sortedSet3', 12, 5, function(err, data) {
callback(err, {'sortedSetAdd': data});
});
}
function sortedSetRemove(callback) {
db.sortedSetRemove('sortedSet3', 12, function(err, data) {
callback(err, {'sortedSetRemove': data});
});
}
function getSortedSetRange(callback) {
db.getSortedSetRevRange('sortedSet3', 0, -1, function(err, data) {
callback(err, {'getSortedSetRange': data});
});
}
function getSortedSetRevRangeByScore(callback) {
var args = ['sortedSet2', '+inf', 100, 'LIMIT', 0, 10];
db.getSortedSetRevRangeByScore(args, function(err, data) {
callback(err, {'getSortedSetRevRangeByScore': data});
});
}
function sortedSetCount(callback) {
db.sortedSetCount('sortedSet3', -Infinity, Infinity, function(err, data) {
callback(err, {'sortedSetCount': data});
});
}
function sortedSetScore(callback) {
db.sortedSetScore('users:joindate', 1, function(err, data) {
callback(err, {'sortedSetScore': data});
});
}
function sortedSetsScore(callback) {
db.sortedSetsScore(['users:joindate', 'users:derp', 'users:postcount'], 1, function(err, data) {
callback(err, {'sortedSetsScore': data});
});
}
var sortedSetTasks = [
sortedSetAdd,
getSortedSetRange,
sortedSetAdd,
getSortedSetRange,
sortedSetRemove,
getSortedSetRange,
sortedSetCount,
sortedSetScore,
sortedSetsScore,
getSortedSetRevRangeByScore
];
async.series(sortedSetTasks, function(err, results) {
assert.equal(err, null, 'error in sorted set methods');
assert.ok(results);
done();
});
});
it('should not throw err', function(done) {
function listAppend(callback) {
db.listAppend('myList5', 5, function(err, data) {
callback(err, {'listAppend': data});
});
}
function listPrepend(callback) {
db.listPrepend('myList5', 4, function(err, data) {
callback(err, {'listPrepend': data});
});
}
function listRemoveLast(callback) {
db.listRemoveLast('myList5', function(err, data) {
callback(err, {'listRemoveLast': data});
});
}
function getListRange(callback) {
db.getListRange('myList5', 0, -1, function(err, data) {
callback(err, {'getListRange': data});
});
}
var listTasks = [
listAppend,
listPrepend,
getListRange,
listRemoveLast,
getListRange
];
async.series(listTasks, function(err, results) {
assert.equal(err, null, 'error in list methods');
assert.ok(results);
done();
});
});
it('should not throw err', function(done) {
function get(callback) {
db.get('testingStr', function(err, data) {
callback(err, {'get': data});
});
}
function set(callback) {
db.set('testingStr', 'opppa gangastayla', function(err, data) {
callback(err, {'set': data});
});
}
function deleteKey(callback) {
db.delete('testingStr', function(err, data) {
callback(err, {'delete': data});
});
}
function exists(callback) {
db.exists('testingStr', function(err, data) {
callback(err, {'exists': data});
});
}
var keyTasks = [
get,
set,
get,
exists,
deleteKey,
deleteKey,
get,
exists
];
async.series(keyTasks, function(err, results) {
assert.equal(err, null, 'error in key methods');
assert.ok(results);
done();
});
});
it('should not throw err', function(done) {
function setAdd(callback) {
db.setAdd('myTestSet', 15, function(err, data) {
callback(err, {'setAdd': data});
});
}
function setRemove(callback) {
db.setRemove('myTestSet', 15, function(err, data) {
callback(err, {'setRemove': data});
});
}
function getSetMembers(callback) {
db.getSetMembers('myTestSet', function(err, data) {
callback(err, {'getSetMembers': data});
});
}
function isSetMember(callback) {
db.isSetMember('myTestSet', 15, function(err, data) {
callback(err, {'isSetMember': data});
});
}
function isMemberOfSets(callback) {
db.isMemberOfSets(['doesntexist', 'myTestSet', 'nonexistingSet'], 15, function(err, data) {
callback(err, {'isMemberOfSets': data});
});
}
function setRemoveRandom(callback) {
db.setRemoveRandom('myTestSet', function(err, data) {
callback(err, {'setRemoveRandom': data});
});
}
function setCount(callback) {
db.setCount('myTestSet', function(err, data) {
callback(err, {'setCount': data});
});
}
var setTasks = [
getSetMembers,
setAdd,
getSetMembers,
setRemove,
getSetMembers,
isSetMember,
setAdd,
getSetMembers,
isSetMember,
setRemoveRandom,
getSetMembers,
setCount,
isMemberOfSets
];
require('async').series(setTasks, function(err, results) {
assert.equal(err, null, 'error in set methods');
assert.ok(results);
done();
});
});
});

@ -1,10 +0,0 @@
var assert = require('assert');
describe('Test database', function() {
it('should work', function(){
assert.doesNotThrow(function(){
var RDB = require('../mocks/redismock');
});
});
});

@ -5,14 +5,8 @@ process.on('uncaughtException', function (err) {
});
var assert = require('assert'),
RDB = require('../mocks/redismock');
db = require('../mocks/databasemock');
// Reds is not technically used in this test suite, but its invocation is required to stop the included
// libraries from trying to connect to the default Redis host/port
var reds = require('reds');
reds.createClient = function () {
return reds.client || (reds.client = RDB);
};
var Topics = require('../src/topics');
@ -43,7 +37,7 @@ describe('Topic\'s', function() {
topic.userId = null;
Topics.post(topic.userId, topic.title, topic.content, topic.categoryId, function(err, result) {
assert.equal(err.message, 'not-logged-in');
assert.equal(err.message, 'invalid-user');
done();
});
});
@ -75,11 +69,6 @@ describe('Topic\'s', function() {
});
after(function() {
RDB.send_command('flushdb', [], function(error){
if(error){
winston.error(error);
throw new Error(error);
}
});
db.flushdb();
});
});

@ -7,7 +7,7 @@ process.on('uncaughtException', function (err) {
});
var assert = require('assert'),
RDB = require('../mocks/redismock');
db = require('../mocks/databasemock');
var User = require('../src/user');
@ -43,12 +43,6 @@ describe('User', function() {
});
after(function() {
//Clean up
RDB.send_command('flushdb', [], function(error){
if(error){
winston.error(error);
throw new Error(error);
}
});
db.flushdb();
});
});
Loading…
Cancel
Save