@justair/justair-library 4.7.25 → 4.7.26

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,400 +1,401 @@
1
- import mongoose from "mongoose";
2
- const parametersEnum = [
3
- "NO2",
4
- "SO2",
5
- "PM2.5",
6
- "PM10",
7
- "Temperature",
8
- "Humidity",
9
- "OZONE",
10
- "VOC",
11
- "CO",
12
- "NO",
13
- "PM1",
14
- "WS And Direction",
15
- "DP",
16
- "CH4",
17
- ];
18
-
19
- const noteSchema = mongoose.Schema({
20
- note: {
21
- type: String,
22
- required: true,
23
- },
24
- type: {
25
- type: String,
26
- required: true,
27
- },
28
- monitorState: {
29
- type: String,
30
- required: true,
31
- },
32
- adminId: {
33
- type: mongoose.Types.ObjectId,
34
- ref: "Admin",
35
- required: true,
36
- },
37
- adminName: {
38
- type: String,
39
- },
40
- date: {
41
- type: Date,
42
- default: Date.now,
43
- },
44
- });
45
-
46
- const correctionSchema = mongoose.Schema(
47
- {
48
- equationType: {
49
- type: String,
50
- enum: ["linear", "custom"],
51
- required: true,
52
- },
53
- equation: {
54
- type: String,
55
- required: function () {
56
- return this.equationType === "custom";
57
- },
58
- validate: {
59
- validator: function (value) {
60
- if (!value) return true; // Allow empty for non-custom types
61
- if (value.includes("\0")) return false;
62
- try {
63
- const encoder = new TextEncoder();
64
- const decoder = new TextDecoder("utf-8", { fatal: true });
65
- decoder.decode(encoder.encode(value));
66
- return true;
67
- } catch (e) {
68
- return false;
69
- }
70
- },
71
- message: "Equation contains invalid UTF-8 characters",
72
- },
73
- },
74
- context: {
75
- type: String,
76
- enum: ["field", "colocation"],
77
- required: false,
78
- },
79
- variables: {
80
- required: function () {
81
- return this.equationType === "linear";
82
- },
83
- type: Object,
84
- },
85
- dateCreated: {
86
- type: Date,
87
- default: Date.now,
88
- },
89
- applyCorrection: { type: Boolean, default: true },
90
- },
91
- { _id: false } // no separate _id for sub-docs
92
- );
93
-
94
- const correctionHistorySchema = mongoose.Schema(
95
- {
96
- pollutant: {
97
- type: String,
98
- required: true,
99
- validate: {
100
- validator: function (value) {
101
- // Check for null bytes and validate UTF-8
102
- if (value.includes("\0")) return false;
103
- try {
104
- const encoder = new TextEncoder();
105
- const decoder = new TextDecoder("utf-8", { fatal: true });
106
- decoder.decode(encoder.encode(value));
107
- return true;
108
- } catch (e) {
109
- return false;
110
- }
111
- },
112
- message: "Pollutant contains invalid UTF-8 characters",
113
- },
114
- },
115
- oldValue: {
116
- type: correctionSchema,
117
- default: undefined,
118
- },
119
- newValue: {
120
- type: correctionSchema,
121
- default: undefined,
122
- },
123
- changedAt: {
124
- type: Date,
125
- default: Date.now,
126
- },
127
- },
128
- { _id: false }
129
- );
130
-
131
- // Monitor Audit Schema
132
- const monitorAuditSchema = mongoose.Schema(
133
- {
134
- monitorId: { type: mongoose.Types.ObjectId, ref: "Monitors" },
135
- orgId: { type: mongoose.Types.ObjectId, ref: "Organizations" },
136
- timeUpdated: Date,
137
- deletedAt: { type: Date, default: Date.now }, // Only populated on delete
138
- monitorProperties: Object, // Stores properties of the monitor
139
- monitorState: String, // Tracks the state of the monitor (e.g., "Deployed", "Maintenance")
140
- monitorLocation: {
141
- // Monitor GPS location (lat, lon)
142
- type: { type: String, enum: ["Point"], required: true },
143
- coordinates: { type: [Number], required: true },
144
- },
145
- parameterThresholds: Object,
146
- corrections: {
147
- type: Map,
148
- of: correctionSchema,
149
- default: {},
150
- },
151
- correctionHistory: {
152
- type: [correctionHistorySchema],
153
- default: [],
154
- },
155
- },
156
- {
157
- timestamps: true,
158
- }
159
- );
160
-
161
- // Create the MonitorAudit model
162
- const MonitorAudit = mongoose.model("MonitorAudit", monitorAuditSchema);
163
-
164
- // Monitors Schema
165
- const monitorsSchema = mongoose.Schema(
166
- {
167
- monitorCode: String,
168
- monitorSupplier: {
169
- type: String,
170
- enum: [
171
- "clarity",
172
- "aeroqual",
173
- "purple air",
174
- "reference monitor",
175
- "earthview",
176
- "sensit",
177
- "blue sky",
178
- "aq mesh",
179
- "quant aq",
180
- "airGradient",
181
- "oizom",
182
- "metOne",
183
- "aqMesh",
184
- ],
185
- },
186
- monitorType: String,
187
- monitorIdFromSupplier: String,
188
- measurementUpdate: Date,
189
- monitorProperties: Object,
190
- isPrivate: { type: Boolean, default: false, required: true },
191
- monitorState: {
192
- type: String,
193
- enum: ["Collocation", "Deployed", "Maintenance", "Pending Deployment"],
194
- },
195
- monitorStateHistory: [Object],
196
- monitorAlertStatus: {
197
- type: String,
198
- enum: [
199
- "Good",
200
- "Moderate",
201
- "Unhealthy for SG",
202
- "Unhealthy",
203
- "Very Unhealthy",
204
- "Hazardous",
205
- "Bad",
206
- ],
207
- default: "Good",
208
- },
209
- sponsor: { type: mongoose.Types.ObjectId, ref: "Organizations" },
210
- sponsorName: String,
211
- monitorLatitude: Number,
212
- monitorLongitude: Number,
213
- gpsLocation: {
214
- type: { type: String, enum: ["Point"], required: true },
215
- coordinates: { type: [Number], required: true },
216
- },
217
- location: Object,
218
- context: [String],
219
- colocationDate: Date,
220
- deploymentDate: Date,
221
- subscriptionDate: Date,
222
- parameters: [
223
- {
224
- type: String,
225
- enum: parametersEnum,
226
- },
227
- ],
228
- latestPM2_5: Number,
229
- latestAQI_PM2_5: Number,
230
- notes: [noteSchema],
231
- calculatedAverages: [Object],
232
- images: [String],
233
- isActive: { type: Boolean, default: true },
234
- parameterThresholds: Object,
235
- completionPercentageThresholds: Object,
236
- anomalyPercentageThresholds: Object,
237
- // A Map, keyed by pollutant (e.g. "PM2_5"), storing Correction sub-docs
238
- corrections: {
239
- type: Map,
240
- of: correctionSchema,
241
- default: {},
242
- },
243
- correctionHistory: {
244
- type: [correctionHistorySchema],
245
- default: [],
246
- },
247
- applyCorrections: { type: Boolean, default: false },
248
- pausedParameters: {
249
- type: [{ parameter: String, timestamp: Date, isPaused: Boolean }],
250
- default: [],
251
- },
252
- },
253
- {
254
- timestamps: true,
255
- }
256
- );
257
-
258
- // Geographic queries - already exists
259
- monitorsSchema.index({ gpsLocation: "2dsphere" });
260
-
261
- // Sponsor-based queries
262
- monitorsSchema.index({ sponsor: 1, isPrivate: 1, isActive: 1 });
263
-
264
- // Location-based filtering
265
- monitorsSchema.index({ "location.city": 1, "location.state": 1 });
266
- monitorsSchema.index({ "location.neighborhood": 1 });
267
- monitorsSchema.index({ "location.county": 1 });
268
-
269
- // Query parameter filtering
270
- monitorsSchema.index({ monitorSupplier: 1, monitorState: 1 });
271
- monitorsSchema.index({ context: 1 });
272
- monitorsSchema.index({ parameters: 1 });
273
-
274
- // Monitor lookups by supplier
275
- monitorsSchema.index({ sponsor: 1, monitorSupplier: 1 });
276
-
277
- // Keep existing single-field indexes for backward compatibility
278
- monitorsSchema.index({ monitorSupplier: 1 });
279
- monitorsSchema.index({ monitorIdFromSupplier: 1 });
280
- monitorsSchema.index({ monitorState: 1 });
281
-
282
- //network metrics for anomalies
283
- monitorsSchema.index({ sponsor: 1, isActive: 1, monitorSupplier: 1 })
284
-
285
- // Pre-hook to log single document deletions
286
- monitorsSchema.pre("findOneAndDelete", async function () {
287
- const docToDelete = await this.model.findOne(this.getFilter()).lean();
288
- if (docToDelete) {
289
- console.log("Logging findOneAndDelete to monitor audit", docToDelete);
290
- const auditLog = new MonitorAudit({
291
- monitorId: docToDelete._id,
292
- orgId: docToDelete.sponsor,
293
- timeUpdated: docToDelete.updatedAt,
294
- monitorProperties: docToDelete.monitorProperties,
295
- monitorState: docToDelete.monitorState,
296
- monitorLocation: {
297
- type: "Point",
298
- coordinates: [
299
- docToDelete.monitorLongitude,
300
- docToDelete.monitorLatitude,
301
- ],
302
- },
303
- parameterThresholds: docToDelete.parameterThresholds,
304
- corrections: docToDelete.corrections,
305
- correctionHistory: docToDelete.correctionHistory,
306
- applyCorrections: docToDelete.applyCorrections,
307
- deletedAt: new Date(),
308
- });
309
- await auditLog.save();
310
- }
311
- });
312
-
313
- // Pre-hook to log multiple document deletions
314
- monitorsSchema.pre("deleteMany", async function () {
315
- console.log("deleteMany pre-hook triggered for monitors");
316
- const docsToDelete = await this.model.find(this.getFilter()).lean();
317
-
318
- if (docsToDelete.length) {
319
- console.log(`Logging ${docsToDelete.length} monitor documents to audit`);
320
- const auditLogs = docsToDelete.map((doc) => ({
321
- monitorId: doc._id,
322
- orgId: doc.sponsor,
323
- timeUpdated: doc.updatedAt,
324
- monitorProperties: doc.monitorProperties,
325
- monitorState: doc.monitorState,
326
- monitorLocation: {
327
- type: "Point",
328
- coordinates: [doc.monitorLongitude, doc.monitorLatitude],
329
- },
330
- parameterThresholds: doc.parameterThresholds,
331
- corrections: doc.corrections,
332
- correctionHistory: doc.correctionHistory,
333
- applyCorrections: doc.applyCorrections,
334
- deletedAt: new Date(),
335
- }));
336
-
337
- await MonitorAudit.insertMany(auditLogs);
338
- }
339
- });
340
-
341
- // Pre-hook to log a single document deletion (for deleteOne)
342
- monitorsSchema.pre("deleteOne", async function () {
343
- console.log("deleteOne pre-hook triggered for monitors");
344
- const docToDelete = await this.model.findOne(this.getFilter()).lean();
345
-
346
- if (docToDelete) {
347
- console.log("Logging deleteOne to monitor audit", docToDelete);
348
- const auditLog = new MonitorAudit({
349
- monitorId: docToDelete._id,
350
- orgId: docToDelete.sponsor,
351
- timeUpdated: docToDelete.updatedAt,
352
- monitorProperties: docToDelete.monitorProperties,
353
- monitorState: docToDelete.monitorState,
354
- monitorLocation: {
355
- type: "Point",
356
- coordinates: [
357
- docToDelete.monitorLongitude,
358
- docToDelete.monitorLatitude,
359
- ],
360
- },
361
- parameterThresholds: docToDelete.parameterThresholds,
362
- corrections: docToDelete.corrections,
363
- correctionHistory: docToDelete.correctionHistory,
364
- applyCorrections: docToDelete.applyCorrections,
365
- deletedAt: new Date(),
366
- });
367
- await auditLog.save();
368
- }
369
- });
370
-
371
- // Pre-hook to log multiple document updates
372
- monitorsSchema.pre("updateMany", async function () {
373
- const docsToUpdate = await this.model.find(this.getFilter()).lean();
374
- if (docsToUpdate.length) {
375
- console.log(`Logging ${docsToUpdate.length} monitor documents to audit`);
376
- const auditLogs = docsToUpdate.map((doc) => ({
377
- monitorId: doc._id,
378
- orgId: doc.sponsor,
379
- timeUpdated: doc.updatedAt,
380
- monitorProperties: doc.monitorProperties,
381
- monitorState: doc.monitorState,
382
- monitorLocation: {
383
- type: "Point",
384
- coordinates: [doc.monitorLongitude, doc.monitorLatitude],
385
- },
386
- parameterThresholds: doc.parameterThresholds,
387
- corrections: doc.corrections,
388
- correctionHistory: doc.correctionHistory,
389
- applyCorrections: doc.applyCorrections,
390
- deletedAt: null, // Not a deletion, so this field is null
391
- }));
392
-
393
- await MonitorAudit.insertMany(auditLogs);
394
- }
395
- });
396
-
397
- // Create the Monitors model
398
- const Monitors = mongoose.model("Monitors", monitorsSchema);
399
-
400
- export { monitorsSchema, Monitors, monitorAuditSchema, MonitorAudit };
1
+ import mongoose from "mongoose";
2
+ const parametersEnum = [
3
+ "NO2",
4
+ "SO2",
5
+ "PM2.5",
6
+ "PM10",
7
+ "Temperature",
8
+ "Humidity",
9
+ "OZONE",
10
+ "VOC",
11
+ "CO",
12
+ "NO",
13
+ "PM1",
14
+ "WS And Direction",
15
+ "DP",
16
+ "CO2",
17
+ "CH4",
18
+ ];
19
+
20
+ const noteSchema = mongoose.Schema({
21
+ note: {
22
+ type: String,
23
+ required: true,
24
+ },
25
+ type: {
26
+ type: String,
27
+ required: true,
28
+ },
29
+ monitorState: {
30
+ type: String,
31
+ required: true,
32
+ },
33
+ adminId: {
34
+ type: mongoose.Types.ObjectId,
35
+ ref: "Admin",
36
+ required: true,
37
+ },
38
+ adminName: {
39
+ type: String,
40
+ },
41
+ date: {
42
+ type: Date,
43
+ default: Date.now,
44
+ },
45
+ });
46
+
47
+ const correctionSchema = mongoose.Schema(
48
+ {
49
+ equationType: {
50
+ type: String,
51
+ enum: ["linear", "custom"],
52
+ required: true,
53
+ },
54
+ equation: {
55
+ type: String,
56
+ required: function () {
57
+ return this.equationType === "custom";
58
+ },
59
+ validate: {
60
+ validator: function (value) {
61
+ if (!value) return true; // Allow empty for non-custom types
62
+ if (value.includes("\0")) return false;
63
+ try {
64
+ const encoder = new TextEncoder();
65
+ const decoder = new TextDecoder("utf-8", { fatal: true });
66
+ decoder.decode(encoder.encode(value));
67
+ return true;
68
+ } catch (e) {
69
+ return false;
70
+ }
71
+ },
72
+ message: "Equation contains invalid UTF-8 characters",
73
+ },
74
+ },
75
+ context: {
76
+ type: String,
77
+ enum: ["field", "colocation"],
78
+ required: false,
79
+ },
80
+ variables: {
81
+ required: function () {
82
+ return this.equationType === "linear";
83
+ },
84
+ type: Object,
85
+ },
86
+ dateCreated: {
87
+ type: Date,
88
+ default: Date.now,
89
+ },
90
+ applyCorrection: { type: Boolean, default: true },
91
+ },
92
+ { _id: false } // no separate _id for sub-docs
93
+ );
94
+
95
+ const correctionHistorySchema = mongoose.Schema(
96
+ {
97
+ pollutant: {
98
+ type: String,
99
+ required: true,
100
+ validate: {
101
+ validator: function (value) {
102
+ // Check for null bytes and validate UTF-8
103
+ if (value.includes("\0")) return false;
104
+ try {
105
+ const encoder = new TextEncoder();
106
+ const decoder = new TextDecoder("utf-8", { fatal: true });
107
+ decoder.decode(encoder.encode(value));
108
+ return true;
109
+ } catch (e) {
110
+ return false;
111
+ }
112
+ },
113
+ message: "Pollutant contains invalid UTF-8 characters",
114
+ },
115
+ },
116
+ oldValue: {
117
+ type: correctionSchema,
118
+ default: undefined,
119
+ },
120
+ newValue: {
121
+ type: correctionSchema,
122
+ default: undefined,
123
+ },
124
+ changedAt: {
125
+ type: Date,
126
+ default: Date.now,
127
+ },
128
+ },
129
+ { _id: false }
130
+ );
131
+
132
+ // Monitor Audit Schema
133
+ const monitorAuditSchema = mongoose.Schema(
134
+ {
135
+ monitorId: { type: mongoose.Types.ObjectId, ref: "Monitors" },
136
+ orgId: { type: mongoose.Types.ObjectId, ref: "Organizations" },
137
+ timeUpdated: Date,
138
+ deletedAt: { type: Date, default: Date.now }, // Only populated on delete
139
+ monitorProperties: Object, // Stores properties of the monitor
140
+ monitorState: String, // Tracks the state of the monitor (e.g., "Deployed", "Maintenance")
141
+ monitorLocation: {
142
+ // Monitor GPS location (lat, lon)
143
+ type: { type: String, enum: ["Point"], required: true },
144
+ coordinates: { type: [Number], required: true },
145
+ },
146
+ parameterThresholds: Object,
147
+ corrections: {
148
+ type: Map,
149
+ of: correctionSchema,
150
+ default: {},
151
+ },
152
+ correctionHistory: {
153
+ type: [correctionHistorySchema],
154
+ default: [],
155
+ },
156
+ },
157
+ {
158
+ timestamps: true,
159
+ }
160
+ );
161
+
162
+ // Create the MonitorAudit model
163
+ const MonitorAudit = mongoose.model("MonitorAudit", monitorAuditSchema);
164
+
165
+ // Monitors Schema
166
+ const monitorsSchema = mongoose.Schema(
167
+ {
168
+ monitorCode: String,
169
+ monitorSupplier: {
170
+ type: String,
171
+ enum: [
172
+ "clarity",
173
+ "aeroqual",
174
+ "purple air",
175
+ "reference monitor",
176
+ "earthview",
177
+ "sensit",
178
+ "blue sky",
179
+ "aq mesh",
180
+ "quant aq",
181
+ "airGradient",
182
+ "oizom",
183
+ "metOne",
184
+ "aqMesh",
185
+ ],
186
+ },
187
+ monitorType: String,
188
+ monitorIdFromSupplier: String,
189
+ measurementUpdate: Date,
190
+ monitorProperties: Object,
191
+ isPrivate: { type: Boolean, default: false, required: true },
192
+ monitorState: {
193
+ type: String,
194
+ enum: ["Collocation", "Deployed", "Maintenance", "Pending Deployment"],
195
+ },
196
+ monitorStateHistory: [Object],
197
+ monitorAlertStatus: {
198
+ type: String,
199
+ enum: [
200
+ "Good",
201
+ "Moderate",
202
+ "Unhealthy for SG",
203
+ "Unhealthy",
204
+ "Very Unhealthy",
205
+ "Hazardous",
206
+ "Bad",
207
+ ],
208
+ default: "Good",
209
+ },
210
+ sponsor: { type: mongoose.Types.ObjectId, ref: "Organizations" },
211
+ sponsorName: String,
212
+ monitorLatitude: Number,
213
+ monitorLongitude: Number,
214
+ gpsLocation: {
215
+ type: { type: String, enum: ["Point"], required: true },
216
+ coordinates: { type: [Number], required: true },
217
+ },
218
+ location: Object,
219
+ context: [String],
220
+ colocationDate: Date,
221
+ deploymentDate: Date,
222
+ subscriptionDate: Date,
223
+ parameters: [
224
+ {
225
+ type: String,
226
+ enum: parametersEnum,
227
+ },
228
+ ],
229
+ latestPM2_5: Number,
230
+ latestAQI_PM2_5: Number,
231
+ notes: [noteSchema],
232
+ calculatedAverages: [Object],
233
+ images: [String],
234
+ isActive: { type: Boolean, default: true },
235
+ parameterThresholds: Object,
236
+ completionPercentageThresholds: Object,
237
+ anomalyPercentageThresholds: Object,
238
+ // A Map, keyed by pollutant (e.g. "PM2_5"), storing Correction sub-docs
239
+ corrections: {
240
+ type: Map,
241
+ of: correctionSchema,
242
+ default: {},
243
+ },
244
+ correctionHistory: {
245
+ type: [correctionHistorySchema],
246
+ default: [],
247
+ },
248
+ applyCorrections: { type: Boolean, default: false },
249
+ pausedParameters: {
250
+ type: [{ parameter: String, timestamp: Date, isPaused: Boolean }],
251
+ default: [],
252
+ },
253
+ },
254
+ {
255
+ timestamps: true,
256
+ }
257
+ );
258
+
259
+ // Geographic queries - already exists
260
+ monitorsSchema.index({ gpsLocation: "2dsphere" });
261
+
262
+ // Sponsor-based queries
263
+ monitorsSchema.index({ sponsor: 1, isPrivate: 1, isActive: 1 });
264
+
265
+ // Location-based filtering
266
+ monitorsSchema.index({ "location.city": 1, "location.state": 1 });
267
+ monitorsSchema.index({ "location.neighborhood": 1 });
268
+ monitorsSchema.index({ "location.county": 1 });
269
+
270
+ // Query parameter filtering
271
+ monitorsSchema.index({ monitorSupplier: 1, monitorState: 1 });
272
+ monitorsSchema.index({ context: 1 });
273
+ monitorsSchema.index({ parameters: 1 });
274
+
275
+ // Monitor lookups by supplier
276
+ monitorsSchema.index({ sponsor: 1, monitorSupplier: 1 });
277
+
278
+ // Keep existing single-field indexes for backward compatibility
279
+ monitorsSchema.index({ monitorSupplier: 1 });
280
+ monitorsSchema.index({ monitorIdFromSupplier: 1 });
281
+ monitorsSchema.index({ monitorState: 1 });
282
+
283
+ //network metrics for anomalies
284
+ monitorsSchema.index({ sponsor: 1, isActive: 1, monitorSupplier: 1 })
285
+
286
+ // Pre-hook to log single document deletions
287
+ monitorsSchema.pre("findOneAndDelete", async function () {
288
+ const docToDelete = await this.model.findOne(this.getFilter()).lean();
289
+ if (docToDelete) {
290
+ console.log("Logging findOneAndDelete to monitor audit", docToDelete);
291
+ const auditLog = new MonitorAudit({
292
+ monitorId: docToDelete._id,
293
+ orgId: docToDelete.sponsor,
294
+ timeUpdated: docToDelete.updatedAt,
295
+ monitorProperties: docToDelete.monitorProperties,
296
+ monitorState: docToDelete.monitorState,
297
+ monitorLocation: {
298
+ type: "Point",
299
+ coordinates: [
300
+ docToDelete.monitorLongitude,
301
+ docToDelete.monitorLatitude,
302
+ ],
303
+ },
304
+ parameterThresholds: docToDelete.parameterThresholds,
305
+ corrections: docToDelete.corrections,
306
+ correctionHistory: docToDelete.correctionHistory,
307
+ applyCorrections: docToDelete.applyCorrections,
308
+ deletedAt: new Date(),
309
+ });
310
+ await auditLog.save();
311
+ }
312
+ });
313
+
314
+ // Pre-hook to log multiple document deletions
315
+ monitorsSchema.pre("deleteMany", async function () {
316
+ console.log("deleteMany pre-hook triggered for monitors");
317
+ const docsToDelete = await this.model.find(this.getFilter()).lean();
318
+
319
+ if (docsToDelete.length) {
320
+ console.log(`Logging ${docsToDelete.length} monitor documents to audit`);
321
+ const auditLogs = docsToDelete.map((doc) => ({
322
+ monitorId: doc._id,
323
+ orgId: doc.sponsor,
324
+ timeUpdated: doc.updatedAt,
325
+ monitorProperties: doc.monitorProperties,
326
+ monitorState: doc.monitorState,
327
+ monitorLocation: {
328
+ type: "Point",
329
+ coordinates: [doc.monitorLongitude, doc.monitorLatitude],
330
+ },
331
+ parameterThresholds: doc.parameterThresholds,
332
+ corrections: doc.corrections,
333
+ correctionHistory: doc.correctionHistory,
334
+ applyCorrections: doc.applyCorrections,
335
+ deletedAt: new Date(),
336
+ }));
337
+
338
+ await MonitorAudit.insertMany(auditLogs);
339
+ }
340
+ });
341
+
342
+ // Pre-hook to log a single document deletion (for deleteOne)
343
+ monitorsSchema.pre("deleteOne", async function () {
344
+ console.log("deleteOne pre-hook triggered for monitors");
345
+ const docToDelete = await this.model.findOne(this.getFilter()).lean();
346
+
347
+ if (docToDelete) {
348
+ console.log("Logging deleteOne to monitor audit", docToDelete);
349
+ const auditLog = new MonitorAudit({
350
+ monitorId: docToDelete._id,
351
+ orgId: docToDelete.sponsor,
352
+ timeUpdated: docToDelete.updatedAt,
353
+ monitorProperties: docToDelete.monitorProperties,
354
+ monitorState: docToDelete.monitorState,
355
+ monitorLocation: {
356
+ type: "Point",
357
+ coordinates: [
358
+ docToDelete.monitorLongitude,
359
+ docToDelete.monitorLatitude,
360
+ ],
361
+ },
362
+ parameterThresholds: docToDelete.parameterThresholds,
363
+ corrections: docToDelete.corrections,
364
+ correctionHistory: docToDelete.correctionHistory,
365
+ applyCorrections: docToDelete.applyCorrections,
366
+ deletedAt: new Date(),
367
+ });
368
+ await auditLog.save();
369
+ }
370
+ });
371
+
372
+ // Pre-hook to log multiple document updates
373
+ monitorsSchema.pre("updateMany", async function () {
374
+ const docsToUpdate = await this.model.find(this.getFilter()).lean();
375
+ if (docsToUpdate.length) {
376
+ console.log(`Logging ${docsToUpdate.length} monitor documents to audit`);
377
+ const auditLogs = docsToUpdate.map((doc) => ({
378
+ monitorId: doc._id,
379
+ orgId: doc.sponsor,
380
+ timeUpdated: doc.updatedAt,
381
+ monitorProperties: doc.monitorProperties,
382
+ monitorState: doc.monitorState,
383
+ monitorLocation: {
384
+ type: "Point",
385
+ coordinates: [doc.monitorLongitude, doc.monitorLatitude],
386
+ },
387
+ parameterThresholds: doc.parameterThresholds,
388
+ corrections: doc.corrections,
389
+ correctionHistory: doc.correctionHistory,
390
+ applyCorrections: doc.applyCorrections,
391
+ deletedAt: null, // Not a deletion, so this field is null
392
+ }));
393
+
394
+ await MonitorAudit.insertMany(auditLogs);
395
+ }
396
+ });
397
+
398
+ // Create the Monitors model
399
+ const Monitors = mongoose.model("Monitors", monitorsSchema);
400
+
401
+ export { monitorsSchema, Monitors, monitorAuditSchema, MonitorAudit };