@justair/justair-library 4.8.20 → 4.8.22

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@justair/justair-library",
3
- "version": "4.8.20",
3
+ "version": "4.8.22",
4
4
  "description": "JustAir Internal Library",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -58,6 +58,15 @@ import { NetworkMetrics, networkMetricsSchema } from "./models/networkMetrics.js
58
58
  import Database from "./config/db.js"; // Import the new Database class
59
59
  import CustomLogger from "./config/logger.js";
60
60
 
61
+ // SampleSites related imports
62
+ import {
63
+ sampleSitesSchema,
64
+ SampleSites,
65
+ sampleSiteAuditSchema,
66
+ SampleSiteAudit,
67
+ sampleParametersEnum,
68
+ } from "./models/sampleSites.js";
69
+
61
70
  export function createLoggerInstance({ DATADOG_API_KEY, APPLICATION_NAME }) {
62
71
  return new CustomLogger({ DATADOG_API_KEY, APPLICATION_NAME });
63
72
  }
@@ -115,4 +124,10 @@ export {
115
124
  DataCompleteness,
116
125
  networkMetricsSchema,
117
126
  NetworkMetrics,
127
+ // SampleSites related exports
128
+ sampleSitesSchema,
129
+ SampleSites,
130
+ sampleSiteAuditSchema,
131
+ SampleSiteAudit,
132
+ sampleParametersEnum,
118
133
  };
