@dynamatix/cat-shared 0.0.82 → 0.0.83
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/middlewares/audit.middleware.js +95 -15
- package/package.json +1 -1
|
@@ -12,11 +12,32 @@ const customResolvers = {
|
|
|
12
12
|
// expenditureRationale: (doc) => `Rationale for ${doc.type}: ${doc.rationale}`
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
//
|
|
15
|
+
// Expression-based description resolver: resolves ${fieldName} in the expression using doc, and via Lookup if needed
|
|
16
|
+
async function resolveExpressionDescription(doc, expression, resolverType) {
|
|
17
|
+
const matches = [...expression.matchAll(/\$\{([^}]+)\}/g)];
|
|
18
|
+
let result = expression;
|
|
19
|
+
for (const match of matches) {
|
|
20
|
+
const fieldName = match[1];
|
|
21
|
+
let value = doc[fieldName];
|
|
22
|
+
if (resolverType === 'lookup' && value && mongoose.models['Lookup']) {
|
|
23
|
+
const lookupDoc = await mongoose.models['Lookup'].findById(value).lean();
|
|
24
|
+
value = lookupDoc?.name || value;
|
|
25
|
+
}
|
|
26
|
+
result = result.replace(match[0], value ?? '');
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Update resolveDescription to support expression-based descriptionField
|
|
16
32
|
async function resolveDescription(field, doc) {
|
|
17
|
-
const
|
|
33
|
+
const modelName = doc.constructor.modelName;
|
|
34
|
+
const config = await ValueReferenceMap.findOne({ field, model: modelName });
|
|
18
35
|
if (!config) return '';
|
|
19
36
|
|
|
37
|
+
if (config.descriptionField && config.descriptionField.includes('${')) {
|
|
38
|
+
return await resolveExpressionDescription(doc, config.descriptionField, config.descriptionResolverType);
|
|
39
|
+
}
|
|
40
|
+
|
|
20
41
|
switch (config.descriptionResolverType) {
|
|
21
42
|
case 'lookup': {
|
|
22
43
|
const lookupId = doc[config.descriptionField];
|
|
@@ -41,6 +62,31 @@ async function resolveDescription(field, doc) {
|
|
|
41
62
|
}
|
|
42
63
|
}
|
|
43
64
|
|
|
65
|
+
// Update resolveEntityDescription to support expression-based descriptionField
|
|
66
|
+
async function resolveEntityDescription(doc, auditConfig) {
|
|
67
|
+
if (!auditConfig.descriptionResolutorForExternalData) return '';
|
|
68
|
+
const field = auditConfig.descriptionResolutorForExternalData;
|
|
69
|
+
const value = doc[field];
|
|
70
|
+
// Try to resolve via ValueReferenceMap
|
|
71
|
+
const map = await ValueReferenceMap.findOne({ field });
|
|
72
|
+
if (map && map.descriptionField && map.descriptionField.includes('${')) {
|
|
73
|
+
return await resolveExpressionDescription(doc, map.descriptionField, map.descriptionResolverType);
|
|
74
|
+
}
|
|
75
|
+
if (map && value) {
|
|
76
|
+
const Model = mongoose.models[map.model];
|
|
77
|
+
if (Model) {
|
|
78
|
+
const refDoc = await Model.findById(value).lean();
|
|
79
|
+
return refDoc?.[map.displayField] || value;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Fallback: If it's an ObjectId and Lookup model exists, resolve to name
|
|
83
|
+
if (mongoose.Types.ObjectId.isValid(value) && mongoose.models['Lookup']) {
|
|
84
|
+
const lookupDoc = await mongoose.models['Lookup'].findById(value).lean();
|
|
85
|
+
return lookupDoc?.name || value;
|
|
86
|
+
}
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
|
|
44
90
|
function applyAuditMiddleware(schema, collectionName) {
|
|
45
91
|
// Handle creation audit
|
|
46
92
|
schema.post('save', async function (doc) {
|
|
@@ -51,20 +97,50 @@ function applyAuditMiddleware(schema, collectionName) {
|
|
|
51
97
|
const userId = context?.userId || 'anonymous';
|
|
52
98
|
const contextId = context?.contextId;
|
|
53
99
|
|
|
54
|
-
const
|
|
55
|
-
|
|
100
|
+
const entityDescription = await resolveEntityDescription(doc, auditConfig);
|
|
101
|
+
const logs = [];
|
|
102
|
+
|
|
103
|
+
// Per-field logs
|
|
104
|
+
if (auditConfig.fields?.length) {
|
|
105
|
+
for (const field of auditConfig.fields) {
|
|
106
|
+
const newValue = doc[field];
|
|
107
|
+
const fieldDescription = await resolveDescription(field, doc);
|
|
108
|
+
logs.push({
|
|
109
|
+
name: fieldDescription,
|
|
110
|
+
entity: entityDescription,
|
|
111
|
+
recordId: contextId || doc._id,
|
|
112
|
+
oldValue: null,
|
|
113
|
+
newValue,
|
|
114
|
+
createdBy: userId,
|
|
115
|
+
externalData: {
|
|
116
|
+
description: entityDescription,
|
|
117
|
+
contextId
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Overall document creation log (optional)
|
|
124
|
+
logs.push({
|
|
125
|
+
name: 'created',
|
|
126
|
+
entity: entityDescription,
|
|
56
127
|
recordId: contextId || doc._id,
|
|
57
128
|
oldValue: null,
|
|
58
129
|
newValue: 'Document created',
|
|
59
130
|
createdBy: userId,
|
|
60
131
|
contextId,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
132
|
+
externalData: {
|
|
133
|
+
description: entityDescription,
|
|
134
|
+
contextId
|
|
135
|
+
}
|
|
136
|
+
});
|
|
65
137
|
|
|
66
|
-
if (
|
|
67
|
-
await
|
|
138
|
+
if (logs.length) {
|
|
139
|
+
await AuditLog.insertMany(logs);
|
|
140
|
+
await updateContextAuditCount(contextId, logs.length);
|
|
141
|
+
if (onAuditLogCreated) {
|
|
142
|
+
await onAuditLogCreated(logs, contextId);
|
|
143
|
+
}
|
|
68
144
|
}
|
|
69
145
|
});
|
|
70
146
|
|
|
@@ -87,6 +163,8 @@ function applyAuditMiddleware(schema, collectionName) {
|
|
|
87
163
|
const userId = context?.userId || 'anonymous';
|
|
88
164
|
const contextId = context?.contextId;
|
|
89
165
|
|
|
166
|
+
const entityDescription = await resolveEntityDescription(result, auditConfig);
|
|
167
|
+
|
|
90
168
|
for (const field of auditConfig.fields) {
|
|
91
169
|
const hasChanged =
|
|
92
170
|
update?.$set?.hasOwnProperty(field) || update?.hasOwnProperty(field);
|
|
@@ -96,16 +174,18 @@ function applyAuditMiddleware(schema, collectionName) {
|
|
|
96
174
|
const oldValue = this._originalDoc ? this._originalDoc[field] : '';
|
|
97
175
|
|
|
98
176
|
if (oldValue !== newValue) {
|
|
99
|
-
|
|
100
|
-
const description = await resolveDescription(field, result);
|
|
101
|
-
|
|
177
|
+
const fieldDescription = await resolveDescription(field, result);
|
|
102
178
|
logs.push({
|
|
103
|
-
name:
|
|
179
|
+
name: fieldDescription,
|
|
180
|
+
entity: entityDescription,
|
|
104
181
|
recordId: contextId || result._id,
|
|
105
182
|
oldValue,
|
|
106
183
|
newValue,
|
|
107
184
|
createdBy: userId,
|
|
108
|
-
|
|
185
|
+
externalData: {
|
|
186
|
+
description: entityDescription,
|
|
187
|
+
contextId: contextId || result._id
|
|
188
|
+
}
|
|
109
189
|
});
|
|
110
190
|
}
|
|
111
191
|
}
|