@dynamatix/cat-shared 0.0.65 ā 0.0.67
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/README.md +6 -6
- package/index.js +2 -2
- package/middlewares/audit.middleware.js +111 -111
- package/middlewares/index.js +2 -2
- package/models/audit-config.model.js +12 -11
- package/models/audit.model.js +16 -16
- package/models/form-configuration.model.js +257 -255
- package/models/index.js +3 -3
- package/models/value-reference-map.model.js +12 -12
- package/package.json +21 -21
- package/seeders/value-reference-map.seeder.js +66 -66
- package/services/audit-log.hook.js +2 -2
- package/services/index.js +1 -1
- package/services/request-context.service.js +8 -8
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
This is the shared library for Catura Services.
|
|
2
|
-
# Purpose
|
|
3
|
-
|
|
4
|
-
The purposes of this repository are to include:
|
|
5
|
-
- All the models of Catura
|
|
6
|
-
- Shared services in Catura
|
|
1
|
+
This is the shared library for Catura Services.
|
|
2
|
+
# Purpose
|
|
3
|
+
|
|
4
|
+
The purposes of this repository are to include:
|
|
5
|
+
- All the models of Catura
|
|
6
|
+
- Shared services in Catura
|
|
7
7
|
- Shared middlewares in Catura
|
package/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from './models/index.js';
|
|
2
|
-
export * from './middlewares/index.js';
|
|
1
|
+
export * from './models/index.js';
|
|
2
|
+
export * from './middlewares/index.js';
|
|
3
3
|
export * from './services/index.js';
|
|
@@ -1,111 +1,111 @@
|
|
|
1
|
-
import AuditConfigModel from '../models/audit-config.model.js';
|
|
2
|
-
import AuditLog from '../models/audit.model.js';
|
|
3
|
-
import ValueReferenceMap from '../models/value-reference-map.model.js';
|
|
4
|
-
import { getContext } from '../services/request-context.service.js';
|
|
5
|
-
import mongoose from 'mongoose';
|
|
6
|
-
|
|
7
|
-
let onAuditLogCreated = null;
|
|
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
|
-
|
|
28
|
-
function applyAuditMiddleware(schema, collectionName) {
|
|
29
|
-
// Handle creation audit
|
|
30
|
-
schema.post('save', async function (doc) {
|
|
31
|
-
const auditConfig = await AuditConfigModel.findOne({ collectionName });
|
|
32
|
-
if (!auditConfig?.trackCreation) return;
|
|
33
|
-
|
|
34
|
-
const context = getContext();
|
|
35
|
-
const userId = context?.userId || 'anonymous';
|
|
36
|
-
const contextId = context?.contextId;
|
|
37
|
-
|
|
38
|
-
const log = {
|
|
39
|
-
name: `${collectionName} created`,
|
|
40
|
-
recordId: doc._id,
|
|
41
|
-
oldValue: null,
|
|
42
|
-
newValue: 'Document created',
|
|
43
|
-
createdBy: userId,
|
|
44
|
-
contextId
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
await AuditLog.create(log);
|
|
48
|
-
|
|
49
|
-
if (onAuditLogCreated) {
|
|
50
|
-
await onAuditLogCreated([log], contextId);
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
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();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// Handle update audits
|
|
61
|
-
schema.post(['findOneAndUpdate', 'findByIdAndUpdate'], async function (result) {
|
|
62
|
-
if (!result || !this._originalDoc) return;
|
|
63
|
-
|
|
64
|
-
const auditConfig = await AuditConfigModel.findOne({ collectionName });
|
|
65
|
-
if (!auditConfig?.fields?.length) return;
|
|
66
|
-
|
|
67
|
-
const update = this.getUpdate();
|
|
68
|
-
const logs = [];
|
|
69
|
-
const context = getContext();
|
|
70
|
-
const userId = context?.userId || 'anonymous';
|
|
71
|
-
const contextId = context?.contextId;
|
|
72
|
-
|
|
73
|
-
for (const field of auditConfig.fields) {
|
|
74
|
-
const hasChanged =
|
|
75
|
-
update?.$set?.hasOwnProperty(field) || update?.hasOwnProperty(field);
|
|
76
|
-
|
|
77
|
-
if (hasChanged) {
|
|
78
|
-
const newValue = update?.$set?.[field] ?? update?.[field];
|
|
79
|
-
const oldValue = this._originalDoc[field];
|
|
80
|
-
|
|
81
|
-
if (oldValue !== newValue) {
|
|
82
|
-
// Resolve human-readable values
|
|
83
|
-
const { oldValue: resolvedOld, newValue: resolvedNew } =
|
|
84
|
-
await resolveAuditValues(field, oldValue, newValue);
|
|
85
|
-
|
|
86
|
-
logs.push({
|
|
87
|
-
name: `${collectionName}.${field}`,
|
|
88
|
-
recordId: result._id,
|
|
89
|
-
oldValue: resolvedOld,
|
|
90
|
-
newValue: resolvedNew,
|
|
91
|
-
createdBy: userId,
|
|
92
|
-
contextId
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (logs.length) {
|
|
99
|
-
await AuditLog.insertMany(logs);
|
|
100
|
-
if (onAuditLogCreated) {
|
|
101
|
-
await onAuditLogCreated(logs, contextId);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function registerAuditHook(callback) {
|
|
108
|
-
onAuditLogCreated = callback;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export default applyAuditMiddleware;
|
|
1
|
+
import AuditConfigModel from '../models/audit-config.model.js';
|
|
2
|
+
import AuditLog from '../models/audit.model.js';
|
|
3
|
+
import ValueReferenceMap from '../models/value-reference-map.model.js';
|
|
4
|
+
import { getContext } from '../services/request-context.service.js';
|
|
5
|
+
import mongoose from 'mongoose';
|
|
6
|
+
|
|
7
|
+
let onAuditLogCreated = null;
|
|
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
|
+
|
|
28
|
+
function applyAuditMiddleware(schema, collectionName) {
|
|
29
|
+
// Handle creation audit
|
|
30
|
+
schema.post('save', async function (doc) {
|
|
31
|
+
const auditConfig = await AuditConfigModel.findOne({ collectionName });
|
|
32
|
+
if (!auditConfig?.trackCreation) return;
|
|
33
|
+
|
|
34
|
+
const context = getContext();
|
|
35
|
+
const userId = context?.userId || 'anonymous';
|
|
36
|
+
const contextId = context?.contextId;
|
|
37
|
+
|
|
38
|
+
const log = {
|
|
39
|
+
name: `${collectionName} created`,
|
|
40
|
+
recordId: doc._id,
|
|
41
|
+
oldValue: null,
|
|
42
|
+
newValue: 'Document created',
|
|
43
|
+
createdBy: userId,
|
|
44
|
+
contextId
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
await AuditLog.create(log);
|
|
48
|
+
|
|
49
|
+
if (onAuditLogCreated) {
|
|
50
|
+
await onAuditLogCreated([log], contextId);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
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();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Handle update audits
|
|
61
|
+
schema.post(['findOneAndUpdate', 'findByIdAndUpdate'], async function (result) {
|
|
62
|
+
if (!result || !this._originalDoc) return;
|
|
63
|
+
|
|
64
|
+
const auditConfig = await AuditConfigModel.findOne({ collectionName });
|
|
65
|
+
if (!auditConfig?.fields?.length) return;
|
|
66
|
+
|
|
67
|
+
const update = this.getUpdate();
|
|
68
|
+
const logs = [];
|
|
69
|
+
const context = getContext();
|
|
70
|
+
const userId = context?.userId || 'anonymous';
|
|
71
|
+
const contextId = context?.contextId;
|
|
72
|
+
|
|
73
|
+
for (const field of auditConfig.fields) {
|
|
74
|
+
const hasChanged =
|
|
75
|
+
update?.$set?.hasOwnProperty(field) || update?.hasOwnProperty(field);
|
|
76
|
+
|
|
77
|
+
if (hasChanged) {
|
|
78
|
+
const newValue = update?.$set?.[field] ?? update?.[field];
|
|
79
|
+
const oldValue = this._originalDoc[field];
|
|
80
|
+
|
|
81
|
+
if (oldValue !== newValue) {
|
|
82
|
+
// Resolve human-readable values
|
|
83
|
+
const { oldValue: resolvedOld, newValue: resolvedNew } =
|
|
84
|
+
await resolveAuditValues(field, oldValue, newValue);
|
|
85
|
+
|
|
86
|
+
logs.push({
|
|
87
|
+
name: `${collectionName}.${field}`,
|
|
88
|
+
recordId: result._id,
|
|
89
|
+
oldValue: resolvedOld,
|
|
90
|
+
newValue: resolvedNew,
|
|
91
|
+
createdBy: userId,
|
|
92
|
+
contextId
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (logs.length) {
|
|
99
|
+
await AuditLog.insertMany(logs);
|
|
100
|
+
if (onAuditLogCreated) {
|
|
101
|
+
await onAuditLogCreated(logs, contextId);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function registerAuditHook(callback) {
|
|
108
|
+
onAuditLogCreated = callback;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default applyAuditMiddleware;
|
package/middlewares/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {default as applyAuditMiddleware} from './audit.middleware.js';
|
|
2
|
-
export { registerAuditHook } from './audit.middleware.js';
|
|
1
|
+
export {default as applyAuditMiddleware} from './audit.middleware.js';
|
|
2
|
+
export { registerAuditHook } from './audit.middleware.js';
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import mongoose from 'mongoose';
|
|
2
|
-
|
|
3
|
-
const auditConfigSchema = new mongoose.Schema({
|
|
4
|
-
collectionName: String,
|
|
5
|
-
fields: [String],
|
|
6
|
-
trackCreation: { type: Boolean, default: false }
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
|
|
3
|
+
const auditConfigSchema = new mongoose.Schema({
|
|
4
|
+
collectionName: String,
|
|
5
|
+
fields: [String],
|
|
6
|
+
trackCreation: { type: Boolean, default: false },
|
|
7
|
+
descriptionResolutorForExternalData: { type: String, default: null },
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const AuditConfigModel = mongoose.model('AuditConfig', auditConfigSchema);
|
|
11
|
+
|
|
12
|
+
export default AuditConfigModel;
|
package/models/audit.model.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import mongoose from 'mongoose';
|
|
2
|
-
|
|
3
|
-
const auditSchema = new mongoose.Schema({
|
|
4
|
-
name: String,
|
|
5
|
-
recordId: mongoose.Schema.Types.ObjectId,
|
|
6
|
-
contextId: mongoose.Schema.Types.ObjectId,
|
|
7
|
-
oldValue: mongoose.Schema.Types.Mixed,
|
|
8
|
-
newValue: mongoose.Schema.Types.Mixed,
|
|
9
|
-
timestamp: { type: Date, default: Date.now },
|
|
10
|
-
createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
|
|
11
|
-
externalData: mongoose.Schema.Types.Mixed
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
const AuditLog = mongoose.model('AuditLog', auditSchema);
|
|
15
|
-
|
|
16
|
-
export default AuditLog;
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
|
|
3
|
+
const auditSchema = new mongoose.Schema({
|
|
4
|
+
name: String,
|
|
5
|
+
recordId: mongoose.Schema.Types.ObjectId,
|
|
6
|
+
contextId: mongoose.Schema.Types.ObjectId,
|
|
7
|
+
oldValue: mongoose.Schema.Types.Mixed,
|
|
8
|
+
newValue: mongoose.Schema.Types.Mixed,
|
|
9
|
+
timestamp: { type: Date, default: Date.now },
|
|
10
|
+
createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
|
|
11
|
+
externalData: mongoose.Schema.Types.Mixed
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const AuditLog = mongoose.model('AuditLog', auditSchema);
|
|
15
|
+
|
|
16
|
+
export default AuditLog;
|
|
@@ -1,255 +1,257 @@
|
|
|
1
|
-
import mongoose from 'mongoose';
|
|
2
|
-
|
|
3
|
-
const optionSchema = new mongoose.Schema({
|
|
4
|
-
label: { type: mongoose.Schema.Types.Mixed },
|
|
5
|
-
value: { type: mongoose.Schema.Types.Mixed }
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
|
|
3
|
+
const optionSchema = new mongoose.Schema({
|
|
4
|
+
label: { type: mongoose.Schema.Types.Mixed },
|
|
5
|
+
value: { type: mongoose.Schema.Types.Mixed },
|
|
6
|
+
text: { type: mongoose.Schema.Types.Mixed }
|
|
7
|
+
|
|
8
|
+
}, { _id: false });
|
|
9
|
+
|
|
10
|
+
const formfieldSchema = new mongoose.Schema({
|
|
11
|
+
isFutureDateBlocked: {
|
|
12
|
+
type: Boolean,
|
|
13
|
+
},
|
|
14
|
+
addToPayload: {
|
|
15
|
+
type: Boolean,
|
|
16
|
+
default: false
|
|
17
|
+
},
|
|
18
|
+
fieldName: {
|
|
19
|
+
type: String,
|
|
20
|
+
required: true,
|
|
21
|
+
trim: true
|
|
22
|
+
},
|
|
23
|
+
isHidden: {
|
|
24
|
+
type: Boolean,
|
|
25
|
+
default: false
|
|
26
|
+
},
|
|
27
|
+
isEditable: {
|
|
28
|
+
type: Boolean,
|
|
29
|
+
default: true
|
|
30
|
+
},
|
|
31
|
+
size: {
|
|
32
|
+
type: Number,
|
|
33
|
+
default: 6,
|
|
34
|
+
enum: [3, 4, 6, 8, 10, 12],
|
|
35
|
+
},
|
|
36
|
+
layout: {
|
|
37
|
+
type: String,
|
|
38
|
+
enum: ['horizontal', 'vertical'],
|
|
39
|
+
},
|
|
40
|
+
label: {
|
|
41
|
+
type: String,
|
|
42
|
+
required: true,
|
|
43
|
+
trim: true
|
|
44
|
+
},
|
|
45
|
+
dataType: {
|
|
46
|
+
type: String,
|
|
47
|
+
required: true,
|
|
48
|
+
enum: ['text-area', 'string', 'number', 'boolean-check', 'boolean-radio', 'percentage', 'date', 'array', 'object', 'select', 'radio', 'drop-down', 'sort-code', 'post-code', 'pound', 'lookup', 'multi-select', 'email', 'phone', 'internationalPhone'] // Added common form field types
|
|
49
|
+
},
|
|
50
|
+
isRequired: {
|
|
51
|
+
type: Boolean,
|
|
52
|
+
default: false
|
|
53
|
+
},
|
|
54
|
+
helpText: {
|
|
55
|
+
type: String,
|
|
56
|
+
default: null
|
|
57
|
+
},
|
|
58
|
+
isForceUpdate: {
|
|
59
|
+
type: Boolean,
|
|
60
|
+
default: false
|
|
61
|
+
},
|
|
62
|
+
requiredErrorMessage: {
|
|
63
|
+
type: String,
|
|
64
|
+
default: 'Field is required'
|
|
65
|
+
},
|
|
66
|
+
defaultValue: {
|
|
67
|
+
type: mongoose.Schema.Types.Mixed
|
|
68
|
+
},
|
|
69
|
+
validationRules: [{
|
|
70
|
+
type: mongoose.Schema.Types.Mixed,
|
|
71
|
+
default: {}
|
|
72
|
+
}],
|
|
73
|
+
visibilityCondition: { // (LendingType=='BTL' || ApplicationType=='Company')
|
|
74
|
+
type: mongoose.Schema.Types.Mixed,
|
|
75
|
+
default: null
|
|
76
|
+
},
|
|
77
|
+
options: [optionSchema],
|
|
78
|
+
section: {
|
|
79
|
+
type: String,
|
|
80
|
+
default: null
|
|
81
|
+
},
|
|
82
|
+
// Ex: sort-code, post-code, pound, drop-down, lookup
|
|
83
|
+
uiHint: {
|
|
84
|
+
type: String
|
|
85
|
+
},
|
|
86
|
+
list: {
|
|
87
|
+
name: { // Lookup.BusinessType, DocumentType(collection)
|
|
88
|
+
type: String
|
|
89
|
+
},
|
|
90
|
+
label: { // name
|
|
91
|
+
type: String
|
|
92
|
+
},
|
|
93
|
+
value: { // _id
|
|
94
|
+
type: String
|
|
95
|
+
},
|
|
96
|
+
listFilterQuery: { // MongoDB query ex: applicationId:applicationId
|
|
97
|
+
type: mongoose.Schema.Types.Mixed,
|
|
98
|
+
default: null
|
|
99
|
+
},
|
|
100
|
+
dynamicSource: {
|
|
101
|
+
sourceMapping: [{
|
|
102
|
+
sourceField: {
|
|
103
|
+
type: String,
|
|
104
|
+
default: null
|
|
105
|
+
},
|
|
106
|
+
sourceValue: { type: mongoose.Schema.Types.Mixed },
|
|
107
|
+
listName: { type: String }, // The collection name to use when source field has this value
|
|
108
|
+
fixedValue: { type: mongoose.Schema.Types.Mixed } // It's not req , and it can be a number like 0 , or a string etc.
|
|
109
|
+
}]
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
isSyncEnabled: { // Fixed typo (was isSyncEnabed)
|
|
113
|
+
type: Boolean,
|
|
114
|
+
default: true
|
|
115
|
+
},
|
|
116
|
+
dataElement: { //<collectionName>.<fieldName>
|
|
117
|
+
type: String,
|
|
118
|
+
default: null
|
|
119
|
+
},
|
|
120
|
+
syncTargetDataElement: { //<collectionName>.<fieldName> of Apprivo
|
|
121
|
+
type: String,
|
|
122
|
+
default: null
|
|
123
|
+
},
|
|
124
|
+
syncMapping: mongoose.Schema.Types.Mixed, // Ex: {true:'Checked', false: ''}
|
|
125
|
+
order: {
|
|
126
|
+
type: Number,
|
|
127
|
+
default: 0
|
|
128
|
+
},
|
|
129
|
+
maxLength: {
|
|
130
|
+
type: Number,
|
|
131
|
+
default: null,
|
|
132
|
+
min: 0
|
|
133
|
+
},
|
|
134
|
+
maxValue: { // this is not required used only when given else ignore ex- 100% max
|
|
135
|
+
type: mongoose.Schema.Types.Mixed
|
|
136
|
+
},
|
|
137
|
+
minValue: { // this is not required used only when given else ignore ex- 0% min
|
|
138
|
+
type: Number
|
|
139
|
+
},
|
|
140
|
+
isReadOnly: {
|
|
141
|
+
type: Boolean,
|
|
142
|
+
default: false
|
|
143
|
+
},
|
|
144
|
+
collectionName: { // name of collection for field if field doesnt belong to main collection
|
|
145
|
+
type: String,
|
|
146
|
+
default: null
|
|
147
|
+
},
|
|
148
|
+
foreignReferenceField: { // Ex: when field is from forieng collection then foreignReferenceField could be applicationId(which refers to the application)
|
|
149
|
+
type: String,
|
|
150
|
+
default: null
|
|
151
|
+
},
|
|
152
|
+
shouldfilterOptionsAfterAdd: {
|
|
153
|
+
type : Boolean,
|
|
154
|
+
default : false
|
|
155
|
+
},
|
|
156
|
+
shouldfilterOptionsAfterDelete: {
|
|
157
|
+
type : Boolean,
|
|
158
|
+
default : false
|
|
159
|
+
}
|
|
160
|
+
}, { timestamps: true, _id: false });
|
|
161
|
+
|
|
162
|
+
const formConfigurationSchema = new mongoose.Schema({
|
|
163
|
+
formName: {
|
|
164
|
+
type: String,
|
|
165
|
+
required: true,
|
|
166
|
+
trim: true,
|
|
167
|
+
unique: true // Consider making form names unique
|
|
168
|
+
},
|
|
169
|
+
collectionName: {
|
|
170
|
+
type: String,
|
|
171
|
+
required: true
|
|
172
|
+
},
|
|
173
|
+
sectionLayout: {
|
|
174
|
+
type: String,
|
|
175
|
+
trim: true
|
|
176
|
+
},
|
|
177
|
+
parentKey: String,
|
|
178
|
+
populations: [Object],
|
|
179
|
+
visibilityCondition: {
|
|
180
|
+
type: mongoose.Schema.Types.Mixed,
|
|
181
|
+
default: null
|
|
182
|
+
},
|
|
183
|
+
sections: [{
|
|
184
|
+
sectionName: {
|
|
185
|
+
type: String,
|
|
186
|
+
trim: true
|
|
187
|
+
},
|
|
188
|
+
sectionLabel: {
|
|
189
|
+
type: String,
|
|
190
|
+
trim: true
|
|
191
|
+
},
|
|
192
|
+
isArray: {
|
|
193
|
+
type: Boolean,
|
|
194
|
+
default: false
|
|
195
|
+
},
|
|
196
|
+
isTable: {
|
|
197
|
+
type: Boolean,
|
|
198
|
+
default: false
|
|
199
|
+
},
|
|
200
|
+
dataFromSectionName: {
|
|
201
|
+
type: Boolean,
|
|
202
|
+
default: false
|
|
203
|
+
},
|
|
204
|
+
fields: [formfieldSchema],
|
|
205
|
+
order: {
|
|
206
|
+
type: Number,
|
|
207
|
+
default: 0
|
|
208
|
+
},
|
|
209
|
+
visibilityCondition: { // Added section-level visibility
|
|
210
|
+
type: mongoose.Schema.Types.Mixed,
|
|
211
|
+
default: null
|
|
212
|
+
},
|
|
213
|
+
sections: [{
|
|
214
|
+
sectionName: {
|
|
215
|
+
type: String,
|
|
216
|
+
trim: true
|
|
217
|
+
},
|
|
218
|
+
sectionLabel: {
|
|
219
|
+
type: String,
|
|
220
|
+
trim: true
|
|
221
|
+
},
|
|
222
|
+
fields: [formfieldSchema],
|
|
223
|
+
order: {
|
|
224
|
+
type: Number,
|
|
225
|
+
default: 0
|
|
226
|
+
},
|
|
227
|
+
isTable: {
|
|
228
|
+
type: Boolean,
|
|
229
|
+
default: false
|
|
230
|
+
},
|
|
231
|
+
visibilityCondition: { // Added section-level visibility
|
|
232
|
+
type: mongoose.Schema.Types.Mixed,
|
|
233
|
+
default: null
|
|
234
|
+
}
|
|
235
|
+
}]
|
|
236
|
+
}],
|
|
237
|
+
isActive: { // Consider adding status flag
|
|
238
|
+
type: Boolean,
|
|
239
|
+
default: true
|
|
240
|
+
},
|
|
241
|
+
isDeleteApprivoSync: {
|
|
242
|
+
type: Boolean,
|
|
243
|
+
default: true
|
|
244
|
+
},
|
|
245
|
+
isCreateApprivoSync: {
|
|
246
|
+
type: Boolean,
|
|
247
|
+
default: true
|
|
248
|
+
},
|
|
249
|
+
version: { // Consider adding versioning
|
|
250
|
+
type: Number,
|
|
251
|
+
default: 1
|
|
252
|
+
}
|
|
253
|
+
}, { timestamps: true });
|
|
254
|
+
|
|
255
|
+
const FormConfigurationModel = mongoose.model('FormConfiguration', formConfigurationSchema);
|
|
256
|
+
|
|
257
|
+
export default FormConfigurationModel;
|
package/models/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { default as AuditConfigModel } from './audit-config.model.js';
|
|
2
|
-
export { default as AuditModel } from './audit.model.js';
|
|
3
|
-
export { default as ValueReferenceMapModel } from './value-reference-map.model.js';
|
|
1
|
+
export { default as AuditConfigModel } from './audit-config.model.js';
|
|
2
|
+
export { default as AuditModel } from './audit.model.js';
|
|
3
|
+
export { default as ValueReferenceMapModel } from './value-reference-map.model.js';
|
|
4
4
|
export { default as FormConfigurationModel } from './form-configuration.model.js';
|
|
@@ -1,12 +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;
|
|
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,21 +1,21 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@dynamatix/cat-shared",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"main": "index.js",
|
|
5
|
-
"scripts": {
|
|
6
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
7
|
-
},
|
|
8
|
-
"author": "",
|
|
9
|
-
"license": "ISC",
|
|
10
|
-
"type": "module",
|
|
11
|
-
"description": "",
|
|
12
|
-
"exports": {
|
|
13
|
-
"./models": "./models/index.js",
|
|
14
|
-
"./middlewares": "./middlewares/index.js",
|
|
15
|
-
"./services": "./services/index.js"
|
|
16
|
-
},
|
|
17
|
-
"dependencies": {
|
|
18
|
-
"dotenv": "^16.4.7",
|
|
19
|
-
"mongoose": "^8.13.1"
|
|
20
|
-
}
|
|
21
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@dynamatix/cat-shared",
|
|
3
|
+
"version": "0.0.67",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
7
|
+
},
|
|
8
|
+
"author": "",
|
|
9
|
+
"license": "ISC",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"description": "",
|
|
12
|
+
"exports": {
|
|
13
|
+
"./models": "./models/index.js",
|
|
14
|
+
"./middlewares": "./middlewares/index.js",
|
|
15
|
+
"./services": "./services/index.js"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"dotenv": "^16.4.7",
|
|
19
|
+
"mongoose": "^8.13.1"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,66 +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();
|
|
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', 'applicantId']
|
|
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();
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
|
|
2
|
+
|
package/services/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { getContext, setContext } from './request-context.service.js';
|
|
1
|
+
export { getContext, setContext } from './request-context.service.js';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// utils/request-context.js
|
|
2
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
|
-
|
|
4
|
-
const asyncLocalStorage = new AsyncLocalStorage();
|
|
5
|
-
|
|
6
|
-
export const setContext = (data) => asyncLocalStorage.enterWith(data);
|
|
7
|
-
|
|
8
|
-
export const getContext = () => asyncLocalStorage.getStore();
|
|
1
|
+
// utils/request-context.js
|
|
2
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
|
+
|
|
4
|
+
const asyncLocalStorage = new AsyncLocalStorage();
|
|
5
|
+
|
|
6
|
+
export const setContext = (data) => asyncLocalStorage.enterWith(data);
|
|
7
|
+
|
|
8
|
+
export const getContext = () => asyncLocalStorage.getStore();
|