UNPKG

git-like-audit-trail

Version:
989 lines (956 loc) 35.2 kB
import _ from "lodash" import { detailedDiff } from 'deep-object-diff' import { getESClient, elasticsearchInsert, elasticsearchSearch } from "./elasticsearch" import { v4 as uuid } from "uuid" import crypto from "crypto" import { yamlLikeStringParser, yamlLikeStringify, diffParser } from "./utils" const auditTrail = function (options) { // audit trail database info this.auditTrailDBType = options?.DBType || "elasticsearch" this.auditTrailHost = options?.host || "http://localhost:9200" this.indexName = options?.indexName || "audit-trail" this.ESinfo = options?.ESinfo // custom database this.customClient = options?.customClient this.customAddData = options?.customAddData this.customQueryFunction = options?.customQueryFunction // target database (non audit trail database) manipulation (for revert) this.databaseAddOneRowFunction = options?.databaseAddOneRowFunction this.databaseUpdateOneRowFunction = options?.databaseUpdateOneRowFunction this.databaseDeleteOneRowFunction = options?.databaseDeleteOneRowFunction this.databaseCustomFunction = options?.databaseCustomActionFunction if (this.auditTrailDBType === "elasticsearch") { this.client = getESClient({ endpoint: this.auditTrailHost, ...this.ESinfo }) } else if (this.auditTrailDBType === "CUSTOM") { console.log("[Audit Trail] Custom DB Client") } } function getRightmostIndexBeforeEnd({ input = "", searchText = "", index = 0, end }) { let i = 0 while (i = input.indexOf(searchText, index)) { if (i >= end || i === -1) { break } index = i + 1 } return index - 1 } function createTreeDiagram({ commitHashMap, index = 0, level = 0, size = 10, path = "", onlyCurrentBranch = false, before = 5, currentCommit }) { if (size <= 0) { const result = {} _.set(result, path, {}) return result } const currentIndex = index index = commitHashMap.indexOf(`^${level}_`, index) if (new RegExp('\\^[0-9]{1,}_').test(commitHashMap.substring(currentIndex + 2, index - 1))) { // currentIndex + 2 for handling getRightmostIndexBeforeEnd case const result = {} _.set(result, path, {}) return result } if (index === -1) { return createTreeDiagram({ commitHashMap, index, level: level + 1, size: 0, path, onlyCurrentBranch, before: before - 1 }) } let result = {} const commitHash = commitHashMap.substring(index + 1 + level.toString().length + 1, index + 1 + level.toString().length + 1 + 40) if (path) { path = `${path}.${commitHash}` } else { path = `${commitHash}` } if (onlyCurrentBranch && before > 0) { const targetIndex = commitHashMap.indexOf(`_${currentCommit}`) const closestIndex = getRightmostIndexBeforeEnd({ input: commitHashMap.substring(0, targetIndex), searchText: `^${level + 1}_`, index, end: targetIndex }) return createTreeDiagram({ commitHashMap, index: closestIndex, level: level + 1, size: size - 1, path, onlyCurrentBranch, before: before - 1, currentCommit }) } let nextTwoIndex = commitHashMap.indexOf(`^${level + 2}_`, index) if (nextTwoIndex === -1) { nextTwoIndex = commitHashMap.length } const nextSameLevelIndex = commitHashMap.indexOf(`^${level}_`, index + 1) let _indexArray = [] let _index = commitHashMap.indexOf(`^${level + 1}_`, index) while (_index !== -1 && _index < (nextSameLevelIndex != - 1 ? nextSameLevelIndex : commitHashMap.length)) { _indexArray.push(_index) _index = commitHashMap.indexOf(`^${level + 1}_`, _index + 1) } if (_indexArray.length > 1) { // branch out --> array for (let i = 0; i < _indexArray.length; i++) { result = _.merge(result, createTreeDiagram({ commitHashMap, index: _indexArray[i] - 1, level: level + 1, size: size - 1, path, onlyCurrentBranch, before: before - 1 })) } return result } else { return createTreeDiagram({ commitHashMap, index, level: level + 1, size: size - 1, path, onlyCurrentBranch, before: before - 1 }) } } async function createTreeDiagramD3({ commitHashMap, index = 0, level = 0, size = 10, onlyCurrentBranch = false, before = 5, currentCommit, getCommitInfo, ignore, format, client }) { const result = {} result.children = [] const currentIndex = index const commitHash = commitHashMap.substring(index + 1 + level.toString().length + 1, index + 1 + level.toString().length + 1 + 40) result.name = commitHash if (commitHash === currentCommit) { result.currentCommit = true } if (getCommitInfo) { const _result = await queryElasticseaerch({ commitHashArray: [commitHash], client }) let info = _result?.body?.hits?.hits?.[0]?._source ?? {} for (const _ignore of ignore) { if (info?.[_ignore]) { delete info[_ignore] } } if (format === "text") { const _tmp = info ?? {} info = [] for (const [key, value] of Object.entries(_tmp)) { info.push(`${key}: ${value}`) } info = info.join("\n") } result.info = info } index = commitHashMap.indexOf(`^${level + 1}_`, index) if (new RegExp('\\^[0-9]{1,}_').test(commitHashMap.substring(currentIndex + 2, index - 1)) || index === -1 || size <= 0) { // currentIndex + 2 for handling getRightmostIndexBeforeEnd case // if have other level between current level & next level or no next level --> current level is branch out / head --> no children --> return delete result.children return result } if (onlyCurrentBranch && before > 0) { const targetIndex = commitHashMap.indexOf(`_${currentCommit}`) const closestIndex = getRightmostIndexBeforeEnd({ input: commitHashMap.substring(0, targetIndex), searchText: `^${level + 1}_`, index: index + 1, end: targetIndex }) const child = await createTreeDiagramD3({ commitHashMap, index: closestIndex, level: level + 1, size: size - 1, onlyCurrentBranch, before: before - 1, getCommitInfo, currentCommit, ignore, format, client }) result.children.push(child) return result } // branch out checking let nextTwoIndex = commitHashMap.indexOf(`^${level + 2}_`, index) if (nextTwoIndex === -1) { nextTwoIndex = commitHashMap.length } const nextSameLevelIndex = commitHashMap.indexOf(`^${level}_`, index + 1) let _indexArray = [] let _index = commitHashMap.indexOf(`^${level + 1}_`, index) while (_index !== -1 && _index < (nextSameLevelIndex != - 1 ? nextSameLevelIndex : commitHashMap.length)) { _indexArray.push(_index) _index = commitHashMap.indexOf(`^${level + 1}_`, _index + 1) } if (_indexArray.length > 1) { // branch out --> array for (let i = 0; i < _indexArray.length; i++) { const child = await createTreeDiagramD3({ commitHashMap, index: _indexArray[i], level: level + 1, size: size - 1, onlyCurrentBranch, before: before - 1, getCommitInfo, currentCommit, ignore, format, client }) result.children.push(child) } return result } else { const child = await createTreeDiagramD3({ commitHashMap, index, level: level + 1, size: size - 1, onlyCurrentBranch, before: before - 1, getCommitInfo, currentCommit, ignore, format, client }) result.children.push(child) return result } } async function queryElasticseaerch({ commitHashArray, client }) { const result = await elasticsearchSearch({ commitHashArray: commitHashArray, size: commitHashArray.length, client }) return result } function addCurrentCommit({ input, currentCommit }) { const result = {} const inputKeys = Object.keys(input) for (let i = 0; i < inputKeys.length; i++) { const _tmp = addCurrentCommit({ input: input[inputKeys[i]], currentCommit }) if (currentCommit === inputKeys[i]) { result[inputKeys[i]] = { ..._tmp, currentCommit: true } } else { result[inputKeys[i]] = { ..._tmp, } } } return result } async function getCommitHashInfo({ input, client, currentCommit }) { const result = {} const inputKeys = Object.keys(input) for (let i = 0; i < inputKeys.length; i++) { const info = await queryElasticseaerch({ commitHashArray: [inputKeys[i]], client }) const _tmp = await getCommitHashInfo({ input: input[inputKeys[i]], client, currentCommit }) if (currentCommit === inputKeys[i]) { result[inputKeys[i]] = { ..._tmp, info: info?.body?.hits?.hits?.[0]?._source, currentCommit: true } } else { result[inputKeys[i]] = { ..._tmp, info: info?.body?.hits?.hits?.[0]?._source } } } return result } function getChanges({ changed = { added: {}, deleted: {}, updated: {} }, revert }) { const addedChange = diffParser(changed.added ?? {}, "after") const deletedChange = diffParser(changed.deleted ?? {}, "before") let updatedChange if (revert) { updatedChange = diffParser(changed.updated ?? {}, "before") } else { updatedChange = diffParser(changed.updated ?? {}, "after") } return { addedChange, deletedChange, updatedChange } } function getShortestPath({ commitHashMap, commitHash, currentCommit, }) { const currentLevel = parseInt((commitHashMap.match(new RegExp(`\\^([0-9]+)_${currentCommit}`))?.[1] ?? 0), 10) ?? 0 const targetLevel = parseInt((commitHashMap.match(new RegExp(`\\^([0-9]+)_${commitHash}`))?.[1] ?? 0), 10) ?? 0 const currentIndex = commitHashMap.indexOf(`^${currentLevel}_${currentCommit}`) const targetIndex = commitHashMap.indexOf(`^${targetLevel}_${commitHash}`) const forward = [] const backward = [] if (currentLevel > targetLevel) { // current level revert to target level ---> same = end & different = find common point const difference = currentLevel - targetLevel for (let i = 0; i < difference; i++) { const _tmp = getRightmostIndexBeforeEnd({ input: commitHashMap.substring(0, currentIndex), searchText: `^${currentLevel - 1 - i}_`, end: currentIndex }) backward.push(commitHashMap.substring(_tmp + 1 + (currentLevel - 1 - i).toString().length + 1, _tmp + 1 + (currentLevel - 1 - i).toString().length + 1 + 40)) } if (backward[backward.length - 1] === commitHash) { return { backward } } const result = getShortestPath({ commitHashMap: commitHashMap.substring(0, Math.max(commitHashMap.indexOf(`^${currentLevel - difference}_${backward[backward.length - 1]}`) + 2 + (currentLevel - difference) + 40, targetIndex + 2 + targetLevel.toString().length + 40)), commitHash, currentCommit: backward[backward.length - 1], }) return { forward: result.forward, backward: backward.concat(result.backward) } } else if (currentLevel < targetLevel) { // target revert to same level --> same = end & diffent = find common point forward.push(commitHash) const difference = targetLevel - currentLevel for (let i = 0; i < difference; i++) { const _tmp = getRightmostIndexBeforeEnd({ input: commitHashMap.substring(0, targetIndex), searchText: `^${targetLevel - 1 - i}_`, end: targetIndex }) forward.push(commitHashMap.substring(_tmp + 1 + (targetLevel - 1 - i).toString().length + 1, _tmp + 1 + (targetLevel - 1 - i).toString().length + 1 + 40)) //reversed } if (forward[forward.length - 1] === currentCommit) { return { forward } } const result = getShortestPath({ commitHashMap: commitHashMap.substring(0, Math.max(commitHashMap.indexOf(`^${targetLevel - difference}_${forward[forward.length - 1]}`) + 2 + (targetLevel - difference).toString().length + 40, currentIndex + 2 + currentLevel.toString().length + 40)), commitHash: forward[forward.length - 1], currentCommit, }) forward.pop() return { forward: forward.concat(result.forward ?? []), backward: result.backward } } else if (currentLevel === targetLevel) { if (currentIndex === targetIndex) { return {} } // both backward 10 --> check same or not --> same = remove duplicated value & diff = re run for (let i = 0; i < 10; i++) { const _tmp = getRightmostIndexBeforeEnd({ input: commitHashMap.substring(0, currentIndex), searchText: `^${currentLevel - 1 - i}_`, end: currentIndex }) if (_tmp !== -1) { backward.push(commitHashMap.substring(_tmp + 1 + (currentLevel - 1 - i).toString().length + 1, _tmp + 1 + (currentLevel - 1 - i).toString().length + 1 + 40)) } else { break } } forward.push(commitHash) for (let i = 0; i < 10; i++) { const _tmp = getRightmostIndexBeforeEnd({ input: commitHashMap.substring(0, targetIndex), searchText: `^${targetLevel - 1 - i}_`, end: targetIndex }) if (_tmp !== -1) { forward.push(commitHashMap.substring(_tmp + 1 + (targetLevel - 1 - i).toString().length + 1, _tmp + 1 + (targetLevel - 1 - i).toString().length + 1 + 40)) } else { break } } while (forward[forward.length - 2] === backward[backward.length -2] && (backward[backward.length -2] !== undefined || isNaN(backward[backward.length -2]))) { forward.pop() backward.pop() } if (forward[forward.length - 1] === backward[backward.length - 1]) { return { forward, backward } } return getShortestPath({ commitHashMap: commitHashMap.substring(0, Math.max(forward[forward.length - 1], backward[backward.length - 1])), commitHash: commitHashMap.substring(forward[forward.length - 1] + 1 + (targetLevel - 1 - 10).toString().length + 1, forward[forward.length - 1] + 1 + (targetLevel - 1 - 10).toString().length + 1 + 40), currentCommit: commitHashMap.substring(backward[backward.length - 1] + 1 + (currentLevel - 1 - 10).toString().length + 1, backward[backward.length - 1] + 1 + (currentLevel - 1 - 10).toString().length + 1 + 40), }) } } // Get commit hash info by ID auditTrail.prototype.queryByCommitHash = async function ({ commitHash, }) { const result = await queryElasticseaerch({ commitHashArray: [commitHash], client: this.client }) return result?.body?.hits?.hits?.[0]?._source } // Get multiple commit hash info by IDs auditTrail.prototype.batchQueryByCommitHash = async function ({ commitHashArray, }) { const result = await queryElasticseaerch({ commitHashArray, client: this.client }) return result?.body?.hits?.hits?.map(data => data?._source) } // Query for tree object array auditTrail.prototype.query = async function ({ commitHashMap = "{}", // existing commit map commitHash = "", // query from hash (user current commit) before = 5, // include 5 commit (level) before after = 5,// include 5 commit (level) after onlyCurrentBranch = false, //only reveal current branch getCommitInfo = true // query commit info }) { const commitMap = yamlLikeStringParser({ input: commitHashMap }) if (Object.keys(commitMap)?.length === 0 || !commitHash || (before === 0 && after === 0)) { console.log("CommitHashMap or commitHash empty or both before and after size set 0") return [] } const currentLevel = parseInt((commitHashMap.match(new RegExp(`\\^([0-9]+)_${commitHash}`))?.[1] ?? 0), 10) ?? 0 let startingIndexArray = [] const currentIndex = commitHashMap.indexOf(`^${currentLevel}_${commitHash}`) if (!onlyCurrentBranch) { let _index = commitHashMap.indexOf(`^${(currentLevel - before) > 0 ? currentLevel - before : 0}_`) while (_index != -1) { startingIndexArray.push(_index) _index = commitHashMap.indexOf(`^${(currentLevel - before) > 0 ? currentLevel - before : 0}_`, _index + 1) } } else { const index = getRightmostIndexBeforeEnd({ input: commitHashMap.substring(0, currentIndex), searchText: `^${currentIndex - before}_`, end: currentIndex }) startingIndexArray = [index] } const realIndexArray = [] for (let j = 0; j < startingIndexArray.length; j++) { let startingIndex = startingIndexArray[j] const realStart = ((currentLevel - before) > 0 ? currentLevel - before : 0) let result = createTreeDiagram({ commitHashMap, index: startingIndex, level: realStart, size: before + after, path: "", onlyCurrentBranch, before: before > currentLevel ? currentLevel : before, currentCommit: commitHash }) if (getCommitInfo) { result = await getCommitHashInfo({ input: result, client: this.client, currentCommit: commitHash }) } else { result = addCurrentCommit({ input: result, currentCommit: commitHash }) } realIndexArray.push(result) } return realIndexArray } // Query for D3 tree object array auditTrail.prototype.queryD3 = async function ({ commitHashMap = "{}", // existing commit map commitHash = "", // query from hash (user current commit) before = 5, // include 5 commit (level) before after = 5,// include 5 commit (level) after onlyCurrentBranch = false, //only reveal current branch getCommitInfo = true, // query commit info ignore = [], // ignore field in info format = "object", // "text" or "object" }) { if (typeof ignore === "string") { ignore = [ignore] } const commitMap = yamlLikeStringParser({ input: commitHashMap }) if (Object.keys(commitMap)?.length === 0 || !commitHash || (before === 0 && after === 0)) { console.log("CommitHashMap or commitHash empty or both before and after size set 0") return [] } const currentLevel = parseInt((commitHashMap.match(new RegExp(`\\^([0-9]+)_${commitHash}`))?.[1] ?? 0), 10) ?? 0 let startingIndexArray = [] const currentIndex = commitHashMap.indexOf(`^${currentLevel}_${commitHash}`) if (!onlyCurrentBranch) { let _index = commitHashMap.indexOf(`^${(currentLevel - before) > 0 ? currentLevel - before : 0}_`) while (_index != -1) { startingIndexArray.push(_index) _index = commitHashMap.indexOf(`^${(currentLevel - before) > 0 ? currentLevel - before : 0}_`, _index + 1) } } else { const index = getRightmostIndexBeforeEnd({ input: commitHashMap.substring(0, currentIndex), searchText: `^${currentLevel - before}_`, end: currentIndex }) startingIndexArray = [index] } const realIndexArray = [] for (let j = 0; j < startingIndexArray.length; j++) { let startingIndex = (startingIndexArray[j] !== -1 ? startingIndexArray[j] : currentIndex) const realStart = ((currentLevel - before) > 0 ? currentLevel - before : 0) // create tree diagram with starting node starting level (current level - before level) let result = await createTreeDiagramD3({ commitHashMap, index: startingIndex, level: realStart, size: currentLevel - before > 0 ? before + after : currentLevel + after, onlyCurrentBranch, before: before > currentLevel ? currentLevel : before, currentCommit: commitHash, getCommitInfo, ignore, format, client: this.client, }) realIndexArray.push(result) } return realIndexArray } async function revertCommit({ action, changes, auditTrail = true, source, parentTrail, cherryPick = false, databaseDeleteOneRowFunction, databaseAddOneRowFunction, databaseUpdateOneRowFunction, databaseCustomFunction }){ const result = {} if (action === "CREATE") { // new row --> delete if (cherryPick) { result.added = await databaseAddOneRowFunction({ data: source, changedObj: changes.deletedChange?.diff, flattedchanged: changes.deletedChange?.change, auditTrail, parentTrail }) } else { result.deleted = await databaseDeleteOneRowFunction({ data: source, changedObj: changes.addedChange?.diff, flattedchanged: changes.addedChange?.change, auditTrail, parentTrail, }) } return result } else if (action === "DELETE") { // removed row --> insert back if (cherryPick) { result.deleted = await databaseDeleteOneRowFunction({ data: source, changedObj: changes.addedChange?.diff, flattedchanged: changes.addedChange?.change, auditTrail, parentTrail }) } else { result.added = await databaseAddOneRowFunction({ data: source, changedObj: changes.deletedChange?.diff, flattedchanged: changes.deletedChange?.change, auditTrail, parentTrail, }) } return result } else if (action === "UPDATE") { result.updated = await databaseUpdateOneRowFunction({ data: source, changedObj: changes.updatedChange?.diff, flattedchanged: changes.updatedChange?.change, addChangedObj: changes.addedChange?.diff, addFlattedchanged: changes.addedChange?.change, deleteChangedObj: changes.deletedChange?.diff, deleteFlattedchanged: changes.deletedChange?.change, revert: !cherryPick, auditTrail }) return result } // unknown action --> direct return data result.updated = await databaseCustomFunction({ action: action ?? "ERROR", data: source, changedObj: changes.updatedChange?.diff, flattedchanged: changes.updatedChange?.change, addChangedObj: changes.addedChange?.diff, addFlattedchanged: changes.addedChange?.change, deleteChangedObj: changes.deletedChange?.diff, deleteFlattedchanged: changes.deletedChange?.change, revert: !cherryPick, auditTrail, }) return result } // revert specific commit (will create audit trail during revert) auditTrail.prototype.revertCommit = async function ({ commitHash, parentTrail }) { const search = await queryElasticseaerch({ commitHashArray: [commitHash], client: this.client }) const data = JSON.parse((search?.body?.hits?.hits?.[0]?._source?.change) ?? null) if (data) { const changes = getChanges({ changed: data, revert: true }) const result = await revertCommit({ action: search?.body?.hits?.hits?.[0]?._source?.action, changes, auditTrail: true, source: search?.body?.hits?.hits?.[0]?._source, parentTrail, databaseDeleteOneRowFunction: this.databaseDeleteOneRowFunction, databaseAddOneRowFunction: this.databaseAddOneRowFunction, databaseUpdateOneRowFunction: this.databaseUpdateOneRowFunction, databaseCustomFunction: this.databaseCustomFunction, }) return result } return null } // revert multiple time to checkout previous commit or switch branch // need compare changes to next commit --> same = won't branch out auditTrail.prototype.checkout = async function ({ commitHashMap, commitHash, currentCommit, }) { if (commitHash === currentCommit) { return {} } const result = getShortestPath({ commitHashMap, commitHash, currentCommit, }) const forward = (result?.forward ?? []).reverse() const backward = [currentCommit, ...(result?.backward ?? [])] const _result = [] if (backward[backward.length - 1] !== forward[0] && forward[0] !== undefined && backward[backward.length - 1] !== undefined) { //shd not happen return [{ error: true, content: "backward and forward doesn't match" }] } else { backward.pop() forward.shift() const checkoutOrder = [...backward, ...forward] const search = await queryElasticseaerch({ commitHashArray: checkoutOrder, client: this.client }) let hits = search?.body?.hits?.hits ?? [] hits = _.sortBy(hits, (o) => checkoutOrder.indexOf(o?._source?.commitHash)) for (let i = 0; i < hits.length; i++) { const data = JSON.parse((hits?.[i]?._source?.change) ?? null) if (data) { let changes if (backward.some(commitHash => commitHash === hits?.[i]?._source?.commitHash)) { changes = getChanges({ changed: data, revert: true }) const result = await revertCommit({ action: hits?.[i]?._source?.action, changes, auditTrail: false, source: hits?.[i]?._source, databaseDeleteOneRowFunction: this.databaseDeleteOneRowFunction, databaseAddOneRowFunction: this.databaseAddOneRowFunction, databaseUpdateOneRowFunction: this.databaseUpdateOneRowFunction, databaseCustomFunction: this.databaseCustomFunction, }) _result.push(result) } else if (forward.some(commitHash => commitHash === hits?.[i]?._source?.commitHash)) { changes = getChanges({ changed: data, revert: false }) const result = await revertCommit({ action: hits?.[i]?._source?.action, changes, auditTrail: false, source: hits?.[i]?._source, cherryPick: true, databaseDeleteOneRowFunction: this.databaseDeleteOneRowFunction, databaseAddOneRowFunction: this.databaseAddOneRowFunction, databaseUpdateOneRowFunction: this.databaseUpdateOneRowFunction, databaseCustomFunction: this.databaseCustomFunction, }) _result.push(result) } else { changes = { addedChange: {}, deletedChange: {}, updatedChange: {} } _result.push({}) } } } } return _result } // pick one commit to run again auditTrail.prototype.cherryPick = async function ({ commitHash, parentTrail }) { const search = await queryElasticseaerch({ commitHashArray: [commitHash], client: this.client }) const data = JSON.parse((search?.body?.hits?.hits?.[0]?._source?.change) ?? null) if (data) { const changes = getChanges({ changed: data }) const result = await revertCommit({ action: search?.body?.hits?.hits?.[0]?._source?.action, changes, auditTrail: true, source: search?.body?.hits?.hits?.[0]?._source, cherryPick: true, databaseDeleteOneRowFunction: this.databaseDeleteOneRowFunction, databaseAddOneRowFunction: this.databaseAddOneRowFunction, databaseUpdateOneRowFunction: this.databaseUpdateOneRowFunction, databaseCustomFunction: this.databaseCustomFunction, parentTrail }) return result } else { return null } } // add data into db auditTrail.prototype.createData = async function ({ categoryId, // expect target table name or type userId, // user id for tracking who change the data dataId, // target data id name, // extra target name info before = {}, // data before change after, // data after change parent, // parent commit id (for large trail that change multiple data) action, // "CREATE" || "UPDATE" || "DELETE", 3 type of audit trail action ignore = [], // ignore field in before and after that won't write in audit trail ...otherArgs // other args that want to log into this audit trail }) { const indexName = this.indexName if ((_.isEmpty(action) || !action) && action !== "") { // guess action type (use "" to exclude action) if (!before && after) { action = "CREATE" } if (before && after) { action = "UPDATE" } if (before && !after) { action = "DELETE" } } let change for (let i = 0; i < ignore.length; i++) { delete before[ignore[i]] delete after[ignore[i]] } if (!_.isNull(after)) { let diff = detailedDiff(before, after) ignore?.forEach((key) => { if (diff.added[key]) { delete diff.added[key] } if (diff.deleted[key]) { delete diff.deleted[key] } if (diff.updated[key]) { delete diff.updated[key] } }) change = JSON.stringify(diff) } let parentTrail let trailSession if (_.isNull(parent) || _.isUndefined(parent)) { parentTrail = uuid() } else { trailSession = parent } const hash = crypto.createHash('sha1') const data = hash.update(JSON.stringify({...change, time: Date.now()}), 'utf-8'); const commitHash = data.digest('hex'); const insertdata = { categoryId, userId, dataId, name, parent, action, ...otherArgs, change, time: Date.now(), parentTrail, trailSession, commitHash } await elasticsearchInsert({ indexName, insertdata, client: this.client }) return insertdata } // modify commit map object auditTrail.prototype.appendCommitMap = function ({ currentCommitMap = "{}", // existing commit map ("{}" for init) currentCommitHash = "", // current commit hash in category newCommitHash = "" //new commit hash from createData }) { const commitMap = yamlLikeStringParser({ input: currentCommitMap }) if (!currentCommitHash) { if (Object.keys(commitMap)?.length === 0) { // first commit commitMap[newCommitHash] = newCommitHash return `{${yamlLikeStringify({ input: commitMap })}}` } else { // new commit but haven't info console.log("Append Commit map error, no current commit hash but commit map is not empty!!!") return currentCommitMap } } else { if (!currentCommitMap) { console.log("Append Commit map error, no commit map is found!!!") return "{}" } // append const currentLevel = parseInt((currentCommitMap.match(new RegExp(`\\^([0-9]+)_${currentCommitHash}`))?.[1] ?? 0), 10) ?? 0 const currentIndex = currentCommitMap.indexOf(`^${currentLevel}_${currentCommitHash}`) let levelIndexArray = [currentIndex] for (let i = 0; i < currentLevel; i++) { levelIndexArray.push(getRightmostIndexBeforeEnd({ input: currentCommitMap.substring(0, levelIndexArray[i]), searchText: `^${currentLevel - 1 - i}_`, end: levelIndexArray[i] })) } levelIndexArray = levelIndexArray.reverse() const path = [] for (let i = 0; i < levelIndexArray.length; i++) { const substring = currentCommitMap.substring(levelIndexArray[i], levelIndexArray[i] + 40 + 4 + (i.toString()?.length ?? 1) - 1) path.push(substring.match(`\\^${i}_(.*?):`)?.[1] ?? 0) } let append = {} if (typeof _.get(commitMap, path) !== "string") { append = { ..._.get(commitMap, path) } } append[newCommitHash] = newCommitHash _.set(commitMap, path, append) return `{${yamlLikeStringify({ input: commitMap })}}` } } module.exports = auditTrail