base-repository
Version:
[](https://travis-ci.org/joehua87/base-repository)
728 lines (603 loc) • 25.1 kB
JavaScript
'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;