@justair/justair-library 4.8.22 → 4.8.24-alpha-demo

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/package.json CHANGED
@@ -1,27 +1,27 @@
1
- {
2
- "name": "@justair/justair-library",
3
- "version": "4.8.22",
4
- "description": "JustAir Internal Library",
5
- "main": "src/index.js",
6
- "type": "module",
7
- "types": "dist/index.d.ts",
8
- "scripts": {
9
- "test": "echo \"Error: no test specified\" && exit 1",
10
- "build": "tsc",
11
- "prepublish": "npm run build"
12
- },
13
- "keywords": [
14
- "JustAir"
15
- ],
16
- "author": "Britney Epps",
17
- "license": "ISC",
18
- "dependencies": {
19
- "mongoose": "^7.8.3",
20
- "winston": "^3.10.0"
21
- },
22
- "devDependencies": {
23
- "typescript": "^5.3.2",
24
- "@types/node": "^16.0.0",
25
- "ts-node": "^10.0.0"
26
- }
27
- }
1
+ {
2
+ "name": "@justair/justair-library",
3
+ "version": "4.8.24-alpha-demo",
4
+ "description": "JustAir Internal Library",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "test": "echo \"Error: no test specified\" && exit 1",
10
+ "build": "tsc",
11
+ "prepublish": "npm run build"
12
+ },
13
+ "keywords": [
14
+ "JustAir"
15
+ ],
16
+ "author": "Britney Epps",
17
+ "license": "ISC",
18
+ "dependencies": {
19
+ "mongoose": "^7.8.3",
20
+ "winston": "^3.10.0"
21
+ },
22
+ "devDependencies": {
23
+ "typescript": "^5.3.2",
24
+ "@types/node": "^16.0.0",
25
+ "ts-node": "^10.0.0"
26
+ }
27
+ }
package/src/index.js CHANGED
@@ -1,133 +1,133 @@
1
- import { adminSchema, Admin } from "./models/admin.js";
2
- import {
3
- configurationsSchema,
4
- Configurations,
5
- } from "./models/configurations.js";
6
- import {
7
- measurementsSchema,
8
- Measurements,
9
- Audit,
10
- auditSchema,
11
- } from "./models/measurements.js";
12
- import {
13
- monitorRequestsSchema,
14
- MonitorRequests,
15
- } from "./models/monitorRequests.js";
16
- import {
17
- monitorsSchema,
18
- Monitors,
19
- monitorAuditSchema,
20
- MonitorAudit,
21
- parametersEnum,
22
- } from "./models/monitors.js";
23
- import { organizationsSchema, Organizations } from "./models/organizations.js";
24
- import {
25
- referenceMonitorInfoSchema,
26
- ReferenceMonitorInfo,
27
- } from "./models/referenceMonitorInfo.js";
28
- import { lightMonitorSchema, LightMonitors } from "./models/lightmonitors.js";
29
- import {
30
- monitorSuppliersSchema,
31
- MonitorSuppliers,
32
- } from "./models/monitorSuppliers.js";
33
- import { contextsSchema, Contexts } from "./models/contexts.js";
34
- import { parametersSchema, Parameters } from "./models/parameters.js";
35
- import { usersSchema, Users } from "./models/users.js";
36
- import {
37
- eventsSchema,
38
- Events,
39
- eventsAuditSchema,
40
- EventsAudit,
41
- } from "./models/events.js";
42
- import { announcementSchema, Announcements } from "./models/announcements.js";
43
- import { jobsSchema, Jobs } from "./models/jobs.js";
44
- import { apiKeySchema, ApiKey } from "./models/apiKey.js";
45
- import { UsageMetrics, usageMetricsSchema } from "./models/usageMetrics.js";
46
- import {
47
- AlertsAudit,
48
- Alerts,
49
- alertsSchema,
50
- alertsAuditSchema,
51
- } from "./models/alerts.js";
52
- import { Features, featuresSchema } from "./models/features.js";
53
- import {
54
- DataCompleteness,
55
- dataCompletenessSchema,
56
- } from "./models/dataCompleteness.js";
57
- import { NetworkMetrics, networkMetricsSchema } from "./models/networkMetrics.js";
58
- import Database from "./config/db.js"; // Import the new Database class
59
- import CustomLogger from "./config/logger.js";
60
-
61
- // SampleSites related imports
62
- import {
63
- sampleSitesSchema,
64
- SampleSites,
65
- sampleSiteAuditSchema,
66
- SampleSiteAudit,
67
- sampleParametersEnum,
68
- } from "./models/sampleSites.js";
69
-
70
- export function createLoggerInstance({ DATADOG_API_KEY, APPLICATION_NAME }) {
71
- return new CustomLogger({ DATADOG_API_KEY, APPLICATION_NAME });
72
- }
73
-
74
- export {
75
- Database, // Export the Database class
76
- adminSchema,
77
- Admin,
78
- configurationsSchema,
79
- Configurations,
80
- measurementsSchema,
81
- Measurements,
82
- monitorRequestsSchema,
83
- MonitorRequests,
84
- monitorsSchema,
85
- Monitors,
86
- organizationsSchema,
87
- Organizations,
88
- referenceMonitorInfoSchema,
89
- ReferenceMonitorInfo,
90
- usersSchema,
91
- Users,
92
- eventsSchema,
93
- Events,
94
- lightMonitorSchema,
95
- LightMonitors,
96
- monitorSuppliersSchema,
97
- MonitorSuppliers,
98
- contextsSchema,
99
- Contexts,
100
- parametersSchema,
101
- Parameters,
102
- announcementSchema,
103
- Announcements,
104
- jobsSchema,
105
- Jobs,
106
- apiKeySchema,
107
- ApiKey,
108
- UsageMetrics,
109
- usageMetricsSchema,
110
- Audit,
111
- auditSchema,
112
- EventsAudit,
113
- eventsAuditSchema,
114
- MonitorAudit,
115
- monitorAuditSchema,
116
- parametersEnum,
117
- AlertsAudit,
118
- Alerts,
119
- alertsSchema,
120
- alertsAuditSchema,
121
- Features,
122
- featuresSchema,
123
- dataCompletenessSchema,
124
- DataCompleteness,
125
- networkMetricsSchema,
126
- NetworkMetrics,
127
- // SampleSites related exports
128
- sampleSitesSchema,
129
- SampleSites,
130
- sampleSiteAuditSchema,
131
- SampleSiteAudit,
132
- sampleParametersEnum,
133
- };
1
+ import { adminSchema, Admin } from "./models/admin.js";
2
+ import {
3
+ configurationsSchema,
4
+ Configurations,
5
+ } from "./models/configurations.js";
6
+ import {
7
+ measurementsSchema,
8
+ Measurements,
9
+ Audit,
10
+ auditSchema,
11
+ } from "./models/measurements.js";
12
+ import {
13
+ monitorRequestsSchema,
14
+ MonitorRequests,
15
+ } from "./models/monitorRequests.js";
16
+ import {
17
+ monitorsSchema,
18
+ Monitors,
19
+ monitorAuditSchema,
20
+ MonitorAudit,
21
+ parametersEnum,
22
+ } from "./models/monitors.js";
23
+ import { organizationsSchema, Organizations } from "./models/organizations.js";
24
+ import {
25
+ referenceMonitorInfoSchema,
26
+ ReferenceMonitorInfo,
27
+ } from "./models/referenceMonitorInfo.js";
28
+ import { lightMonitorSchema, LightMonitors } from "./models/lightmonitors.js";
29
+ import {
30
+ monitorSuppliersSchema,
31
+ MonitorSuppliers,
32
+ } from "./models/monitorSuppliers.js";
33
+ import { contextsSchema, Contexts } from "./models/contexts.js";
34
+ import { parametersSchema, Parameters } from "./models/parameters.js";
35
+ import { usersSchema, Users } from "./models/users.js";
36
+ import {
37
+ eventsSchema,
38
+ Events,
39
+ eventsAuditSchema,
40
+ EventsAudit,
41
+ } from "./models/events.js";
42
+ import { announcementSchema, Announcements } from "./models/announcements.js";
43
+ import { jobsSchema, Jobs } from "./models/jobs.js";
44
+ import { apiKeySchema, ApiKey } from "./models/apiKey.js";
45
+ import { UsageMetrics, usageMetricsSchema } from "./models/usageMetrics.js";
46
+ import {
47
+ AlertsAudit,
48
+ Alerts,
49
+ alertsSchema,
50
+ alertsAuditSchema,
51
+ } from "./models/alerts.js";
52
+ import { Features, featuresSchema } from "./models/features.js";
53
+ import {
54
+ DataCompleteness,
55
+ dataCompletenessSchema,
56
+ } from "./models/dataCompleteness.js";
57
+ import { NetworkMetrics, networkMetricsSchema } from "./models/networkMetrics.js";
58
+ import Database from "./config/db.js"; // Import the new Database class
59
+ import CustomLogger from "./config/logger.js";
60
+
61
+ // SampleSites related imports
62
+ import {
63
+ sampleSitesSchema,
64
+ SampleSites,
65
+ sampleSiteAuditSchema,
66
+ SampleSiteAudit,
67
+ sampleParametersEnum,
68
+ } from "./models/sampleSites.js";
69
+
70
+ export function createLoggerInstance({ DATADOG_API_KEY, APPLICATION_NAME }) {
71
+ return new CustomLogger({ DATADOG_API_KEY, APPLICATION_NAME });
72
+ }
73
+
74
+ export {
75
+ Database, // Export the Database class
76
+ adminSchema,
77
+ Admin,
78
+ configurationsSchema,
79
+ Configurations,
80
+ measurementsSchema,
81
+ Measurements,
82
+ monitorRequestsSchema,
83
+ MonitorRequests,
84
+ monitorsSchema,
85
+ Monitors,
86
+ organizationsSchema,
87
+ Organizations,
88
+ referenceMonitorInfoSchema,
89
+ ReferenceMonitorInfo,
90
+ usersSchema,
91
+ Users,
92
+ eventsSchema,
93
+ Events,
94
+ lightMonitorSchema,
95
+ LightMonitors,
96
+ monitorSuppliersSchema,
97
+ MonitorSuppliers,
98
+ contextsSchema,
99
+ Contexts,
100
+ parametersSchema,
101
+ Parameters,
102
+ announcementSchema,
103
+ Announcements,
104
+ jobsSchema,
105
+ Jobs,
106
+ apiKeySchema,
107
+ ApiKey,
108
+ UsageMetrics,
109
+ usageMetricsSchema,
110
+ Audit,
111
+ auditSchema,
112
+ EventsAudit,
113
+ eventsAuditSchema,
114
+ MonitorAudit,
115
+ monitorAuditSchema,
116
+ parametersEnum,
117
+ AlertsAudit,
118
+ Alerts,
119
+ alertsSchema,
120
+ alertsAuditSchema,
121
+ Features,
122
+ featuresSchema,
123
+ dataCompletenessSchema,
124
+ DataCompleteness,
125
+ networkMetricsSchema,
126
+ NetworkMetrics,
127
+ // SampleSites related exports
128
+ sampleSitesSchema,
129
+ SampleSites,
130
+ sampleSiteAuditSchema,
131
+ SampleSiteAudit,
132
+ sampleParametersEnum,
133
+ };
@@ -158,6 +158,7 @@ measurementsSchema.pre("findOneAndDelete", async function () {
158
158
  });
