apostrophe
Version:
The Apostrophe Content Management System.
179 lines (174 loc) • 6.58 kB
JavaScript
// This module establishes `apos.db`, the MongoDB database object.
//
// ## Options
//
// ### `uri`
//
// The MongoDB connection URI. See the [MongoDB URI documentation](https://docs.mongodb.com/manual/reference/connection-string/).
//
// ### `connect`
//
// If present, this object is passed on as options to MongoDB's "connect"
// method, along with the uri. See the [MongoDB connect settings documentation](http://mongodb.github.io/node-mongodb-native/2.2/reference/connecting/connection-settings/).
//
// By default, Apostrophe sets options to retry lost connections forever,
// however you can override this via the `connect` object if you want to.
//
// ### `user`, `host`, `port`, `name`, `password`
//
// These options are used only if `uri` is not present.
//
// ### `client`
//
// An existing MongoDB connection (MongoClient) object. If present, it is used
// and `uri`, `host`, `connect`, etc. are ignored.
//
// ### `versionCheck`
//
// If `true`, check to make sure the database does not belong to an
// older, incompatible major release release of Apostrophe and exit if it does.
// Defaults to `true`. You can set this to `false` to avoid an extra query at
// startup time.
//
// ## Command line tasks
//
// ### `@apostrophecms/db:reset`
//
// Drops ALL collections in the database (including those not created by
// Apostrophe), then emits the `@apostrophecms/db:reset` async event to
// allow other modules to drop related non-MongoDB resources at the
// same time, if desired.
//
// Note that `apos.db` is the MongoDB database object, not this module.
// You shouldn't need to talk to this module after startup, but you can
// access it as `apos.modules['@apostrophecms/db']` if you wish. You can
// also access `apos.dbClient` if you need the MongoClient object.
//
// If you need to change the way MongoDB connections are made,
// override `connectToMongo` in `modules/@apostrophecms/db/index.js`
// in your project. However you may find it easier to just use the
// `client` option.
const mongodbConnect = require('../../../lib/mongodb-connect');
const escapeHost = require('../../../lib/escape-host');
module.exports = {
options: {
versionCheck: true
},
async init(self) {
await self.connectToMongo();
await self.versionCheck();
},
handlers(self) {
return {
'apostrophe:destroy': {
async closeDb() {
if (!self.apos.db) {
return;
}
if (self.connectionReused) {
// If we close our db object, which is reusing a connection
// shared by someone else, they will lose their connection
// too, resulting in unexpected "topology destroyed" errors.
// This responsibility should fall to the parent
return;
}
await self.apos.dbClient.close(false);
}
}
};
},
methods(self) {
return {
// Open the database connection. Always uses MongoClient with its
// sensible defaults. Builds a URI if necessary, so we can call it
// in a consistent way.
//
// One default we override: if the connection is lost, we keep
// attempting to reconnect forever. This is the most sensible behavior
// for a persistent process that requires MongoDB in order to operate.
async connectToMongo() {
if (self.options.client) {
// Reuse a single client connection http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#db
self.apos.dbClient = self.options.client;
self.apos.db = self.options.client.db(self.options.name || self.apos.shortName);
self.connectionReused = true;
return;
}
let uri = 'mongodb://';
if (process.env.APOS_MONGODB_URI) {
uri = process.env.APOS_MONGODB_URI;
} else if (self.options.uri) {
uri = self.options.uri;
} else {
if (self.options.user) {
uri += self.options.user + ':' + self.options.password + '@';
}
if (!self.options.host) {
self.options.host = 'localhost';
}
if (!self.options.port) {
self.options.port = 27017;
}
if (!self.options.name) {
self.options.name = self.apos.shortName;
}
uri += escapeHost(self.options.host) + ':' + self.options.port + '/' + self.options.name;
}
self.apos.dbClient = await mongodbConnect(uri, self.options.connect);
self.uri = uri;
// Automatically uses the db name in the connection string
self.apos.db = self.apos.dbClient.db();
},
async versionCheck() {
if (!self.options.versionCheck) {
return;
}
const oldGlobal = await self.apos.db.collection('aposDocs').findOne({
type: 'apostrophe-global'
});
if (oldGlobal) {
throw new Error(`There is a problem with the database: ${self.uri ? (self.uri + ' ') : ''}
This database contains an Apostrophe 2.x website. Exiting to avoid content loss.`);
}
},
async dropAllCollections() {
const collections = await self.apos.db.collections();
for (const collection of collections) {
// drop the collections. Cannot drop system collections
// of MongoDB
if (!collection.collectionName.match(/^system\./)) {
await collection.drop();
}
}
}
};
},
tasks(self) {
return {
// Reset the database. Drops ALL collections. If you have
// collections in the same database unrelated to Apostrophe they WILL
// be removed.
//
// Then Apostrophe carries out the usual reinitialization of collection
// indexes and creation of parked pages, etc.
//
// PLEASE NOTE: this will drop collections UNRELATED to apostrophe.
// If that is a concern for you, drop Apostrophe's collections yourself
// and start up your app, which will recreate them.
reset: {
usage: 'Usage: node app /db:reset\n\nThis destroys ALL of your content. EVERYTHING in your database.\n',
afterModuleReady: true,
exitAfter: false,
task: async () => {
const argv = self.apos.argv;
if (argv._.length !== 1) {
throw new Error('Incorrect number of arguments.');
}
await self.dropAllCollections();
// let other modules run their own tasks now that db has been reset
await self.emit('reset');
}
}
};
}
};