@dynamatix/cat-shared 0.0.16 → 0.0.18

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.
@@ -1,14 +1,34 @@
1
1
  import AuditConfigModel from '../models/audit-config.model.js';
2
2
  import AuditLog from '../models/audit.model.js';
3
+ import ValueReferenceMap from '../models/value-reference-map.model.js';
3
4
  import { getContext } from '../services/request-context.service.js';
4
-
5
+ import mongoose from 'mongoose';
5
6
 
6
7
  let onAuditLogCreated = null;
7
8
 
9
+ // Generic resolver: fetch display values using ValueReferenceMap
10
+ async function resolveAuditValues(field, oldValue, newValue) {
11
+ const map = await ValueReferenceMap.findOne({ field });
12
+ if (!map || (!oldValue && !newValue)) return { oldValue, newValue };
13
+
14
+ const Model = mongoose.models[map.model];
15
+ if (!Model) return { oldValue, newValue };
16
+
17
+ const [oldDoc, newDoc] = await Promise.all([
18
+ oldValue ? Model.findById(oldValue).lean() : null,
19
+ newValue ? Model.findById(newValue).lean() : null
20
+ ]);
21
+
22
+ return {
23
+ oldValue: oldDoc?.[map.displayField] || oldValue,
24
+ newValue: newDoc?.[map.displayField] || newValue
25
+ };
26
+ }
27
+
8
28
  function applyAuditMiddleware(schema, collectionName) {
9
- // Handle create
29
+ // Handle creation audit
10
30
  schema.post('save', async function (doc) {
11
- const auditConfig = await AuditLog.findOne({ collectionName });
31
+ const auditConfig = await AuditConfigModel.findOne({ collectionName });
12
32
  if (!auditConfig?.trackCreation) return;
13
33
 
14
34
  const context = getContext();
@@ -23,39 +43,51 @@ function applyAuditMiddleware(schema, collectionName) {
23
43
  createdBy: userId,
24
44
  contextId
25
45
  };
46
+
47
+ await AuditLog.create(log);
48
+
26
49
  if (onAuditLogCreated) {
27
- onAuditLogCreated(logs, contextId); // šŸ‘ˆ Call the hook with the logs
50
+ await onAuditLogCreated([log], contextId);
28
51
  }
52
+ });
29
53
 
30
- await AuditLog.create(log);
54
+ // Capture the original doc before update
55
+ schema.pre(['findOneAndUpdate', 'findByIdAndUpdate'], async function (next) {
56
+ this._originalDoc = await this.model.findOne(this.getQuery()).lean();
57
+ next();
31
58
  });
32
59
 
33
- // Handle updates (after successful update)
60
+ // Handle update audits
34
61
  schema.post(['findOneAndUpdate', 'findByIdAndUpdate'], async function (result) {
35
- if (!result) return; // No doc was updated
62
+ if (!result || !this._originalDoc) return;
36
63
 
37
64
  const auditConfig = await AuditConfigModel.findOne({ collectionName });
38
65
  if (!auditConfig?.fields?.length) return;
39
66
 
40
- const query = this;
41
- const update = query.getUpdate();
42
-
67
+ const update = this.getUpdate();
43
68
  const logs = [];
44
69
  const context = getContext();
45
70
  const userId = context?.userId || 'anonymous';
46
71
  const contextId = context?.contextId;
47
72
 
48
73
  for (const field of auditConfig.fields) {
49
- if (update?.$set?.hasOwnProperty(field) || update?.hasOwnProperty(field)) {
74
+ const hasChanged =
75
+ update?.$set?.hasOwnProperty(field) || update?.hasOwnProperty(field);
76
+
77
+ if (hasChanged) {
50
78
  const newValue = update?.$set?.[field] ?? update?.[field];
51
- const oldValue = result[field];
79
+ const oldValue = this._originalDoc[field];
52
80
 
53
81
  if (oldValue !== newValue) {
82
+ // Resolve human-readable values
83
+ const { oldValue: resolvedOld, newValue: resolvedNew } =
84
+ await resolveAuditValues(field, oldValue, newValue);
85
+
54
86
  logs.push({
55
87
  name: `${collectionName}.${field}`,
56
88
  recordId: result._id,
57
- oldValue,
58
- newValue,
89
+ oldValue: resolvedOld,
90
+ newValue: resolvedNew,
59
91
  createdBy: userId,
60
92
  contextId
61
93
  });
@@ -63,16 +95,17 @@ function applyAuditMiddleware(schema, collectionName) {
63
95
  }
64
96
  }
65
97
 
66
- if (logs.length) await AuditLog.insertMany(logs);
67
- if (onAuditLogCreated) {
68
- onAuditLogCreated(logs, contextId); // šŸ‘ˆ Call the hook with the logs
98
+ if (logs.length) {
99
+ await AuditLog.insertMany(logs);
100
+ if (onAuditLogCreated) {
101
+ await onAuditLogCreated(logs, contextId);
102
+ }
69
103
  }
70
104
  });
71
105
  }
72
106
 
73
-
74
107
  export function registerAuditHook(callback) {
75
108
  onAuditLogCreated = callback;
76
109
  }
77
110
 
78
- export default applyAuditMiddleware;
111
+ export default applyAuditMiddleware;
package/models/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { default as AuditConfigModel } from './audit-config.model.js';
2
- export { default as AuditModel } from './audit.model.js';
2
+ export { default as AuditModel } from './audit.model.js';
3
+ export { default as ValueReferenceMapModel } from './value-reference-map.model.js';
@@ -0,0 +1,12 @@
1
+ import mongoose from 'mongoose';
2
+
3
+ const valueReferenceMapSchema = new mongoose.Schema({
4
+ field: String, // e.g. 'documentTypeId', 'createdBy', 'status'
5
+ model: String, // e.g. 'DocumentType', 'User', 'ApplicationStatus'
6
+ displayField: String, // e.g. 'name', 'fullName', 'label'
7
+ });
8
+
9
+ valueReferenceMapSchema.index({ field: 1 }, { unique: true });
10
+
11
+ const ValueReferenceMapModel = mongoose.model('ValueReferenceMap', valueReferenceMapSchema);
12
+ export default ValueReferenceMapModel;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamatix/cat-shared",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -13,5 +13,9 @@
13
13
  "./models": "./models/index.js",
14
14
  "./middlewares": "./middlewares/index.js",
15
15
  "./services": "./services/index.js"
16
+ },
17
+ "dependencies": {
18
+ "dotenv": "^16.4.7",
19
+ "mongoose": "^8.13.1"
16
20
  }
17
21
  }
@@ -0,0 +1,57 @@
1
+ import mongoose from 'mongoose';
2
+ import dotenv from 'dotenv';
3
+ import ValueReferenceMapModel from '../models/value-reference-map.model.js';
4
+
5
+ dotenv.config();
6
+
7
+ const MONGO_URI = process.env.MONGO_URI;
8
+
9
+ // Grouped definition (DRY)
10
+ const groupedMappings = [
11
+ {
12
+ model: 'DocumentType',
13
+ displayField: 'name',
14
+ fields: ['documentTypeId']
15
+ },
16
+ {
17
+ model: 'User',
18
+ displayField: 'fullName',
19
+ fields: ['createdBy', 'updatedBy', 'ownerId']
20
+ }
21
+ ];
22
+
23
+ // Flatten the mappings
24
+ const referenceMappings = groupedMappings.flatMap(group =>
25
+ group.fields.map(field => ({
26
+ field,
27
+ model: group.model,
28
+ displayField: group.displayField
29
+ }))
30
+ );
31
+
32
+ async function seed() {
33
+ try {
34
+ console.log(MONGO_URI);
35
+ await mongoose.connect(MONGO_URI);
36
+ console.log('āœ… Connected to MongoDB');
37
+
38
+ for (const mapping of referenceMappings) {
39
+ const existing = await ValueReferenceMapModel.findOne({ field: mapping.field });
40
+ if (!existing) {
41
+ await ValueReferenceMapModel.create(mapping);
42
+ console.log(`āž• Inserted mapping for '${mapping.field}'`);
43
+ } else {
44
+ console.log(`āš ļø Mapping for '${mapping.field}' already exists`);
45
+ }
46
+ }
47
+
48
+ console.log('\n🌱 Seeding complete āœ…');
49
+ } catch (err) {
50
+ console.error('āŒ Seeding failed', err);
51
+ } finally {
52
+ await mongoose.disconnect();
53
+ process.exit();
54
+ }
55
+ }
56
+
57
+ seed();