'use strict'const Promise = require('bluebird')const { HistoryLogModel } = require('./model')const DeepDiff = require('./diffHelper')const { isEmpty, get, assign, pick, set } = require('lodash')
function HistoryPlugin (schema, options = {}) { schema.statics.addLoggedUser = function (loggedUser) { set(schema, '_loggedUser', loggedUser) } schema.statics.getChangeDiffs = async function (old, current) { return _processGetDiffs.call(this, { old, current }, options) }
schema.statics.createHistory = async function (params) { await _processCreateHistory.call(getCurrentUser.call(this, schema, params), params, options) }
schema.pre('save', async function () { await initializeProcess.call(getCurrentUser.call(this, schema), _processPreSave, options) })
schema.pre(/update|updateOne|findOneAndUpdate|findByIdAndUpdate|updateMany|findOneAndReplace|replaceOne/, async function () { await initializeProcess.call(getCurrentUser.call(this, schema), _processPreUpdate, options) })
schema.pre(/deleteOne|remove/, async function () { await initializeProcess.call(getCurrentUser.call(this, schema), _processPreRemove, options) })
schema.pre(/deleteMany/, async function () { await initializeProcess.call(getCurrentUser.call(this, schema), _processRemoveMany, options) })
schema.pre(/remove/, { document: false, query: true }, async function () { await initializeProcess.call(getCurrentUser.call(this, schema), _processRemoveMany, options) })
schema.post(/findOneAndRemove|findByIdAndRemove|findByIdAndDelete|findOneAndDelete/, async function (doc) { await initializeProcess.call(getCurrentUser.call(this, schema), _processPosRemove, options, doc) })
schema.post('insertMany', async function (docs) { await initializeProcess.call(getCurrentUser.call(this, schema), _processPosInsertMany, options, docs) })}
function getCurrentUser (schema, params) { const _loggedUser = get(schema, '_loggedUser', {}) const loggedUser = get(params, 'loggedUser', {}) this._loggedUser = _toJSON(!isEmpty(_loggedUser) ? _loggedUser : loggedUser) delete schema._loggedUser return this}
async function initializeProcess (method, options, docs) { await method.call(this, options, docs)}
function _toJSON (json) { if (isEmpty(json)) { return {} } return JSON.parse(JSON.stringify(json))}
function _mappedFieldsLog (doc = {}) { const actions = { updated: 'Edited Document', deleted: 'Removed Document', created: 'Created Document', undefined: 'No Defined' } const { changes, module, documentNumber, method, user } = doc const action = get(actions, `${method}`, method) return { changes, action, module, documentNumber, method, user }}
async function _saveHistoryLog (object, options) { if (isEmpty(get(object, 'changes'))) { console.warn('MONGOOSE-HISTORY-TRACE: path: {changes} no diffs documents.') return } if (isEmpty(get(object, 'user')) && get(options, 'isAuthenticated', true)) { console.warn('MONGOOSE-HISTORY-TRACE: path: {user} is required. Example: Model.addLoggedUser(req.user)') return } const HistoryLog = HistoryLogModel(options) const history = new HistoryLog(object) return await history.save()}
async function _processGetDiffs (params, options) { const old = get(params, 'old', {}) const current = get(params, 'current', {})
return DeepDiff.getDiff({ old, current }, {}, options)}
async function mountDocToSave ({ options, params, doc }, method) { const self = this let result = {}
set(result, 'old', _toJSON(get(params, 'old', {}))) set(result, 'current', _toJSON(get(params, 'current', {}))) set(result, 'schema', get(self, 'schema')) set(result, 'user', pick(get(self, '_loggedUser'), get(options, 'userPaths', []))) set(result, 'module', get(options, 'moduleName', get(self, 'mongooseCollection.name', get(self, 'collection.name')))) set(result, 'method', (method || get(params, 'method', 'undefined')))
if ({}.hasOwnProperty.call(self, 'isNew')) { result = await _helperPreSave.call(this, result) } if (!isEmpty(doc)) { result = _helperPosSaveWithDoc.call(this, result, doc) } if (method === 'updated') { result = await _helperPreUpdate.call(this, result) } set(result, 'documentNumber', get(result, 'old._id', get(result, 'current._id'))) return result}
async function _helperPreSave (result) { const self = this set(result, 'method', (get(self, 'isNew') ? 'created' : 'updated')) set(result, 'current', _toJSON(self.toObject ? self.toObject() : self)) const _id = get(result, 'current._id')
if (!self.isNew) { set(result, 'old', _toJSON(await self.constructor.findOne({ _id }))) } return result}
async function _helperPreUpdate (result) { const self = this
const query = get(self, '_conditions') if (isEmpty(query)) { return console.warn(`Not found documents to ${result.method} history logs.`) } const old = _toJSON(await self.findOne(query)) if (isEmpty(old)) { return console.warn(`Not found documents to ${result.method} history logs.`) } const mod = _toJSON(get(self, '_update.$set', get(self, '_update')))
set(result, 'old', old) set(result, 'current', assign({}, old, mod))
return result}
function _helperPosSaveWithDoc (result, doc) { if (result.method === 'created') { set(result, 'current', _toJSON(doc.toObject ? doc.toObject() : doc)) } if (result.method === 'deleted') { set(result, 'old', _toJSON((doc.toObject ? doc.toObject() : doc))) } return result}
async function _processCreateHistory (params, options) { const { schema, user, module, method, old, current, documentNumber } = await mountDocToSave.call(this, { options, params })
const changes = DeepDiff.getDiff({ old, current }, schema, options) const docMapped = _mappedFieldsLog({ changes, method, module, documentNumber, user })
await _saveHistoryLog(docMapped, options)}
async function _processRemoveMany (options) { const query = get(this, '_conditions') const olds = _toJSON(await this.find(query)) Promise.map(olds, async (eachOld) => await _processPosRemove.call(this, options, eachOld))}
async function _processPosInsertMany (options, docs) { return Promise.map(docs, async (eachDoc) => await _processPosSave.call(this, options, eachDoc))}
async function _processPreRemove (options) { const query = get(this, '_conditions') const old = _toJSON(await this.findOne(query))
await _processPosRemove.call(this, options, old)}
async function _processPreUpdate (options) { const { schema, user, module, method, old, current, documentNumber } = await mountDocToSave.call(this, { options }, 'updated')
const changes = DeepDiff.getDiff({ old, current }, schema, options) const docMapped = _mappedFieldsLog({ changes, method, module, documentNumber, user })
await _saveHistoryLog(docMapped, options)}
async function _processPreSave (options) { const { schema, user, module, method, old, current, documentNumber } = await mountDocToSave.call(this, { options })
const changes = DeepDiff.getDiff({ old, current }, schema, options) const docMapped = _mappedFieldsLog({ changes, method, module, documentNumber, user })
await _saveHistoryLog(docMapped, options)}
async function _processPosSave (options, doc) { const { schema, user, module, method, current, documentNumber } = await mountDocToSave.call(this, { options, doc }, 'created')
const changes = DeepDiff.getDiff({ current }, schema, options) const docMapped = _mappedFieldsLog({ changes, method, module, documentNumber, user })
await _saveHistoryLog(docMapped, options)}
async function _processPosRemove (options, doc) { const { schema, user, module, method, old, documentNumber } = await mountDocToSave.call(this, { options, doc }, 'deleted')
const changes = DeepDiff.getDiff({ old }, schema, options) const docMapped = _mappedFieldsLog({ changes, method, module, documentNumber, user })
await _saveHistoryLog(docMapped, options)}
module.exports = HistoryPlugin