UNPKG

@brownnrl/tcdc-audit-backend-lib

Version:

Backend library for managing audit trail data

183 lines (182 loc) 7.34 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createIgnoreKeysFilter = exports.trackDeletion = exports.trackUpdate = exports.trackCreation = void 0; /** * Logs the creation of a record by capturing its initial values as an audit trail event. * Designed to return a raw object representation (IRawAuditEvent) without MongoDB-specific details. * * @param {object} params - Input parameters for tracking creation. * @param {Record<string, any>} params.data - The object being created. * @param {object} params.changedBy - Metadata about the user or system creating the record. * @param {string} params.changedBy.profileId - Unique identifier for the user. * @param {string} params.changedBy.name - Display name of the user. * @param {boolean} [params.changedBy.isSystem] - Whether the action was system-generated. * @param {string[]} [params.notes] - General notes describing the creation event. * @param {string[]} [params.ignoreKeys] - Keys in the object to exclude from the audit trail. * @param {Record<string, string>} [params.fieldNotes] - Custom notes for specific fields in the object. * * @returns {IRawAuditEvent} A raw audit trail event object containing field-level changes and metadata. * * @example * const data = { name: 'John Doe', age: 30, admin: true }; * const changedBy = { profileId: 'user123', name: 'Admin User' }; * const notes = ['Record created during signup']; * const ignoreKeys = ['admin']; * const fieldNotes = { name: 'User name at signup' }; * * const auditEvent = trackCreation({ data, changedBy, notes, ignoreKeys, fieldNotes }); * console.log(JSON.stringify(auditEvent, null, 4); * /* * { * "changeType": "created", * "notes": ["Record created during signup"], * "changes": [ * { * "field": "name", * "oldValue": null, * "newValue": "John Doe", * "notes": "User name at signup" * }, * { * "field": "age", * "oldValue": null, * "newValue": 30 * } * ], * "changedBy": { * "profileId": "user123", * "name": "Admin User", * "isSystem": false * }, * "timestamp": "2024-01-01T12:00:00Z" * } */ /* */ const trackCreation = ({ data, changedBy, notes = [], ignoreKeys = [/audittrail/i, 'createdAt', 'modifiedAt'], fieldNotes = {}, }) => { const changes = Object.keys(data) .filter((0, exports.createIgnoreKeysFilter)(ignoreKeys)) .map(field => ({ field, oldValue: undefined, newValue: data[field], notes: fieldNotes[field] ?? undefined })); const creationEvent = { changeType: 'created', notes, changes, changedBy, timestamp: new Date(), }; return creationEvent; }; exports.trackCreation = trackCreation; /** * Tracks updates by comparing old and new states of an object. * * @param {object} params - Input parameters for tracking updates. * @param {Record<string, any>} params.oldState - The old state of the object. * @param {Record<string, any>} params.newState - The new state of the object. * @param {object} params.changedBy - Metadata about the user/system making the update. * @param {string} params.changedBy.profileId - The user's profile ID. * @param {string} params.changedBy.name - The user's name. * @param {boolean} [params.changedBy.isSystem] - Indicates if the update was system-initiated. * @param {string[]} [params.notes] - Record-level notes describing the update event. * @param {string[]} [params.ignoreKeys] - Keys to exclude from tracking. * @param {Record<string, string>} [params.fieldNotes] - Custom notes for specific fields. * * @returns {IRawAuditEvent} A raw audit trail event object containing field-level changes and metadata. */ const trackUpdate = ({ oldState, newState, changedBy, notes = [], ignoreKeys = [/auditTrail/i, '_id'], fieldNotes = {}, }) => { const changes = Object.keys(newState) .filter((0, exports.createIgnoreKeysFilter)(ignoreKeys)) .reduce((acc, field) => { const oldValue = oldState[field]; const newValue = newState[field]; if (field === '_id' && oldValue?.toString() === newValue?.toString()) return acc; if (oldValue === newValue) return acc; // Handle Date objects if (oldValue instanceof Date && newValue instanceof Date && oldValue.toISOString() === newValue.toISOString()) { return acc; // No change in Date } // Handle Date and string comparisons if (oldValue instanceof Date && typeof newValue === 'string' && oldValue.toISOString() === newValue) { return acc; } else if (typeof oldValue === 'string' && newValue instanceof Date && oldValue === newValue.toISOString()) { return acc; } // Handle empty string and null comparisons if ((oldValue === '' && newValue === null) || (oldValue === null && newValue === '')) { return acc; // No change if both are empty string or null } acc.push({ field, oldValue, newValue, notes: fieldNotes[field] ?? undefined, }); return acc; }, []); const updateEvent = { changeType: 'updated', notes, changes, changedBy, timestamp: new Date(), }; return updateEvent; }; exports.trackUpdate = trackUpdate; /** * Tracks a deletion event for an object. * * @param {object} params - Input parameters for tracking deletion. * @param {object} params.changedBy - Metadata about the user/system initiating the deletion. * @param {string} params.changedBy.profileId - The user's profile ID. * @param {string} params.changedBy.name - The user's name. * @param {boolean} [params.changedBy.isSystem] - Indicates if the deletion was system-initiated. * @param {string[]} [params.notes] - Record-level notes describing the deletion event. * * @returns {IRawAuditEvent} A raw audit trail event object containing metadata for the deletion. */ const trackDeletion = ({ data = {}, changedBy, notes = [], ignoreKeys = [/audittrail/i, 'createdAt', 'modifiedAt'], fieldNotes = {}, }) => { const changes = Object.keys(data) .filter((0, exports.createIgnoreKeysFilter)(ignoreKeys)) .map(field => { return { field, oldValue: data[field], newValue: undefined, notes: fieldNotes[field] ?? undefined }; }); return { changeType: 'deleted', notes, changes, changedBy, timestamp: new Date(), }; }; exports.trackDeletion = trackDeletion; /** * Creates a filter function to check if a key should be ignored based on a list of strings or regex patterns. * * @param {Array<string | RegExp>} ignoreKeys - List of keys or patterns to ignore. * @returns {(key: string) => boolean} A function that takes a key and returns true if it should be ignored. */ const createIgnoreKeysFilter = (ignoreKeys) => { return (key) => { return !ignoreKeys.some((ignoreKey) => { return typeof ignoreKey === 'string' ? key === ignoreKey : ignoreKey.test(key); }); }; }; exports.createIgnoreKeysFilter = createIgnoreKeysFilter;