@@ -38,12 +38,12 @@ const noteSchema = mongoose.Schema({
38
38
  // Sample Site Audit Schema
39
39
  const sampleSiteAuditSchema = mongoose.Schema(
40
40
  {
41
- monitorId: { type: mongoose.Types.ObjectId, ref: "SampleSites" },
41
+ sampleSiteId: { type: mongoose.Types.ObjectId, ref: "SampleSites" },
42
42
  orgId: { type: mongoose.Types.ObjectId, ref: "Organizations" },
43
43
  timeUpdated: Date,
44
44
  deletedAt: { type: Date, default: Date.now }, // Only populated on delete
45
- monitorLocation: {
46
- // Monitor GPS location (lat, lon)
45
+ sampleSiteLocation: {
46
+ // Sample Site GPS location (lat, lon)
47
47
  type: { type: String, enum: ["Point"], required: true },
48
48
  coordinates: { type: [Number], required: true },
49
49
  },
@@ -59,30 +59,48 @@ const SampleSiteAudit = mongoose.model("SampleSiteAudit", sampleSiteAuditSchema)
59
59
  // Sample Sites Schema
60
60
  const sampleSitesSchema = mongoose.Schema(
61
61
  {
62
- monitorCode: String,
63
- // Managed By and Sponsored By fields
64
- managedBy: String,
65
- sponsoredBy: String,
66
- measurementUpdate: Date,
62
+ name: { type: String },
63
+ managedBy: { type: String },
64
+ sponsoredBy: { type: String },
65
+ lastSamplePeriodStartDate: { type: Date },
66
+ lastSamplePeriodEndDate: { type: Date },
67
67
  isPrivate: { type: Boolean, default: false, required: true },
68
68
  sponsor: { type: mongoose.Types.ObjectId, ref: "Organizations" },
69
- sponsorName: String,
70
- monitorLatitude: Number,
71
- monitorLongitude: Number,
72
- gpsLocation: {
73
- type: { type: String, enum: ["Point"], required: true },
74
- coordinates: { type: [Number], required: true },
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
+ },
75
94
  },
76
- location: Object,
77
- context: [String],
95
+ context: { type: [String] },
78
96
  parameters: [
79
97
  {
80
98
  type: String,
81
99
  enum: sampleParametersEnum,
82
100
  },
83
101
  ],
84
- notes: [noteSchema],
85
- image: String,
102
+ notes: { type: [noteSchema] },
103
+ image: { type: String },
86
104
  },
87
105
  {
88
106
  timestamps: true,
@@ -90,10 +108,10 @@ const sampleSitesSchema = mongoose.Schema(
90
108
  );
91
109
 
92
110
  // Geographic queries - already exists
93
- sampleSitesSchema.index({ gpsLocation: "2dsphere" });
111
+ sampleSitesSchema.index({ "location.gps": "2dsphere" }); // use location.gps for geospatial queries
94
112
 
95
113
  // Sponsor-based queries
96
- sampleSitesSchema.index({ sponsor: 1, isActive: 1 });
114
+ sampleSitesSchema.index({ sponsor: 1, isPrivate: 1 });
97
115
 
98
116
  // Location-based filtering
99
117
  sampleSitesSchema.index({ "location.city": 1, "location.state": 1 });
@@ -109,20 +127,25 @@ sampleSitesSchema.pre("findOneAndDelete", async function () {
109
127
  const docToDelete = await this.model.findOne(this.getFilter()).lean();
110
128
  if (docToDelete) {
111
129
  console.log("Logging findOneAndDelete to sample site audit", docToDelete);
112
- const auditLog = new SampleSiteAudit({
113
- monitorId: docToDelete._id,
114
- orgId: docToDelete.sponsor,
115
- timeUpdated: docToDelete.updatedAt,
116
- monitorLocation: {
117
- type: "Point",
118
- coordinates: [
119
- docToDelete.monitorLongitude,
120
- docToDelete.monitorLatitude,
121
- ],
122
- },
123
- deletedAt: new Date(),
124
- });
125
- await auditLog.save();
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
+ }
126
149
  }
127
150
  });
128
151
 
@@ -133,18 +156,31 @@ sampleSitesSchema.pre("deleteMany", async function () {
133
156
 
134
157
  if (docsToDelete.length) {
135
158
  console.log(`Logging ${docsToDelete.length} sample site documents to audit`);
136
- const auditLogs = docsToDelete.map((doc) => ({
137
- monitorId: doc._id,
138
- orgId: doc.sponsor,
139
- timeUpdated: doc.updatedAt,
140
- monitorLocation: {
141
- type: "Point",
142
- coordinates: [doc.monitorLongitude, doc.monitorLatitude],
143
- },
144
- deletedAt: new Date(),
145
- }));
146
-
147
- await SampleSiteAudit.insertMany(auditLogs);
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
+ }
148
184
  }
149
185
  });
150
186
 
@@ -155,20 +191,25 @@ sampleSitesSchema.pre("deleteOne", async function () {
155
191
 
156
192
  if (docToDelete) {
157
193
  console.log("Logging deleteOne to sample site audit", docToDelete);
158
- const auditLog = new SampleSiteAudit({
159
- monitorId: docToDelete._id,
160
- orgId: docToDelete.sponsor,
161
- timeUpdated: docToDelete.updatedAt,
162
- monitorLocation: {
163
- type: "Point",
164
- coordinates: [
165
- docToDelete.monitorLongitude,
166
- docToDelete.monitorLatitude,
167
- ],
168
- },
169
- deletedAt: new Date(),
170
- });
171
- await auditLog.save();
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
+ }
172
213
  }
173
214
  });
174
215
 
@@ -177,18 +218,31 @@ sampleSitesSchema.pre("updateMany", async function () {
177
218
  const docsToUpdate = await this.model.find(this.getFilter()).lean();
178
219
  if (docsToUpdate.length) {
179
220
  console.log(`Logging ${docsToUpdate.length} sample site documents to audit`);
180
- const auditLogs = docsToUpdate.map((doc) => ({
181
- monitorId: doc._id,
182
- orgId: doc.sponsor,
183
- timeUpdated: doc.updatedAt,
184
- monitorLocation: {
185
- type: "Point",
186
- coordinates: [doc.monitorLongitude, doc.monitorLatitude],
187
- },
188
- deletedAt: null, // Not a deletion, so this field is null
189
- }));
190
-
191
- await SampleSiteAudit.insertMany(auditLogs);
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
+ }
192
246
  }
193
247
  });
194
248