alekzonder/maf

View on GitHub
src/Model/Mongodb/index.js

Summary

Maintainability
D
2 days
Test Coverage
'use strict';

var path = require('path');

var ModelError = require(path.join(__dirname, '..', 'Error'));

var FindCursorChain = require(path.join(__dirname, 'FindCursorChain'));

var DebugTimer = require(path.join(__dirname, '..', '..', 'Debug', 'Timer'));

/**
 * @class
 * @abstract
 */
class ModelMongodb {

    /**
     * @param  {mongodb} db
     * @param {String} collectionName
     */
    constructor (db, collectionName) {
        this._db = db;
        this._collectionName = collectionName;
        this._indexes = null;
        this._collection = null;

        this._debug = null;

        this.Error = ModelError;
    }

    /**
     * set debugger object
     *
     * @param {Request/Debug} debug
     */
    setDebug (debug) {
        this._debug = debug;
    }

    /**
     * init collection of model
     *
     * @return {Promise}
     */
    init () {

        if (!this._collectionName) {
            throw new this.Error(this.Error.CODES.NO_COLLECTION_NAME);
        }

        this._collection = this._db.collection(this._collectionName);

        return this;
    }

    /**
     * ensure collection indexes
     *
     * @return {Promise}
     */
    ensureIndexes (/* options */) {

        return new Promise((resolve, reject) => {

            if (!this._indexes) {
                resolve({collection: this._collectionName, indexes: []});
                return;
            }

            var existsPromises = [];

            for (var i in this._indexes) {

                if (i in this._indexes === false) {
                    throw new this.Error(
                        this.Error.CODES.INVALID_ENSURE_INDEXES,
                        'no index data for index ' + i
                    );
                }

                var index = this._indexes[i];

                if ('options' in index === false || 'name' in index.options === false) {
                    throw new this.Error(
                        this.Error.CODES.INVALID_ENSURE_INDEXES,
                        'no options.name for index ' + i
                    );
                }

                existsPromises.push(
                    this._collection.indexExists(index.options.name)
                );

            }

            Promise.all(existsPromises)
                .then((data) => {
                    var createPromises = [];

                    for (var key in data) {

                        if (key in data === false) {
                            console.log('not key ' + key + ' in ensureIndexes result');
                            continue;
                        }

                        var exists = data[key];

                        if (!exists) {
                            createPromises.push(
                                this._collection.createIndex(
                                    this._indexes[key].fields,
                                    this._indexes[key].options
                                )
                            );
                        }
                    }

                    if (!createPromises.length) {
                        resolve({collection: this._collectionName, indexes: []});
                        return;
                    }

                    Promise.all(createPromises)
                        .then((data) => {
                            resolve({collection: this._collectionName, indexes: data});
                        })
                        .catch((error) => {
                            reject(this.Error.ensureError(error));
                        });

                })
                .catch((error) => {
                    reject(this.Error.ensureError(error));
                });


        });

    }

    /**
     * insert new one
     *
     * @param  {Object} data
     * @return {Promise}
     */
    insertOne (data/*, options*/) {

        return new Promise((resolve, reject) => {

            var timer = this._createTimer('insertOne');

            if (data.id) {
                data._id = data.id;
            }

            timer.data = {
                collection: this._collection.namespace,
                data: data
            };

            this._collection.insertOne(data)
                .then((result) => {
                    timer.stop();

                    if (result.ops && result.ops[0]) {
                        resolve(result.ops[0]);
                    } else {
                        resolve(null);
                    }
                })
                .catch((error) => {
                    if (error) {
                        var e;

                        if (error.code && error.code === 11000) {
                            // already exists
                            e = new this.Error(
                                this.Error.CODES.ALREADY_EXISTS,
                                'document already exists'
                            );
                        } else {
                            e = this.Error.ensureError(error);
                        }

                        timer.error(e.message);

                        reject(e);
                        return;
                    }
                });

        });
    }

    /**
     * findOneAndUpdate
     *
     * @param  {Object} filter
     * @param  {Object} update
     * @param  {Object} options
     * @return {Object}
     */
    findOneAndUpdate (filter, update, options) {

        var timer = this._createTimer('findOneAndUpdate');

        if (!options) {
            options = {
                returnOriginal: false
            };

        } else if (typeof options.returnOriginal === 'undefined') {
            options.returnOriginal = false;
        }

        timer.data = {
            collection: this._collection.namespace,
            filter: filter,
            update: update,
            options: options
        };

        return new Promise((resolve, reject) => {
            this._collection.findOneAndUpdate(filter, update, options)
                .then((data) => {
                    timer.stop();

                    /*
                        data = {
                            value: { _id: 1, id: 1, name: '100' },
                            lastErrorObject: { updatedExisting: true, n: 1 },
                            ok: 1
                        }
                    */

                    if (data.value) {
                        resolve(data.value);
                    } else {
                        resolve(null);
                    }

                })
                .catch((error) => {
                    timer.error(error.message);
                    reject(this.Error.ensureError(error));
                });
        });


    }

    /**
     * get one
     *
     * @param  {Object} query
     * @param {Object} options
     * @return {Promise}
     */
    findOne (query, options) {
        return new Promise((resolve, reject) => {

            var timer = this._createTimer('findOne');

            timer.data = {
                collection: this._collection.namespace,
                query: query,
                options: options
            };

            this._collection.findOne(query, options)
                .then((doc) => {
                    timer.stop();
                    if (doc) {
                        resolve(doc);
                    } else {
                        resolve(null);
                    }
                })
                .catch((error) => {
                    timer.error(error.message);
                    reject(this.Error.ensureError(error));
                });
        });
    }

