UNPKG

expresser

Version:

A ready to use Node.js web app wrapper, built on top of Express.

337 lines (278 loc) 14.1 kB
# EXPRESSER DATABASE # ----------------------------------------------------------------------------- # Handles MongoDB database transactions using the `mongoskin` module. It supports # a very simple failover mechanism where you can specify a "backup" connection # string to which the module will connect in case the main database is down. # If you prefer tp access Mongo directly, you can use the `db` property, for example: # expresser.database.db.collection("mycollection").findAndModify(args...). # <!-- # @see Settings.database # --> class Database lodash = require "lodash" logger = require "./logger.coffee" settings = require "./settings.coffee" mongo = require "mongoskin" # @property [Object] Database object (using mongoskin), will be set during `init`. db: null # INIT # ------------------------------------------------------------------------- # Init the database module and test the connection straight away. # @param [Object] options Database init options. init: (options) => logger.debug "Database.init", options lodash.assign settings.database, options if options? if settings.database.connString? and settings.database.connString isnt "" @setDb settings.database.connString, settings.database.options else logger.debug "Database.init", "No connection string set.", "Database module won't work." # CRUD IMPLEMENTATION # ------------------------------------------------------------------------- # Get data from the database. A `collection` and `callback` must be specified. The `filter` is optional. # Please note that if `filter` has an _id or id field, or if it's a plain string or number, it will be used # to return documents by ID. Otherwise it's used as keys-values object for filtering. # @param [String] collection The collection name. # @param [String, Object] filter Optional, if a string or number, assume it's the document ID. Otherwise assume keys-values filter. # @param [Object] options Options to be passed to the query. # @option options [Integer] limit Limits the resultset to X documents. # @param [Method] callback Callback (err, result) when operation has finished. get: (collection, filter, options, callback) => if not callback? if lodash.isFunction options callback = options options = null else if lodash.isFunction filter callback = filter filter = null # Callback is mandatory! if not callback? throw new Error "Database.get: a callback (last argument) must be specified." # No DB set? Throw exception. if not @db? return callback "Database.insert: the db was not initialized, please check database settings and call its 'init' method." # Create the DB callback helper. dbCallback = (err, result) => if callback? result = @normalizeId result if settings.database.normalizeId callback err, result # Set collection object. dbCollection = @db.collection collection # Parse ID depending on `filter`. if filter? if filter._id? id = filter._id else if filter.id? and settings.database.normalizeId id = filter.id else t = typeof filter id = filter if t is "string" or t is "integer" # Get `limit` option. if options?.limit? limit = options.limit else limit = 0 # Find documents depending on `filter` and `options`. # If id is set, use the shorter findById. if id? dbCollection.findById id, dbCallback # Create a params object for the find method. else if filter? findParams = {$query: filter} findParams["$orderby"] = options.orderBy if options?.orderBy? if limit > 0 dbCollection.find(findParams).limit(limit).toArray dbCallback else dbCollection.find(findParams).toArray dbCallback # Search everything! else if limit > 0 dbCollection.find({}).limit(limit).toArray dbCallback else dbCollection.find({}).toArray dbCallback if filter? filterLog = filter filterLog.password = "***" if filterLog.password? filterLog.passwordHash = "***" if filterLog.passwordHash? logger.debug "Database.get", collection, filterLog, options else logger.debug "Database.get", collection, "No filter.", options # Add new documents to the database. # The `options` parameter is optional. # @param [String] collection The collection name. # @param [Object] obj Document or array of documents to be added. # @param [Method] callback Callback (err, result) when operation has finished. insert: (collection, obj, callback) => if not obj? if callback? callback "Database.insert: no object (second argument) was specified." return false # No DB set? Throw exception. if not @db? if callback? callback "Database.insert: the db was not initialized, please check database settings and call its 'init' method." return false # Create the DB callback helper. dbCallback = (err, result) => if callback? result = @normalizeId(result) if settings.database.normalizeId callback err, result # Set collection object. dbCollection = @db.collection collection # Execute insert! dbCollection.insert obj, dbCallback logger.debug "Database.insert", collection # Update existing documents on the database. # The `options` parameter is optional. # @param [String] collection The collection name. # @param [Object] obj Document or data to be updated. # @param [Object] options Optional, options to control and filter the insert behaviour. # @option options [Object] filter Defines the query filter. If not specified, will try using the ID of the passed object. # @option options [Boolean] patch Default is false, if true replace only the specific properties of documents instead of the whole data, using $set. # @option options [Boolean] upsert Default is false, if true it will create documents if none was found. # @param [Method] callback Callback (err, result) when operation has finished. update: (collection, obj, options, callback) => if not callback? and lodash.isFunction options callback = options options = {} # Object or filter is mandatory. if not obj? if callback? callback "Database.update: no object (second argument) was specified." return false # No DB set? Throw exception. if not @db? if callback? callback "Database.update: the db was not initialized, please check database settings and call its 'init' method." return false # Create the DB callback helper. dbCallback = (err, result) => if callback? result = @normalizeId(result) if settings.database.normalizeId callback err, result # Set collection object. dbCollection = @db.collection collection # Make sure the ID is converted to ObjectID. if obj._id? id = mongo.ObjectID.createFromHexString obj._id.toString() else if obj.id? and settings.database.normalizeId id = mongo.ObjectID.createFromHexString obj.id.toString() # Make sure options is valid. options = {} if not options? # If a `filter` option was set, use it as the query filter otherwise use the "_id" property. if options.filter? filter = options.filter else filter = {"_id": id} # If options patch is set, replace specified document properties only instead of replacing the whole document. if options.patch docData = {$set: obj} else docData = obj # Set default options. options = lodash.defaults options, {"new": true, "insert": false} # Execute update! dbCollection.update filter, docData, options, dbCallback if id? logger.debug "Database.update", collection, options, "ID: #{id}" else logger.debug "Database.update", collection, options, "New document." # DEPRECATED! Alias for `update`, will be removed soon. set: => console.warn "Database.set", "Method is deprecated, use .insert or .update instead!" @update.apply this, arguments # Delete an object from the database. The `obj` argument can be either the document itself, or its integer/string ID. # @param [String] collection The collection name. # @param [String, Object] filter If a string or number, assume it's the document ID. Otherwise assume the document itself. # @param [Method] callback Callback (err, result) when operation has finished. remove: (collection, filter, callback) => if not callback? and lodash.isFunction options callback = options options = {} # Filter is mandatory. if not filter? if callback? callback "Database.remove: no filter (second argument) was specified." return false # No DB set? Throw exception. if not @db? if callback? callback "Database.remove: the db was not initialized, please check database settings and call its 'init' method." return false # Check it the `obj` is the model itself, or only the ID string / number. if filter._id? id = filter._id else if filter.id and settings.database.normalizeId id = filter.id else t = typeof filter id = filter if t is "string" or t is "integer" # Create the DB callback helper. dbCallback = (err, result) => if callback? result = @normalizeId(result) if settings.database.normalizeId callback err, result # Set collection object and remove specified object from the database. dbCollection = @db.collection collection # Remove object by ID or filter. if id? and id isnt "" dbCollection.removeById id, dbCallback else dbCollection.remove filter, dbCallback logger.debug "Database.remove", collection, filter # Alias for `remove`. del: => @remove.apply this, arguments # Count documents from the database. A `collection` must be specified. # If no `filter` is not passed then count all documents. # @param [String] collection The collection name. # @param [Object] filter Optional, keys-values filter of documents to be counted. # @param [Method] callback Callback (err, result) when operation has finished. count: (collection, filter, callback) => if not callback? and lodash.isFunction filter callback = filter filter = {} # Callback is mandatory! if not callback? throw new Error "Database.count: a callback (last argument) must be specified." # Create the DB callback helper. dbCallback = (err, result) => if callback? logger.debug "Database.count", collection, filter, "Result #{result}" callback err, result # MongoDB has a built-in count so use it. dbCollection = @db.collection collection dbCollection.count filter, dbCallback # HELPER METHODS # ------------------------------------------------------------------------- # Helper to transform MongoDB document "_id" to "id". # @param [Object] result The document or result to be normalized. # @return [Object] Returns the normalized document. normalizeId: (result) => return if not result? isArray = lodash.isArray result or lodash.isArguments result # Check if result is a collection / array or a single document. if isArray for obj in result if obj["_id"]? obj["id"] = obj["_id"].toString() delete obj["_id"] else if result["_id"]? result["id"] = result["_id"].toString() delete result["_id"] return result # Helper to set the current DB object. Can be called externally but ideally you should control # the connection string by updating your app settings.json file. # @param [Object] connString The connection string, for example user:password@hostname/dbname. # @param [Object] options Additional options to be passed when creating the DB connection object. setDb: (connString, options) => @db = mongo.db connString, options # Safe logging, strip username and password. sep = connString.indexOf "@" connStringSafe = connString connStringSafe = connStringSafe.substring sep if sep > 0 logger.debug "Database.setDb", connStringSafe, options # Singleton implementation. # ----------------------------------------------------------------------------- Database.getInstance = -> @instance = new Database() if not @instance? return @instance module.exports = exports = Database.getInstance()