UNPKG

mares-mongoose-model

Version:

슬로그업에���� 사용하는 node.js DDD패턴 구현 시 필요합니다. mongoose 모델 생성시 반드시 해당 모듈을 상속받아�� 합니다.

408 lines (371 loc) 10 kB
let MaresMongooseError = require('mares-mongoose-error') const _ = require('lodash') class MaresMongooseModel { /** * return collection fields * @returns {string} */ static getAttributes() { return '' } /** * 일반 Error를 mongoose Error로 변경해준다 * @param {Error} e - 몽구스에서 throw 한 일반 에러 * @returns {MaresMongooseError} - mongoose error. */ static throwRefineError(e) { throw new MaresMongooseError(e) } /** * notfound 에러를 만들어준다. * @throws {Error} - 404 error throw */ static throwNotfoundError() { throw new Error('404') } /** * undefined 값을 제거한 객체를 리턴한다. * @param {string[]} keys - key array * @param {Object} object - key/value object * @param {string} [prefix] - prefix */ static getValidFields(keys, object, prefix) { let ret = {} _.map(keys, (key) => { if (object[key] !== undefined) { if (prefix) key = prefix + '.' + key ret[key] = object[key] } }) return ret } /** * get projection fields for aggregate. * @param {string} [attributes = ''] - attribute string, ex) '_id a b c' * @param {string} [prefix] - prefix for join * @returns {Object} */ static getProjection(attributes, prefix) { let attr if (attributes) { attr = attributes } else { attr = this.getAttributes() } let arr = attr.split(' ') let obj = {} _.map(arr, (item) => { if (prefix) { item = prefix + '.' + item } if (item.indexOf('-') === 0) { obj[item] = -1 } else { obj[item] = 1 } }) return obj } /** * convert all item of mongoose instance array * @param {BaseModel[] | BaseModel} instance instance array * @returns {Object[] | Object} */ static toPure(instance) { if (instance instanceof Array) { return _.map(instance, (item) => this.toPureObject(item)) } else { return this.toPureObject(instance) } } /** * return pure object * @returns {Object} */ static toPureObject(instance) { return JSON.parse(JSON.stringify(instance)) } /** * remove undefined value in object recursively. * @param {Object} obj */ static cleanObject(obj) { obj = _.cloneDeep(obj) const recursive = (obj, key) => { let current = obj[key] if (current instanceof Object) { _.map(current, (value, key) => { recursive(current, key) }) if (Object.values(current).length === 0) { delete obj[key] } } else if (current instanceof Array) { _.map(current, (value, key) => { recursive(current, key) }) } } obj = JSON.parse(JSON.stringify(obj)) _.map(obj, (value, key) => { recursive(obj, key) }) return obj } /** * remove undefined value in array recursively. * @param {Object} arr */ static cleanArray(arr) { let obj = this.cleanObject({obj: arr}) return _.cloneDeep(obj.obj) } /** * generate grouping aggregate query * @param namespace - ex. 'order.orderItems.inventories' * @param fieldArray - ex. ['_id name', '_id field', '_id invField'] * @returns {Array} - $group aggregate query */ static buildGroupQuery(namespace, fieldArray) { let namespaceArray = namespace.split('.') let length = namespaceArray.length let query = [] const token = '---' const makeNamespace = (currentIdx, token) => { let ret = '' for (let i = 0; i < length - currentIdx - 1; ++i) { if (ret === '') { ret = namespaceArray[i] } else { ret = ret + token + namespaceArray[i] } } return ret } const getFields = (fieldString) => { let arr = fieldString.split(' ') let retArr = [] arr.map((item) => { if (item.indexOf('-') === -1) { retArr.push(item) } }) return retArr } const makeProp = (prefix = '', i, isValueOnly = false, isLastDot = false, isFirst = true) => { let fields = getFields(fieldArray[i]) let obj = {} fields.map((value) => { if (prefix === '') { obj[value] = {$first: '$' + value} } else { if (!isValueOnly) { if (!isLastDot) { let realValue = prefix + value if (!isFirst) { obj[realValue] = '$' + realValue.replace(new RegExp(token, 'g'), '.') } else { obj[realValue] = {$first: '$' + realValue.replace(new RegExp(token, 'g'), '.')} } } else { if (!isFirst) { let realValue = prefix + value obj[realValue] = '$' + prefix + value } else { let realValue = prefix + value obj[realValue] = {$first: '$' + prefix + value} } } } else { if (!isLastDot) { if (!isFirst) { let realValue = prefix + value obj[value] = '$' + realValue.replace(new RegExp(token, 'g'), '.') } else { let realValue = prefix + value obj[value] = {$first: '$' + realValue.replace(new RegExp(token, 'g'), '.')} } } else { if (!isFirst) { obj[value] = '$' + prefix + value } else { obj[value] = {$first: '$' + prefix + value} } } } } }) return obj } let arr = [] let stack = [] for (let i = 0; i < length; ++i) { let obj = {} if (i === 0) { obj['_id'] = '$' + makeNamespace(i, '.') + '._id' obj['rootId'] = {$first: '$_id'} } else if (i < length - 1) { obj['rootId'] = {$first: '$rootId'} obj['_id'] = '$' + makeNamespace(i, token) + token + '_id' } else { obj['_id'] = '$rootId' } obj = {...makeProp('', 0), ...obj} let space = '' for (let j = 0; j < length - i; ++j) { if (space === '') { space = namespaceArray[j] } else { space = space + token + namespaceArray[j] } if (j < length - i - 1) { obj = {...obj, ...makeProp(space + token, j + 1)} } else { let obj2 = {} let obj3 if (i === 0) { obj3 = makeProp(space + token, j + 1, true, false, false) } else { obj3 = makeProp(space + token, j + 1, true, true, false) } let pop = stack.pop() obj2[space] = {$push: {...obj3}} if (pop) { let popArr = pop.split(token) let lastItem = popArr.slice(-1).pop() obj2[space]['$push'][lastItem] = '$' + pop } obj = {...obj, ...obj2} stack.push(space) } } arr.push({$group: obj}) } return arr } /** * 널값을 가진 모든 필드를 지워준다. 생성시 null 값이 왔을 경우 사용한다. * @param {Object} obj - plain object * @param {boolean} isClean - undefined 및 빈 객체를 지울지 여부 * @returns {Object} obj - null이 지워진 plain object */ static removeNullFields(obj, isClean = true) { const recursive = (val) => { if (val instanceof Object || val instanceof Array) { for (let key in val) { if (recursive(val[key])) { delete val[key] } } if (val instanceof Array) { for (let i = val.length; i >= 0; --i) { if (val[i] === undefined) { val.splice(i, 1) } } } } else { if (val === null) { return true } } return false } obj = _.cloneDeep(obj) this.toNullRecursive(obj) recursive(obj) if (isClean) { return this.cleanObject(obj) } else { return obj } } /** * 실질적으로 객체를 받아 널값을 지워주는 함수. 콜 바이 레퍼런스이기 때문에 외부에서는 사용 안하는 것을 추천. * @param {Object} val - plain object * @returns {boolean} - null 값을 발견했었다면 true, null 값을 발견못했으면 false */ static toNullRecursive(val) { if (!(val instanceof Buffer) && (val instanceof Object || val instanceof Array)) { let isAllNull = true for (let key in val) { if (val[key] !== null) { let isRecursiveAllNull = this.toNullRecursive(val[key]) if (!isRecursiveAllNull) { isAllNull = false } else { val[key] = null } } } if (isAllNull) { return true } else { return false } } else if (val === null) { return true } else { return false } } /** * Null값을 가진 필드를 unset필드로 만들어서 쿼리를 작성해 준다. * @param {Object} obj - plain object * @param {boolean} isArrayUnset - 배열의 원소 값도 unset으로 추가할지 여부 * @param {boolean} isClean - 빈배열, 빈객체, undefined 값을 제거할지 여부 * @returns {Object} obj - unset query */ static toUnsetFromNullFields(obj, isArrayUnset = false, isClean = true) { const makeUnsetStringRecursive = (parent, val, madeObj) => { if ((!isArrayUnset && (val instanceof Object && !(val instanceof Array))) || (isArrayUnset && (val instanceof Object))) { for (let key in val) { let keyString = '' if (parent === '') { keyString = key } else { keyString = parent + '.' + key } makeUnsetStringRecursive(keyString, val[key], madeObj) } } else if (val === null) { madeObj[parent] = '' } return madeObj } if (isClean) { // 모든 undefined, 빈객체, 빈배열 제거 obj = this.cleanObject(obj) } // 모든 객체의 속성이 null이면 해당 값을 객체에서 null로 변경 this.toNullRecursive(obj) return makeUnsetStringRecursive('', obj, {}) } /** * mongoose update시 필요한 set query를 자동으로 null필드를 제거 해주면서 만들어 준다. * @param {Object} obj - plain object * @param {boolean} isClean - 빈배열, 빈객체, undefined 값을 제거할지 여부 */ static toSetFromNotNullFields(obj, isClean = true,) { let refinedObj = this.removeNullFields(obj, isClean) let returnObj = {} const recursive = (val, parent) => { if (val instanceof Object && !(val instanceof Array)) { for (let key in val) { let nextKey = '' if (parent === '') { nextKey = key } else { nextKey = parent + '.' + key } recursive(val[key], nextKey) } } else { returnObj[parent] = val } } recursive(refinedObj, '') return returnObj } } module.exports = MaresMongooseModel