Refactor database utility functions and enhance audit logging
All checks were successful
farmcontrol/farmcontrol-ws/pipeline/head This commit looks good

- Removed unnecessary console logging in the `SocketUser` class for cleaner output.
- Updated the `distributeNew` function to accept the entire object instead of just the ID, improving flexibility in handling new entries.
- Enhanced the `newAuditLog` and `editAuditLog` functions to filter out excluded models and changes, ensuring sensitive data is not logged.
- Introduced new utility functions for omitting specific paths and excluded changes from audit logs, enhancing data privacy and integrity.
This commit is contained in:
Tom Butcher 2026-09-01 12:34:31 +01:00
parent ccbf2ff388
commit 9416a94a23
3 changed files with 95 additions and 18 deletions

View File

@ -143,7 +143,7 @@ export const listObjects = async ({
sort, sort,
order, order,
project, project,
cached, cached
})}` })}`
); );
@ -199,7 +199,7 @@ export const listObjects = async ({
order, order,
project, project,
cached, cached,
length: finalResult.length, length: finalResult.length
})}` })}`
); );
return finalResult; return finalResult;
@ -221,7 +221,7 @@ export const getObject = async ({
`Getting object: ${formatTraceData({ `Getting object: ${formatTraceData({
model, model,
id, id,
populate, populate
})}` })}`
); );
@ -259,7 +259,7 @@ export const getObject = async ({
`Retreived object from database: ${formatTraceData({ `Retreived object from database: ${formatTraceData({
model, model,
id, id,
populate, populate
})}` })}`
); );
@ -394,9 +394,15 @@ export const aggregateRollupsHistory = async ({
if (rollup.operation === 'count') { if (rollup.operation === 'count') {
value = matchingObjects.length; value = matchingObjects.length;
} else if (rollup.operation === 'sum') { } else if (rollup.operation === 'sum') {
value = _.sumBy(matchingObjects, obj => _.get(obj, rollup.property) || 0); value = _.sumBy(
matchingObjects,
obj => _.get(obj, rollup.property) || 0
);
} else if (rollup.operation === 'avg') { } else if (rollup.operation === 'avg') {
const sum = _.sumBy(matchingObjects, obj => _.get(obj, rollup.property) || 0); const sum = _.sumBy(
matchingObjects,
obj => _.get(obj, rollup.property) || 0
);
value = matchingObjects.length ? sum / matchingObjects.length : 0; value = matchingObjects.length ? sum / matchingObjects.length : 0;
} }
@ -486,7 +492,9 @@ export const aggregateRollupsHistory = async ({
logIndex++; logIndex++;
} }
results.push(snapshotRollups(Array.from(workingObjects.values()), bucketDate)); results.push(
snapshotRollups(Array.from(workingObjects.values()), bucketDate)
);
} }
return results.reverse(); return results.reverse();
@ -616,7 +624,7 @@ export const newObject = async ({
await newAuditLog(newData, created._id, parentType, owner, ownerType); await newAuditLog(newData, created._id, parentType, owner, ownerType);
} }
await distributeNew(created._id, parentType); await distributeNew(created, parentType);
await updateObjectCache({ await updateObjectCache({
model: model, model: model,

View File

@ -11,6 +11,12 @@ const NOTIFICATION_EXCLUDED_MODELS = [
'userNotifier', 'userNotifier',
'auditLog' 'auditLog'
]; ];
const AUDIT_EXCLUDED_MODELS = [
'notification',
'userNotifier',
'marketplaceEvent'
];
const AUDIT_EXCLUDED_CHANGES = ['state.message'];
const SENSITIVE_KEYS = ['secret']; const SENSITIVE_KEYS = ['secret'];
function omitSensitive(obj) { function omitSensitive(obj) {
@ -24,6 +30,58 @@ function omitSensitive(obj) {
return result; return result;
} }
function omitPath(obj, path) {
if (obj == null || typeof obj !== 'object') return obj;
const keys = path.split('.');
if (keys.length === 1) {
const result = { ...obj };
delete result[keys[0]];
return result;
}
const [first, ...rest] = keys;
if (
obj[first] == null ||
typeof obj[first] !== 'object' ||
Array.isArray(obj[first])
) {
return obj;
}
const nested = obj[first];
const leafKey = rest[rest.length - 1];
const result = { ...obj };
if (rest.length === 1) {
const nestedKeys = Object.keys(nested);
if (nestedKeys.length === 1 && nestedKeys[0] === leafKey) {
delete result[first];
}
return result;
}
const nestedKeys = Object.keys(nested);
if (
nestedKeys.length > 1 ||
(nestedKeys.length === 1 && nestedKeys[0] !== rest[0])
) {
return obj;
}
const updatedNested = omitPath({ ...nested }, rest.join('.'));
if (updatedNested == null || Object.keys(updatedNested).length === 0) {
delete result[first];
} else {
result[first] = updatedNested;
}
return result;
}
function omitExcludedChanges(obj) {
if (obj == null || typeof obj !== 'object') return obj;
return AUDIT_EXCLUDED_CHANGES.reduce((acc, path) => omitPath(acc, path), {
...obj
});
}
let modelsCache = null; let modelsCache = null;
async function getModelEntryByType(parentType) { async function getModelEntryByType(parentType) {
@ -391,8 +449,10 @@ function getChangedValues(oldObj, newObj, old = false) {
} }
async function newAuditLog(newValue, parentId, parentType, owner, ownerType) { async function newAuditLog(newValue, parentId, parentType, owner, ownerType) {
// Filter out createdAt and updatedAt from newValue if (AUDIT_EXCLUDED_MODELS.includes(parentType)) return;
const filteredNewValue = { ...newValue };
// Filter out createdAt, updatedAt, and sensitive fields from newValue
const filteredNewValue = omitSensitive({ ...newValue });
delete filteredNewValue.createdAt; delete filteredNewValue.createdAt;
delete filteredNewValue.updatedAt; delete filteredNewValue.updatedAt;
const auditLog = new auditLogModel({ const auditLog = new auditLogModel({
@ -408,7 +468,7 @@ async function newAuditLog(newValue, parentId, parentType, owner, ownerType) {
await auditLog.save(); await auditLog.save();
await distributeNew(auditLog._id, 'auditLog'); await distributeNew(auditLog, 'auditLog');
} }
async function editAuditLog( async function editAuditLog(
@ -422,9 +482,19 @@ async function editAuditLog(
if (parentType === 'stockEvent') { if (parentType === 'stockEvent') {
return; return;
} }
const filteredOldValue = omitExcludedChanges(oldValue);
const filteredNewValue = omitExcludedChanges(newValue);
// Get only the changed values // Get only the changed values
const changedOldValues = getChangedValues(oldValue, newValue, true); const changedOldValues = getChangedValues(
const changedNewValues = getChangedValues(oldValue, newValue, false); filteredOldValue,
filteredNewValue,
true
);
const changedNewValues = getChangedValues(
filteredOldValue,
filteredNewValue,
false
);
// If no values changed, don't create an audit log // If no values changed, don't create an audit log
if ( if (
@ -448,7 +518,7 @@ async function editAuditLog(
await auditLog.save(); await auditLog.save();
await distributeNew(auditLog._id, 'auditLog'); await distributeNew(auditLog, 'auditLog');
} }
async function deleteAuditLog( async function deleteAuditLog(
@ -471,7 +541,7 @@ async function deleteAuditLog(
await auditLog.save(); await auditLog.save();
await distributeNew(auditLog._id, 'auditLog'); await distributeNew(auditLog, 'auditLog');
} }
async function getAuditLogs(idOrIds) { async function getAuditLogs(idOrIds) {
@ -490,8 +560,8 @@ async function distributeStats(value, type) {
await natsServer.publish(`${type}s.stats`, value); await natsServer.publish(`${type}s.stats`, value);
} }
async function distributeNew(id, type) { async function distributeNew(value, type) {
await natsServer.publish(`${type}s.new`, id); await natsServer.publish(`${type}s.new`, value);
} }
async function editNotification( async function editNotification(

View File

@ -113,7 +113,6 @@ export class SocketUser {
logger.info('User authenticated and valid.'); logger.info('User authenticated and valid.');
this.user = result.user; this.user = result.user;
console.log('user', this.user);
this.id = this.user._id.toString(); this.id = this.user._id.toString();
this.authenticated = true; this.authenticated = true;
await this.notificationManager.subscribe(); await this.notificationManager.subscribe();