/**
- Export constructor to create instance of Database
@exports database
@author kevin leptons <kevin.leptons@gmail.com>
@example
const database = require('./lib/database')
var url = 'mongodb://localhost/gwisp'
var db = database(url)
*/
module.exports = function (url, username, password, logger) {
return new Database(url, username, password, logger)
}
const async = require('async')
const mongodb = require('mongodb')
const flatlog = require('./flatlog')
const MongoClient = mongodb.MongoClient
/**
- Default logger use for database if logger parameter is not specify
@memberof module:database
@const {module:flatlog.FlatLog} DEFAULT_LOGGER
*/
const DEFAULT_LOGGER = flatlog({caller: 'database'})
/**
Path to directory contains an sample data of database.
It must contains files below
- region-iso-639.csv
This file contains region code follow iso 639. Database will use alpha-2 code.
For quickly, simply is copy from source code file
[asset/sample-data/region-iso-639.csv](sample-data/region-iso-639.csv)
- scheduler-001.json
This file contains an instance of scheduler. It must real scheduler. That mean
people can use them. see document for
{@link module:collection.SCHEDULER_COL}
for detail of an scheduler object. If have not idea to change that, copy
from source code in
[asset/sample-data/scheduler-001.json](sample-data/scheduler-001.json)
- scheduler-random.json
This file contains list of schedulers. It is generated by tools and have
about more than 100 elements. That data use for test purpose. If have not
test, create file with empty array. If have not tools to generate, copy it
from source code in
[asset/sample-data/scheduler-random.json](sample-data/scheduler-random.json)
@memberof module:database
@typedef {string} AssetDir
@example
var assetDir = 'asset/sample-data'
*/
/**
- Create a new Database from specify
- It is wrap of MongoClient
- All of operations with database must pass though this object to ensure that
every thing is fine
- Do not use mongodb.MongoClient directly
@constructor
@memberof module:database
@class Database
@param {string} url - Specify address of mongodb server
@param {string} [username=null] - Account to access to mongodb server
@param {string} [password=null] - Credential to access to mongodb server
@param {module:flatlog.FlatLog} [logger=DEFAULT_LOGGER] - Use to make write
message uniform
@return {Database} - The new database object
@example
// default database
var db1 = new Database('mongodb://localhost/gwisp')
// access database by credential
var db2 = new Database('mongodb://localhost/gwisp', 'kevin', 'secret')
// custom logger
const flatlog = require('./lib/flatlog')
var logger = flatlog({caller 'kevin'})
var db3 = new Database('mongodb://localhost/gwisp', null, null, logger)
*/
function Database (url, username, password, logger) {
this._url = url
this._username = username
this._password = password
this._logger = logger || DEFAULT_LOGGER
this._dbclient = null
}
// Shortcut to database prototype
const prototype = Database.prototype
/**
- Async connect to database
@memberof module:database.Database
@param {StdCallback} callback - Function will be call after done
@example
// see constructor for detail
var db = new Database(url)
db.connect(function(err, dbclient) {
if (err) return
// do some thing with database here
})
*/
prototype.connect = function (callback) {
const self = this
MongoClient.connect(this._url, function (err, db) {
if (err) return callback(err)
self._dbclient = db
callback(err, db)
})
}
/**
- Close connection to database
@memberof module:database.Database
@param {StdCallback} callback - Function will be call after done
@example
// see constructor for detail
var db = new Database(url)
db.connect(function(err, dbclient) {
if (err) return
db.close(function(err) {
if (err) return
// connection to database server was close
})
})
*/
prototype.close = function (callback) {
const self = this
if (!self._dbclient) return callback(null)
self._dbclient.close(true, function (err) {
if (err) return callback(err)
self._dbclient = null
callback(null)
})
}
/**
- Setup new database
- If database is not exists, create new database then push sample data to it
- If database is early exists, drop database then push sample data to it
@memberof module:database.Database
@param {module:database.AssetDir} assetDir - Path to directory contains static data
@param {StdCallback} callback - Function will be call after done
@example
// see constructor for detail
var db = new Database(url)
db.setup('asset/static', function(err) {
// handle for result of setup here
})
*/
prototype.setup = function (assetDir, callback) {
const self = this
self._logger.info('drop database')
self.drop(function (err) {
if (err) {
self._logger.error('drop database')
return callback(err)
}
self.connect(function (err, db) {
if (err) return callback(err)
self._createCollections(require('./collections'), function (err) {
if (err) {
self._logger.error('create collections')
return callback(err)
}
callback()
})
})
})
}
/**
- Drop database on server then close connection
@memberof module:database.Database
@param {StdCallback} callback - Function will be call after done
@example
// see constructor for detail
var db = new Database(url)
db.drop(function(err) {
// handle for result of drop here
})
*/
prototype.drop = function (callback) {
const self = this
self.connect(function (err, db) {
if (err) return callback(err)
db.dropDatabase(function (err, res) {
if (err) return callback(err)
self.close(function (err) {
if (err) return callback(err)
self._dbclient = null
callback(null)
})
})
})
}
/**
- Specification of collection
- It is defined by mongodb
- This is `option` parameter of `db.createCollection`
@memberof module:database
@typedef {object} CollectionSchema
@see {@link https://docs.mongodb.com/manual/reference/method/db.createCollection}
@example
var schema = {
autoIndexId: false,
validator: {
$and: [
{id: {$type: 'string', $regex: /^[a-z]{2}$/}},
{name: {$type: 'string', $regex: /^[a-zA-Z0-9 ]{1,16}$/}}
]
}
}
*/
/**
- Specify indexing of collection
- It is defined by mongodb
- This wrap `keys`, `options` parameters of `db.collection.createIndex`
into object {keys: keys, options: options}
@memberof module:database
@typedef {object} CollectionIndex
@see {@link https://docs.mongodb.com/manual/reference/method/db.collection.createIndex}
@example
var index = {
keys: {id: 1},
options: {unique: true}
}
*/
/**
- Create collection in database from specification
- Create indexing of collection in database from specification
@memberof module:database.Database
@param {string} name - Name of collection
@param {module:database.CollectionSchema} schema - Specify field of collection
@param {module:database.CollectionIndex} indexing - Specify indexing of collection
@param {StdCallback} callback
- Function will be call after done
- Result contains collection object
@example
// see constructor for detail
var db = new Database(url)
var schema = {
autoIndexId: false,
validator: {
$and: [
{id: {$type: 'string', $regex: /^[a-z]{2}$/}},
{name: {$type: 'string', $regex: /^[a-zA-Z0-9 ]{1,16}$/}}
]
}
}
var index = {
keys: {id: 1},
options: {unique: true}
}
db.createCollection('region', schema, index, function(err, collection) {
// handle for result of operation here
})
*/
prototype._createCollection = function (name, schema, indexing, callback) {
const self = this
self._dbclient.createCollection(name, schema, function (err, collection) {
if (err) return callback(err)
collection.createIndex(indexing.keys, indexing.options, function (err) {
callback(err)
})
})
}
/**
@memberof module:database
@typedef {object} CollectionSpec
@property {string} name - Name of collection
@property {module:database.CollectionSchema} schema - Specification of field of collection
@property {module:database.CollectionIndex} indexing - Specification of indexing of collection
*/
/**
- Create multi collection from array of specification
@memberof module:database.Database
@param {module:database.CollectionSpec[]} collections - List of specifications
@param {StdCallback} callback - Function will be call after done
@example
// see constructor for detail
var db = new Database(url)
var collections = [
{
name: 'region',
schema: {
autoIndexId: false,
validator: {
$and: [
{id: {$type: 'string', $regex: /^[a-z]{2}$/}},
{name: {$type: 'string', $regex: /^[a-zA-Z0-9 ]{1,16}$/}}
]
}
},
index: {
keys: {id: 1},
options: {unique: true}
}
},
{
name: 'book',
schema: {
validator: {
$and: [
{name: {$type: 'string', $regex: /^[a-zA-Z0-9 ]{1,16}$/}},
{price: {$type: 'number'}}
]
}
},
index: {
keys: {name: 1},
options: {unique: true}
}
}
]
db._createCollections(collections, function(err) {
// handle for result here
})
*/
prototype._createCollections = function (collections, callback) {
const self = this
const calls = []
self._logger.info('create collections')
collections.forEach(function (item) {
calls.push(function (callback) {
self._createCollection(item.name, item.schema, item.indexing, callback)
})
})
async.series(calls, function (err) {
// todo: push sample data to database
self._logger.info('push sample data to database')
callback(err)
})
}