mares-mongoose-model
Version:
슬로그업에���� 사용하는 node.js DDD패턴 구현 시 필요합니다. mongoose 모델 생성시 반드시 해당 모듈을 상속받아�� 합니다.
408 lines (371 loc) • 10 kB
JavaScript
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