@dynamatix/cat-shared 0.0.17 ā 0.0.19
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,11 +1,32 @@
|
|
|
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';
|
|
5
|
+
import mongoose from 'mongoose';
|
|
4
6
|
|
|
5
7
|
let onAuditLogCreated = null;
|
|
6
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
|
+
|
|
7
28
|
function applyAuditMiddleware(schema, collectionName) {
|
|
8
|
-
// Handle
|
|
29
|
+
// Handle creation audit
|
|
9
30
|
schema.post('save', async function (doc) {
|
|
10
31
|
const auditConfig = await AuditConfigModel.findOne({ collectionName });
|
|
11
32
|
if (!auditConfig?.trackCreation) return;
|
|
@@ -30,13 +51,13 @@ function applyAuditMiddleware(schema, collectionName) {
|
|
|
30
51
|
}
|
|
31
52
|
});
|
|
32
53
|
|
|
33
|
-
// Capture original doc before update
|
|
54
|
+
// Capture the original doc before update
|
|
34
55
|
schema.pre(['findOneAndUpdate', 'findByIdAndUpdate'], async function (next) {
|
|
35
56
|
this._originalDoc = await this.model.findOne(this.getQuery()).lean();
|
|
36
57
|
next();
|
|
37
58
|
});
|
|
38
59
|
|
|
39
|
-
// Handle
|
|
60
|
+
// Handle update audits
|
|
40
61
|
schema.post(['findOneAndUpdate', 'findByIdAndUpdate'], async function (result) {
|
|
41
62
|
if (!result || !this._originalDoc) return;
|
|
42
63
|
|
|
@@ -50,16 +71,23 @@ function applyAuditMiddleware(schema, collectionName) {
|
|
|
50
71
|
const contextId = context?.contextId;
|
|
51
72
|
|
|
52
73
|
for (const field of auditConfig.fields) {
|
|
53
|
-
|
|
74
|
+
const hasChanged =
|
|
75
|
+
update?.$set?.hasOwnProperty(field) || update?.hasOwnProperty(field);
|
|
76
|
+
|
|
77
|
+
if (hasChanged) {
|
|
54
78
|
const newValue = update?.$set?.[field] ?? update?.[field];
|
|
55
79
|
const oldValue = this._originalDoc[field];
|
|
56
80
|
|
|
57
81
|
if (oldValue !== newValue) {
|
|
82
|
+
// Resolve human-readable values
|
|
83
|
+
const { oldValue: resolvedOld, newValue: resolvedNew } =
|
|
84
|
+
await resolveAuditValues(field, oldValue, newValue);
|
|
85
|
+
|
|
58
86
|
logs.push({
|
|
59
87
|
name: `${collectionName}.${field}`,
|
|
60
88
|
recordId: result._id,
|
|
61
|
-
oldValue,
|
|
62
|
-
newValue,
|
|
89
|
+
oldValue: resolvedOld,
|
|
90
|
+
newValue: resolvedNew,
|
|
63
91
|
createdBy: userId,
|
|
64
92
|
contextId
|
|
65
93
|
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
|
|
3
|
+
const formfieldSchema = new mongoose.Schema({
|
|
4
|
+
fieldName: {
|
|
5
|
+
type: String,
|
|
6
|
+
required: true,
|
|
7
|
+
trim: true
|
|
8
|
+
},
|
|
9
|
+
isEditable:{
|
|
10
|
+
type: Boolean,
|
|
11
|
+
default: true
|
|
12
|
+
},
|
|
13
|
+
size: {
|
|
14
|
+
type: Number,
|
|
15
|
+
default: 6,
|
|
16
|
+
enum: [3,4,6,8,10,12],
|
|
17
|
+
},
|
|
18
|
+
layout:{
|
|
19
|
+
type: String,
|
|
20
|
+
enum: ['horizontal', 'vertical'],
|
|
21
|
+
},
|
|
22
|
+
label: {
|
|
23
|
+
type: String,
|
|
24
|
+
required: true,
|
|
25
|
+
trim: true
|
|
26
|
+
},
|
|
27
|
+
dataType: {
|
|
28
|
+
type: String,
|
|
29
|
+
required: true,
|
|
30
|
+
enum: ['string', 'number', 'boolean', 'date', 'array', 'object', 'select', 'radio', 'list'] // Added common form field types
|
|
31
|
+
},
|
|
32
|
+
isRequired: {
|
|
33
|
+
type: Boolean,
|
|
34
|
+
default: false
|
|
35
|
+
},
|
|
36
|
+
helpText: {
|
|
37
|
+
type: String,
|
|
38
|
+
default: null
|
|
39
|
+
},
|
|
40
|
+
requiredErrorMessage:{
|
|
41
|
+
type: String,
|
|
42
|
+
default: 'Field is required'
|
|
43
|
+
},
|
|
44
|
+
defaultValue: {
|
|
45
|
+
type: mongoose.Schema.Types.Mixed
|
|
46
|
+
},
|
|
47
|
+
validationRules: {
|
|
48
|
+
type: mongoose.Schema.Types.Mixed,
|
|
49
|
+
default: {}
|
|
50
|
+
},
|
|
51
|
+
visibilityCondition: {
|
|
52
|
+
type: mongoose.Schema.Types.Mixed,
|
|
53
|
+
default: null
|
|
54
|
+
},
|
|
55
|
+
options: {
|
|
56
|
+
type: [mongoose.Schema.Types.Mixed],
|
|
57
|
+
default: []
|
|
58
|
+
},
|
|
59
|
+
section: {
|
|
60
|
+
type: String,
|
|
61
|
+
default: null
|
|
62
|
+
},
|
|
63
|
+
list:{
|
|
64
|
+
type: {
|
|
65
|
+
name: {
|
|
66
|
+
type: String,
|
|
67
|
+
required: true
|
|
68
|
+
},
|
|
69
|
+
label: {
|
|
70
|
+
type: String,
|
|
71
|
+
required: true
|
|
72
|
+
},
|
|
73
|
+
value: {
|
|
74
|
+
type: String,
|
|
75
|
+
required: true
|
|
76
|
+
},
|
|
77
|
+
optionFilterLogic: {
|
|
78
|
+
type: mongoose.Schema.Types.Mixed,
|
|
79
|
+
default: null
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
default: null
|
|
83
|
+
},
|
|
84
|
+
isSyncEnabled: { // Fixed typo (was isSyncEnabed)
|
|
85
|
+
type: Boolean,
|
|
86
|
+
default: true
|
|
87
|
+
},
|
|
88
|
+
dataElement: { //<collectionName>.<fieldName>
|
|
89
|
+
type: String,
|
|
90
|
+
default: null
|
|
91
|
+
},
|
|
92
|
+
syncTargetDataElement: { //<collectionName>.<fieldName> of Apprivo
|
|
93
|
+
type: String,
|
|
94
|
+
default: null
|
|
95
|
+
},
|
|
96
|
+
order: {
|
|
97
|
+
type: Number,
|
|
98
|
+
default: 0
|
|
99
|
+
},
|
|
100
|
+
maxLength: {
|
|
101
|
+
type: Number,
|
|
102
|
+
default: null,
|
|
103
|
+
min: 0
|
|
104
|
+
}
|
|
105
|
+
}, { timestamps: true });
|
|
106
|
+
|
|
107
|
+
const formConfigurationSchema = new mongoose.Schema({
|
|
108
|
+
formName: {
|
|
109
|
+
type: String,
|
|
110
|
+
required: true,
|
|
111
|
+
trim: true,
|
|
112
|
+
unique: true // Consider making form names unique
|
|
113
|
+
},
|
|
114
|
+
apiPath: {
|
|
115
|
+
type: String,
|
|
116
|
+
required: true,
|
|
117
|
+
trim: true
|
|
118
|
+
},
|
|
119
|
+
visibilityCondition: {
|
|
120
|
+
type: mongoose.Schema.Types.Mixed,
|
|
121
|
+
default: null
|
|
122
|
+
},
|
|
123
|
+
sections: [{
|
|
124
|
+
sectionName: {
|
|
125
|
+
type: String,
|
|
126
|
+
required: true,
|
|
127
|
+
trim: true
|
|
128
|
+
},
|
|
129
|
+
sectionLabel: {
|
|
130
|
+
type: String,
|
|
131
|
+
required: true,
|
|
132
|
+
trim: true
|
|
133
|
+
},
|
|
134
|
+
fields: [{
|
|
135
|
+
type: [formfieldSchema]
|
|
136
|
+
}],
|
|
137
|
+
order: {
|
|
138
|
+
type: Number,
|
|
139
|
+
default: 0
|
|
140
|
+
},
|
|
141
|
+
visibilityCondition: { // Added section-level visibility
|
|
142
|
+
type: mongoose.Schema.Types.Mixed,
|
|
143
|
+
default: null
|
|
144
|
+
}
|
|
145
|
+
}],
|
|
146
|
+
isActive: { // Consider adding status flag
|
|
147
|
+
type: Boolean,
|
|
148
|
+
default: true
|
|
149
|
+
},
|
|
150
|
+
version: { // Consider adding versioning
|
|
151
|
+
type: Number,
|
|
152
|
+
default: 1
|
|
153
|
+
}
|
|
154
|
+
}, { timestamps: true });
|
|
155
|
+
|
|
156
|
+
const FormConfiguration = mongoose.model('FormConfiguration', formConfigurationSchema);
|
|
157
|
+
|
|
158
|
+
module.exports = FormConfiguration;
|
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.
|
|
3
|
+
"version": "0.0.19",
|
|
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,66 @@
|
|
|
1
|
+
import mongoose, { model } 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 = 'mongodb+srv://qa-user:vw4sIy3lZpyTPoX0@gatehouse-testing.e08r2.mongodb.net/gatehouse-qa?retryWrites=true&w=majority';
|
|
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','documentOwnerId']
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
model: 'Applicant',
|
|
23
|
+
displayField: 'firstName',
|
|
24
|
+
fields: ['documentOwnerId']
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
// Flatten the mappings
|
|
29
|
+
const referenceMappings = groupedMappings.flatMap(group =>
|
|
30
|
+
group.fields.map(field => ({
|
|
31
|
+
field,
|
|
32
|
+
model: group.model,
|
|
33
|
+
displayField: group.displayField
|
|
34
|
+
}))
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
async function seed() {
|
|
38
|
+
try {
|
|
39
|
+
console.log(MONGO_URI);
|
|
40
|
+
await mongoose.connect(MONGO_URI);
|
|
41
|
+
console.log('ā
Connected to MongoDB');
|
|
42
|
+
|
|
43
|
+
for (const mapping of referenceMappings) {
|
|
44
|
+
const existing = await ValueReferenceMapModel.findOne({ field: mapping.field });
|
|
45
|
+
if (existing) {
|
|
46
|
+
await ValueReferenceMapModel.updateOne(
|
|
47
|
+
{ field: mapping.field },
|
|
48
|
+
{ $set: mapping }
|
|
49
|
+
);
|
|
50
|
+
console.log(`š Updated mapping for '${mapping.field}'`);
|
|
51
|
+
} else {
|
|
52
|
+
await ValueReferenceMapModel.create(mapping);
|
|
53
|
+
console.log(`ā Inserted mapping for '${mapping.field}'`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log('\nš± Seeding complete ā
');
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error('ā Seeding failed', err);
|
|
60
|
+
} finally {
|
|
61
|
+
await mongoose.disconnect();
|
|
62
|
+
process.exit();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
seed();
|