    /**
     * get one by id
     *
     * @param  {String} id
     * @param {Object} options
     * @return {Promise}
     */
    findOneById (id, options) {
        return new Promise((resolve, reject) => {

            var timer = this._createTimer('findOneById');

            timer.data = {
                collection: this._collection.namespace,
                id: id,
                options: options
            };

            this._collection.findOne({_id: id})
                .then((doc) => {
                    timer.stop();

                    if (doc) {
                        resolve(doc);
                    } else {
                        resolve(null);
                    }
                })
                .catch((error) => {
                    timer.error(error.message);
                    reject(this.Error.ensureError(error));
                });

        });
    }

    /**
     * search by params
     *
     * @param  {Object} filter
     * @param  {Object} fields
     * @return {Promise}
     */
    find (filter, fields) {

        var timer = this._createTimer('find');

        fields = this._prepareFields(fields);

        var chain = new FindCursorChain(this._collection, filter, fields, timer);

        chain.onExec((cursor) => {

            return new Promise((resolve, reject) => {

                timer.stop();

                Promise.all([cursor.count(), cursor.toArray()])
                    .then((data) => {

                        resolve({
                            total: data[0],
                            docs: data[1]
                        });

                    })
                    .catch((error) => {
                        timer.error(error.message);
                        reject(this.Error.ensureError(error));
                    });

            });

        });

        return chain;
    }

    /**
     * update
     *
     * @param  {Object} filter
     * @param  {Object} data
     * @param  {Object} options
     * @return {Promise}
     */
    update (filter, data, options) {

        return new Promise((resolve, reject) => {

            var timer = this._createTimer('update');

            timer.data = {
                collection: this._collection.namespace,
                filter: filter,
                data: data,
                options: options
            };

            var promise;

            if (options && options.multi) {
                promise = this._collection.updateMany(filter, data, {});
            } else {
                promise = this._collection.updateOne(filter, data, {});
            }

            promise
                .then((result) => {
                    timer.stop();

                    /*
                       result = {result: { ok: 1, nModified: 0, n: 0 }, connection: {...}};
                     */

                    resolve(result.result.n);
                })
                .catch((error) => {
                    timer.error(error.message);
                    reject(this.Error.ensureError(error));
                });

        });

    }

    /**
     * remove docs
     *
     * @param {Object} filter
     * @param {Object} options
     * @return {Promise}
     */
    remove (filter, options) {

        return new Promise((resolve, reject) => {
            var timer = this._createTimer('remove');

            timer.data = {
                collection: this._collection.namespace,
                filter: filter,
                options: options
            };

            this._collection.remove(filter, options)
                .then((data) => {
                    timer.stop();
                    /*
                    {
                        ok: data.result.ok,
                        count: data.result.n
                    }
                     */
                    resolve(data.result.n);
                })
                .catch((error) => {
                    timer.error(error.message);
                    reject(this.Error.ensureError(error));
                });

        });

    }

    /**
     * remove one
     *
     * @param  {Object} filter
     * @param {Object} options
     *
     * @return {Promise}
     */
    removeOne (filter, options) {
        var timer = this._createTimer('removeOne');

        timer.data = {
            collection: this._collection.namespace,
            filter: filter,
            options: options
        };

        return new Promise((resolve, reject) => {
            this._collection.remove(filter, {single: true})
                .then((data) => {
                    timer.stop();
                    resolve(data.result.n);
                })
                .catch((error) => {
                    timer.error(error.message);
                    reject(this.Error.ensureError(error));
                });
        });

    }

    /**
     * get cound by filters
     *
     * @param  {Object} filter
     * @param  {Object} options
     * @return {Promise}
     */
    count (filter, options) {
        var timer = this._createTimer('count');

        timer.data = {
            collection: this._collection.namespace,
            filter: filter,
            options: options
        };

        return new Promise((resolve, reject) => {

            this._collection.count(filter)
                .then((data) => {
                    timer.stop();
                    resolve(data);
                })
                .catch((error) => {
                    timer.error(error.message);
                    reject(this.Error.ensureError(error));
                });

        });

    }

    /**
     * aggregate
     *
     * @param  {Object} pipeline
     * @param  {Object} options
     * @return {AggregationCursor}
     */
    aggregate (pipeline, options) {
        var timer = this._createTimer('aggregate');

        timer.data = {
            collection: this._collection.namespace,
            filter: pipeline,
            options: options
        };

        timer.stop();

        return this._collection.aggregate(pipeline, options);
    }

    /**
     * emit debug data
     *
     * @private
     * @param  {Object} data
     */
    _logDebug (data) {

        if (!this._debug || !this._debug.log) {
            return;
        }

        this._debug.log(data);
    }

    /**
     * create debug timer
     *
     * @private
     * @param  {String} name
     * @return {DebugTimer}
     */
    _createTimer (name) {
        var timer = new DebugTimer('mongo', name);

        timer.onStop((data) => {
            this._logDebug(data);
        });

        return timer;
    }

    /**
      * prepare fields
      *
      * @param  {Object} fields
      * @return {Object}
      */
    _prepareFields (fields) {
        var result = {};

        if (!fields) {
            return null;
        }

        if (Array.isArray(fields)) {
            for (var name of fields) {
                result[name] = 1;
            }
        } else if (typeof fields === 'object') {
            result = fields;
        } else {
            throw this.Error(this.Error.CODES.INVALID_FIELDS_FORMAT);
        }

        return result;
    }

}

module.exports = ModelMongodb;