159
159
 
160
160
  // Pre-hook to log multiple document deletions
161
+ /*
161
162
  measurementsSchema.pre("deleteMany", async function () {
162
163
  console.log("deleteMany pre-hook triggered");
163
164
  const docsToDelete = await this.model.find(this.getFilter()).lean();
@@ -183,6 +184,7 @@ measurementsSchema.pre("deleteMany", async function () {
183
184
  await Audit.insertMany(auditLogs);
184
185
  }
185
186
  });
187
+ */
186
188
 
187
189
  // Pre-hook to log a single document deletion (for deleteOne)
188
190
  measurementsSchema.pre("deleteOne", async function () {
@@ -1,252 +1,255 @@
1
- import mongoose from "mongoose";
2
- const sampleParametersEnum = [
3
- "C6H6", // Benzene
4
- "PB", // Lead
5
- "AS", // Arsenic
6
- "CD", // Cadmium
7
- "CR", // Chromium
8
- "NI", // Nickel
9
- "BA", // Barium
10
- "FE", // Iron
11
- "CU", // Copper
12
- "ZN", // Zinc
13
- ];
14
-
15
- const noteSchema = mongoose.Schema({
16
- note: {
17
- type: String,
18
- required: true,
19
- },
20
- type: {
21
- type: String,
22
- required: true,
23
- },
24
- adminId: {
25
- type: mongoose.Types.ObjectId,
26
- ref: "Admin",
27
- required: true,
28
- },
29
- adminName: {
30
- type: String,
31
- },
32
- date: {
33
- type: Date,
34
- default: Date.now,
35
- },
36
- });
37
-
38
- // Sample Site Audit Schema
39
- const sampleSiteAuditSchema = mongoose.Schema(
40
- {
41
- sampleSiteId: { type: mongoose.Types.ObjectId, ref: "SampleSites" },
42
- orgId: { type: mongoose.Types.ObjectId, ref: "Organizations" },
43
- timeUpdated: Date,
44
- deletedAt: { type: Date, default: Date.now }, // Only populated on delete
45
- sampleSiteLocation: {
46
- // Sample Site GPS location (lat, lon)
47
- type: { type: String, enum: ["Point"], required: true },
48
- coordinates: { type: [Number], required: true },
49
- },
50
- },
51
- {
52
- timestamps: true,
53
- }
54
- );
55
-
56
- // Create the SampleSiteAudit model
57
- const SampleSiteAudit = mongoose.model("SampleSiteAudit", sampleSiteAuditSchema);
58
-
59
- // Sample Sites Schema
60
- const sampleSitesSchema = mongoose.Schema(
61
- {
62
- name: { type: String },
63
- managedBy: { type: String },
64
- sponsoredBy: { type: String },
65
- lastSamplePeriodStartDate: { type: Date },
66
- lastSamplePeriodEndDate: { type: Date },
67
- isPrivate: { type: Boolean, default: false, required: true },
68
- sponsor: { type: mongoose.Types.ObjectId, ref: "Organizations" },
69
- sponsorName: { type: String },
70
- /*
71
- * Location object: unified structure for all address and geospatial data.
72
- * - monitorLatitude and monitorLongitude are deprecated and no longer used.
73
- * - The location object (including location.gps) is now built in config/google.js.
74
- * - location.gps is a GeoJSON Point used for all geospatial queries (replaces gpsLocation).
75
- */
76
- location: {
77
- city: { type: String },
78
- state: { type: String },
79
- county: { type: String },
80
- street: { type: String },
81
- zip_code: { type: String },
82
- neighborhood: { type: String },
83
- gps: {
84
- type: {
85
- type: String,
86
- enum: ["Point"],
87
- required: true,
88
- },
89
- coordinates: {
90
- type: [Number],
91
- required: true,
92
- },
93
- },
94
- },
95
- context: { type: [String] },
96
- parameters: [
97
- {
98
- type: String,
99
- enum: sampleParametersEnum,
100
- },
101
- ],
102
- notes: { type: [noteSchema] },
103
- image: { type: String },
104
- },
105
- {
106
- timestamps: true,
107
- }
108
- );
109
-
110
- // Geographic queries - already exists
111
- sampleSitesSchema.index({ "location.gps": "2dsphere" }); // use location.gps for geospatial queries
112
-
113
- // Sponsor-based queries
114
- sampleSitesSchema.index({ sponsor: 1, isPrivate: 1 });
115
-
116
- // Location-based filtering
117
- sampleSitesSchema.index({ "location.city": 1, "location.state": 1 });
118
- sampleSitesSchema.index({ "location.neighborhood": 1 });
119
- sampleSitesSchema.index({ "location.county": 1 });
120
-
121
- // Query parameter filtering
122
- sampleSitesSchema.index({ context: 1 });
123
- sampleSitesSchema.index({ parameters: 1 });
124
-
125
- // Pre-hook to log single document deletions
126
- sampleSitesSchema.pre("findOneAndDelete", async function () {
127
- const docToDelete = await this.model.findOne(this.getFilter()).lean();
128
- if (docToDelete) {
129
- console.log("Logging findOneAndDelete to sample site audit", docToDelete);
130
- let coordinates = null;
131
- if (docToDelete.location && docToDelete.location.gps && Array.isArray(docToDelete.location.gps.coordinates)) {
132
- coordinates = docToDelete.location.gps.coordinates;
133
- } else if (docToDelete.gpsLocation && Array.isArray(docToDelete.gpsLocation.coordinates)) {
134
- coordinates = docToDelete.gpsLocation.coordinates;
135
- }
136
- if (coordinates) {
137
- const auditLog = new SampleSiteAudit({
138
- sampleSiteId: docToDelete._id,
139
- orgId: docToDelete.sponsor,
140
- timeUpdated: docToDelete.updatedAt,
141
- sampleSiteLocation: {
142
- type: "Point",
143
- coordinates,
144
- },
145
- deletedAt: new Date(),
146
- });
147
- await auditLog.save();
148
- }
149
- }
150
- });
151
-
152
- // Pre-hook to log multiple document deletions
153
- sampleSitesSchema.pre("deleteMany", async function () {
154
- console.log("deleteMany pre-hook triggered for sample sites");
155
- const docsToDelete = await this.model.find(this.getFilter()).lean();
156
-
157
- if (docsToDelete.length) {
158
- console.log(`Logging ${docsToDelete.length} sample site documents to audit`);
159
- const auditLogs = docsToDelete.map((doc) => {
160
- let coordinates = null;
161
- if (doc.location && doc.location.gps && Array.isArray(doc.location.gps.coordinates)) {
162
- coordinates = doc.location.gps.coordinates;
163
- } else if (doc.gpsLocation && Array.isArray(doc.gpsLocation.coordinates)) {
164
- coordinates = doc.gpsLocation.coordinates;
165
- }
166
- if (coordinates) {
167
- return {
168
- sampleSiteId: doc._id,
169
- orgId: doc.sponsor,
170
- timeUpdated: doc.updatedAt,
171
- sampleSiteLocation: {
172
- type: "Point",
173
- coordinates,
174
- },
175
- deletedAt: new Date(),
176
- };
177
- }
178
- return null;
179
- }).filter(Boolean);
180
-
181
- if (auditLogs.length) {
182
- await SampleSiteAudit.insertMany(auditLogs);
183
- }
184
- }
185
- });
186
-
187
- // Pre-hook to log a single document deletion (for deleteOne)
188
- sampleSitesSchema.pre("deleteOne", async function () {
189
- console.log("deleteOne pre-hook triggered for sample sites");
190
- const docToDelete = await this.model.findOne(this.getFilter()).lean();
191
-
192
- if (docToDelete) {
193
- console.log("Logging deleteOne to sample site audit", docToDelete);
194
- let coordinates = null;
195
- if (docToDelete.location && docToDelete.location.gps && Array.isArray(docToDelete.location.gps.coordinates)) {
196
- coordinates = docToDelete.location.gps.coordinates;
197
- } else if (docToDelete.gpsLocation && Array.isArray(docToDelete.gpsLocation.coordinates)) {
198
- coordinates = docToDelete.gpsLocation.coordinates;
199
- }
200
- if (coordinates) {
201
- const auditLog = new SampleSiteAudit({
202
- sampleSiteId: docToDelete._id,
203
- orgId: docToDelete.sponsor,
204
- timeUpdated: docToDelete.updatedAt,
205
- sampleSiteLocation: {
206
- type: "Point",
207
- coordinates,
208
- },
209
- deletedAt: new Date(),
210
- });
211
- await auditLog.save();
212
- }
213
- }
214
- });
215
-
216
- // Pre-hook to log multiple document updates
217
- sampleSitesSchema.pre("updateMany", async function () {
218
- const docsToUpdate = await this.model.find(this.getFilter()).lean();
219
- if (docsToUpdate.length) {
220
- console.log(`Logging ${docsToUpdate.length} sample site documents to audit`);
221
- const auditLogs = docsToUpdate.map((doc) => {
222
- let coordinates = null;
223
- if (doc.location && doc.location.gps && Array.isArray(doc.location.gps.coordinates)) {
224
- coordinates = doc.location.gps.coordinates;
225
- } else if (doc.gpsLocation && Array.isArray(doc.gpsLocation.coordinates)) {
226
- coordinates = doc.gpsLocation.coordinates;
227
- }
228
- if (coordinates) {
229
- return {
230
- sampleSiteId: doc._id,
231
- orgId: doc.sponsor,
232
- timeUpdated: doc.updatedAt,
233
- sampleSiteLocation: {
234
- type: "Point",
235
- coordinates,
236
- },
237
- deletedAt: null, // Not a deletion, so this field is null
238
- };
239
- }
240
- return null;
241
- }).filter(Boolean);
242
-
243
- if (auditLogs.length) {
244
- await SampleSiteAudit.insertMany(auditLogs);
245
- }
246
- }
247
- });
248
-
249
- // Create the SampleSites model
250
- const SampleSites = mongoose.model("SampleSites", sampleSitesSchema);
251
-
1
+ import mongoose from "mongoose";
2
+ const sampleParametersEnum = [
3
+ "C6H6", // Benzene
4
+ "PB", // Lead
5
+ "AS", // Arsenic
6
+ "CD", // Cadmium
7
+ "CR", // Chromium
8
+ "NI", // Nickel
9
+ "BA", // Barium
10
+ "FE", // Iron
11
+ "CU", // Copper
12
+ "ZN", // Zinc
13
+ ];
14
+
15
+ const noteSchema = mongoose.Schema({
16
+ note: {
17
+ type: String,
18
+ required: true,
19
+ },
20
+ type: {
21
+ type: String,
22
+ required: true,
23
+ },
24
+ adminId: {
25
+ type: mongoose.Types.ObjectId,
26
+ ref: "Admin",
27
+ required: true,
28
+ },
29
+ adminName: {
30
+ type: String,
31
+ },
32
+ date: {
33
+ type: Date,
34
+ default: Date.now,
35
+ },
36
+ });
37
+
38
+ // Sample Site Audit Schema
39
+ const sampleSiteAuditSchema = mongoose.Schema(
40
+ {
41
+ sampleSiteId: { type: mongoose.Types.ObjectId, ref: "SampleSites" },
42
+ orgId: { type: mongoose.Types.ObjectId, ref: "Organizations" },
43
+ timeUpdated: Date,
44
+ deletedAt: { type: Date, default: Date.now }, // Only populated on delete
45
+ sampleSiteLocation: {
46
+ // Sample Site GPS location (lat, lon)
47
+ type: { type: String, enum: ["Point"], required: true },
48
+ coordinates: {
49
+ latitude: { type: Number, required: true },
50
+ longitude: { type: Number, required: true }
51
+ },
52
+ },
53
+ },
54
+ {
55
+ timestamps: true,
56
+ }
57
+ );
58
+
59
+ // Create the SampleSiteAudit model
60
+ const SampleSiteAudit = mongoose.model("SampleSiteAudit", sampleSiteAuditSchema);
61
+
62
+ // Sample Sites Schema
63
+ const sampleSitesSchema = mongoose.Schema(
64
+ {
65
+ name: { type: String },
66
+ managedBy: { type: String },
67
+ sponsoredBy: { type: String },
68
+ lastSamplePeriodStartDate: { type: Date },
69
+ lastSamplePeriodEndDate: { type: Date },
70
+ isPrivate: { type: Boolean, default: false, required: true },
71
+ sponsor: { type: mongoose.Types.ObjectId, ref: "Organizations" },
72
+ sponsorName: { type: String },
73
+ /*
74
+ * Location object: unified structure for all address and geospatial data.
75
+ * - monitorLatitude and monitorLongitude are deprecated and no longer used.
76
+ * - The location object (including location.gps) is now built in config/google.js.
77
+ * - location.gps is a GeoJSON Point used for all geospatial queries (replaces gpsLocation).
78
+ */
79
+ location: {
80
+ city: { type: String },
81
+ state: { type: String },
82
+ county: { type: String },
83
+ street: { type: String },
84
+ zip_code: { type: String },
85
+ neighborhood: { type: String },
86
+ gps: {
87
+ type: {
88
+ type: String,
89
+ enum: ["Point"],
90
+ required: true,
91
+ },
92
+ coordinates: {
93
+ latitude: { type: Number, required: true },
94
+ longitude: { type: Number, required: true }
95
+ },
96
+ },
97
+ },
98
+ context: { type: [String] },
99
+ parameters: [
100
+ {
101
+ type: String,
102
+ enum: sampleParametersEnum,
103
+ },
104
+ ],
105
+ notes: { type: [noteSchema] },
106
+ image: { type: String },
107
+ },
108
+ {
109
+ timestamps: true,
110
+ }
111
+ );
112
+
113
+ // Geographic queries - already exists
114
+ sampleSitesSchema.index({ "location.gps": "2dsphere" }); // use location.gps for geospatial queries
115
+
116
+ // Sponsor-based queries
117
+ sampleSitesSchema.index({ sponsor: 1, isPrivate: 1 });
118
+
119
+ // Location-based filtering
120
+ sampleSitesSchema.index({ "location.city": 1, "location.state": 1 });
121
+ sampleSitesSchema.index({ "location.neighborhood": 1 });
122
+ sampleSitesSchema.index({ "location.county": 1 });
123
+
124
+ // Query parameter filtering
125
+ sampleSitesSchema.index({ context: 1 });
126
+ sampleSitesSchema.index({ parameters: 1 });
127
+
128
+ // Pre-hook to log single document deletions
129
+ sampleSitesSchema.pre("findOneAndDelete", async function () {
130
+ const docToDelete = await this.model.findOne(this.getFilter()).lean();
131
+ if (docToDelete) {
132
+ console.log("Logging findOneAndDelete to sample site audit", docToDelete);
133
+ let coordinates = null;
134
+ if (docToDelete.location && docToDelete.location.gps && Array.isArray(docToDelete.location.gps.coordinates)) {
135
+ coordinates = docToDelete.location.gps.coordinates;
136
+ } else if (docToDelete.gpsLocation && Array.isArray(docToDelete.gpsLocation.coordinates)) {
137
+ coordinates = docToDelete.gpsLocation.coordinates;
138
+ }
139
+ if (coordinates) {
140
+ const auditLog = new SampleSiteAudit({
141
+ sampleSiteId: docToDelete._id,
142
+ orgId: docToDelete.sponsor,
143
+ timeUpdated: docToDelete.updatedAt,
144
+ sampleSiteLocation: {
145
+ type: "Point",
146
+ coordinates,
147
+ },
148
+ deletedAt: new Date(),
149
+ });
150
+ await auditLog.save();
151
+ }
152
+ }
153
+ });
154
+
155
+ // Pre-hook to log multiple document deletions
156
+ sampleSitesSchema.pre("deleteMany", async function () {
157
+ console.log("deleteMany pre-hook triggered for sample sites");
158
+ const docsToDelete = await this.model.find(this.getFilter()).lean();
159
+
160
+ if (docsToDelete.length) {
161
+ console.log(`Logging ${docsToDelete.length} sample site documents to audit`);
162
+ const auditLogs = docsToDelete.map((doc) => {
163
+ let coordinates = null;
164
+ if (doc.location && doc.location.gps && Array.isArray(doc.location.gps.coordinates)) {
165
+ coordinates = doc.location.gps.coordinates;
166
+ } else if (doc.gpsLocation && Array.isArray(doc.gpsLocation.coordinates)) {
167
+ coordinates = doc.gpsLocation.coordinates;
168
+ }
169
+ if (coordinates) {
170
+ return {
171
+ sampleSiteId: doc._id,
172
+ orgId: doc.sponsor,
173
+ timeUpdated: doc.updatedAt,
174
+ sampleSiteLocation: {
175
+ type: "Point",
176
+ coordinates,
177
+ },
178
+ deletedAt: new Date(),
179
+ };
180
+ }
181
+ return null;
182
+ }).filter(Boolean);
183
+
184
+ if (auditLogs.length) {
185
+ await SampleSiteAudit.insertMany(auditLogs);
186
+ }
187
+ }
188
+ });
189
+
190
+ // Pre-hook to log a single document deletion (for deleteOne)
191
+ sampleSitesSchema.pre("deleteOne", async function () {
192
+ console.log("deleteOne pre-hook triggered for sample sites");
193
+ const docToDelete = await this.model.findOne(this.getFilter()).lean();
194
+
195
+ if (docToDelete) {
196
+ console.log("Logging deleteOne to sample site audit", docToDelete);
197
+ let coordinates = null;
198
+ if (docToDelete.location && docToDelete.location.gps && Array.isArray(docToDelete.location.gps.coordinates)) {
199
+ coordinates = docToDelete.location.gps.coordinates;
200
+ } else if (docToDelete.gpsLocation && Array.isArray(docToDelete.gpsLocation.coordinates)) {
201
+ coordinates = docToDelete.gpsLocation.coordinates;
202
+ }
203
+ if (coordinates) {
204
+ const auditLog = new SampleSiteAudit({
205
+ sampleSiteId: docToDelete._id,
206
+ orgId: docToDelete.sponsor,
207
+ timeUpdated: docToDelete.updatedAt,
208
+ sampleSiteLocation: {
209
+ type: "Point",
210
+ coordinates,
211
+ },
212
+ deletedAt: new Date(),
213
+ });
214
+ await auditLog.save();
215
+ }
216
+ }
217
+ });
218
+
219
+ // Pre-hook to log multiple document updates
220
+ sampleSitesSchema.pre("updateMany", async function () {
221
+ const docsToUpdate = await this.model.find(this.getFilter()).lean();
222
+ if (docsToUpdate.length) {
223
+ console.log(`Logging ${docsToUpdate.length} sample site documents to audit`);
224
+ const auditLogs = docsToUpdate.map((doc) => {
225
+ let coordinates = null;
226
+ if (doc.location && doc.location.gps && Array.isArray(doc.location.gps.coordinates)) {
227
+ coordinates = doc.location.gps.coordinates;
228
+ } else if (doc.gpsLocation && Array.isArray(doc.gpsLocation.coordinates)) {
229
+ coordinates = doc.gpsLocation.coordinates;
230
+ }
231
+ if (coordinates) {
232
+ return {
233
+ sampleSiteId: doc._id,
234
+ orgId: doc.sponsor,
235
+ timeUpdated: doc.updatedAt,
236
+ sampleSiteLocation: {
237
+ type: "Point",
238
+ coordinates,
239
+ },
240
+ deletedAt: null, // Not a deletion, so this field is null
241
+ };
242
+ }
243
+ return null;
244
+ }).filter(Boolean);
245
+
246
+ if (auditLogs.length) {
247
+ await SampleSiteAudit.insertMany(auditLogs);
248
+ }
249
+ }
250
+ });
251
+
252
+ // Create the SampleSites model
253
+ const SampleSites = mongoose.model("SampleSites", sampleSitesSchema);
254
+
252
255
  export { sampleSitesSchema, SampleSites, sampleSiteAuditSchema, SampleSiteAudit, sampleParametersEnum };