@brownnrl/tcdc-audit-backend-lib
Version:
Backend library for managing audit trail data
183 lines (182 loc) • 7.34 kB
JavaScript
;
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;