UNPKG

base-repository

Version:

[![Build Status](https://travis-ci.org/joehua87/base-repository.svg?branch=master)](https://travis-ci.org/joehua87/base-repository)

728 lines (603 loc) 25.1 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _moment = require('moment'); var _moment2 = _interopRequireDefault(_moment); var _slug = require('slug'); var _slug2 = _interopRequireDefault(_slug); var _lodash = require('lodash'); var _lodash2 = _interopRequireDefault(_lodash); var _constants = require('./constants'); var constant = _interopRequireWildcard(_constants); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { return step("next", value); }, function (err) { return step("throw", err); }); } } return step("next"); }); }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var debug = require('debug')('base-repository:base-repository'); var processQueryDebug = require('debug')('base-repository:base-repository:process-query'); var BaseRepository = function () { // TODO Refactor this to accept schemaDefinition /** * * @param Model * @param {RepositoryConfig} config */ function BaseRepository(Model, config) { _classCallCheck(this, BaseRepository); this._Model = Model; this._config = config; } _createClass(BaseRepository, [{ key: 'getConfig', value: function getConfig() { return this._config; } }, { key: 'getSchema', value: function getSchema() { return this._Model.schema; } }, { key: 'insert', value: function insert(entities) { return this._Model.create(entities).then(function (response) { return Array.isArray(response) ? response.map(function (entity) { return entity.toObject(); }) : response.toObject(); }); } }, { key: 'findName', value: function () { var ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() { var _ref; var filter = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; var _config$createOption, slugField, nameField, prefix, slugPrefix, condition, entities, slugs, newIdx; return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _config$createOption = this._config.createOption; slugField = _config$createOption.slugField; nameField = _config$createOption.nameField; prefix = _config$createOption.prefix; slugPrefix = (0, _slug2.default)(prefix, { lower: true }); condition = _extends({}, filter, { slug: new RegExp('^' + slugPrefix + '-\\d+') }); _context.next = 8; return this._Model.find(condition).select(slugField).lean(); case 8: entities = _context.sent; slugs = (0, _lodash2.default)(entities).pluck(slugField).map(function (slug) { return parseInt(slug.replace(new RegExp(slugPrefix + '-'), ''), 0); }).value(); newIdx = 1; if (slugs.length > 0) { newIdx = _lodash2.default.max(slugs) + 1; } return _context.abrupt('return', (_ref = {}, _defineProperty(_ref, slugField, slugPrefix + '-' + newIdx), _defineProperty(_ref, nameField, prefix + ' ' + newIdx), _ref)); case 13: case 'end': return _context.stop(); } } }, _callee, this); })); return function findName(_x) { return ref.apply(this, arguments); }; }() }, { key: 'create', value: function () { var ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee2() { var filter = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; var overrideEntity = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; var entity; return regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: _context2.next = 2; return this.findName(filter); case 2: entity = _context2.sent; _context2.next = 5; return this.insert(_extends({}, this._config.defaultEntity, filter, entity, overrideEntity)); case 5: return _context2.abrupt('return', _context2.sent); case 6: case 'end': return _context2.stop(); } } }, _callee2, this); })); return function create(_x3, _x4) { return ref.apply(this, arguments); }; }() }, { key: 'getByKey', value: function getByKey(key) { var projection = arguments.length <= 1 || arguments[1] === undefined ? this._config.detailProjection : arguments[1]; return this._Model.findOne(_defineProperty({}, this._config.key, key)).select(projection).lean(); } }, { key: 'getById', value: function getById(_id) { var projection = arguments.length <= 1 || arguments[1] === undefined ? this._config.detailProjection : arguments[1]; return this._Model.findOne({ _id: _id }).select(projection).lean(); } }, { key: 'getByIds', value: function getByIds(ids) { var projection = arguments.length <= 1 || arguments[1] === undefined ? this._config.detailProjection : arguments[1]; return this._Model.find({ _id: { $in: [ids] } }).select(projection).lean(); } }, { key: 'getByFilter', value: function getByFilter() { var filter = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; var selection = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; var select = _extends({ projection: this._config.detailProjection, sort: this._config.defaultSort }, selection); debug('getByFilter - Api Filter', filter); debug('getByFilter - Api Select', select); var projection = select.projection; var sort = select.sort; var condition = this.processFilter(filter || {}, this._config); debug('getByFilter - Mongoose Filter', condition); return this._Model.findOne(condition).select(projection).sort(sort).lean(); } }, { key: 'query', value: function () { var ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee3() { var filter = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; var selection = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; var select, projection, sort, page, limit, getAll, condition, count, query, entities; return regeneratorRuntime.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: select = _extends({ projection: this._config.queryProjection, sort: this._config.defaultSort, page: 1, limit: this._config.defaultLimit, getAll: false }, selection); debug('query - Api Filter', filter); debug('query - Api Select', select); projection = select.projection; sort = select.sort; page = select.page; limit = select.limit; getAll = select.getAll; condition = this.processFilter(filter || {}, this._config); debug('query - Mongoose Filter', condition); _context3.next = 12; return this._Model.count(condition); case 12: count = _context3.sent; query = this._Model.find(condition).select(projection).sort(sort); if (!getAll) { query = query.skip((page - 1) * limit).limit(limit).lean(); } _context3.next = 17; return query; case 17: entities = _context3.sent; if (!getAll) { _context3.next = 20; break; } return _context3.abrupt('return', { count: count, entities: entities, filter: filter, sort: sort, projection: projection }); case 20: return _context3.abrupt('return', { count: count, entities: entities, filter: filter, sort: sort, projection: projection, page: page, limit: limit }); case 21: case 'end': return _context3.stop(); } } }, _callee3, this); })); return function query(_x12, _x13) { return ref.apply(this, arguments); }; }() }, { key: 'all', value: function () { var ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee4() { var filter = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; var select = arguments[1]; var condition, defaultSelect, _defaultSelect$select, projection, sort, count, entities; return regeneratorRuntime.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: condition = this.processFilter(filter, this._config); defaultSelect = { projection: this._config.queryProjection, sort: this._config.defaultSort }; _defaultSelect$select = _extends({}, defaultSelect, select); projection = _defaultSelect$select.projection; sort = _defaultSelect$select.sort; _context4.next = 7; return this._Model.count(condition); case 7: count = _context4.sent; _context4.next = 10; return this._Model.find(condition).select(projection).sort(sort).lean(); case 10: entities = _context4.sent; return _context4.abrupt('return', { count: count, entities: entities, filter: filter, sort: sort }); case 12: case 'end': return _context4.stop(); } } }, _callee4, this); })); return function all(_x16, _x17) { return ref.apply(this, arguments); }; }() }, { key: 'validateUpdate', value: function () { var ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee5(_id, item) { var entity; return regeneratorRuntime.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: _context5.next = 2; return this._Model.findOne({ _id: _id }); case 2: entity = _context5.sent; if (entity) { _context5.next = 5; break; } throw new Error('Entity not exists'); case 5: entity = Object.assign(entity, item); _context5.next = 8; return entity.save(); case 8: return _context5.abrupt('return', entity); case 9: case 'end': return _context5.stop(); } } }, _callee5, this); })); return function validateUpdate(_x19, _x20) { return ref.apply(this, arguments); }; }() }, { key: 'update', value: function update(_ref2) { var _id = _ref2._id; var item = _objectWithoutProperties(_ref2, ['_id']); return this._Model.update({ _id: _id }, { $set: item }); } }, { key: 'deleteById', value: function () { var ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee6(_id) { var entity; return regeneratorRuntime.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: _context6.next = 2; return this._Model.findOne({ _id: _id }).select('_id'); case 2: entity = _context6.sent; if (entity) { _context6.next = 5; break; } throw new Error('Not exists entity'); case 5: _context6.next = 7; return this._Model.remove({ _id: _id }); case 7: return _context6.abrupt('return', entity); case 8: case 'end': return _context6.stop(); } } }, _callee6, this); })); return function deleteById(_x21) { return ref.apply(this, arguments); }; }() }, { key: 'deleteByKey', value: function deleteByKey(keyValue) { var condition = {}; condition[this._config.key] = keyValue; return this._Model.remove(condition); } }, { key: 'addChild', value: function () { var ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee7(_id, field, item) { var parent, child; return regeneratorRuntime.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: _context7.next = 2; return this._Model.findOne({ _id: _id }).select(field); case 2: parent = _context7.sent; if (parent) { _context7.next = 5; break; } throw new Error('Not exists parent'); case 5: child = parent[field].create(item); parent[field].push(child); _context7.next = 9; return parent.save(); case 9: return _context7.abrupt('return', child); case 10: case 'end': return _context7.stop(); } } }, _callee7, this); })); return function addChild(_x22, _x23, _x24) { return ref.apply(this, arguments); }; }() }, { key: 'removeChild', value: function () { var ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee8(_id, field, itemId) { var parent, child; return regeneratorRuntime.wrap(function _callee8$(_context8) { while (1) { switch (_context8.prev = _context8.next) { case 0: _context8.next = 2; return this._Model.findOne({ _id: _id }).select(field); case 2: parent = _context8.sent; if (parent) { _context8.next = 5; break; } throw new Error('Not exists parent'); case 5: child = parent[field].id(itemId); if (child) { _context8.next = 8; break; } throw new Error('Not exists child'); case 8: child.remove(); _context8.next = 11; return parent.save(); case 11: return _context8.abrupt('return', child); case 12: case 'end': return _context8.stop(); } } }, _callee8, this); })); return function removeChild(_x25, _x26, _x27) { return ref.apply(this, arguments); }; }() /** * * @param filter */ }, { key: 'processFilter', value: function processFilter(filter) { var result = {}; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = this._config.fields[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var _item = _step.value; var value = filter[_item.filterField]; if (!value || value.toString() === '') { continue; } processQueryDebug('Db Type', _item.dbType); processQueryDebug('Compare Type', _item.compareType); // Validate Value switch (_item.dbType) { case constant.STRING: if (typeof value !== 'string') { throw new Error(value + ' is not a valid String'); } break; case constant.INTEGER: value = parseInt(value, 10); if (!Number.isInteger(value)) { throw new Error(value + ' is not a valid Number'); } break; case constant.FLOAT: value = parseFloat(value); if (isNaN(value)) { throw new Error(value + ' is not a valid Float'); } break; case constant.STRING_ARRAY: if (typeof value === 'string') value = [value]; if (!Array.isArray(value)) { throw new Error(value + ' is not a valid String Array'); } var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = value[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var ele = _step2.value; if (typeof ele !== 'string') { throw new Error(ele + ' is not a valid String'); } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } break; case constant.INTEGER_ARRAY: if (typeof value === 'number') value = [value]; if (!Array.isArray(value)) { throw new Error(value + ' is not a valid Number Array'); } value = value.map(function (ele) { return parseInt(ele, 10); }); if (_lodash2.default.any(value, function (ele) { return !Number.isInteger(ele); })) { throw new Error('Cannot parse ' + value + ' into array of integer'); } break; case constant.FLOAT_ARRAY: if (typeof value === 'number') value = [value]; if (!Array.isArray(value)) { throw new Error(value + ' is not a valid Number Array'); } value = value.map(function (ele) { return parseFloat(ele); }); if (_lodash2.default.any(value, function (ele) { return isNaN(ele); })) { throw new Error('Cannot parse ' + value + ' into array of integer'); } break; case constant.BOOLEAN: value = value.toString().toLowerCase() === 'true'; if (value !== true && value !== false) { throw new Error(value + ' is not a valid Boolean'); } break; case constant.DATE: value = (0, _moment2.default)(value, this._config.defaultDateFormat); if (!value.isValid()) { throw new Error(value + ' is not a valid Date'); } break; default: throw new Error('dbType must be in STRING, INTEGER, FLOAT, DATE or BOOLEAN, STRING_ARRAY, INTEGER_ARRAY, FLOAT_ARRAY'); } // Process Data switch (_item.compareType) { case constant.EQUAL: result[_item.dbField] = value; break; case constant.GT: result[_item.dbField] = _extends({}, result[_item.dbField], { $gt: value }); break; case constant.GTE: result[_item.dbField] = _extends({}, result[_item.dbField], { $gte: value }); break; case constant.LT: result[_item.dbField] = _extends({}, result[_item.dbField], { $lt: value }); break; case constant.LTE: result[_item.dbField] = _extends({}, result[_item.dbField], { $lte: value }); break; case constant.REG_EX: result[_item.dbField] = new RegExp(value); break; case constant.REG_EX_I: result[_item.dbField] = new RegExp(value, 'i'); break; case constant.EXISTS: result[_item.dbField] = _extends({}, result[_item.dbField], { $exists: value }); break; case constant.FULL_TEXT: result.$text = { $search: value }; break; case constant.CONTAIN: result[_item.dbField] = { $in: value }; break; default: throw new Error('compareType must be in EQUAL, GT, GTE, LT, LTE, REG_EX, REG_EX_I, FULL_TEXT, EXISTS'); } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } return result; } }]); return BaseRepository; }(); /** * @typedef {Object} RepositoryConfig * @property {String} key * @property {FilterFieldConfig[]} fields * @property {Number} defaultLimit * @property {String} queryProjection * @property {String} detailProjection * @property {String} defaultDateFormat - Default to YYYY-MM-DD HH:mm * @property {Object} createOption * @property {Object} defaultEntity */ /** * @typedef {Object} FilterFieldConfig * @property {String} filterField * @property {String} dbField * @property {String} compareType - must be in EQUAL, GT, GTE, LT, LTE, REG_EX, REG_EX_I, FULL_TEXT, EXISTS * @property {String} dbType - must be in STRING, DATE, INTEGER, FLOAT, BOOLEAN */ exports.default = BaseRepository;