Files
NodeBB/src/database/mongo.js

292 lines
8.5 KiB
JavaScript
Raw Normal View History

2013-12-02 16:19:30 -05:00
2014-03-01 16:59:04 -05:00
'use strict';
2013-12-02 16:19:30 -05:00
2014-10-03 16:31:53 -04:00
2017-04-22 00:19:23 -04:00
var winston = require('winston');
var async = require('async');
var nconf = require('nconf');
var session = require('express-session');
2017-05-26 01:39:40 -06:00
var _ = require('lodash');
2017-04-22 00:19:23 -04:00
var semver = require('semver');
2017-10-23 15:09:13 -04:00
var prompt = require('prompt');
2018-11-28 19:24:54 -05:00
var utils = require('../utils');
2017-04-22 00:19:23 -04:00
2019-08-06 17:35:03 -04:00
let client;
2017-04-22 00:19:23 -04:00
var mongoModule = module.exports;
2017-10-23 15:09:13 -04:00
function isUriNotSpecified() {
return !prompt.history('mongo:uri').value;
}
2017-04-22 00:19:23 -04:00
mongoModule.questions = [
2017-10-23 15:09:13 -04:00
{
name: 'mongo:uri',
description: 'MongoDB connection URI: (leave blank if you wish to specify host, port, username/password and database individually)\nFormat: mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]',
default: nconf.get('mongo:uri') || '',
2017-11-22 12:19:08 -05:00
hideOnWebInstall: true,
2017-10-23 15:09:13 -04:00
},
2017-04-22 00:19:23 -04:00
{
name: 'mongo:host',
description: 'Host IP or address of your MongoDB instance',
default: nconf.get('mongo:host') || '127.0.0.1',
2017-10-23 15:09:13 -04:00
ask: isUriNotSpecified,
2017-04-22 00:19:23 -04:00
},
{
name: 'mongo:port',
description: 'Host port of your MongoDB instance',
default: nconf.get('mongo:port') || 27017,
2017-10-23 15:09:13 -04:00
ask: isUriNotSpecified,
2017-04-22 00:19:23 -04:00
},
{
name: 'mongo:username',
description: 'MongoDB username',
default: nconf.get('mongo:username') || '',
2017-10-23 15:09:13 -04:00
ask: isUriNotSpecified,
2017-04-22 00:19:23 -04:00
},
{
name: 'mongo:password',
description: 'Password of your MongoDB database',
default: nconf.get('mongo:password') || '',
2017-10-23 15:09:13 -04:00
hidden: true,
ask: isUriNotSpecified,
2017-04-22 00:19:23 -04:00
before: function (value) { value = value || nconf.get('mongo:password') || ''; return value; },
},
{
name: 'mongo:database',
description: 'MongoDB database name',
default: nconf.get('mongo:database') || 'nodebb',
2017-10-23 15:09:13 -04:00
ask: isUriNotSpecified,
2017-04-22 00:19:23 -04:00
},
];
mongoModule.getConnectionString = function (mongo) {
mongo = mongo || nconf.get('mongo');
2017-04-22 00:19:23 -04:00
var usernamePassword = '';
var uri = mongo.uri || '';
if (mongo.username && mongo.password) {
2017-04-22 00:19:23 -04:00
usernamePassword = nconf.get('mongo:username') + ':' + encodeURIComponent(nconf.get('mongo:password')) + '@';
2018-10-30 18:29:52 -04:00
} else if (!uri.includes('@') || !uri.slice(uri.indexOf('://') + 3, uri.indexOf('@'))) {
2017-05-12 15:40:09 -04:00
winston.warn('You have no mongo username/password setup!');
2017-04-22 00:19:23 -04:00
}
// Sensible defaults for Mongo, if not set
if (!mongo.host) {
mongo.host = '127.0.0.1';
2017-04-22 00:19:23 -04:00
}
if (!mongo.port) {
mongo.port = 27017;
2017-04-22 00:19:23 -04:00
}
const dbName = mongo.database;
2018-10-16 20:27:14 -04:00
if (dbName === undefined || dbName === '') {
winston.warn('You have no database name, using "nodebb"');
mongo.database = 'nodebb';
2017-04-22 00:19:23 -04:00
}
var hosts = mongo.host.split(',');
var ports = mongo.port.toString().split(',');
2017-04-22 00:19:23 -04:00
var servers = [];
for (var i = 0; i < hosts.length; i += 1) {
servers.push(hosts[i] + ':' + ports[i]);
}
return uri || 'mongodb://' + usernamePassword + servers.join() + '/' + mongo.database;
2018-05-25 11:56:40 -04:00
};
2017-04-22 00:19:23 -04:00
mongoModule.getConnectionOptions = function (mongo) {
mongo = mongo || nconf.get('mongo');
2017-04-22 00:19:23 -04:00
var connOptions = {
poolSize: 10,
reconnectTries: 3600,
reconnectInterval: 1000,
autoReconnect: true,
connectTimeoutMS: 90000,
2018-09-26 15:02:57 -04:00
useNewUrlParser: true,
2017-04-22 00:19:23 -04:00
};
return _.merge(connOptions, mongo.options || {});
2018-05-25 11:56:40 -04:00
};
mongoModule.init = function (callback) {
callback = callback || function () { };
2019-08-06 17:35:03 -04:00
mongoModule.connect(nconf.get('mongo'), function (err, _client) {
2017-04-22 00:19:23 -04:00
if (err) {
winston.error('NodeBB could not connect to your Mongo database. Mongo returned the following error', err);
2017-04-22 00:19:23 -04:00
return callback(err);
2015-04-27 11:08:58 -07:00
}
2019-08-06 17:35:03 -04:00
client = _client;
mongoModule.client = client.db();
2017-05-12 15:40:09 -04:00
callback();
2017-04-22 00:19:23 -04:00
});
};
mongoModule.connect = function (options, callback) {
callback = callback || function () { };
var mongoClient = require('mongodb').MongoClient;
2016-12-08 23:51:56 +03:00
var connString = mongoModule.getConnectionString(options);
var connOptions = mongoModule.getConnectionOptions(options);
2015-12-11 10:57:13 +02:00
mongoClient.connect(connString, connOptions, callback);
};
mongoModule.createSessionStore = function (options, callback) {
mongoModule.connect(options, function (err, client) {
if (err) {
return callback(err);
}
const meta = require('../meta');
const sessionStore = require('connect-mongo')(session);
const store = new sessionStore({
client: client,
ttl: meta.getSessionTTLSeconds(),
2017-04-22 00:19:23 -04:00
});
callback(null, store);
});
2017-04-22 00:19:23 -04:00
};
mongoModule.createIndices = function (callback) {
function createIndex(collection, index, options, callback) {
mongoModule.client.collection(collection).createIndex(index, options, callback);
}
if (!mongoModule.client) {
winston.warn('[database/createIndices] database not initialized');
return callback();
}
winston.info('[database] Checking database indices.');
async.series([
async.apply(createIndex, 'objects', { _key: 1, score: -1 }, { background: true }),
async.apply(createIndex, 'objects', { _key: 1, value: -1 }, { background: true, unique: true, sparse: true }),
async.apply(createIndex, 'objects', { expireAt: 1 }, { expireAfterSeconds: 0, background: true }),
], function (err) {
if (err) {
winston.error('Error creating index', err);
2017-04-22 00:19:23 -04:00
return callback(err);
2015-06-05 13:33:58 -04:00
}
2017-04-22 00:19:23 -04:00
winston.info('[database] Checking database indices done!');
2016-12-09 00:18:25 +03:00
callback();
2017-04-22 00:19:23 -04:00
});
};
mongoModule.checkCompatibility = function (callback) {
var mongoPkg = require('mongodb/package.json');
2017-05-19 20:24:54 -04:00
mongoModule.checkCompatibilityVersion(mongoPkg.version, callback);
};
2017-04-22 00:19:23 -04:00
2017-05-19 20:24:54 -04:00
mongoModule.checkCompatibilityVersion = function (version, callback) {
if (semver.lt(version, '2.0.0')) {
2017-04-22 00:19:23 -04:00
return callback(new Error('The `mongodb` package is out-of-date, please run `./nodebb setup` again.'));
}
callback();
};
mongoModule.info = function (db, callback) {
2017-05-19 20:24:54 -04:00
async.waterfall([
function (next) {
if (db) {
return setImmediate(next, null, db);
}
mongoModule.connect(nconf.get('mongo'), function (err, client) {
next(err, client ? client.db() : undefined);
});
},
function (db, next) {
mongoModule.client = mongoModule.client || db;
2017-05-19 20:24:54 -04:00
async.parallel({
serverStatus: function (next) {
db.command({ serverStatus: 1 }, next);
},
stats: function (next) {
db.command({ dbStats: 1 }, next);
},
listCollections: function (next) {
getCollectionStats(db, next);
},
}, next);
2017-04-22 00:19:23 -04:00
},
2017-05-19 20:24:54 -04:00
function (results, next) {
var stats = results.stats || {};
results.serverStatus = results.serverStatus || {};
2017-05-19 20:24:54 -04:00
var scale = 1024 * 1024 * 1024;
results.listCollections = results.listCollections.map(function (collectionInfo) {
return {
name: collectionInfo.ns,
count: collectionInfo.count,
size: collectionInfo.size,
avgObjSize: collectionInfo.avgObjSize,
storageSize: collectionInfo.storageSize,
totalIndexSize: collectionInfo.totalIndexSize,
indexSizes: collectionInfo.indexSizes,
};
2015-10-17 18:34:04 -04:00
});
stats.mem = results.serverStatus.mem || {};
2017-06-20 16:34:34 -04:00
stats.mem.resident = (stats.mem.resident / 1024).toFixed(3);
stats.mem.virtual = (stats.mem.virtual / 1024).toFixed(3);
stats.mem.mapped = (stats.mem.mapped / 1024).toFixed(3);
2017-05-19 20:24:54 -04:00
stats.collectionData = results.listCollections;
stats.network = results.serverStatus.network || {};
2018-11-28 19:24:54 -05:00
stats.network.bytesIn = (stats.network.bytesIn / scale).toFixed(3);
stats.network.bytesOut = (stats.network.bytesOut / scale).toFixed(3);
stats.network.numRequests = utils.addCommas(stats.network.numRequests);
2017-05-19 20:24:54 -04:00
stats.raw = JSON.stringify(stats, null, 4);
stats.avgObjSize = stats.avgObjSize.toFixed(2);
2017-06-20 16:34:34 -04:00
stats.dataSize = (stats.dataSize / scale).toFixed(3);
stats.storageSize = (stats.storageSize / scale).toFixed(3);
stats.fileSize = stats.fileSize ? (stats.fileSize / scale).toFixed(3) : 0;
stats.indexSize = (stats.indexSize / scale).toFixed(3);
2017-05-19 20:24:54 -04:00
stats.storageEngine = results.serverStatus.storageEngine ? results.serverStatus.storageEngine.name : 'mmapv1';
stats.host = results.serverStatus.host;
stats.version = results.serverStatus.version;
stats.uptime = results.serverStatus.uptime;
stats.mongo = true;
next(null, stats);
},
], callback);
2017-04-22 00:19:23 -04:00
};
2017-05-19 20:24:54 -04:00
function getCollectionStats(db, callback) {
async.waterfall([
function (next) {
db.listCollections().toArray(next);
},
function (items, next) {
async.map(items, function (collection, next) {
db.collection(collection.name).stats(next);
}, next);
},
], callback);
}
mongoModule.close = function (callback) {
callback = callback || function () {};
2019-08-06 17:35:03 -04:00
client.close(function (err) {
callback(err);
});
2017-04-22 00:19:23 -04:00
};
mongoModule.socketAdapter = function () {
var mongoAdapter = require('socket.io-adapter-mongo');
2018-05-25 11:56:40 -04:00
return mongoAdapter(mongoModule.getConnectionString());
};
require('./mongo/main')(mongoModule);
require('./mongo/hash')(mongoModule);
require('./mongo/sets')(mongoModule);
require('./mongo/sorted')(mongoModule);
require('./mongo/list')(mongoModule);
require('./mongo/transaction')(mongoModule);
mongoModule.async = require('../promisify')(mongoModule, ['client', 'sessionStore']);