From 26a3165c41b842a89035df30c066ff08523b1cfd Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sun, 10 Mar 2024 14:19:35 +0530 Subject: [PATCH 01/11] Feat: added mongoose audit plugin --- packages/functions/src/async.js | 44 +- packages/functions/src/traced.js | 3 +- packages/functions/test/async.test.js | 4 +- plugins/mongoose-audit/package.json | 40 ++ plugins/mongoose-audit/readme.md | 66 ++ plugins/mongoose-audit/src/constants.js | 5 + plugins/mongoose-audit/src/index.js | 11 + plugins/mongoose-audit/src/model.js | 25 + plugins/mongoose-audit/src/plugin.js | 178 +++++ plugins/mongoose-audit/src/utils.js | 29 + plugins/mongoose-audit/test/index.test.js | 821 ++++++++++++++++++++++ plugins/mongoose-audit/types/index.d.ts | 36 + 12 files changed, 1237 insertions(+), 25 deletions(-) create mode 100644 plugins/mongoose-audit/package.json create mode 100644 plugins/mongoose-audit/readme.md create mode 100644 plugins/mongoose-audit/src/constants.js create mode 100644 plugins/mongoose-audit/src/index.js create mode 100644 plugins/mongoose-audit/src/model.js create mode 100644 plugins/mongoose-audit/src/plugin.js create mode 100644 plugins/mongoose-audit/src/utils.js create mode 100644 plugins/mongoose-audit/test/index.test.js create mode 100644 plugins/mongoose-audit/types/index.d.ts diff --git a/packages/functions/src/async.js b/packages/functions/src/async.js index 9e29e6a..41f8085 100644 --- a/packages/functions/src/async.js +++ b/packages/functions/src/async.js @@ -6,25 +6,25 @@ const logger = moduleLogger("tracer"); const _asyncHandler = (fn, trace = false) => - async (req, res, next) => { - let fnName; - try { - if (trace) { - fnName = _fnName(fn); - await _traced(fn.bind(this, req, res, next), {}, fnName); - } else { - await fn(req, res, next); - } - if (!res.headersSent) next(); - } catch (err) { - if (!trace) { - fnName = fnName ?? _fnName(fn); - logger.error(`${fnName} execution failed - error: ${err.message} - stack: ${err.stack}`); - } - res.errorLogged = true; - if (!res.headersSent) next(err); + async (req, res, next) => { + let fnName; + try { + if (trace) { + fnName = _fnName(fn); + await _traced(fn.bind(this, req, res, next), {}, fnName); + } else { + await fn(req, res, next); } - }; + if (!res.headersSent) next(); + } catch (err) { + if (!trace) { + fnName = fnName ?? _fnName(fn); + logger.error(`${fnName} execution failed - error: ${err.message} - stack: ${err.stack}`); + } + res.errorLogged = true; + if (!res.headersSent) next(err); + } + }; export const asyncHandler = (fn) => _asyncHandler(fn); @@ -42,16 +42,16 @@ export const fallibleAsyncHandler = (fn) => async (req, res, next) => { export const plainAsyncHandler = (fn) => async (req, res, next) => { try { - const result = fn(req, res, next) + const result = fn(req, res, next); if (result instanceof Promise) await result; } catch (e) { - next(e) + next(e); } -} +}; export default { asyncHandler, tracedAsyncHandler, fallibleAsyncHandler, - plainAsyncHandler, + plainAsyncHandler }; diff --git a/packages/functions/src/traced.js b/packages/functions/src/traced.js index b6f8e31..ba95b85 100644 --- a/packages/functions/src/traced.js +++ b/packages/functions/src/traced.js @@ -7,7 +7,8 @@ const logger = moduleLogger("tracer"); export const _traced = (fn, loggable = {}, fnName, layer, fallible) => { let startTime; - const disableTracing = process.env.DISABLE_FUNCTION_TRACING === "true" || process.env.DISABLE_FUNCTION_TRACING === "1"; + const disableTracing = + process.env.DISABLE_FUNCTION_TRACING === "true" || process.env.DISABLE_FUNCTION_TRACING === "1"; if (!disableTracing) { fnName = fnName ?? _fnName(fn, layer); logger.info(`${fnName} execution initiated`, loggable); diff --git a/packages/functions/test/async.test.js b/packages/functions/test/async.test.js index 1838092..6ec8f16 100644 --- a/packages/functions/test/async.test.js +++ b/packages/functions/test/async.test.js @@ -44,13 +44,13 @@ describe("asyncHandler", () => { }); test("test plain async handler with async function", async () => { await plainAsyncHandler(async () => { - throw new Error("test") + throw new Error("test"); })(mockReq, mockRes, mockNext); expect(mockNext).toHaveBeenCalled(); }); test("test plain async handler with normal function", async () => { await plainAsyncHandler(() => { - throw new Error("test") + throw new Error("test"); })(mockReq, mockRes, mockNext); expect(mockNext).toHaveBeenCalled(); }); diff --git a/plugins/mongoose-audit/package.json b/plugins/mongoose-audit/package.json new file mode 100644 index 0000000..5e8f602 --- /dev/null +++ b/plugins/mongoose-audit/package.json @@ -0,0 +1,40 @@ +{ + "name": "@sliit-foss/mongoose-audit", + "version": "0.0.0", + "description": "A rework of the mongoose-audit-log package to support newer versions of mongoose and more flexible options", + "main": "dist/index.js", + "types": "types/index.d.ts", + "scripts": { + "build": "node ../../scripts/esbuild.config.js", + "build:watch": "bash ../../scripts/esbuild.watch.sh", + "bump-version": "bash ../../scripts/bump-version.sh --name=@sliit-foss/express-http-context", + "lint": "bash ../../scripts/lint.sh", + "release": "bash ../../scripts/release.sh", + "test": "if [ \"$CI\" = \"true\" ]; then \n bash ../../scripts/test/test.sh; else \n echo \"Skipping as it is not a CI environemnt\"; fi" + }, + "dependencies": { + "deep-diff": "^1.0.2" + }, + "peerDependencies": { + "mongoose": ">=5" + }, + "author": "SLIIT FOSS", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/sliit-foss/npm-catalogue.git" + }, + "homepage": "https://github.com/sliit-foss/npm-catalogue/blob/main/plugins/mongoose-audit/readme.md", + "keywords": [ + "mongoose", + "mongoose-plugin", + "audit", + "log", + "trail", + "history", + "version" + ], + "bugs": { + "url": "https://github.com/sliit-foss/npm-catalogue/issues" + } +} \ No newline at end of file diff --git a/plugins/mongoose-audit/readme.md b/plugins/mongoose-audit/readme.md new file mode 100644 index 0000000..90e09b9 --- /dev/null +++ b/plugins/mongoose-audit/readme.md @@ -0,0 +1,66 @@ +# @sliit-foss/mongoose-audit + +#### A rework of the [mongoose-audit-log](https://www.npmjs.com/package/mongoose-audit-log) package to support newer versions of mongoose and more flexible options
+ +It is a mongoose plugin to manage an audit log of changes to a MongoDB database. + +## Features + +- Store changes to entities on persist (save, update, delete) +- Remember the user, that executed the change +- Log when the change has been done + +## Storing the current user + +In order to collect the information about who actually did a change to an entity, the user information is required. +This can be set on a per usage (1) or global (2) level: + +1. Set the current user on an entity right before persisting: + +```javascript +Order.findById(123) + .then((order) => { + order.__user = "me@test.de"; + order.amount = 1000; + }) + .save(); +``` + +2. Set it as an option when registering the plugin: + +```javascript +const { plugin } = require("@sliit-foss/mongoose-audit"); + +SomeSchema.plugin(plugin, { + getUser: () => "user details from wherever you wish to get it" +}); +``` + +## Query history + +Please find below an example express route, to request the history of a given type and id: + +```javascript +const { plugin, Audit } = require("@sliit-foss/mongoose-audit"); + +router.get("/api/users/:id/history", (req, res, next) => { + Audit.find({ entity_id: req.params.id, entity: "User" }) + .then((history) => res.json(history)) + .catch(next); +}); +``` + +## All supported plugin options + +```javascript +const { plugin, AuditType } = require("@sliit-foss/mongoose-audit"); + +SomeSchema.plugin(plugin, { + getUser: () => "user details from wherever you wish to get it", + types: [AuditType.Edit], // default: ['add', 'edit', 'delete'] + exclude: ["field1", "field2"], + onAudit: (audit) => { + // Called before persisting the audit is saved. Use this to use your own audit model instead of the default one. + } +}); +``` diff --git a/plugins/mongoose-audit/src/constants.js b/plugins/mongoose-audit/src/constants.js new file mode 100644 index 0000000..53eb443 --- /dev/null +++ b/plugins/mongoose-audit/src/constants.js @@ -0,0 +1,5 @@ +export const AuditType = { + Add: "Add", + Edit: "Edit", + Delete: "Delete" +}; diff --git a/plugins/mongoose-audit/src/index.js b/plugins/mongoose-audit/src/index.js new file mode 100644 index 0000000..ebc8223 --- /dev/null +++ b/plugins/mongoose-audit/src/index.js @@ -0,0 +1,11 @@ +import { default as plugin } from "./plugin"; +import { default as Audit } from "./model"; +import { AuditType } from "./constants"; + +export { plugin, Audit, AuditType as auditType }; + +export default { + plugin, + Audit, + auditType: AuditType +}; diff --git a/plugins/mongoose-audit/src/model.js b/plugins/mongoose-audit/src/model.js new file mode 100644 index 0000000..baf6cec --- /dev/null +++ b/plugins/mongoose-audit/src/model.js @@ -0,0 +1,25 @@ +const mongoose = require("mongoose"); + +const auditSchema = new mongoose.Schema( + { + entity_id: {}, + entity: String, + collection: String, + changes: {}, + user: { + type: mongoose.Schema.Types.Mixed, + ref: "User", + collection: "users" + } + }, + { + timestamps: { + createdAt: "created_at", + updatedAt: false + } + } +); + +const model = mongoose.model("Audit", auditSchema); + +export default model; diff --git a/plugins/mongoose-audit/src/plugin.js b/plugins/mongoose-audit/src/plugin.js new file mode 100644 index 0000000..2b0bbd1 --- /dev/null +++ b/plugins/mongoose-audit/src/plugin.js @@ -0,0 +1,178 @@ +import { default as deepDiff } from "deep-diff"; +import { default as Audit } from "./model"; +import { AuditType } from "./constants"; +import { extractArray, filter, flattenObject, isEmpty } from "./utils"; + +const options = { + getUser: () => undefined, + types: [AuditType.Add, AuditType.Edit, AuditType.delete], + exclude: [], + onAudit: undefined +}; + +const addAuditLogObject = (currentObject, original) => { + const user = currentObject.__user || options.getUser() || "Unknown User"; + delete currentObject.__user; + let changes = deepDiff(original._doc || original, currentObject._doc || currentObject, filter); + if (changes && changes.length) { + changes = changes.reduce((obj, change) => { + const key = change.path.join("."); + if (options.exclude.includes(key)) { + return obj; + } + if (change.kind === "D") { + handleAudits(change.lhs, "from", AuditType.delete, obj, key); + } else if (change.kind === "N") { + handleAudits(change.rhs, "to", AuditType.Add, obj, key); + } else if (change.kind === "A") { + if (!obj[key] && change.path.length) { + const data = { + from: extractArray(original, change.path), + to: extractArray(currentObject, change.path) + }; + if (data.from.length && data.to.length) { + data.type = AuditType.Edit; + } else if (data.from.length) { + data.type = AuditType.delete; + } else if (data.to.length) { + data.type = AuditType.Add; + } + obj[key] = data; + } + } else { + obj[key] = { + from: change.lhs, + to: change.rhs, + type: AuditType.Edit + }; + } + return obj; + }, {}); + if (isEmpty(changes)) return Promise.resolve(); + const audit = { + entity_id: currentObject._id, + entity: currentObject.constructor.modelName, + collection: currentObject.constructor.collection.collectionName, + changes, + user + }; + if (options.onAudit) { + return options.onAudit(audit); + } + return new Audit(audit).save(); + } + return Promise.resolve(); +}; + +const handleAudits = (changes, target, type, obj, key) => { + if (typeof changes === "object") { + if (Object.keys(changes).filter((key) => key === "_id" || key === "id").length) { + // entity found + obj[key] = { [target]: changes, type }; + } else { + // sibling/sub-object + Object.entries(changes).forEach(([sub, value]) => { + if (!isEmpty(value)) { + obj[`${key}.${sub}`] = { [target]: value, type }; + } + }); + } + } else { + // primitive value + obj[key] = { [target]: changes, type }; + } +}; + +const addAuditLog = (currentObject, next) => { + currentObject.constructor + .findOne({ _id: currentObject._id }) + .then((original) => addAuditLogObject(currentObject, original)) + .then(next) + .catch(next); +}; + +const addUpdate = (query, next, multi) => { + const updated = flattenObject(query._update); + let counter = 0; + return query + .find(query._conditions) + .lean(true) + .cursor() + .eachAsync((fromDb) => { + if (!multi && counter++) { + // handle 'multi: false' + return next(); + } + const orig = Object.assign({ __user: query.options.__user }, fromDb, updated); + orig.constructor.modelName = query._collection.collectionName; + return addAuditLogObject(orig, fromDb); + }) + .then(next) + .catch(next); +}; + +const addDelete = (currentObject, options, next) => { + const orig = Object.assign({}, currentObject._doc || currentObject); + orig.constructor.modelName = currentObject.constructor.modelName; + return addAuditLogObject( + { + _id: currentObject._id, + __user: options.__user + }, + orig + ) + .then(next) + .catch(next); +}; + +const addFindAndDelete = (query, next) => { + query + .find() + .lean(true) + .cursor() + .eachAsync((fromDb) => addDelete(fromDb, query.options, next)) + .then(next) + .catch(next); +}; + +const plugin = (schema, opts = {}) => { + Object.assign(options, opts); + if (options.types.includes(AuditType.Add)) { + schema.pre("save", function (next) { + if (this.isNew) { + return next(); + } + addAuditLog(this, next); + }); + } + if (options.types.includes(AuditType.Edit)) { + schema.pre("update", function (next) { + addUpdate(this, next, !!this.options.multi); + }); + schema.pre("updateOne", function (next) { + addUpdate(this, next, false); + }); + schema.pre("findOneAndUpdate", function (next) { + addUpdate(this, next, false); + }); + schema.pre("updateMany", function (next) { + addUpdate(this, next, true); + }); + schema.pre("replaceOne", function (next) { + addUpdate(this, next, false); + }); + } + if (options.types.includes(AuditType.delete)) { + schema.pre("remove", function (next, options) { + addDelete(this, options, next); + }); + schema.pre("findOneAndDelete", function (next) { + addFindAndDelete(this, next); + }); + schema.pre("findOneAndRemove", function (next) { + addFindAndDelete(this, next); + }); + } +}; + +export default plugin; diff --git a/plugins/mongoose-audit/src/utils.js b/plugins/mongoose-audit/src/utils.js new file mode 100644 index 0000000..4311802 --- /dev/null +++ b/plugins/mongoose-audit/src/utils.js @@ -0,0 +1,29 @@ +export const filter = (path, key) => path.length === 0 && ~["_id", "__v", "createdAt", "updatedAt"].indexOf(key); + +export const isEmpty = (value) => + value === undefined || + value === null || + (typeof value === "object" && Object.keys(value).length === 0) || + (typeof value === "string" && value.trim().length === 0); + +export const extractArray = (data, path) => { + if (path.length === 1) { + return data[path[0]]; + } + const parts = [].concat(path); + const last = parts.pop(); + const value = parts.reduce((current, part) => { + return current ? current[part] : undefined; + }, data); + return value ? value[last] : undefined; +}; + +export const flattenObject = (obj) => + Object.keys(obj).reduce((data, key) => { + if (key.indexOf("$") === 0) { + Object.assign(data, obj[key]); + } else { + data[key] = obj[key]; + } + return data; + }, {}); diff --git a/plugins/mongoose-audit/test/index.test.js b/plugins/mongoose-audit/test/index.test.js new file mode 100644 index 0000000..7cb1b1d --- /dev/null +++ b/plugins/mongoose-audit/test/index.test.js @@ -0,0 +1,821 @@ +import { default as mongoose } from "mongoose"; +import { exec } from "child_process"; +import { promisify } from "util"; +import { plugin, Audit } from "../src"; +import { AuditType } from "../src/constants"; + +const execute = promisify(exec); + +const setupMongoServer = async (done) => { + await execute("docker run -d -p 27017:27017 --name audit-test mongo:4.2.8"); + mongoose.connect("mongodb://localhost:27018/audit-test", { useNewUrlParser: true }).then(done).catch(done); +}; + +const createTestModel = (getUser) => { + const testSchema = new mongoose.Schema( + { + name: String, + number: Number, + date: { + type: Date, + default: Date.now() + }, + empty: String, + child: { + name: String, + number: Number + }, + entity: { + _id: String, + id: String, + name: String, + array: [] + } + }, + { timestamps: true } + ); + testSchema.plugin(plugin, getUser); + return mongoose.model("tests", testSchema); +}; + +describe("audit", function () { + before(setupMongoServer); + afterEach(function (done) { + Promise.all([ + mongoose.connection.collections[TestObject.collection.collectionName].drop(), + mongoose.connection.collections[Audit.collection.collectionName].drop() + ]) + .then(() => done()) + .catch((err) => done(err.code !== 26 && err)); + }); + + const TestObject = createTestModel(); + + it("should return undefined on getUser", function () { + expect(plugin.getUser()).to.be.undefined(); + }); + + describe("plugin: pre *", function () { + const auditUser = "Jack"; + + let test; + beforeEach((done) => { + test = new TestObject({ name: "Lucky", number: 7 }); + test + .save() + .then(() => done()) + .catch(done); + }); + + it("should create single values for changes on siblings (non-entities)", function () { + const expectedName = "test"; + const expectedNumber = 123; + test.child = { name: expectedName, number: expectedNumber }; + test.__user = auditUser; + return test.save().then(() => + Audit.find({ entity_id: test._id }, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + const entry = audit[0]; + expect(entry.changes["child.name"].to).equal(expectedName); + expect(entry.changes["child.name"].type).equal(AuditType.Add); + expect(entry.changes["child.number"].to).equal(expectedNumber); + expect(entry.changes["child.number"].type).equal(AuditType.Add); + }) + ); + }); + + it("should create single values for changes of siblings (non-entities) on remove", function () { + const expectedName = "test"; + const expectedNumber = 123; + test.child = { name: expectedName, number: expectedNumber }; + test.__user = auditUser; + return test.save().then((result) => { + result.child = undefined; + result.__user = auditUser; + return result.save().then(() => + Audit.find({ entity_id: test._id }, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(2); + const entry = audit[1]; + expect(entry.changes["child.name"].from).equal(expectedName); + expect(entry.changes["child.name"].to).to.be.undefined(); + expect(entry.changes["child.name"].type).equal(AuditType.Delete); + expect(entry.changes["child.number"].from).equal(expectedNumber); + expect(entry.changes["child.number"].to).to.be.undefined(); + expect(entry.changes["child.number"].type).equal(AuditType.Delete); + }) + ); + }); + }); + + it('should create combined value for changes on entities (with "_id"-field)', function () { + const expectedId = "123"; + const expectedName = "test"; + test.entity = { name: expectedName, _id: expectedId }; + test.__user = auditUser; + return test.save().then(() => + Audit.find({ entity_id: test._id }, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + const entry = audit[0]; + expect(entry.changes["entity._id"].to).equal(expectedId); + expect(entry.changes["entity.name"].to).equal(expectedName); + expect(entry.changes["entity._id"].type).equal(AuditType.Add); + expect(entry.changes["entity.name"].type).equal(AuditType.Add); + }) + ); + }); + + it('should create combined value for changes on entities (with "id"-field)', function () { + const expectedId = "123"; + const expectedName = "test"; + test.entity = { name: expectedName, id: expectedId }; + test.__user = auditUser; + return test.save().then(() => + Audit.find({ entity_id: test._id }, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + const entry = audit[0]; + expect(entry.changes["entity.id"].to).equal(expectedId); + expect(entry.changes["entity.name"].to).equal(expectedName); + expect(entry.changes["entity.id"].type).equal(AuditType.Add); + expect(entry.changes["entity.name"].type).equal(AuditType.Add); + }) + ); + }); + + it('should create combined value for changes of entities (with "_id"-field) on remove', function () { + const expectedId = "123"; + const expectedName = "test"; + test.entity = { name: expectedName, _id: expectedId }; + test.__user = auditUser; + return test.save().then((result) => { + result.entity = undefined; + result.__user = auditUser; + return result.save().then(() => + Audit.find({ entity_id: test._id }, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(2); + const entry = audit[1]; + expect(entry.changes.entity.from._id).equal(expectedId); + expect(entry.changes.entity.from.name).equal(expectedName); + expect(entry.changes.entity.to).to.be.undefined(); + expect(entry.changes.entity.type).equal(AuditType.Delete); + }) + ); + }); + }); + + it('should create combined value for changes of entities (with "id"-field) on remove', function () { + const expectedId = "123"; + const expectedName = "test"; + test.entity = { name: expectedName, id: expectedId }; + test.__user = auditUser; + return test.save().then((result) => { + result.entity = undefined; + result.__user = auditUser; + return result.save().then(() => + Audit.find({ entity_id: test._id }, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(2); + const entry = audit[1]; + expect(entry.changes.entity.from.id).equal(expectedId); + expect(entry.changes.entity.from.name).equal(expectedName); + expect(entry.changes.entity.to).to.be.undefined(); + expect(entry.changes.entity.type).equal("Delete"); + }) + ); + }); + }); + + it('should create type "Add" for adding values to arrays', function () { + const expectedValues = ["1", "2", "X"]; + test.entity = { array: [].concat(expectedValues) }; + test.__user = auditUser; + return test.save().then(() => + Audit.find({ entity_id: test._id }, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + const entry = audit[0]; + expect(entry.changes["entity.array"].from.length).equal(0); + expect(entry.changes["entity.array"].to).to.have.members(expectedValues); + expect(entry.changes["entity.array"].type).equal("Add"); + }) + ); + }); + + it('should create combined type "Edit" for adding values on arrays', function () { + const previousValues = ["1", "2", "X"]; + const expectedValues = previousValues.concat(["Y"]); + test.entity = { array: [].concat(previousValues) }; + test.__user = auditUser; + return test.save().then((filled) => { + filled.entity.array = [].concat(expectedValues); + filled.__user = auditUser; + return filled.save().then(() => + Audit.find({ entity_id: test._id }, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(2); + const entry = audit[1]; + expect(entry.changes["entity.array"].from).to.have.members(previousValues); + expect(entry.changes["entity.array"].to).to.have.members(expectedValues); + expect(entry.changes["entity.array"].type).equal("Edit"); + }) + ); + }); + }); + + it('should create combined type "Edit" for removing values on arrays', function () { + const previousValues = ["1", "2", "X"]; + const expectedValues = ["1", "X"]; + test.entity = { array: [].concat(previousValues) }; + test.__user = auditUser; + return test.save().then((filled) => { + filled.entity.array = [].concat(expectedValues); + filled.__user = auditUser; + return filled.save().then(() => + Audit.find({ entity_id: test._id }, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(2); + const entry = audit[1]; + expect(entry.changes["entity.array"].from).to.have.members(previousValues); + expect(entry.changes["entity.array"].to).to.have.members(expectedValues); + expect(entry.changes["entity.array"].type).equal("Edit"); + }) + ); + }); + }); + + it('should create type "Delete" for removing all values from arrays', function () { + const previousValues = ["1", "2", "X"]; + const expectedValues = []; + test.entity = { array: [].concat(previousValues) }; + test.__user = auditUser; + return test.save().then((filled) => { + filled.entity.array = [].concat(expectedValues); + filled.__user = auditUser; + return filled.save().then(() => + Audit.find({ entity_id: test._id }, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(2); + const entry = audit[1]; + expect(entry.changes["entity.array"].from).to.have.members(previousValues); + expect(entry.changes["entity.array"].to).to.have.members(expectedValues); + expect(entry.changes["entity.array"].type).equal("Delete"); + }) + ); + }); + }); + + it('should create type "Add" for new values', function () { + test.empty = "test"; + test.__user = auditUser; + return test.save().then(() => + Audit.find({ entity_id: test._id }, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + expect(audit[0].changes.empty.type).equal("Add"); + }) + ); + }); + + it('should create type "Delete" when value is being removed', function () { + test.name = undefined; + test.__user = auditUser; + return test.save().then(() => + Audit.find({ entity_id: test._id }, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + expect(audit[0].changes.name.type).equal("Delete"); + }) + ); + }); + + it("should create audit trail on save", function (done) { + const expectedName = "Unlucky"; + const expectedNumber = 13; + test.name = expectedName; + test.number = expectedNumber; + test.__user = auditUser; + test.save().then(() => { + Audit.find({ entity_id: test._id }, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + const entry = audit[0]; + expect(entry.entity_id.toString()).equal(test._id.toString()); + expect(Object.values(entry.changes).length).equal(2); + expect(entry.changes.name.from).equal("Lucky"); + expect(entry.changes.name.to).equal(expectedName); + expect(entry.changes.number.from).equal(7); + expect(entry.changes.number.to).equal(expectedNumber); + expect(entry.user).equal(auditUser); + expect(entry.createdAt).not.null(); + expect(entry.createdAt).not.undefined(); + expect(entry.updatedAt).not.null(); + expect(entry.updatedAt).not.undefined(); + expect(entry.entity).equal(TestObject.modelName); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(1); + expect(items[0].number).equal(expectedNumber); + expect(items[0].name).equal(expectedName); + done(); + }); + }); + }); + }); + + it("should not create audit trail if nothing changed", function (done) { + test.__user = auditUser; + test.save().then(() => { + Audit.find({ entity_id: test._id }, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(0); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(1); + expect(items[0].number).equal(test.number); + expect(items[0].name).equal(test.name); + done(); + }); + }); + }); + }); + + it("should not create audit trail if only change is updatedAt", function (done) { + test.__user = auditUser; + test.updatedAt = Date.now(); + test.save().then(() => { + Audit.find({ entity_id: test._id }, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(0); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(1); + expect(items[0].number).equal(test.number); + expect(items[0].name).equal(test.name); + done(); + }); + }); + }); + }); + + it("should create audit trail for update on class", function (done) { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + const expected = 123; + test2 + .save() + .then(() => TestObject.update({}, { number: expected }, { __user: auditUser, multi: true })) + .then(() => { + Audit.find({}, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(2); + expect(Object.values(audit[0].changes).length).equal(1); + expect(audit[0].entity_id.toString()).equal(test._id.toString()); + expect(audit[0].changes.number.from).equal(test.number); + expect(audit[0].changes.number.to).equal(expected); + expect(audit[0].user).equal(auditUser); + expect(audit[0].entity).equal(TestObject.modelName); + expect(Object.values(audit[1].changes).length).equal(1); + expect(audit[1].entity_id.toString()).equal(test2._id.toString()); + expect(audit[1].changes.number.from).equal(test2.number); + expect(audit[1].changes.number.to).equal(expected); + expect(audit[1].user).equal(auditUser); + expect(audit[1].entity).equal(TestObject.modelName); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(2); + expect(items[0].number).equal(expected); + expect(items[1].number).equal(expected); + done(); + }); + }); + }) + .catch(done); + }); + + it("should create audit trail for updateMany", function (done) { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + const expected = 123; + test2 + .save() + .then(() => TestObject.updateMany({}, { number: expected }, { __user: auditUser })) + .then(() => { + Audit.find({}, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(2); + expect(Object.values(audit[0].changes).length).equal(1); + expect(audit[0].entity_id.toString()).equal(test._id.toString()); + expect(audit[0].changes.number.from).equal(test.number); + expect(audit[0].changes.number.to).equal(expected); + expect(audit[0].user).equal(auditUser); + expect(audit[0].entity).equal(TestObject.modelName); + expect(Object.values(audit[1].changes).length).equal(1); + expect(audit[1].entity_id.toString()).equal(test2._id.toString()); + expect(audit[1].changes.number.from).equal(test2.number); + expect(audit[1].changes.number.to).equal(expected); + expect(audit[1].user).equal(auditUser); + expect(audit[1].entity).equal(TestObject.modelName); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(2); + expect(items[0].number).equal(expected); + expect(items[1].number).equal(expected); + done(); + }); + }); + }) + .catch(done); + }); + + it("should create audit trail for updateMany ignoring multi value", function (done) { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + const expected = 123; + test2 + .save() + .then(() => TestObject.updateMany({}, { number: expected }, { __user: auditUser, multi: false })) + .then(() => { + Audit.find({}, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(2); + expect(audit[0].entity_id.toString()).equal(test._id.toString()); + expect(audit[1].entity_id.toString()).equal(test2._id.toString()); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(2); + expect(items[0].number).equal(expected); + expect(items[1].number).equal(expected); + done(); + }); + }); + }) + .catch(done); + }); + + it("should create audit trail for update only for first elem if not multi", function (done) { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + const expected = 123; + test2 + .save() + .then(() => TestObject.update({}, { number: expected }, { __user: auditUser, multi: false })) + .then(() => { + Audit.find({}, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + expect(Object.values(audit[0].changes).length).equal(1); + expect(audit[0].entity_id.toString()).equal(test._id.toString()); + expect(audit[0].changes.number.from).equal(test.number); + expect(audit[0].changes.number.to).equal(expected); + expect(audit[0].user).equal(auditUser); + expect(audit[0].entity).equal(TestObject.modelName); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(2); + expect(items[0].number).equal(expected); + expect(items[1].number).equal(test2.number); + done(); + }); + }); + }) + .catch(done); + }); + + it("should create audit trail for update on instance", function (done) { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + const expected = 123; + test2 + .save() + .then(() => test.update({ number: expected }, { __user: auditUser })) + .then(() => { + Audit.find({}, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + expect(Object.values(audit[0].changes).length).equal(1); + expect(audit[0].entity_id.toString()).equal(test._id.toString()); + expect(audit[0].changes.number.from).equal(test.number); + expect(audit[0].changes.number.to).equal(expected); + expect(audit[0].user).equal(auditUser); + expect(audit[0].entity).equal(TestObject.modelName); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(2); + expect(items[0].number).equal(expected); + expect(items[1].number).equal(test2.number); + done(); + }); + }); + }) + .catch(done); + }); + + it("should create audit trail on update with $set", function (done) { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + const expected = 123; + test2 + .save() + .then(() => TestObject.update({}, { $set: { number: expected } }, { __user: auditUser })) + .then(() => { + Audit.find({}, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + expect(audit[0].entity_id.toString()).equal(test._id.toString()); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(2); + expect(items[0].number).equal(expected); + expect(items[1].number).equal(test2.number); + done(); + }); + }); + }) + .catch(done); + }); + + it("should create audit trail on update with $set if multi", function (done) { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + const expected = 123; + test2 + .save() + .then(() => TestObject.update({}, { $set: { number: expected } }, { multi: true, __user: auditUser })) + .then(() => { + Audit.find({}, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(2); + expect(audit[0].entity_id.toString()).equal(test._id.toString()); + expect(audit[1].entity_id.toString()).equal(test2._id.toString()); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(2); + expect(items[0].number).equal(expected); + expect(items[1].number).equal(expected); + done(); + }); + }); + }) + .catch(done); + }); + + it("should create audit trail on updateOne", function (done) { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + const expected = 123; + test2 + .save() + .then(() => TestObject.updateOne({ _id: test._id }, { number: expected }, { __user: auditUser })) + .then(() => { + Audit.find({}, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + expect(Object.values(audit[0].changes).length).equal(1); + expect(audit[0].entity_id.toString()).equal(test._id.toString()); + expect(audit[0].changes.number.from).equal(test.number); + expect(audit[0].changes.number.to).equal(expected); + expect(audit[0].user).equal(auditUser); + expect(audit[0].entity).equal(TestObject.modelName); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(2); + expect(items[0].number).equal(expected); + expect(items[1].number).equal(test2.number); + done(); + }); + }); + }) + .catch(done); + }); + + it("should create audit trail on findOneAndUpdate", function (done) { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + const expected = 123; + test2 + .save() + .then(() => TestObject.findOneAndUpdate({ _id: test._id }, { number: expected }, { __user: auditUser })) + .then(() => { + Audit.find({}, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + expect(Object.values(audit[0].changes).length).equal(1); + expect(audit[0].entity_id.toString()).equal(test._id.toString()); + expect(audit[0].changes.number.from).equal(test.number); + expect(audit[0].changes.number.to).equal(expected); + expect(audit[0].user).equal(auditUser); + expect(audit[0].entity).equal(TestObject.modelName); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(2); + expect(items[0].number).equal(expected); + expect(items[1].number).equal(test2.number); + done(); + }); + }); + }) + .catch(done); + }); + + it("should create audit trail on replaceOne", function (done) { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + const expected = 123; + const replace = Object.assign({}, test._doc); + replace.number = expected; + replace.__v += 1; + test2 + .save() + .then(() => TestObject.replaceOne({ _id: test._id }, replace, { __user: auditUser })) + .then(() => { + Audit.find({}, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + expect(Object.values(audit[0].changes).length).equal(1); + expect(audit[0].entity_id.toString()).equal(test._id.toString()); + expect(audit[0].changes.number.from).equal(test.number); + expect(audit[0].changes.number.to).equal(expected); + expect(audit[0].user).equal(auditUser); + expect(audit[0].entity).equal(TestObject.modelName); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(2); + expect(items[0].number).equal(expected); + expect(items[0].__v).equal(1); + expect(items[1].number).equal(test2.number); + done(); + }); + }); + }) + .catch(done); + }); + + const expectDeleteValues = (entry) => { + expect(Object.values(entry.changes).length).equal(3); + expect(entry.entity_id.toString()).equal(test._id.toString()); + expect(entry.changes.date.type).equal("Delete"); + expect(entry.changes.name.type).equal("Delete"); + expect(entry.changes.number.type).equal("Delete"); + expect(entry.changes.date.from).equal(test.date.toISOString()); + expect(entry.changes.name.from).equal(test.name); + expect(entry.changes.number.from).equal(test.number); + expect(entry.user).equal(auditUser); + expect(entry.entity).equal(TestObject.modelName); + }; + + it("should create audit trail on remove", function (done) { + test + .remove({ __user: auditUser }) + .then(() => + Audit.find({}, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + expectDeleteValues(audit[0]); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(0); + done(); + }); + }) + ) + .catch(done); + }); + + it("should create audit trail on findOneAndDelete", function (done) { + TestObject.findOneAndDelete({ _id: test._id }, { __user: auditUser }) + .then(() => + Audit.find({}, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + expectDeleteValues(audit[0]); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(0); + done(); + }); + }) + ) + .catch(done); + }); + + it("should create audit trail on findOneAndDelete only for one item", function (done) { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + test2 + .save() + .then(() => TestObject.findOneAndDelete({ _id: test._id }, { __user: auditUser })) + .then(() => + Audit.find({}, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + expectDeleteValues(audit[0]); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(1); + expect(items[0]._id.toString()).equal(test2._id.toString()); + done(); + }); + }) + ) + .catch(done); + }); + + it("should create audit trail on findOneAndRemove", function (done) { + TestObject.findOneAndRemove({ _id: test._id }, { __user: auditUser }) + .then(() => + Audit.find({}, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + expectDeleteValues(audit[0]); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(0); + done(); + }); + }) + ) + .catch(done); + }); + + it("should create audit trail on findOneAndRemove only for one item", function (done) { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + test2 + .save() + .then(() => TestObject.findOneAndRemove({ _id: test._id }, { __user: auditUser })) + .then(() => + Audit.find({}, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + expectDeleteValues(audit[0]); + TestObject.find({}, function (err, items) { + expect(err).to.null(); + expect(items.length).equal(1); + expect(items[0]._id.toString()).equal(test2._id.toString()); + done(); + }); + }) + ) + .catch(done); + }); + }); + + describe("plugin: user callback", function () { + it("should use the user callback if provided", function (done) { + const expectedUser = "User from function"; + plugin.getUser = () => expectedUser; + + const test = new TestObject({ name: "Lucky", number: 7 }); + test + .save() + .then((test) => { + test.name = "Unlucky"; + test.number = 13; + test.save().then(() => { + Audit.find({ entity_id: test._id }, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + expect(audit[0].user).equal(expectedUser); + done(); + }); + }); + }) + .catch(done); + }); + + it("should use the user if provided", function (done) { + const expectedUser = "User from parameter"; + plugin.getUser = () => expectedUser; + + const test = new TestObject({ name: "Lucky", number: 7 }); + test + .save() + .then((test) => { + test.name = "Unlucky"; + test.number = 13; + test.__user = ""; + test.save().then(() => { + Audit.find({ entity_id: test._id }, function (err, audit) { + expect(err).to.null(); + expect(audit.length).equal(1); + expect(audit[0].user).equal(expectedUser); + done(); + }); + }); + }) + .catch(done); + }); + + it("should throw error if no user is provided", function (done) { + plugin.getUser = () => undefined; + + const test = new TestObject({ name: "Lucky", number: 7 }); + test + .save() + .then((test) => { + test.name = "Unlucky"; + test.number = 13; + test + .save() + .then((_) => done(new Error("should not have succeeded!"))) + .catch((err) => { + expect(err.message).to.be.equal("User missing in audit log!"); + done(); + }); + }) + .catch(done); + }); + }); +}); diff --git a/plugins/mongoose-audit/types/index.d.ts b/plugins/mongoose-audit/types/index.d.ts new file mode 100644 index 0000000..fffbed2 --- /dev/null +++ b/plugins/mongoose-audit/types/index.d.ts @@ -0,0 +1,36 @@ +import mongoose from "mongoose"; + +type auditType = "add" | "edit" | "delete"; + +interface Audit { + entity_id: any; + entity: string; + collection: string; + changes: any; + user: any; + created_at: string; +} + +interface PluginOptions { + /** The user extractor function to use. This probably will be fetching the current user from a context or something similar. */ + getUser?: () => any; + /** The types of audit to record. */ + types?: auditType[]; + /** The fields to exclude from the audit. */ + exclude?: string[]; + /** Called before persisting the audit is saved. Use this to use your own audit model instead of the default one. */ + onAudit?: (audit: Audit) => Promise; +} + +/** Registers the plugin with the provided options. */ +export declare function plugin(schema: mongoose.Schema, options: PluginOptions): void; + +/** + * The Audit model. + */ +export const Audit: mongoose.Model; + +/** + * The audit type enum. + */ +export const AuditType: Record; From d792d393e9b2bbf6740228536e80e541622ef068 Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sun, 10 Mar 2024 17:29:28 +0000 Subject: [PATCH 02/11] Fix(mongoose-audit): unit tests --- plugins/mongoose-audit/package.json | 6 +- plugins/mongoose-audit/readme.md | 2 + plugins/mongoose-audit/src/constants.js | 7 + plugins/mongoose-audit/src/model.js | 1 - plugins/mongoose-audit/src/plugin.js | 180 ++-- plugins/mongoose-audit/src/utils.js | 6 - plugins/mongoose-audit/test/index.test.js | 1124 +++++++-------------- plugins/mongoose-audit/types/index.d.ts | 5 +- pnpm-lock.yaml | 191 +++- 9 files changed, 666 insertions(+), 856 deletions(-) diff --git a/plugins/mongoose-audit/package.json b/plugins/mongoose-audit/package.json index 5e8f602..2e0be17 100644 --- a/plugins/mongoose-audit/package.json +++ b/plugins/mongoose-audit/package.json @@ -10,10 +10,12 @@ "bump-version": "bash ../../scripts/bump-version.sh --name=@sliit-foss/express-http-context", "lint": "bash ../../scripts/lint.sh", "release": "bash ../../scripts/release.sh", - "test": "if [ \"$CI\" = \"true\" ]; then \n bash ../../scripts/test/test.sh; else \n echo \"Skipping as it is not a CI environemnt\"; fi" + "test": "if [ \"$CI\" = \"true\" ]; then \n bash ../../scripts/test/test.sh; else \n echo \"Skipping as it is not a CI environment\"; fi" }, "dependencies": { - "deep-diff": "^1.0.2" + "deep-diff": "1.0.2", + "dot-object": "2.1.4", + "lodash": "4.17.10" }, "peerDependencies": { "mongoose": ">=5" diff --git a/plugins/mongoose-audit/readme.md b/plugins/mongoose-audit/readme.md index 90e09b9..a6889fc 100644 --- a/plugins/mongoose-audit/readme.md +++ b/plugins/mongoose-audit/readme.md @@ -2,6 +2,8 @@ #### A rework of the [mongoose-audit-log](https://www.npmjs.com/package/mongoose-audit-log) package to support newer versions of mongoose and more flexible options
+#### !IMPORTANT - The behaviour of this is different from the original `mongoose-audit-log` package and cannot be considered as a drop in replacement for it. + It is a mongoose plugin to manage an audit log of changes to a MongoDB database. ## Features diff --git a/plugins/mongoose-audit/src/constants.js b/plugins/mongoose-audit/src/constants.js index 53eb443..4cde018 100644 --- a/plugins/mongoose-audit/src/constants.js +++ b/plugins/mongoose-audit/src/constants.js @@ -3,3 +3,10 @@ export const AuditType = { Edit: "Edit", Delete: "Delete" }; + +export const ChangeAuditType = { + N: AuditType.Add, + D: AuditType.Delete, + E: AuditType.Edit, + A: AuditType.Edit, +} \ No newline at end of file diff --git a/plugins/mongoose-audit/src/model.js b/plugins/mongoose-audit/src/model.js index baf6cec..f892e37 100644 --- a/plugins/mongoose-audit/src/model.js +++ b/plugins/mongoose-audit/src/model.js @@ -4,7 +4,6 @@ const auditSchema = new mongoose.Schema( { entity_id: {}, entity: String, - collection: String, changes: {}, user: { type: mongoose.Schema.Types.Mixed, diff --git a/plugins/mongoose-audit/src/plugin.js b/plugins/mongoose-audit/src/plugin.js index 2b0bbd1..202c0b7 100644 --- a/plugins/mongoose-audit/src/plugin.js +++ b/plugins/mongoose-audit/src/plugin.js @@ -1,30 +1,27 @@ import { default as deepDiff } from "deep-diff"; +import { dot } from "dot-object"; +import { isEmpty, set } from "lodash" import { default as Audit } from "./model"; -import { AuditType } from "./constants"; -import { extractArray, filter, flattenObject, isEmpty } from "./utils"; +import { AuditType, ChangeAuditType } from "./constants"; +import { extractArray, filter, flattenObject } from "./utils"; const options = { getUser: () => undefined, types: [AuditType.Add, AuditType.Edit, AuditType.delete], exclude: [], - onAudit: undefined + onAudit: undefined, + background: true }; const addAuditLogObject = (currentObject, original) => { - const user = currentObject.__user || options.getUser() || "Unknown User"; + const user = currentObject.__user || options.getUser?.() || "Unknown User"; delete currentObject.__user; - let changes = deepDiff(original._doc || original, currentObject._doc || currentObject, filter); - if (changes && changes.length) { + let changes = deepDiff(JSON.parse(JSON.stringify(original ?? {})), JSON.parse(JSON.stringify(currentObject ?? {})), filter); + if (changes?.length) { changes = changes.reduce((obj, change) => { const key = change.path.join("."); - if (options.exclude.includes(key)) { - return obj; - } - if (change.kind === "D") { - handleAudits(change.lhs, "from", AuditType.delete, obj, key); - } else if (change.kind === "N") { - handleAudits(change.rhs, "to", AuditType.Add, obj, key); - } else if (change.kind === "A") { + if (options.exclude?.includes(key)) return obj; + if (change.kind === "A") { if (!obj[key] && change.path.length) { const data = { from: extractArray(original, change.path), @@ -33,26 +30,39 @@ const addAuditLogObject = (currentObject, original) => { if (data.from.length && data.to.length) { data.type = AuditType.Edit; } else if (data.from.length) { - data.type = AuditType.delete; + data.type = AuditType.Delete; } else if (data.to.length) { data.type = AuditType.Add; } - obj[key] = data; + set(obj, key, data) } + } else if (typeof change.lhs === "object") { + Object.entries(dot(change.lhs)).forEach(([subKeys, value]) => { + set(obj, `${key}.${subKeys}`, { + from: value, + type: ChangeAuditType[change.kind] + }) + }) + } else if (typeof change.rhs === "object") { + Object.entries(dot(change.rhs)).forEach(([subKeys, value]) => { + set(obj, `${key}.${subKeys}`, { + to: value, + type: ChangeAuditType[change.kind] + }) + }) } else { - obj[key] = { + set(obj, key, { from: change.lhs, to: change.rhs, - type: AuditType.Edit - }; + type: ChangeAuditType[change.kind] + }) } - return obj; + return obj }, {}); - if (isEmpty(changes)) return Promise.resolve(); + if (isEmpty(changes)) return; const audit = { entity_id: currentObject._id, entity: currentObject.constructor.modelName, - collection: currentObject.constructor.collection.collectionName, changes, user }; @@ -61,116 +71,86 @@ const addAuditLogObject = (currentObject, original) => { } return new Audit(audit).save(); } - return Promise.resolve(); + return; }; -const handleAudits = (changes, target, type, obj, key) => { - if (typeof changes === "object") { - if (Object.keys(changes).filter((key) => key === "_id" || key === "id").length) { - // entity found - obj[key] = { [target]: changes, type }; - } else { - // sibling/sub-object - Object.entries(changes).forEach(([sub, value]) => { - if (!isEmpty(value)) { - obj[`${key}.${sub}`] = { [target]: value, type }; - } - }); - } - } else { - // primitive value - obj[key] = { [target]: changes, type }; - } +const addAuditLog = async (currentObject) => { + const original = await currentObject.constructor.findOne({ _id: currentObject._id }).lean() + const result = addAuditLogObject(currentObject, original) + /* istanbul ignore else */ + if (!options.background) await result; }; -const addAuditLog = (currentObject, next) => { - currentObject.constructor - .findOne({ _id: currentObject._id }) - .then((original) => addAuditLogObject(currentObject, original)) - .then(next) - .catch(next); -}; - -const addUpdate = (query, next, multi) => { +const addUpdate = async (query, multi) => { const updated = flattenObject(query._update); let counter = 0; - return query - .find(query._conditions) - .lean(true) - .cursor() - .eachAsync((fromDb) => { - if (!multi && counter++) { - // handle 'multi: false' - return next(); - } - const orig = Object.assign({ __user: query.options.__user }, fromDb, updated); - orig.constructor.modelName = query._collection.collectionName; - return addAuditLogObject(orig, fromDb); - }) - .then(next) - .catch(next); + const originalDocs = await query.find(query._conditions).lean(true) + const promises = originalDocs.map((original) => { + if (!multi && counter++) { + return + } + const currentObject = Object.assign({ __user: query.options.__user }, original, updated); + currentObject.constructor.modelName = query.model.modelName; + return addAuditLogObject(currentObject, original); + }) + /* istanbul ignore else */ + if (!options.background) await Promise.allSettled(promises); }; -const addDelete = (currentObject, options, next) => { - const orig = Object.assign({}, currentObject._doc || currentObject); - orig.constructor.modelName = currentObject.constructor.modelName; - return addAuditLogObject( +const addDelete = async (currentObject, options) => { + const result = addAuditLogObject( { _id: currentObject._id, __user: options.__user }, - orig + JSON.parse(JSON.stringify(currentObject)) ) - .then(next) - .catch(next); + /* istanbul ignore else */ + if (!options.background) await result; }; -const addFindAndDelete = (query, next) => { - query - .find() - .lean(true) - .cursor() - .eachAsync((fromDb) => addDelete(fromDb, query.options, next)) - .then(next) - .catch(next); +const addFindAndDelete = async (query) => { + const originalDocs = await query.find().lean(true) + const promises = originalDocs.map((original) => { + return addDelete(original, query.options) + }) + /* istanbul ignore else */ + if (!options.background) await Promise.allSettled(promises); }; const plugin = (schema, opts = {}) => { Object.assign(options, opts); if (options.types.includes(AuditType.Add)) { - schema.pre("save", function (next) { - if (this.isNew) { - return next(); - } - addAuditLog(this, next); + schema.pre("save", function () { + return addAuditLog(this); }); } if (options.types.includes(AuditType.Edit)) { - schema.pre("update", function (next) { - addUpdate(this, next, !!this.options.multi); + schema.pre("update", function () { + return addUpdate(this, !!this.options.multi); }); - schema.pre("updateOne", function (next) { - addUpdate(this, next, false); + schema.pre("updateOne", function () { + return addUpdate(this, false); }); - schema.pre("findOneAndUpdate", function (next) { - addUpdate(this, next, false); + schema.pre("findOneAndUpdate", function () { + return addUpdate(this, false); }); - schema.pre("updateMany", function (next) { - addUpdate(this, next, true); + schema.pre("updateMany", function () { + return addUpdate(this, true); }); - schema.pre("replaceOne", function (next) { - addUpdate(this, next, false); + schema.pre("replaceOne", function () { + return addUpdate(this, false); }); } if (options.types.includes(AuditType.delete)) { - schema.pre("remove", function (next, options) { - addDelete(this, options, next); + schema.pre("remove", function (_, options) { + return addDelete(this, options); }); - schema.pre("findOneAndDelete", function (next) { - addFindAndDelete(this, next); + schema.pre("findOneAndDelete", function () { + return addFindAndDelete(this); }); - schema.pre("findOneAndRemove", function (next) { - addFindAndDelete(this, next); + schema.pre("findOneAndRemove", function () { + return addFindAndDelete(this); }); } }; diff --git a/plugins/mongoose-audit/src/utils.js b/plugins/mongoose-audit/src/utils.js index 4311802..2240a3c 100644 --- a/plugins/mongoose-audit/src/utils.js +++ b/plugins/mongoose-audit/src/utils.js @@ -1,11 +1,5 @@ export const filter = (path, key) => path.length === 0 && ~["_id", "__v", "createdAt", "updatedAt"].indexOf(key); -export const isEmpty = (value) => - value === undefined || - value === null || - (typeof value === "object" && Object.keys(value).length === 0) || - (typeof value === "string" && value.trim().length === 0); - export const extractArray = (data, path) => { if (path.length === 1) { return data[path[0]]; diff --git a/plugins/mongoose-audit/test/index.test.js b/plugins/mongoose-audit/test/index.test.js index 7cb1b1d..ec0308f 100644 --- a/plugins/mongoose-audit/test/index.test.js +++ b/plugins/mongoose-audit/test/index.test.js @@ -1,17 +1,12 @@ import { default as mongoose } from "mongoose"; -import { exec } from "child_process"; -import { promisify } from "util"; import { plugin, Audit } from "../src"; import { AuditType } from "../src/constants"; -const execute = promisify(exec); - -const setupMongoServer = async (done) => { - await execute("docker run -d -p 27017:27017 --name audit-test mongo:4.2.8"); - mongoose.connect("mongodb://localhost:27018/audit-test", { useNewUrlParser: true }).then(done).catch(done); +const connectToDatabase = async () => { + await mongoose.connect("mongodb://localhost:27017/test") }; -const createTestModel = (getUser) => { +const createTestModel = () => { const testSchema = new mongoose.Schema( { name: String, @@ -34,788 +29,431 @@ const createTestModel = (getUser) => { }, { timestamps: true } ); - testSchema.plugin(plugin, getUser); - return mongoose.model("tests", testSchema); + testSchema.plugin(plugin, { background: false }); + return mongoose.model("Test", testSchema); }; -describe("audit", function () { - before(setupMongoServer); - afterEach(function (done) { - Promise.all([ - mongoose.connection.collections[TestObject.collection.collectionName].drop(), - mongoose.connection.collections[Audit.collection.collectionName].drop() - ]) - .then(() => done()) - .catch((err) => done(err.code !== 26 && err)); - }); - - const TestObject = createTestModel(); +const TestObject = createTestModel(); - it("should return undefined on getUser", function () { - expect(plugin.getUser()).to.be.undefined(); - }); - - describe("plugin: pre *", function () { - const auditUser = "Jack"; - - let test; - beforeEach((done) => { - test = new TestObject({ name: "Lucky", number: 7 }); - test - .save() - .then(() => done()) - .catch(done); - }); - - it("should create single values for changes on siblings (non-entities)", function () { - const expectedName = "test"; - const expectedNumber = 123; - test.child = { name: expectedName, number: expectedNumber }; - test.__user = auditUser; - return test.save().then(() => - Audit.find({ entity_id: test._id }, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - const entry = audit[0]; - expect(entry.changes["child.name"].to).equal(expectedName); - expect(entry.changes["child.name"].type).equal(AuditType.Add); - expect(entry.changes["child.number"].to).equal(expectedNumber); - expect(entry.changes["child.number"].type).equal(AuditType.Add); - }) - ); - }); - - it("should create single values for changes of siblings (non-entities) on remove", function () { - const expectedName = "test"; - const expectedNumber = 123; - test.child = { name: expectedName, number: expectedNumber }; - test.__user = auditUser; - return test.save().then((result) => { - result.child = undefined; - result.__user = auditUser; - return result.save().then(() => - Audit.find({ entity_id: test._id }, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(2); - const entry = audit[1]; - expect(entry.changes["child.name"].from).equal(expectedName); - expect(entry.changes["child.name"].to).to.be.undefined(); - expect(entry.changes["child.name"].type).equal(AuditType.Delete); - expect(entry.changes["child.number"].from).equal(expectedNumber); - expect(entry.changes["child.number"].to).to.be.undefined(); - expect(entry.changes["child.number"].type).equal(AuditType.Delete); - }) - ); - }); - }); +const cleanup = async () => { + await Promise.all([ + mongoose.connection.collections[TestObject.collection.collectionName].drop(), + mongoose.connection.collections[Audit.collection.collectionName].drop() + ]).catch(() => { }); +} - it('should create combined value for changes on entities (with "_id"-field)', function () { - const expectedId = "123"; - const expectedName = "test"; - test.entity = { name: expectedName, _id: expectedId }; - test.__user = auditUser; - return test.save().then(() => - Audit.find({ entity_id: test._id }, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - const entry = audit[0]; - expect(entry.changes["entity._id"].to).equal(expectedId); - expect(entry.changes["entity.name"].to).equal(expectedName); - expect(entry.changes["entity._id"].type).equal(AuditType.Add); - expect(entry.changes["entity.name"].type).equal(AuditType.Add); - }) - ); - }); +beforeAll(connectToDatabase); - it('should create combined value for changes on entities (with "id"-field)', function () { - const expectedId = "123"; - const expectedName = "test"; - test.entity = { name: expectedName, id: expectedId }; - test.__user = auditUser; - return test.save().then(() => - Audit.find({ entity_id: test._id }, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - const entry = audit[0]; - expect(entry.changes["entity.id"].to).equal(expectedId); - expect(entry.changes["entity.name"].to).equal(expectedName); - expect(entry.changes["entity.id"].type).equal(AuditType.Add); - expect(entry.changes["entity.name"].type).equal(AuditType.Add); - }) - ); - }); +describe("audit", function () { - it('should create combined value for changes of entities (with "_id"-field) on remove', function () { - const expectedId = "123"; - const expectedName = "test"; - test.entity = { name: expectedName, _id: expectedId }; - test.__user = auditUser; - return test.save().then((result) => { - result.entity = undefined; - result.__user = auditUser; - return result.save().then(() => - Audit.find({ entity_id: test._id }, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(2); - const entry = audit[1]; - expect(entry.changes.entity.from._id).equal(expectedId); - expect(entry.changes.entity.from.name).equal(expectedName); - expect(entry.changes.entity.to).to.be.undefined(); - expect(entry.changes.entity.type).equal(AuditType.Delete); - }) - ); - }); - }); + afterEach(cleanup); - it('should create combined value for changes of entities (with "id"-field) on remove', function () { - const expectedId = "123"; - const expectedName = "test"; - test.entity = { name: expectedName, id: expectedId }; - test.__user = auditUser; - return test.save().then((result) => { - result.entity = undefined; - result.__user = auditUser; - return result.save().then(() => - Audit.find({ entity_id: test._id }, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(2); - const entry = audit[1]; - expect(entry.changes.entity.from.id).equal(expectedId); - expect(entry.changes.entity.from.name).equal(expectedName); - expect(entry.changes.entity.to).to.be.undefined(); - expect(entry.changes.entity.type).equal("Delete"); - }) - ); - }); - }); + const auditUser = "Jack"; - it('should create type "Add" for adding values to arrays', function () { - const expectedValues = ["1", "2", "X"]; - test.entity = { array: [].concat(expectedValues) }; - test.__user = auditUser; - return test.save().then(() => - Audit.find({ entity_id: test._id }, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - const entry = audit[0]; - expect(entry.changes["entity.array"].from.length).equal(0); - expect(entry.changes["entity.array"].to).to.have.members(expectedValues); - expect(entry.changes["entity.array"].type).equal("Add"); - }) - ); - }); + let test; - it('should create combined type "Edit" for adding values on arrays', function () { - const previousValues = ["1", "2", "X"]; - const expectedValues = previousValues.concat(["Y"]); - test.entity = { array: [].concat(previousValues) }; - test.__user = auditUser; - return test.save().then((filled) => { - filled.entity.array = [].concat(expectedValues); - filled.__user = auditUser; - return filled.save().then(() => - Audit.find({ entity_id: test._id }, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(2); - const entry = audit[1]; - expect(entry.changes["entity.array"].from).to.have.members(previousValues); - expect(entry.changes["entity.array"].to).to.have.members(expectedValues); - expect(entry.changes["entity.array"].type).equal("Edit"); - }) - ); - }); - }); + beforeEach(async () => { + test = new TestObject({ name: "Lucky", number: 7 }); + await test.save() + }); - it('should create combined type "Edit" for removing values on arrays', function () { - const previousValues = ["1", "2", "X"]; - const expectedValues = ["1", "X"]; - test.entity = { array: [].concat(previousValues) }; - test.__user = auditUser; - return test.save().then((filled) => { - filled.entity.array = [].concat(expectedValues); - filled.__user = auditUser; - return filled.save().then(() => - Audit.find({ entity_id: test._id }, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(2); - const entry = audit[1]; - expect(entry.changes["entity.array"].from).to.have.members(previousValues); - expect(entry.changes["entity.array"].to).to.have.members(expectedValues); - expect(entry.changes["entity.array"].type).equal("Edit"); - }) - ); - }); - }); + it("should create values for newly created entity", async () => { + await Audit.find({ entity_id: test._id }).then((audit) => { + expect(audit.length).toBe(1); + const entry = audit[0]; + expect(entry.changes["name"].to).toBe("Lucky"); + expect(entry.changes["name"].type).toBe(AuditType.Add); + expect(entry.user).toBe("Unknown User") + expect(entry.entity).toBe("Test") + }) + }); - it('should create type "Delete" for removing all values from arrays', function () { - const previousValues = ["1", "2", "X"]; - const expectedValues = []; - test.entity = { array: [].concat(previousValues) }; - test.__user = auditUser; - return test.save().then((filled) => { - filled.entity.array = [].concat(expectedValues); - filled.__user = auditUser; - return filled.save().then(() => - Audit.find({ entity_id: test._id }, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(2); - const entry = audit[1]; - expect(entry.changes["entity.array"].from).to.have.members(previousValues); - expect(entry.changes["entity.array"].to).to.have.members(expectedValues); - expect(entry.changes["entity.array"].type).equal("Delete"); - }) - ); - }); + it("should create values for changes on siblings (non-entities)", async () => { + const expectedName = "test"; + const expectedNumber = 123; + test.child = { name: expectedName, number: expectedNumber }; + test.__user = auditUser; + await test.save().then(async () => { + await Audit.find({ entity_id: test._id }).then((audit) => { + expect(audit.length).toBe(2); + const entry = audit[1]; + expect(entry.changes.child.name.to).toBe(expectedName); + expect(entry.changes.child.name.type).toBe(AuditType.Add); + expect(entry.changes.child.number.to).toBe(expectedNumber); + expect(entry.changes.child.number.type).toBe(AuditType.Add); + }) }); + }); - it('should create type "Add" for new values', function () { - test.empty = "test"; - test.__user = auditUser; - return test.save().then(() => - Audit.find({ entity_id: test._id }, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - expect(audit[0].changes.empty.type).equal("Add"); - }) + it("should create single values for changes of siblings (non-entities) on remove", async () => { + const expectedName = "test"; + const expectedNumber = 123; + test.child = { name: expectedName, number: expectedNumber }; + test.__user = auditUser; + await test.save().then(async (result) => { + result.child = undefined; + result.__user = auditUser; + return result.save().then(async () => { + const audit = await Audit.find({ entity_id: test._id }) + expect(audit.length).toBe(3); + const entry = audit[2]; + expect(entry.changes.child.name.from).toBe(expectedName); + expect(entry.changes.child.name.to).toBe(undefined); + expect(entry.changes.child.name.type).toBe(AuditType.Delete); + expect(entry.changes.child.number.from).toBe(expectedNumber); + expect(entry.changes.child.number.to).toBe(undefined); + expect(entry.changes.child.number.type).toBe(AuditType.Delete); + } ); }); + }); - it('should create type "Delete" when value is being removed', function () { - test.name = undefined; - test.__user = auditUser; - return test.save().then(() => - Audit.find({ entity_id: test._id }, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - expect(audit[0].changes.name.type).equal("Delete"); - }) - ); + it('should create type "Add" for adding values to arrays', async () => { + const expectedValues = ["1", "2", "X"]; + test.entity = { array: [].concat(expectedValues) }; + test.__user = auditUser; + await test.save().then(async () => { + const audit = await Audit.find({ entity_id: test._id }) + expect(audit.length).toBe(2); + const entry = audit[1]; + expect(entry.changes.entity.array.from.length).toBe(0); + expect(entry.changes.entity.array.to).toEqual(expectedValues) + expect(entry.changes.entity.array.type).toBe(AuditType.Add); }); + }); - it("should create audit trail on save", function (done) { - const expectedName = "Unlucky"; - const expectedNumber = 13; - test.name = expectedName; - test.number = expectedNumber; - test.__user = auditUser; - test.save().then(() => { - Audit.find({ entity_id: test._id }, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - const entry = audit[0]; - expect(entry.entity_id.toString()).equal(test._id.toString()); - expect(Object.values(entry.changes).length).equal(2); - expect(entry.changes.name.from).equal("Lucky"); - expect(entry.changes.name.to).equal(expectedName); - expect(entry.changes.number.from).equal(7); - expect(entry.changes.number.to).equal(expectedNumber); - expect(entry.user).equal(auditUser); - expect(entry.createdAt).not.null(); - expect(entry.createdAt).not.undefined(); - expect(entry.updatedAt).not.null(); - expect(entry.updatedAt).not.undefined(); - expect(entry.entity).equal(TestObject.modelName); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(1); - expect(items[0].number).equal(expectedNumber); - expect(items[0].name).equal(expectedName); - done(); - }); - }); + it('should create type "Edit" for adding values on arrays', async () => { + const previousValues = ["1", "2", "X"]; + const expectedValues = previousValues.concat(["Y"]); + test.entity = { array: [].concat(previousValues) }; + test.__user = auditUser; + await test.save().then(async (filled) => { + filled.entity.array = [].concat(expectedValues); + filled.__user = auditUser; + await filled.save().then(async () => { + const audit = await Audit.find({ entity_id: test._id }) + expect(audit.length).toBe(3); + const entry = audit[2]; + expect(entry.changes.entity.array.from).toEqual(previousValues); + expect(entry.changes.entity.array.to).toEqual(expectedValues); + expect(entry.changes.entity.array.type).toBe(AuditType.Edit); }); }); + }); - it("should not create audit trail if nothing changed", function (done) { - test.__user = auditUser; - test.save().then(() => { - Audit.find({ entity_id: test._id }, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(0); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(1); - expect(items[0].number).equal(test.number); - expect(items[0].name).equal(test.name); - done(); - }); - }); + it('should create type "Edit" for removing values on arrays', async () => { + const previousValues = ["1", "2", "X"]; + const expectedValues = ["1", "X"]; + test.entity = { array: [].concat(previousValues) }; + test.__user = auditUser; + await test.save().then(async (filled) => { + filled.entity.array = [].concat(expectedValues); + filled.__user = auditUser; + await filled.save().then(async () => { + const audit = await Audit.find({ entity_id: test._id }) + expect(audit.length).toBe(3); + const entry = audit[2]; + expect(entry.changes.entity.array.from).toEqual(previousValues); + expect(entry.changes.entity.array.to).toEqual(expectedValues); + expect(entry.changes.entity.array.type).toBe(AuditType.Edit); }); }); + }); - it("should not create audit trail if only change is updatedAt", function (done) { - test.__user = auditUser; - test.updatedAt = Date.now(); - test.save().then(() => { - Audit.find({ entity_id: test._id }, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(0); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(1); - expect(items[0].number).equal(test.number); - expect(items[0].name).equal(test.name); - done(); - }); - }); + it('should create type "Delete" for removing all values from arrays', async () => { + const previousValues = ["1", "2", "X"]; + const expectedValues = []; + test.entity = { array: [].concat(previousValues) }; + test.__user = auditUser; + await test.save().then(async (filled) => { + filled.entity.array = [].concat(expectedValues); + filled.__user = auditUser; + await filled.save().then(async () => { + const audit = await Audit.find({ entity_id: test._id }) + expect(audit.length).toBe(3); + const entry = audit[2]; + expect(entry.changes.entity.array.from).toEqual(previousValues); + expect(entry.changes.entity.array.to).toEqual(expectedValues); + expect(entry.changes.entity.array.type).toBe(AuditType.Delete); }); }); + }); - it("should create audit trail for update on class", function (done) { - const test2 = new TestObject({ name: "Unlucky", number: 13 }); - const expected = 123; - test2 - .save() - .then(() => TestObject.update({}, { number: expected }, { __user: auditUser, multi: true })) - .then(() => { - Audit.find({}, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(2); - expect(Object.values(audit[0].changes).length).equal(1); - expect(audit[0].entity_id.toString()).equal(test._id.toString()); - expect(audit[0].changes.number.from).equal(test.number); - expect(audit[0].changes.number.to).equal(expected); - expect(audit[0].user).equal(auditUser); - expect(audit[0].entity).equal(TestObject.modelName); - expect(Object.values(audit[1].changes).length).equal(1); - expect(audit[1].entity_id.toString()).equal(test2._id.toString()); - expect(audit[1].changes.number.from).equal(test2.number); - expect(audit[1].changes.number.to).equal(expected); - expect(audit[1].user).equal(auditUser); - expect(audit[1].entity).equal(TestObject.modelName); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(2); - expect(items[0].number).equal(expected); - expect(items[1].number).equal(expected); - done(); - }); - }); - }) - .catch(done); - }); - - it("should create audit trail for updateMany", function (done) { - const test2 = new TestObject({ name: "Unlucky", number: 13 }); - const expected = 123; - test2 - .save() - .then(() => TestObject.updateMany({}, { number: expected }, { __user: auditUser })) - .then(() => { - Audit.find({}, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(2); - expect(Object.values(audit[0].changes).length).equal(1); - expect(audit[0].entity_id.toString()).equal(test._id.toString()); - expect(audit[0].changes.number.from).equal(test.number); - expect(audit[0].changes.number.to).equal(expected); - expect(audit[0].user).equal(auditUser); - expect(audit[0].entity).equal(TestObject.modelName); - expect(Object.values(audit[1].changes).length).equal(1); - expect(audit[1].entity_id.toString()).equal(test2._id.toString()); - expect(audit[1].changes.number.from).equal(test2.number); - expect(audit[1].changes.number.to).equal(expected); - expect(audit[1].user).equal(auditUser); - expect(audit[1].entity).equal(TestObject.modelName); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(2); - expect(items[0].number).equal(expected); - expect(items[1].number).equal(expected); - done(); - }); - }); - }) - .catch(done); - }); - - it("should create audit trail for updateMany ignoring multi value", function (done) { - const test2 = new TestObject({ name: "Unlucky", number: 13 }); - const expected = 123; - test2 - .save() - .then(() => TestObject.updateMany({}, { number: expected }, { __user: auditUser, multi: false })) - .then(() => { - Audit.find({}, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(2); - expect(audit[0].entity_id.toString()).equal(test._id.toString()); - expect(audit[1].entity_id.toString()).equal(test2._id.toString()); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(2); - expect(items[0].number).equal(expected); - expect(items[1].number).equal(expected); - done(); - }); - }); - }) - .catch(done); - }); - - it("should create audit trail for update only for first elem if not multi", function (done) { - const test2 = new TestObject({ name: "Unlucky", number: 13 }); - const expected = 123; - test2 - .save() - .then(() => TestObject.update({}, { number: expected }, { __user: auditUser, multi: false })) - .then(() => { - Audit.find({}, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - expect(Object.values(audit[0].changes).length).equal(1); - expect(audit[0].entity_id.toString()).equal(test._id.toString()); - expect(audit[0].changes.number.from).equal(test.number); - expect(audit[0].changes.number.to).equal(expected); - expect(audit[0].user).equal(auditUser); - expect(audit[0].entity).equal(TestObject.modelName); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(2); - expect(items[0].number).equal(expected); - expect(items[1].number).equal(test2.number); - done(); - }); - }); - }) - .catch(done); - }); - - it("should create audit trail for update on instance", function (done) { - const test2 = new TestObject({ name: "Unlucky", number: 13 }); - const expected = 123; - test2 - .save() - .then(() => test.update({ number: expected }, { __user: auditUser })) - .then(() => { - Audit.find({}, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - expect(Object.values(audit[0].changes).length).equal(1); - expect(audit[0].entity_id.toString()).equal(test._id.toString()); - expect(audit[0].changes.number.from).equal(test.number); - expect(audit[0].changes.number.to).equal(expected); - expect(audit[0].user).equal(auditUser); - expect(audit[0].entity).equal(TestObject.modelName); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(2); - expect(items[0].number).equal(expected); - expect(items[1].number).equal(test2.number); - done(); - }); - }); - }) - .catch(done); - }); - - it("should create audit trail on update with $set", function (done) { - const test2 = new TestObject({ name: "Unlucky", number: 13 }); - const expected = 123; - test2 - .save() - .then(() => TestObject.update({}, { $set: { number: expected } }, { __user: auditUser })) - .then(() => { - Audit.find({}, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - expect(audit[0].entity_id.toString()).equal(test._id.toString()); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(2); - expect(items[0].number).equal(expected); - expect(items[1].number).equal(test2.number); - done(); - }); - }); - }) - .catch(done); + it('should create type "Add" for new values', async () => { + test.empty = "test"; + test.__user = auditUser; + await test.save().then(async () => { + const audit = await Audit.find({ entity_id: test._id }) + expect(audit.length).toBe(2); + expect(audit[1].changes.empty.type).toBe(AuditType.Add); }); + }); - it("should create audit trail on update with $set if multi", function (done) { - const test2 = new TestObject({ name: "Unlucky", number: 13 }); - const expected = 123; - test2 - .save() - .then(() => TestObject.update({}, { $set: { number: expected } }, { multi: true, __user: auditUser })) - .then(() => { - Audit.find({}, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(2); - expect(audit[0].entity_id.toString()).equal(test._id.toString()); - expect(audit[1].entity_id.toString()).equal(test2._id.toString()); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(2); - expect(items[0].number).equal(expected); - expect(items[1].number).equal(expected); - done(); - }); - }); - }) - .catch(done); + it('should create type "Delete" when value is being removed', async () => { + test.name = undefined; + test.__user = auditUser; + await test.save().then(async () => { + const audit = await Audit.find({ entity_id: test._id }) + expect(audit.length).toBe(2); + expect(audit[1].changes.name.type).toBe("Delete"); }); + }); - it("should create audit trail on updateOne", function (done) { - const test2 = new TestObject({ name: "Unlucky", number: 13 }); - const expected = 123; - test2 - .save() - .then(() => TestObject.updateOne({ _id: test._id }, { number: expected }, { __user: auditUser })) - .then(() => { - Audit.find({}, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - expect(Object.values(audit[0].changes).length).equal(1); - expect(audit[0].entity_id.toString()).equal(test._id.toString()); - expect(audit[0].changes.number.from).equal(test.number); - expect(audit[0].changes.number.to).equal(expected); - expect(audit[0].user).equal(auditUser); - expect(audit[0].entity).equal(TestObject.modelName); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(2); - expect(items[0].number).equal(expected); - expect(items[1].number).equal(test2.number); - done(); - }); - }); - }) - .catch(done); + it("should not create audit trail if nothing changed", async () => { + test.__user = auditUser; + await test.save().then(async () => { + const audit = await Audit.find({ entity_id: test._id }); + expect(audit.length).toBe(1); + const items = await TestObject.find({}); + expect(items.length).toBe(1); + expect(items[0].number).toBe(test.number); + expect(items[0].name).toBe(test.name); }); + }); - it("should create audit trail on findOneAndUpdate", function (done) { - const test2 = new TestObject({ name: "Unlucky", number: 13 }); - const expected = 123; - test2 - .save() - .then(() => TestObject.findOneAndUpdate({ _id: test._id }, { number: expected }, { __user: auditUser })) - .then(() => { - Audit.find({}, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - expect(Object.values(audit[0].changes).length).equal(1); - expect(audit[0].entity_id.toString()).equal(test._id.toString()); - expect(audit[0].changes.number.from).equal(test.number); - expect(audit[0].changes.number.to).equal(expected); - expect(audit[0].user).equal(auditUser); - expect(audit[0].entity).equal(TestObject.modelName); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(2); - expect(items[0].number).equal(expected); - expect(items[1].number).equal(test2.number); - done(); - }); - }); - }) - .catch(done); + it("should not create audit trail if only change is updatedAt", async () => { + test.__user = auditUser; + test.updatedAt = Date.now(); + await test.save().then(async () => { + const audit = await Audit.find({ entity_id: test._id }); + expect(audit.length).toBe(1); + const items = await TestObject.find({}); + expect(items.length).toBe(1); + expect(items[0].number).toBe(test.number); + expect(items[0].name).toBe(test.name); }); + }); - it("should create audit trail on replaceOne", function (done) { + describe("should create audit trail for update", () => { + afterEach(cleanup); + const expected = 123; + const expector = async (updater) => { const test2 = new TestObject({ name: "Unlucky", number: 13 }); - const expected = 123; - const replace = Object.assign({}, test._doc); - replace.number = expected; - replace.__v += 1; - test2 + await test2 .save() - .then(() => TestObject.replaceOne({ _id: test._id }, replace, { __user: auditUser })) - .then(() => { - Audit.find({}, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - expect(Object.values(audit[0].changes).length).equal(1); - expect(audit[0].entity_id.toString()).equal(test._id.toString()); - expect(audit[0].changes.number.from).equal(test.number); - expect(audit[0].changes.number.to).equal(expected); - expect(audit[0].user).equal(auditUser); - expect(audit[0].entity).equal(TestObject.modelName); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(2); - expect(items[0].number).equal(expected); - expect(items[0].__v).equal(1); - expect(items[1].number).equal(test2.number); - done(); - }); - }); + .then(updater) + .then(async () => { + const audit = await Audit.find({}); + expect(audit.length).toBe(4); + expect(Object.values(audit[2].changes).length).toBe(1); + expect([test.number, test2.number]).toContain(audit[2].changes.number.from); + expect(audit[2].changes.number.to).toBe(expected); + expect(audit[2].user).toBe(auditUser); + expect(audit[2].entity).toBe(TestObject.modelName); + expect(Object.values(audit[3].changes).length).toBe(1); + expect(audit[3].changes.number.to).toBe(expected); + expect(audit[3].user).toBe(auditUser); + expect(audit[3].entity).toBe(TestObject.modelName); + const items = await TestObject.find({}); + expect(items.length).toBe(2); + expect(items[0].number).toBe(expected); + expect(items[1].number).toBe(expected); }) - .catch(done); - }); - - const expectDeleteValues = (entry) => { - expect(Object.values(entry.changes).length).equal(3); - expect(entry.entity_id.toString()).equal(test._id.toString()); - expect(entry.changes.date.type).equal("Delete"); - expect(entry.changes.name.type).equal("Delete"); - expect(entry.changes.number.type).equal("Delete"); - expect(entry.changes.date.from).equal(test.date.toISOString()); - expect(entry.changes.name.from).equal(test.name); - expect(entry.changes.number.from).equal(test.number); - expect(entry.user).equal(auditUser); - expect(entry.entity).equal(TestObject.modelName); - }; - - it("should create audit trail on remove", function (done) { - test - .remove({ __user: auditUser }) - .then(() => - Audit.find({}, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - expectDeleteValues(audit[0]); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(0); - done(); - }); - }) - ) - .catch(done); - }); - - it("should create audit trail on findOneAndDelete", function (done) { - TestObject.findOneAndDelete({ _id: test._id }, { __user: auditUser }) - .then(() => - Audit.find({}, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - expectDeleteValues(audit[0]); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(0); - done(); - }); - }) - ) - .catch(done); - }); + } + it("on class", async () => { + await expector(() => TestObject.update({}, { number: expected }, { __user: auditUser, multi: true })) + }); + it("for updateMany", async () => { + await expector(() => TestObject.updateMany({}, { number: expected }, { __user: auditUser })) + }); + }) + + it("should create audit trail for update only for first elem if not multi", async () => { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + const expected = 123; + await test2 + .save() + .then(() => TestObject.update({}, { number: expected }, { __user: auditUser, multi: false })) + .then(async () => { + const audit = await Audit.find({}); + expect(audit.length).toBe(3); + expect(audit[0].entity_id.toString()).toBe(test._id.toString()); + expect(audit[1].entity_id.toString()).toBe(test2._id.toString()); + expect(audit[2].entity_id.toString()).toBe(test._id.toString()); + const items = await TestObject.find({}); + expect(items.length).toBe(2); + expect(items[0].number).toBe(expected); + expect(items[1].number).toBe(test2.number); + }) + }); - it("should create audit trail on findOneAndDelete only for one item", function (done) { - const test2 = new TestObject({ name: "Unlucky", number: 13 }); - test2 - .save() - .then(() => TestObject.findOneAndDelete({ _id: test._id }, { __user: auditUser })) - .then(() => - Audit.find({}, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - expectDeleteValues(audit[0]); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(1); - expect(items[0]._id.toString()).equal(test2._id.toString()); - done(); - }); - }) - ) - .catch(done); - }); + it("should create audit trail for update on instance", async () => { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + const expected = 123; + await test2 + .save() + .then(() => test.update({ number: expected }, { __user: auditUser })) + .then(async () => { + const audit = await Audit.find({}); + expect(audit.length).toBe(3); + expect(Object.values(audit[2].changes).length).toBe(1); + expect(audit[2].entity_id.toString()).toBe(test._id.toString()); + expect(audit[2].changes.number.from).toBe(test.number); + expect(audit[2].changes.number.to).toBe(expected); + expect(audit[2].user).toBe(auditUser); + expect(audit[2].entity).toBe(TestObject.modelName); + const items = await TestObject.find({}); + expect(items.length).toBe(2); + expect(items[0].number).toBe(expected); + expect(items[1].number).toBe(test2.number); + }) + }); - it("should create audit trail on findOneAndRemove", function (done) { - TestObject.findOneAndRemove({ _id: test._id }, { __user: auditUser }) - .then(() => - Audit.find({}, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - expectDeleteValues(audit[0]); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(0); - done(); - }); - }) - ) - .catch(done); - }); + it("should create audit trail on update with $set", async () => { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + const expected = 123; + await test2 + .save() + .then(() => TestObject.update({}, { $set: { number: expected } }, { __user: auditUser })) + .then(async () => { + const audit = await Audit.find({}); + expect(audit.length).toBe(3); + expect(audit[2].entity_id.toString()).toBe(test._id.toString()); + const items = await TestObject.find({}); + expect(items.length).toBe(2); + expect(items[0].number).toBe(expected); + expect(items[1].number).toBe(test2.number); + }) + }); - it("should create audit trail on findOneAndRemove only for one item", function (done) { - const test2 = new TestObject({ name: "Unlucky", number: 13 }); - test2 - .save() - .then(() => TestObject.findOneAndRemove({ _id: test._id }, { __user: auditUser })) - .then(() => - Audit.find({}, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - expectDeleteValues(audit[0]); - TestObject.find({}, function (err, items) { - expect(err).to.null(); - expect(items.length).equal(1); - expect(items[0]._id.toString()).equal(test2._id.toString()); - done(); - }); - }) - ) - .catch(done); - }); + it("should create audit trail on update with $set if multi", async () => { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + const expected = 123; + await test2 + .save() + .then(() => TestObject.update({}, { $set: { number: expected } }, { multi: true, __user: auditUser })) + .then(async () => { + const audit = await Audit.find({}); + expect(audit.length).toBe(4); + const items = await TestObject.find({}); + expect(items.length).toBe(2); + expect(items[0].number).toBe(expected); + expect(items[1].number).toBe(expected); + }) }); - describe("plugin: user callback", function () { - it("should use the user callback if provided", function (done) { - const expectedUser = "User from function"; - plugin.getUser = () => expectedUser; + it("should create audit trail on updateOne", async () => { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + const expected = 123; + await test2 + .save() + .then(() => TestObject.updateOne({ _id: test._id }, { number: expected }, { __user: auditUser })) + .then(async () => { + const audit = await Audit.find({}); + expect(audit.length).toBe(3); + expect(Object.values(audit[2].changes).length).toBe(1); + expect(audit[2].entity_id.toString()).toBe(test._id.toString()); + expect(audit[2].changes.number.from).toBe(test.number); + expect(audit[2].changes.number.to).toBe(expected); + expect(audit[2].user).toBe(auditUser); + expect(audit[2].entity).toBe(TestObject.modelName); + const items = await TestObject.find({}); + expect(items.length).toBe(2); + expect(items[0].number).toBe(expected); + expect(items[1].number).toBe(test2.number); + }) + }); - const test = new TestObject({ name: "Lucky", number: 7 }); - test - .save() - .then((test) => { - test.name = "Unlucky"; - test.number = 13; - test.save().then(() => { - Audit.find({ entity_id: test._id }, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - expect(audit[0].user).equal(expectedUser); - done(); - }); - }); - }) - .catch(done); - }); + it("should create audit trail on findOneAndUpdate", async () => { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + const expected = 123; + await test2 + .save() + .then(() => TestObject.findOneAndUpdate({ _id: test._id }, { number: expected }, { __user: auditUser })) + .then(async () => { + const audit = await Audit.find({}); + expect(audit.length).toBe(3); + expect(Object.values(audit[2].changes).length).toBe(1); + expect(audit[2].entity_id.toString()).toBe(test._id.toString()); + expect(audit[2].changes.number.from).toBe(test.number); + expect(audit[2].changes.number.to).toBe(expected); + expect(audit[2].user).toBe(auditUser); + expect(audit[2].entity).toBe(TestObject.modelName); + const items = await TestObject.find({}); + expect(items.length).toBe(2); + expect(items[0].number).toBe(expected); + expect(items[1].number).toBe(test2.number); + }) + }); - it("should use the user if provided", function (done) { - const expectedUser = "User from parameter"; - plugin.getUser = () => expectedUser; + it("should create audit trail on replaceOne", async () => { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + const expected = 123; + const replace = Object.assign({}, test._doc); + replace.number = expected; + replace.__v += 1; + await test2 + .save() + .then(() => TestObject.replaceOne({ _id: test._id }, replace, { __user: auditUser })) + .then(async () => { + const audit = await Audit.find({}); + expect(audit.length).toBe(3); + expect(Object.values(audit[2].changes).length).toBe(1); + expect(audit[2].entity_id.toString()).toBe(test._id.toString()); + expect(audit[2].changes.number.from).toBe(test.number); + expect(audit[2].changes.number.to).toBe(expected); + expect(audit[2].user).toBe(auditUser); + expect(audit[2].entity).toBe(TestObject.modelName); + const items = await TestObject.find({}); + expect(items.length).toBe(2); + expect(items[0].number).toBe(expected); + expect(items[0].__v).toBe(1); + expect(items[1].number).toBe(test2.number); + }) + }); - const test = new TestObject({ name: "Lucky", number: 7 }); - test - .save() - .then((test) => { - test.name = "Unlucky"; - test.number = 13; - test.__user = ""; - test.save().then(() => { - Audit.find({ entity_id: test._id }, function (err, audit) { - expect(err).to.null(); - expect(audit.length).equal(1); - expect(audit[0].user).equal(expectedUser); - done(); - }); - }); - }) - .catch(done); - }); + const expectDeleteValues = (entry) => { + expect(Object.values(entry.changes).length).toBe(4); + expect(entry.entity_id.toString()).toBe(test._id.toString()); + expect(entry.changes.date.type).toBe(AuditType.Delete); + expect(entry.changes.name.type).toBe(AuditType.Delete); + expect(entry.changes.number.type).toBe(AuditType.Delete); + expect(entry.changes.date.from).toBe(test.date.toISOString()); + expect(entry.changes.name.from).toBe(test.name); + expect(entry.changes.number.from).toBe(test.number); + expect(entry.user).toBe(auditUser); + expect(entry.entity).toBe(TestObject.modelName); + }; + + it("should create audit trail on remove", async () => { + await test + .remove({ __user: auditUser }) + .then(async () => { + const audit = await Audit.find({}) + expect(audit.length).toBe(2); + expectDeleteValues(audit[1]); + const items = await TestObject.find({}); + expect(items.length).toBe(0); + }); + }); - it("should throw error if no user is provided", function (done) { - plugin.getUser = () => undefined; + it("should create audit trail on findOneAndRemove only for one item", async () => { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + await test2 + .save() + .then(() => TestObject.findOneAndRemove({ _id: test._id }, { __user: auditUser })) + .then(async () => { + const audit = await Audit.find({}) + expect(audit.length).toBe(3); + expectDeleteValues(audit[2]); + const items = await TestObject.find({}); + expect(items.length).toBe(1); + expect(items[0]._id.toString()).toBe(test2._id.toString()); + }) + }); - const test = new TestObject({ name: "Lucky", number: 7 }); - test - .save() - .then((test) => { - test.name = "Unlucky"; - test.number = 13; - test - .save() - .then((_) => done(new Error("should not have succeeded!"))) - .catch((err) => { - expect(err.message).to.be.equal("User missing in audit log!"); - done(); - }); - }) - .catch(done); - }); + it("should create audit trail on findOneAndRemove only for one item", async () => { + const test2 = new TestObject({ name: "Unlucky", number: 13 }); + await test2 + .save() + .then(() => TestObject.findOneAndRemove({ _id: test._id }, { __user: auditUser })) + .then(async () => { + const audit = await Audit.find({}) + expect(audit.length).toBe(3); + expectDeleteValues(audit[2]); + const items = await TestObject.find({}); + expect(items.length).toBe(1); + expect(items[0]._id.toString()).toBe(test2._id.toString()); + }) }); }); diff --git a/plugins/mongoose-audit/types/index.d.ts b/plugins/mongoose-audit/types/index.d.ts index fffbed2..0b0c7d1 100644 --- a/plugins/mongoose-audit/types/index.d.ts +++ b/plugins/mongoose-audit/types/index.d.ts @@ -5,8 +5,7 @@ type auditType = "add" | "edit" | "delete"; interface Audit { entity_id: any; entity: string; - collection: string; - changes: any; + changes: { [key: string]: any }; user: any; created_at: string; } @@ -20,6 +19,8 @@ interface PluginOptions { exclude?: string[]; /** Called before persisting the audit is saved. Use this to use your own audit model instead of the default one. */ onAudit?: (audit: Audit) => Promise; + /** By default audit logs are persisted asynchronously in the background. Change this to false if you want it to be synchronous" */ + background?: boolean; } /** Registers the plugin with the provided options. */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a12450d..c2e4bcd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -176,7 +176,7 @@ importers: version: 7.20.7 '@sliit-foss/functions': specifier: 2.7.0 - version: link:../../packages/functions + version: 2.7.0 devDependencies: '@babel/core': specifier: 7.21.5 @@ -194,6 +194,21 @@ importers: specifier: ^2.27.5 version: 2.27.5(eslint@8.38.0) + plugins/mongoose-audit: + dependencies: + deep-diff: + specifier: 1.0.2 + version: 1.0.2 + dot-object: + specifier: 2.1.4 + version: 2.1.4 + lodash: + specifier: 4.17.10 + version: 4.17.10 + mongoose: + specifier: '>=5' + version: 8.2.1 + packages: /@actions/exec@1.1.1: @@ -2876,6 +2891,12 @@ packages: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 + /@mongodb-js/saslprep@1.1.5: + resolution: {integrity: sha512-XLNOMH66KhJzUJNwT/qlMnS4WsNDWD5ASdyaSH3EtK+F4r/CFGa3jT4GNi4mfOitGvWXtdLgQJkQjxSVrio+jA==} + dependencies: + sparse-bitfield: 3.0.3 + dev: false + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2953,6 +2974,23 @@ packages: '@sinonjs/commons': 2.0.0 dev: false + /@sliit-foss/functions@2.7.0: + resolution: {integrity: sha512-xL2XMTQQgDnObC3NSgzt9HPmd5GlJFVHm9SLxi/2rFJoYWzYOwLdEbw5Ssyn0WSL/N+PxFYG49DUDESt9aYyzQ==} + dependencies: + '@sliit-foss/module-logger': 1.3.0 + chalk: 4.1.2 + express-http-context: 1.2.4 + dev: false + + /@sliit-foss/module-logger@1.3.0: + resolution: {integrity: sha512-xthp1pxXsR9/ZlB1pXlklMl1BrhfiJoNp1EqPM3vvbPf1NRVAGNUnx9V7lWu2IHGwiwvAs1BBxmIrFmMZ3esDQ==} + dependencies: + chalk: 4.1.2 + express-http-context: 1.2.4 + winston: 3.8.2 + winston-daily-rotate-file: 4.7.1(winston@3.8.2) + dev: false + /@tsconfig/node10@1.0.9: resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} @@ -3126,6 +3164,16 @@ packages: resolution: {integrity: sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==} dev: false + /@types/webidl-conversions@7.0.3: + resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} + dev: false + + /@types/whatwg-url@11.0.4: + resolution: {integrity: sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==} + dependencies: + '@types/webidl-conversions': 7.0.3 + dev: false + /@types/yargs-parser@21.0.0: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} dev: false @@ -3546,6 +3594,11 @@ packages: node-int64: 0.4.0 dev: false + /bson@6.4.0: + resolution: {integrity: sha512-6/gSSEdbkuFlSb+ufj5jUSU4+wo8xQOwm2bDSqwmxiPE17JTpsP63eAwoN8iF8Oy4gJYj+PAL3zdRCTdaw5Y1g==} + engines: {node: '>=16.20.1'} + dev: false + /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} dev: false @@ -3807,6 +3860,11 @@ packages: engines: {node: '>=16'} dev: false + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + dev: false + /compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} dependencies: @@ -3988,6 +4046,10 @@ packages: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} dev: false + /deep-diff@1.0.2: + resolution: {integrity: sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==} + dev: false + /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true @@ -4096,6 +4158,14 @@ packages: tslib: 2.5.0 dev: false + /dot-object@2.1.4: + resolution: {integrity: sha512-7FXnyyCLFawNYJ+NhkqyP9Wd2yzuo+7n9pGiYpkmXCTYa8Ci2U0eUNDVg5OuO5Pm6aFXI2SWN8/N/w7SJWu1WA==} + hasBin: true + dependencies: + commander: 4.1.1 + glob: 7.2.3 + dev: false + /dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} @@ -6071,6 +6141,11 @@ packages: setimmediate: 1.0.5 dev: false + /kareem@2.5.1: + resolution: {integrity: sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==} + engines: {node: '>=12.0.0'} + dev: false + /kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -6180,6 +6255,10 @@ packages: resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} dev: true + /lodash@4.17.10: + resolution: {integrity: sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==} + dev: false + /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -6278,6 +6357,10 @@ packages: engines: {node: '>= 0.6'} dev: true + /memory-pager@1.5.0: + resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + dev: false + /meow@8.1.2: resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} engines: {node: '>=10'} @@ -6378,6 +6461,81 @@ packages: resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} dev: false + /mongodb-connection-string-url@3.0.0: + resolution: {integrity: sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==} + dependencies: + '@types/whatwg-url': 11.0.4 + whatwg-url: 13.0.0 + dev: false + + /mongodb@6.3.0: + resolution: {integrity: sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==} + engines: {node: '>=16.20.1'} + peerDependencies: + '@aws-sdk/credential-providers': ^3.188.0 + '@mongodb-js/zstd': ^1.1.0 + gcp-metadata: ^5.2.0 + kerberos: ^2.0.1 + mongodb-client-encryption: '>=6.0.0 <7' + snappy: ^7.2.2 + socks: ^2.7.1 + peerDependenciesMeta: + '@aws-sdk/credential-providers': + optional: true + '@mongodb-js/zstd': + optional: true + gcp-metadata: + optional: true + kerberos: + optional: true + mongodb-client-encryption: + optional: true + snappy: + optional: true + socks: + optional: true + dependencies: + '@mongodb-js/saslprep': 1.1.5 + bson: 6.4.0 + mongodb-connection-string-url: 3.0.0 + dev: false + + /mongoose@8.2.1: + resolution: {integrity: sha512-UgZZbXSJH0pdU936qj3FyVI+sBsMoGowFnL5R/RYrA50ayn6+ZYdVr8ehsRgNxRcMYwoNld5XzHIfkFRJTePEw==} + engines: {node: '>=16.20.1'} + dependencies: + bson: 6.4.0 + kareem: 2.5.1 + mongodb: 6.3.0 + mpath: 0.9.0 + mquery: 5.0.0 + ms: 2.1.3 + sift: 16.0.1 + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - gcp-metadata + - kerberos + - mongodb-client-encryption + - snappy + - socks + - supports-color + dev: false + + /mpath@0.9.0: + resolution: {integrity: sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==} + engines: {node: '>=4.0.0'} + dev: false + + /mquery@5.0.0: + resolution: {integrity: sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==} + engines: {node: '>=14.0.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} dev: true @@ -6896,7 +7054,6 @@ packages: /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} - dev: true /pure-rand@6.0.1: resolution: {integrity: sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg==} @@ -7355,6 +7512,10 @@ packages: object-inspect: 1.12.3 dev: true + /sift@16.0.1: + resolution: {integrity: sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==} + dev: false + /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -7404,6 +7565,12 @@ packages: engines: {node: '>=0.10.0'} dev: false + /sparse-bitfield@3.0.3: + resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} + dependencies: + memory-pager: 1.5.0 + dev: false + /spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} dependencies: @@ -7671,6 +7838,13 @@ packages: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false + /tr46@4.1.1: + resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==} + engines: {node: '>=14'} + dependencies: + punycode: 2.3.0 + dev: false + /trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'} @@ -8000,6 +8174,11 @@ packages: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false + /webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + dev: false + /websocket-driver@0.7.4: resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} engines: {node: '>=0.8.0'} @@ -8018,6 +8197,14 @@ packages: resolution: {integrity: sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==} dev: false + /whatwg-url@13.0.0: + resolution: {integrity: sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==} + engines: {node: '>=16'} + dependencies: + tr46: 4.1.1 + webidl-conversions: 7.0.0 + dev: false + /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: From e72db8413bf09896fe194471904a590bad560359 Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sun, 10 Mar 2024 18:00:06 +0000 Subject: [PATCH 03/11] Feat(mongoose-audit): setup mongo server before tests --- plugins/mongoose-audit/test/index.test.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/mongoose-audit/test/index.test.js b/plugins/mongoose-audit/test/index.test.js index ec0308f..5c53f51 100644 --- a/plugins/mongoose-audit/test/index.test.js +++ b/plugins/mongoose-audit/test/index.test.js @@ -1,8 +1,16 @@ import { default as mongoose } from "mongoose"; +import { exec } from "child_process"; +import { promisify } from "util"; import { plugin, Audit } from "../src"; import { AuditType } from "../src/constants"; +jest.setTimeout(120000) + +const execute = promisify(exec) + const connectToDatabase = async () => { + await execute("docker run -d -p 27017:27017 mongo:5.0") + await new Promise((resolve) => setTimeout(resolve, 3000)) await mongoose.connect("mongodb://localhost:27017/test") }; From 305c54fdf0ee70220ea50766014107881709b8e8 Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sun, 10 Mar 2024 18:08:19 +0000 Subject: [PATCH 04/11] Fix(mongoose-audit): added mongoose as a dev dep --- packages/templates/src/index.js | 0 plugins/mongoose-audit/package.json | 3 + pnpm-lock.yaml | 263 +++++++++++++++++----------- 3 files changed, 159 insertions(+), 107 deletions(-) mode change 100644 => 100755 packages/templates/src/index.js diff --git a/packages/templates/src/index.js b/packages/templates/src/index.js old mode 100644 new mode 100755 diff --git a/plugins/mongoose-audit/package.json b/plugins/mongoose-audit/package.json index 2e0be17..fd69980 100644 --- a/plugins/mongoose-audit/package.json +++ b/plugins/mongoose-audit/package.json @@ -20,6 +20,9 @@ "peerDependencies": { "mongoose": ">=5" }, + "devDependencies": { + "mongoose": "^5" + }, "author": "SLIIT FOSS", "license": "MIT", "repository": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c2e4bcd..8400b7e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -205,9 +205,10 @@ importers: lodash: specifier: 4.17.10 version: 4.17.10 + devDependencies: mongoose: - specifier: '>=5' - version: 8.2.1 + specifier: ^5 + version: 5.13.22 packages: @@ -2891,12 +2892,6 @@ packages: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 - /@mongodb-js/saslprep@1.1.5: - resolution: {integrity: sha512-XLNOMH66KhJzUJNwT/qlMnS4WsNDWD5ASdyaSH3EtK+F4r/CFGa3jT4GNi4mfOitGvWXtdLgQJkQjxSVrio+jA==} - dependencies: - sparse-bitfield: 3.0.3 - dev: false - /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -3039,6 +3034,12 @@ packages: '@types/node': 18.15.11 dev: false + /@types/bson@4.0.5: + resolution: {integrity: sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==} + dependencies: + '@types/node': 18.15.11 + dev: true + /@types/cls-hooked@4.3.3: resolution: {integrity: sha512-gNstDTb/ty5h6gJd6YpSPgsLX9LmRpaKJqGFp7MRlYxhwp4vXXKlJ9+bt1TZ9KbVNXE+Mbxy2AYXcpY21DDtJw==} dependencies: @@ -3124,6 +3125,13 @@ packages: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true + /@types/mongodb@3.6.20: + resolution: {integrity: sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==} + dependencies: + '@types/bson': 4.0.5 + '@types/node': 18.15.11 + dev: true + /@types/node@18.15.11: resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==} @@ -3164,16 +3172,6 @@ packages: resolution: {integrity: sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==} dev: false - /@types/webidl-conversions@7.0.3: - resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} - dev: false - - /@types/whatwg-url@11.0.4: - resolution: {integrity: sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==} - dependencies: - '@types/webidl-conversions': 7.0.3 - dev: false - /@types/yargs-parser@21.0.0: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} dev: false @@ -3515,6 +3513,13 @@ packages: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} + /bl@2.2.1: + resolution: {integrity: sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==} + dependencies: + readable-stream: 2.3.8 + safe-buffer: 5.2.1 + dev: true + /bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} dependencies: @@ -3531,6 +3536,10 @@ packages: readable-stream: 3.6.2 dev: false + /bluebird@3.5.1: + resolution: {integrity: sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==} + dev: true + /body-parser@1.18.2: resolution: {integrity: sha512-XIXhPptoLGNcvFyyOzjNXCjDYIbYj4iuXO0VU9lM0f3kYdG0ar5yg7C+pIc3OyoTlZXDu5ObpLTmS2Cgp89oDg==} engines: {node: '>= 0.8'} @@ -3594,10 +3603,10 @@ packages: node-int64: 0.4.0 dev: false - /bson@6.4.0: - resolution: {integrity: sha512-6/gSSEdbkuFlSb+ufj5jUSU4+wo8xQOwm2bDSqwmxiPE17JTpsP63eAwoN8iF8Oy4gJYj+PAL3zdRCTdaw5Y1g==} - engines: {node: '>=16.20.1'} - dev: false + /bson@1.1.6: + resolution: {integrity: sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==} + engines: {node: '>=0.6.19'} + dev: true /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -3952,7 +3961,6 @@ packages: /core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - dev: false /cosmiconfig-typescript-loader@4.3.0(@types/node@18.15.11)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@5.0.4): resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} @@ -4006,6 +4014,17 @@ packages: ms: 2.0.0 dev: true + /debug@3.1.0: + resolution: {integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.0.0 + dev: true + /debug@3.2.7(supports-color@5.5.0): resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -4092,6 +4111,11 @@ packages: engines: {node: '>=0.4.0'} dev: false + /denque@1.5.1: + resolution: {integrity: sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==} + engines: {node: '>=0.10'} + dev: true + /depd@1.1.1: resolution: {integrity: sha512-Jlk9xvkTDGXwZiIDyoM7+3AsuvJVoyOpRupvEVy9nX3YO3/ieZxhlgh8GpLNZ8AY7HjO6y2YwpMSh1ejhu3uIw==} engines: {node: '>= 0.6'} @@ -5589,7 +5613,6 @@ packages: /isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - dev: false /isbinaryfile@4.0.10: resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} @@ -6141,10 +6164,9 @@ packages: setimmediate: 1.0.5 dev: false - /kareem@2.5.1: - resolution: {integrity: sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==} - engines: {node: '>=12.0.0'} - dev: false + /kareem@2.3.2: + resolution: {integrity: sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==} + dev: true /kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} @@ -6359,7 +6381,9 @@ packages: /memory-pager@1.5.0: resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} - dev: false + requiresBuild: true + dev: true + optional: true /meow@8.1.2: resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} @@ -6461,80 +6485,92 @@ packages: resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} dev: false - /mongodb-connection-string-url@3.0.0: - resolution: {integrity: sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==} - dependencies: - '@types/whatwg-url': 11.0.4 - whatwg-url: 13.0.0 - dev: false - - /mongodb@6.3.0: - resolution: {integrity: sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==} - engines: {node: '>=16.20.1'} + /mongodb@3.7.4: + resolution: {integrity: sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==} + engines: {node: '>=4'} peerDependencies: - '@aws-sdk/credential-providers': ^3.188.0 - '@mongodb-js/zstd': ^1.1.0 - gcp-metadata: ^5.2.0 - kerberos: ^2.0.1 - mongodb-client-encryption: '>=6.0.0 <7' - snappy: ^7.2.2 - socks: ^2.7.1 + aws4: '*' + bson-ext: '*' + kerberos: '*' + mongodb-client-encryption: '*' + mongodb-extjson: '*' + snappy: '*' peerDependenciesMeta: - '@aws-sdk/credential-providers': - optional: true - '@mongodb-js/zstd': + aws4: optional: true - gcp-metadata: + bson-ext: optional: true kerberos: optional: true mongodb-client-encryption: optional: true - snappy: + mongodb-extjson: optional: true - socks: + snappy: optional: true dependencies: - '@mongodb-js/saslprep': 1.1.5 - bson: 6.4.0 - mongodb-connection-string-url: 3.0.0 - dev: false + bl: 2.2.1 + bson: 1.1.6 + denque: 1.5.1 + optional-require: 1.1.8 + safe-buffer: 5.2.1 + optionalDependencies: + saslprep: 1.0.3 + dev: true + + /mongoose-legacy-pluralize@1.0.2(mongoose@5.13.22): + resolution: {integrity: sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==} + peerDependencies: + mongoose: '*' + dependencies: + mongoose: 5.13.22 + dev: true - /mongoose@8.2.1: - resolution: {integrity: sha512-UgZZbXSJH0pdU936qj3FyVI+sBsMoGowFnL5R/RYrA50ayn6+ZYdVr8ehsRgNxRcMYwoNld5XzHIfkFRJTePEw==} - engines: {node: '>=16.20.1'} + /mongoose@5.13.22: + resolution: {integrity: sha512-p51k/c4X/MfqeQ3I1ranlDiggLzNumZrTDD9CeezHwZxt2/btf+YZD7MCe07RAY2NgFYVMayq6jMamw02Jmf9w==} + engines: {node: '>=4.0.0'} dependencies: - bson: 6.4.0 - kareem: 2.5.1 - mongodb: 6.3.0 - mpath: 0.9.0 - mquery: 5.0.0 - ms: 2.1.3 - sift: 16.0.1 + '@types/bson': 4.0.5 + '@types/mongodb': 3.6.20 + bson: 1.1.6 + kareem: 2.3.2 + mongodb: 3.7.4 + mongoose-legacy-pluralize: 1.0.2(mongoose@5.13.22) + mpath: 0.8.4 + mquery: 3.2.5 + ms: 2.1.2 + optional-require: 1.0.3 + regexp-clone: 1.0.0 + safe-buffer: 5.2.1 + sift: 13.5.2 + sliced: 1.0.1 transitivePeerDependencies: - - '@aws-sdk/credential-providers' - - '@mongodb-js/zstd' - - gcp-metadata + - aws4 + - bson-ext - kerberos - mongodb-client-encryption + - mongodb-extjson - snappy - - socks - supports-color - dev: false + dev: true - /mpath@0.9.0: - resolution: {integrity: sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==} + /mpath@0.8.4: + resolution: {integrity: sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==} engines: {node: '>=4.0.0'} - dev: false + dev: true - /mquery@5.0.0: - resolution: {integrity: sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==} - engines: {node: '>=14.0.0'} + /mquery@3.2.5: + resolution: {integrity: sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==} + engines: {node: '>=4.0.0'} dependencies: - debug: 4.3.4 + bluebird: 3.5.1 + debug: 3.1.0 + regexp-clone: 1.0.0 + safe-buffer: 5.1.2 + sliced: 1.0.1 transitivePeerDependencies: - supports-color - dev: false + dev: true /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -6748,6 +6784,18 @@ packages: dependencies: mimic-fn: 2.1.0 + /optional-require@1.0.3: + resolution: {integrity: sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==} + engines: {node: '>=4'} + dev: true + + /optional-require@1.1.8: + resolution: {integrity: sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==} + engines: {node: '>=4'} + dependencies: + require-at: 1.0.6 + dev: true + /optionator@0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} engines: {node: '>= 0.8.0'} @@ -6982,7 +7030,6 @@ packages: /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - dev: false /promise-polyfill@8.1.3: resolution: {integrity: sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==} @@ -7054,6 +7101,7 @@ packages: /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} + dev: true /pure-rand@6.0.1: resolution: {integrity: sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg==} @@ -7130,7 +7178,6 @@ packages: safe-buffer: 5.1.2 string_decoder: 1.1.1 util-deprecate: 1.0.2 - dev: false /readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} @@ -7189,6 +7236,10 @@ packages: '@babel/runtime': 7.21.0 dev: false + /regexp-clone@1.0.0: + resolution: {integrity: sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==} + dev: true + /regexp.prototype.flags@1.4.3: resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} engines: {node: '>= 0.4'} @@ -7226,6 +7277,11 @@ packages: resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==} dev: false + /require-at@1.0.6: + resolution: {integrity: sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==} + engines: {node: '>=4'} + dev: true + /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -7334,7 +7390,6 @@ packages: /safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - dev: false /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -7356,6 +7411,15 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: false + /saslprep@1.0.3: + resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==} + engines: {node: '>=6'} + requiresBuild: true + dependencies: + sparse-bitfield: 3.0.3 + dev: true + optional: true + /selenium-webdriver@4.0.0-rc-1: resolution: {integrity: sha512-bcrwFPRax8fifRP60p7xkWDGSJJoMkPAzufMlk5K2NyLPht/YZzR2WcIk1+3gR8VOCLlst1P2PI+MXACaFzpIw==} engines: {node: '>= 10.15.0'} @@ -7512,9 +7576,9 @@ packages: object-inspect: 1.12.3 dev: true - /sift@16.0.1: - resolution: {integrity: sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==} - dev: false + /sift@13.5.2: + resolution: {integrity: sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==} + dev: true /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -7546,6 +7610,10 @@ packages: engines: {node: '>=12'} dev: false + /sliced@1.0.1: + resolution: {integrity: sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==} + dev: true + /snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} dependencies: @@ -7567,9 +7635,11 @@ packages: /sparse-bitfield@3.0.3: resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} + requiresBuild: true dependencies: memory-pager: 1.5.0 - dev: false + dev: true + optional: true /spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -7685,7 +7755,6 @@ packages: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} dependencies: safe-buffer: 5.1.2 - dev: false /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -7838,13 +7907,6 @@ packages: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false - /tr46@4.1.1: - resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==} - engines: {node: '>=14'} - dependencies: - punycode: 2.3.0 - dev: false - /trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'} @@ -8174,11 +8236,6 @@ packages: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false - /webidl-conversions@7.0.0: - resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} - engines: {node: '>=12'} - dev: false - /websocket-driver@0.7.4: resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} engines: {node: '>=0.8.0'} @@ -8197,14 +8254,6 @@ packages: resolution: {integrity: sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==} dev: false - /whatwg-url@13.0.0: - resolution: {integrity: sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==} - engines: {node: '>=16'} - dependencies: - tr46: 4.1.1 - webidl-conversions: 7.0.0 - dev: false - /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: From 86200f60d0d6b5e16a2839ea001a17423c7a5441 Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sun, 10 Mar 2024 18:24:29 +0000 Subject: [PATCH 05/11] Fix(mongoose-audit): added query clone --- plugins/mongoose-audit/src/plugin.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/mongoose-audit/src/plugin.js b/plugins/mongoose-audit/src/plugin.js index 202c0b7..3cbb48a 100644 --- a/plugins/mongoose-audit/src/plugin.js +++ b/plugins/mongoose-audit/src/plugin.js @@ -84,6 +84,7 @@ const addAuditLog = async (currentObject) => { const addUpdate = async (query, multi) => { const updated = flattenObject(query._update); let counter = 0; + if (query.clone) query = query.clone() const originalDocs = await query.find(query._conditions).lean(true) const promises = originalDocs.map((original) => { if (!multi && counter++) { @@ -110,6 +111,7 @@ const addDelete = async (currentObject, options) => { }; const addFindAndDelete = async (query) => { + if (query.clone) query = query.clone() const originalDocs = await query.find().lean(true) const promises = originalDocs.map((original) => { return addDelete(original, query.options) From d0aaf6fbaf92fc77021632b21c593802544b15cc Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sun, 10 Mar 2024 18:33:05 +0000 Subject: [PATCH 06/11] Feat(mongoose-audit): added include key option --- plugins/mongoose-audit/src/plugin.js | 3 +-- plugins/mongoose-audit/types/index.d.ts | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/mongoose-audit/src/plugin.js b/plugins/mongoose-audit/src/plugin.js index 3cbb48a..d78d0f8 100644 --- a/plugins/mongoose-audit/src/plugin.js +++ b/plugins/mongoose-audit/src/plugin.js @@ -8,8 +8,6 @@ import { extractArray, filter, flattenObject } from "./utils"; const options = { getUser: () => undefined, types: [AuditType.Add, AuditType.Edit, AuditType.delete], - exclude: [], - onAudit: undefined, background: true }; @@ -21,6 +19,7 @@ const addAuditLogObject = (currentObject, original) => { changes = changes.reduce((obj, change) => { const key = change.path.join("."); if (options.exclude?.includes(key)) return obj; + if (options.include && !options.include?.includes(key)) return obj; if (change.kind === "A") { if (!obj[key] && change.path.length) { const data = { diff --git a/plugins/mongoose-audit/types/index.d.ts b/plugins/mongoose-audit/types/index.d.ts index 0b0c7d1..a3e4f6b 100644 --- a/plugins/mongoose-audit/types/index.d.ts +++ b/plugins/mongoose-audit/types/index.d.ts @@ -15,8 +15,10 @@ interface PluginOptions { getUser?: () => any; /** The types of audit to record. */ types?: auditType[]; - /** The fields to exclude from the audit. */ + /** The fields to exclude from the audit. Cannot be used along with include. */ exclude?: string[]; + /** The fields to consider for the audit. Cannot be used along with exclude. */ + include?: string[]; /** Called before persisting the audit is saved. Use this to use your own audit model instead of the default one. */ onAudit?: (audit: Audit) => Promise; /** By default audit logs are persisted asynchronously in the background. Change this to false if you want it to be synchronous" */ From bd4227425b59bde4f7fdee971fac6de0f7843442 Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sun, 10 Mar 2024 18:38:20 +0000 Subject: [PATCH 07/11] Feat(mongoose-audit): exported audit options --- plugins/mongoose-audit/types/index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mongoose-audit/types/index.d.ts b/plugins/mongoose-audit/types/index.d.ts index a3e4f6b..32495ac 100644 --- a/plugins/mongoose-audit/types/index.d.ts +++ b/plugins/mongoose-audit/types/index.d.ts @@ -10,7 +10,7 @@ interface Audit { created_at: string; } -interface PluginOptions { +export interface Options { /** The user extractor function to use. This probably will be fetching the current user from a context or something similar. */ getUser?: () => any; /** The types of audit to record. */ @@ -26,7 +26,7 @@ interface PluginOptions { } /** Registers the plugin with the provided options. */ -export declare function plugin(schema: mongoose.Schema, options: PluginOptions): void; +export declare function plugin(schema: mongoose.Schema, options: Options): void; /** * The Audit model. From f6fade477793635ca15350be7330edf3ee039e32 Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sun, 10 Mar 2024 18:38:20 +0000 Subject: [PATCH 08/11] Fix(mongoose-audit): audit type defintion --- plugins/mongoose-audit/types/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mongoose-audit/types/index.d.ts b/plugins/mongoose-audit/types/index.d.ts index 32495ac..33a7a57 100644 --- a/plugins/mongoose-audit/types/index.d.ts +++ b/plugins/mongoose-audit/types/index.d.ts @@ -1,6 +1,6 @@ import mongoose from "mongoose"; -type auditType = "add" | "edit" | "delete"; +type auditType = "Add" | "Edit" | "Delete"; interface Audit { entity_id: any; From a75e386d9e19b89f188c2822f3b60871ca1fea6e Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sun, 10 Mar 2024 18:56:05 +0000 Subject: [PATCH 09/11] Fix(mongoose-audit): audit type export --- plugins/mongoose-audit/src/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/mongoose-audit/src/index.js b/plugins/mongoose-audit/src/index.js index ebc8223..2612f71 100644 --- a/plugins/mongoose-audit/src/index.js +++ b/plugins/mongoose-audit/src/index.js @@ -2,10 +2,10 @@ import { default as plugin } from "./plugin"; import { default as Audit } from "./model"; import { AuditType } from "./constants"; -export { plugin, Audit, AuditType as auditType }; +export { plugin, Audit, AuditType }; export default { plugin, Audit, - auditType: AuditType + AuditType }; From 6f038a4b2c76bb3a24199df658fe2d07d52dd02f Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sun, 10 Mar 2024 19:04:47 +0000 Subject: [PATCH 10/11] Fix(mongoose-audit): default audit user --- plugins/mongoose-audit/src/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mongoose-audit/src/plugin.js b/plugins/mongoose-audit/src/plugin.js index d78d0f8..2bb2fba 100644 --- a/plugins/mongoose-audit/src/plugin.js +++ b/plugins/mongoose-audit/src/plugin.js @@ -12,7 +12,7 @@ const options = { }; const addAuditLogObject = (currentObject, original) => { - const user = currentObject.__user || options.getUser?.() || "Unknown User"; + const user = currentObject.__user || options.getUser?.() || "Unknown"; delete currentObject.__user; let changes = deepDiff(JSON.parse(JSON.stringify(original ?? {})), JSON.parse(JSON.stringify(currentObject ?? {})), filter); if (changes?.length) { From 1dca2c65bd67b881a8161b045e5150f89839b992 Mon Sep 17 00:00:00 2001 From: Akalanka Perera Date: Sun, 10 Mar 2024 19:14:00 +0000 Subject: [PATCH 11/11] Feat!(mongoose-audit): initial release --- plugins/mongoose-audit/test/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mongoose-audit/test/index.test.js b/plugins/mongoose-audit/test/index.test.js index 5c53f51..b999a13 100644 --- a/plugins/mongoose-audit/test/index.test.js +++ b/plugins/mongoose-audit/test/index.test.js @@ -71,7 +71,7 @@ describe("audit", function () { const entry = audit[0]; expect(entry.changes["name"].to).toBe("Lucky"); expect(entry.changes["name"].type).toBe(AuditType.Add); - expect(entry.user).toBe("Unknown User") + expect(entry.user).toBe("Unknown") expect(entry.entity).toBe("Test") }) });