UNPKG

expresser

Version:

A ready-to-use platform for Node.js web apps, built on top of Express.

690 lines (378 loc) 32.8 kB
<!DOCTYPE html> <html> <head> <title>database.coffee</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <link rel="stylesheet" media="all" href="public/stylesheets/normalize.css" /> <link rel="stylesheet" media="all" href="docco.css" /> </head> <body> <div class="container"> <div class="page"> <div class="header"> <h1>database.coffee</h1> <div class="toc"> <h3>Table of Contents</h3> <ol> <li> <a class="source" href="index.html"> index.coffee </a> </li> <li> <a class="source" href="app.html"> app.coffee </a> </li> <li> <a class="source" href="cron.html"> cron.coffee </a> </li> <li> <a class="source" href="database.html"> database.coffee </a> </li> <li> <a class="source" href="downloader.html"> downloader.coffee </a> </li> <li> <a class="source" href="events.html"> events.coffee </a> </li> <li> <a class="source" href="firewall.html"> firewall.coffee </a> </li> <li> <a class="source" href="imaging.html"> imaging.coffee </a> </li> <li> <a class="source" href="logger.html"> logger.coffee </a> </li> <li> <a class="source" href="mailer.html"> mailer.coffee </a> </li> <li> <a class="source" href="settings.html"> settings.coffee </a> </li> <li> <a class="source" href="sockets.html"> sockets.coffee </a> </li> <li> <a class="source" href="utils.html"> utils.coffee </a> </li> </ol> </div> </div> <h2 id="expresser-database">EXPRESSER DATABASE</h2> <p>Handles MongoDB database transactions using the <code>mongoskin</code> 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 <code>db</code> property, for example: expresser.database.db.collection(“mycollection”).findAndModify(args…). <!-- @see Settings.database --></p> <div class='highlight'><pre><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Database</span></span> lodash = <span class="hljs-built_in">require</span> <span class="hljs-string">"lodash"</span> logger = <span class="hljs-built_in">require</span> <span class="hljs-string">"./logger.coffee"</span> settings = <span class="hljs-built_in">require</span> <span class="hljs-string">"./settings.coffee"</span> mongo = <span class="hljs-built_in">require</span> <span class="hljs-string">"mongoskin"</span></pre></div> <p>@property [Object] Database object (using mongoskin), will be set during <code>init</code>.</p> <div class='highlight'><pre> <span class="hljs-attribute">db</span>: <span class="hljs-literal">null</span></pre></div> <h2 id="init">INIT</h2> <p>Init the databse module and test the connection straight away. @param [Object] options Database init options.</p> <div class='highlight'><pre> <span class="hljs-attribute">init</span>: <span class="hljs-function"><span class="hljs-params">(options)</span> =&gt;</span> <span class="hljs-keyword">if</span> settings.database.connString? <span class="hljs-keyword">and</span> settings.database.connString <span class="hljs-keyword">isnt</span> <span class="hljs-string">""</span> <span class="hljs-property">@setDb</span> settings.database.connString, settings.database.options <span class="hljs-keyword">else</span> logger.debug <span class="hljs-string">"Database.init"</span>, <span class="hljs-string">"No connection string set."</span>, <span class="hljs-string">"Database module won't work."</span></pre></div> <h2 id="crud-implementation">CRUD IMPLEMENTATION</h2> <p>Get data from the database. A <code>collection</code> and <code>callback</code> must be specified. The <code>filter</code> is optional. Please note that if <code>filter</code> 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.</p> <div class='highlight'><pre> <span class="hljs-attribute">get</span>: <span class="hljs-function"><span class="hljs-params">(collection, filter, options, callback)</span> =&gt;</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> callback? <span class="hljs-keyword">if</span> lodash.isFunction options callback = options options = <span class="hljs-literal">null</span> <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> lodash.isFunction filter callback = filter filter = <span class="hljs-literal">null</span></pre></div> <p>Callback is mandatory!</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> callback? <span class="hljs-keyword">if</span> settings.logger.autoLogErrors logger.error <span class="hljs-string">"Database.get"</span>, <span class="hljs-string">"No callback specified. Abort!"</span>, collection, filter <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Error <span class="hljs-string">"Database.get: a callback (last argument) must be specified."</span></pre></div> <p>No DB set? Throw exception.</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> <span class="hljs-property">@db</span>? <span class="hljs-keyword">if</span> settings.logger.autoLogErrors logger.error <span class="hljs-string">"Database.get"</span>, <span class="hljs-string">"The db is null or was not initialized. Abort!"</span>, collection, filter <span class="hljs-keyword">return</span> callback <span class="hljs-string">"Database.insert: the db was not initialized, please check database settings and call its 'init' method."</span></pre></div> <p>Create the DB callback helper.</p> <div class='highlight'><pre> <span class="hljs-function"><span class="hljs-title">dbCallback</span> = <span class="hljs-params">(err, result)</span> =&gt;</span> <span class="hljs-keyword">if</span> callback? result = <span class="hljs-property">@normalizeId</span> result <span class="hljs-keyword">if</span> settings.database.normalizeId callback err, result</pre></div> <p>Set collection object.</p> <div class='highlight'><pre> dbCollection = <span class="hljs-property">@db</span>.collection collection</pre></div> <p>Parse ID depending on <code>filter</code>.</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> filter? <span class="hljs-keyword">if</span> filter._id? id = filter._id <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> filter.id? <span class="hljs-keyword">and</span> settings.database.normalizeId id = filter.id <span class="hljs-keyword">else</span> t = <span class="hljs-keyword">typeof</span> filter id = filter <span class="hljs-keyword">if</span> t <span class="hljs-keyword">is</span> <span class="hljs-string">"string"</span> <span class="hljs-keyword">or</span> t <span class="hljs-keyword">is</span> <span class="hljs-string">"integer"</span></pre></div> <p>Get <code>limit</code> option.</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> options?.limit? limit = options.limit <span class="hljs-keyword">else</span> limit = <span class="hljs-number">0</span></pre></div> <p>Find documents depending on <code>filter</code> and <code>options</code>. If id is set, use the shorter findById.</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> id? dbCollection.findById id, dbCallback</pre></div> <p>Create a params object for the find method.</p> <div class='highlight'><pre> <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> filter? findParams = {<span class="hljs-attribute">$query</span>: filter} findParams[<span class="hljs-string">"$orderby"</span>] = options.orderBy <span class="hljs-keyword">if</span> options?.orderBy? <span class="hljs-keyword">if</span> limit &gt; <span class="hljs-number">0</span> dbCollection.find(findParams).limit(limit).toArray dbCallback <span class="hljs-keyword">else</span> dbCollection.find(findParams).toArray dbCallback</pre></div> <p>Search everything!</p> <div class='highlight'><pre> <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> limit &gt; <span class="hljs-number">0</span> dbCollection.find({}).limit(limit).toArray dbCallback <span class="hljs-keyword">else</span> dbCollection.find({}).toArray dbCallback <span class="hljs-keyword">if</span> filter? filterLog = filter filterLog.password = <span class="hljs-string">"***"</span> <span class="hljs-keyword">if</span> filterLog.password? filterLog.passwordHash = <span class="hljs-string">"***"</span> <span class="hljs-keyword">if</span> filterLog.passwordHash? logger.debug <span class="hljs-string">"Database.get"</span>, collection, filterLog, options <span class="hljs-keyword">else</span> logger.debug <span class="hljs-string">"Database.get"</span>, collection, <span class="hljs-string">"No filter."</span>, options</pre></div> <p>Add new documents to the database. The <code>options</code> 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.</p> <div class='highlight'><pre> <span class="hljs-attribute">insert</span>: <span class="hljs-function"><span class="hljs-params">(collection, obj, callback)</span> =&gt;</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> obj? <span class="hljs-keyword">if</span> settings.logger.autoLogErrors logger.error <span class="hljs-string">"Database.insert"</span>, <span class="hljs-string">"No object specified. Abort!"</span>, collection <span class="hljs-keyword">if</span> callback? callback <span class="hljs-string">"Database.insert: no object (second argument) was specified."</span> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span></pre></div> <p>No DB set? Throw exception.</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> <span class="hljs-property">@db</span>? <span class="hljs-keyword">if</span> settings.logger.autoLogErrors logger.error <span class="hljs-string">"Database.insert"</span>, <span class="hljs-string">"The db is null or was not initialized. Abort!"</span>, collection <span class="hljs-keyword">if</span> callback? callback <span class="hljs-string">"Database.insert: the db was not initialized, please check database settings and call its 'init' method."</span> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span></pre></div> <p>Create the DB callback helper.</p> <div class='highlight'><pre> <span class="hljs-function"><span class="hljs-title">dbCallback</span> = <span class="hljs-params">(err, result)</span> =&gt;</span> <span class="hljs-keyword">if</span> callback? result = <span class="hljs-property">@normalizeId</span>(result) <span class="hljs-keyword">if</span> settings.database.normalizeId callback err, result</pre></div> <p>Set collection object.</p> <div class='highlight'><pre> dbCollection = <span class="hljs-property">@db</span>.collection collection</pre></div> <p>Execute insert!</p> <div class='highlight'><pre> dbCollection.insert obj, dbCallback logger.debug <span class="hljs-string">"Database.insert"</span>, collection</pre></div> <p>Update existing documents on the database. The <code>options</code> 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.</p> <div class='highlight'><pre> <span class="hljs-attribute">update</span>: <span class="hljs-function"><span class="hljs-params">(collection, obj, options, callback)</span> =&gt;</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> callback? <span class="hljs-keyword">and</span> lodash.isFunction options callback = options options = {}</pre></div> <p>Object or filter is mandatory.</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> obj? <span class="hljs-keyword">if</span> settings.logger.autoLogErrors logger.error <span class="hljs-string">"Database.update"</span>, <span class="hljs-string">"No object specified. Abort!"</span>, collection <span class="hljs-keyword">if</span> callback? callback <span class="hljs-string">"Database.update: no object (second argument) was specified."</span> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span></pre></div> <p>No DB set? Throw exception.</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> <span class="hljs-property">@db</span>? <span class="hljs-keyword">if</span> settings.logger.autoLogErrors logger.error <span class="hljs-string">"Database.update"</span>, <span class="hljs-string">"The db is null or was not initialized. Abort!"</span>, collection <span class="hljs-keyword">if</span> callback? callback <span class="hljs-string">"Database.update: the db was not initialized, please check database settings and call its 'init' method."</span> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span></pre></div> <p>Create the DB callback helper.</p> <div class='highlight'><pre> <span class="hljs-function"><span class="hljs-title">dbCallback</span> = <span class="hljs-params">(err, result)</span> =&gt;</span> <span class="hljs-keyword">if</span> callback? result = <span class="hljs-property">@normalizeId</span>(result) <span class="hljs-keyword">if</span> settings.database.normalizeId callback err, result</pre></div> <p>Set collection object.</p> <div class='highlight'><pre> dbCollection = <span class="hljs-property">@db</span>.collection collection</pre></div> <p>Make sure the ID is converted to ObjectID.</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> obj._id? id = mongo.ObjectID.createFromHexString obj._id.toString() <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> obj.id? <span class="hljs-keyword">and</span> settings.database.normalizeId id = mongo.ObjectID.createFromHexString obj.id.toString()</pre></div> <p>Make sure options is valid.</p> <div class='highlight'><pre> options = {} <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> options?</pre></div> <p>If a <code>filter</code> option was set, use it as the query filter otherwise use the “_id” property.</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> options.filter? filter = options.filter <span class="hljs-keyword">else</span> filter = {<span class="hljs-string">"_id"</span>: id}</pre></div> <p>If options patch is set, replace specified document properties only instead of replacing the whole document.</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> options.patch docData = {<span class="hljs-attribute">$set</span>: obj} <span class="hljs-keyword">else</span> docData = obj</pre></div> <p>Set default options.</p> <div class='highlight'><pre> options = lodash.defaults options, {<span class="hljs-string">"new"</span>: <span class="hljs-literal">true</span>, <span class="hljs-string">"insert"</span>: <span class="hljs-literal">false</span>}</pre></div> <p>Execute update!</p> <div class='highlight'><pre> dbCollection.update filter, docData, options, dbCallback <span class="hljs-keyword">if</span> id? logger.debug <span class="hljs-string">"Database.update"</span>, collection, options, <span class="hljs-string">"ID: <span class="hljs-subst">#{id}</span>"</span> <span class="hljs-keyword">else</span> logger.debug <span class="hljs-string">"Database.update"</span>, collection, options, <span class="hljs-string">"New document."</span></pre></div> <p>DEPRECATED! Alias for <code>update</code>, will be removed soon.</p> <div class='highlight'><pre> <span class="hljs-attribute">set</span>:<span class="hljs-function"> =&gt;</span> <span class="hljs-built_in">console</span>.warn <span class="hljs-string">"Database.set"</span>, <span class="hljs-string">"Method is deprecated, use .insert or .update instead!"</span> <span class="hljs-property">@update</span>.apply <span class="hljs-keyword">this</span>, arguments</pre></div> <p>Delete an object from the database. The <code>obj</code> 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.</p> <div class='highlight'><pre> <span class="hljs-attribute">remove</span>: <span class="hljs-function"><span class="hljs-params">(collection, filter, callback)</span> =&gt;</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> callback? <span class="hljs-keyword">and</span> lodash.isFunction options callback = options options = {}</pre></div> <p>Filter is mandatory.</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> filter? <span class="hljs-keyword">if</span> settings.logger.autoLogErrors logger.error <span class="hljs-string">"Database.remove"</span>, <span class="hljs-string">"No filter specified. Abort!"</span>, collection <span class="hljs-keyword">if</span> callback? callback <span class="hljs-string">"Database.remove: no filter (second argument) was specified."</span> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span></pre></div> <p>No DB set? Throw exception.</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> <span class="hljs-property">@db</span>? <span class="hljs-keyword">if</span> settings.logger.autoLogErrors logger.error <span class="hljs-string">"Database.remove"</span>, <span class="hljs-string">"The db is null or was not initialized. Abort!"</span>, collection <span class="hljs-keyword">if</span> callback? callback <span class="hljs-string">"Database.remove: the db was not initialized, please check database settings and call its 'init' method."</span> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span></pre></div> <p>Check it the <code>obj</code> is the model itself, or only the ID string / number.</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> filter._id? id = filter._id <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> filter.id <span class="hljs-keyword">and</span> settings.database.normalizeId id = filter.id <span class="hljs-keyword">else</span> t = <span class="hljs-keyword">typeof</span> filter id = filter <span class="hljs-keyword">if</span> t <span class="hljs-keyword">is</span> <span class="hljs-string">"string"</span> <span class="hljs-keyword">or</span> t <span class="hljs-keyword">is</span> <span class="hljs-string">"integer"</span></pre></div> <p>Create the DB callback helper.</p> <div class='highlight'><pre> <span class="hljs-function"><span class="hljs-title">dbCallback</span> = <span class="hljs-params">(err, result)</span> =&gt;</span> <span class="hljs-keyword">if</span> callback? result = <span class="hljs-property">@normalizeId</span>(result) <span class="hljs-keyword">if</span> settings.database.normalizeId callback err, result</pre></div> <p>Set collection object and remove specified object from the database.</p> <div class='highlight'><pre> dbCollection = <span class="hljs-property">@db</span>.collection collection</pre></div> <p>Remove object by ID or filter.</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> id? <span class="hljs-keyword">and</span> id <span class="hljs-keyword">isnt</span> <span class="hljs-string">""</span> dbCollection.removeById id, dbCallback <span class="hljs-keyword">else</span> dbCollection.remove filter, dbCallback logger.debug <span class="hljs-string">"Database.remove"</span>, collection, filter</pre></div> <p>Alias for <code>remove</code>.</p> <div class='highlight'><pre> <span class="hljs-attribute">del</span>:<span class="hljs-function"> =&gt;</span> <span class="hljs-property">@remove</span>.apply <span class="hljs-keyword">this</span>, arguments</pre></div> <p>Count documents from the database. A <code>collection</code> must be specified. If no <code>filter</code> 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.</p> <div class='highlight'><pre> <span class="hljs-attribute">count</span>: <span class="hljs-function"><span class="hljs-params">(collection, filter, callback)</span> =&gt;</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> callback? <span class="hljs-keyword">and</span> lodash.isFunction filter callback = filter filter = {}</pre></div> <p>Callback is mandatory!</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> callback? <span class="hljs-keyword">if</span> settings.logger.autoLogErrors logger.error <span class="hljs-string">"Database.count"</span>, <span class="hljs-string">"No callback specified. Abort!"</span>, collection, filter <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Error <span class="hljs-string">"Database.count: a callback (last argument) must be specified."</span></pre></div> <p>Create the DB callback helper.</p> <div class='highlight'><pre> <span class="hljs-function"><span class="hljs-title">dbCallback</span> = <span class="hljs-params">(err, result)</span> =&gt;</span> <span class="hljs-keyword">if</span> callback? logger.debug <span class="hljs-string">"Database.count"</span>, collection, filter, <span class="hljs-string">"Result <span class="hljs-subst">#{result}</span>"</span> callback err, result</pre></div> <p>MongoDB has a built-in count so use it.</p> <div class='highlight'><pre> dbCollection = <span class="hljs-property">@db</span>.collection collection dbCollection.count filter, dbCallback</pre></div> <h2 id="helper-methods">HELPER METHODS</h2> <p>Helper to transform MongoDB document “_id” to “id”. @param [Object] result The document or result to be normalized. @return [Object] Returns the normalized document.</p> <div class='highlight'><pre> <span class="hljs-attribute">normalizeId</span>: <span class="hljs-function"><span class="hljs-params">(result)</span> =&gt;</span> <span class="hljs-keyword">return</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> result? isArray = lodash.isArray result <span class="hljs-keyword">or</span> lodash.isArguments result</pre></div> <p>Check if result is a collection / array or a single document.</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> isArray <span class="hljs-keyword">for</span> obj <span class="hljs-keyword">in</span> result <span class="hljs-keyword">if</span> obj[<span class="hljs-string">"_id"</span>]? obj[<span class="hljs-string">"id"</span>] = obj[<span class="hljs-string">"_id"</span>].toString() <span class="hljs-keyword">delete</span> obj[<span class="hljs-string">"_id"</span>] <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> result[<span class="hljs-string">"_id"</span>]? result[<span class="hljs-string">"id"</span>] = result[<span class="hljs-string">"_id"</span>].toString() <span class="hljs-keyword">delete</span> result[<span class="hljs-string">"_id"</span>] <span class="hljs-keyword">return</span> result</pre></div> <p>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.</p> <div class='highlight'><pre> <span class="hljs-attribute">setDb</span>: <span class="hljs-function"><span class="hljs-params">(connString, options)</span> =&gt;</span> <span class="hljs-property">@db</span> = mongo.db connString, options</pre></div> <p>Safe logging, strip username and password.</p> <div class='highlight'><pre> sep = connString.indexOf <span class="hljs-string">"@"</span> connStringSafe = connString connStringSafe = connStringSafe.substring sep <span class="hljs-keyword">if</span> sep &gt; <span class="hljs-number">0</span> logger.debug <span class="hljs-string">"Database.setDb"</span>, connStringSafe, options</pre></div> <h2 id="singleton-implementation-">Singleton implementation.</h2> <div class='highlight'><pre>Database.<span class="hljs-function"><span class="hljs-title">getInstance</span> = -&gt;</span> <span class="hljs-property">@instance</span> = <span class="hljs-keyword">new</span> Database() <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> <span class="hljs-property">@instance</span>? <span class="hljs-keyword">return</span> <span class="hljs-property">@instance</span> <span class="hljs-built_in">module</span>.<span class="hljs-built_in">exports</span> = <span class="hljs-built_in">exports</span> = Database.getInstance()</pre></div> <div class="fleur">h</div> </div> </div> </body> </html>