@justair/justair-library 4.8.28 → 4.8.30

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.28",
3
+ "version": "4.8.30",
4
4
  "description": "JustAir Internal Library",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -17,11 +17,12 @@
17
17
  "license": "ISC",
18
18
  "dependencies": {
19
19
  "mongoose": "^7.8.3",
20
+ "nanoid": "^5.1.6",
20
21
  "winston": "^3.10.0"
21
22
  },
22
23
  "devDependencies": {
23
- "typescript": "^5.3.2",
24
24
  "@types/node": "^16.0.0",
25
- "ts-node": "^10.0.0"
25
+ "ts-node": "^10.0.0",
26
+ "typescript": "^5.3.2"
26
27
  }
27
28
  }
@@ -237,7 +237,8 @@ const monitorsSchema = mongoose.Schema(
237
237
  latestAQI_PM2_5: Number,
238
238
  notes: [noteSchema],
239
239
  calculatedAverages: [Object],
240
- images: [String],
240
+ image: String,
241
+ images: [String], // deprecated as of 2/9/26
241
242
  isActive: { type: Boolean, default: true },
242
243
  parameterThresholds: Object,
243
244
  completionPercentageThresholds: Object,
@@ -1,4 +1,7 @@
1
1
  import mongoose from "mongoose";
2
+ import { customAlphabet } from 'nanoid';
3
+ const generateSiteCode = customAlphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 8);
4
+
2
5
  const sampleParametersEnum = [
3
6
  "C6H6", // Benzene
4
7
  "PB", // Lead
@@ -39,6 +42,7 @@ const noteSchema = mongoose.Schema({
39
42
  const sampleSiteAuditSchema = mongoose.Schema(
40
43
  {
41
44
  sampleSiteId: { type: mongoose.Types.ObjectId, ref: "SampleSites" },
45
+ sampleSiteCode: { type: String }, // reference to SampleSites.code
42
46
  orgId: { type: mongoose.Types.ObjectId, ref: "Organizations" },
43
47
  timeUpdated: Date,
44
48
  deletedAt: { type: Date, default: Date.now }, // Only populated on delete
@@ -60,6 +64,7 @@ const SampleSiteAudit = mongoose.model("SampleSiteAudit", sampleSiteAuditSchema)
60
64
  const sampleSitesSchema = mongoose.Schema(
61
65
  {
62
66
  name: { type: String },
67
+ code: { type: String, unique: true },
63
68
  managedBy: { type: String },
64
69
  sponsoredBy: { type: String },
65
70
  lastSamplePeriodStartDate: { type: Date },
@@ -108,6 +113,14 @@ const sampleSitesSchema = mongoose.Schema(
108
113
  }
109
114
  );
110
115
 
116
+ // Update pre-save hook
117
+ sampleSitesSchema.pre('save', function (next) {
118
+ if (!this.code) {
119
+ this.code = generateSiteCode();
120
+ }
121
+ next();
122
+ });
123
+
111
124
  // Geographic queries - already exists
112
125
  sampleSitesSchema.index({ "location.gps": "2dsphere" }); // use location.gps for geospatial queries
113
126
 
@@ -137,6 +150,7 @@ sampleSitesSchema.pre("findOneAndDelete", async function () {
137
150
  if (coordinates) {
138
151
  const auditLog = new SampleSiteAudit({
139
152
  sampleSiteId: docToDelete._id,
153
+ sampleSiteCode: docToDelete.code,
140
154
  orgId: docToDelete.sponsor,
141
155
  timeUpdated: docToDelete.updatedAt,
142
156
  sampleSiteLocation: {
@@ -167,6 +181,7 @@ sampleSitesSchema.pre("deleteMany", async function () {
167
181
  if (coordinates) {
168
182
  return {
169
183
  sampleSiteId: doc._id,
184
+ sampleSiteCode: doc.code,
170
185
  orgId: doc.sponsor,
171
186
  timeUpdated: doc.updatedAt,
172
187
  sampleSiteLocation: {
@@ -201,6 +216,7 @@ sampleSitesSchema.pre("deleteOne", async function () {
201
216
  if (coordinates) {
202
217
  const auditLog = new SampleSiteAudit({
203
218
  sampleSiteId: docToDelete._id,
219
+ sampleSiteCode: docToDelete.code,
204
220
  orgId: docToDelete.sponsor,
205
221
  timeUpdated: docToDelete.updatedAt,
206
222
  sampleSiteLocation: {
@@ -229,6 +245,7 @@ sampleSitesSchema.pre("updateMany", async function () {
229
245
  if (coordinates) {
230
246
  return {
231
247
  sampleSiteId: doc._id,
248
+ sampleSiteCode: doc.code,
232
249
  orgId: doc.sponsor,
233
250
  timeUpdated: doc.updatedAt,
234
251
  sampleSiteLocation: {
@@ -0,0 +1,165 @@
1
+ import mongoose from "mongoose";
2
+
3
+ // Samples Audit Schema
4
+ const samplesAuditSchema = mongoose.Schema(
5
+ {
6
+ siteId: { type: mongoose.Types.ObjectId, ref: "SampleSites" },
7
+ orgId: { type: mongoose.Types.ObjectId, ref: "Organizations" },
8
+ siteCode: { type: String }, // reference to SampleSites.code
9
+ periodStartDate: { type: Date },
10
+ periodEndDate: { type: Date },
11
+ parameterName: { type: String },
12
+ parameterValue: { type: Number },
13
+ parameterUnit: { type: String },
14
+ upperDetectionLimit: { type: Number },
15
+ lowerDetectionLimit: { type: Number },
16
+ deletedAt: { type: Date, default: Date.now }, // Only populated on delete
17
+ },
18
+ {
19
+ timestamps: true,
20
+
21
+ }
22
+ );
23
+
24
+ // Create the SamplesAudit model
25
+ const SamplesAudit = mongoose.model("SamplesAudit", samplesAuditSchema);
26
+
27
+ // Samples Schema
28
+ const samplesSchema = mongoose.Schema(
29
+ {
30
+ siteId: { type: mongoose.Types.ObjectId, ref: "SampleSites" },
31
+ orgId: { type: mongoose.Types.ObjectId, ref: "Organizations" },
32
+ siteCode: { type: String }, // reference to SampleSites.code
33
+ periodStartDate: { type: Date },
34
+ periodEndDate: { type: Date },
35
+ parameterName: { type: String },
36
+ parameterValue: { type: Number },
37
+ parameterUnit: { type: String },
38
+ upperDetectionLimit: { type: Number },
39
+ lowerDetectionLimit: { type: Number },
40
+ },
41
+ {
42
+ timestamps: true,
43
+ }
44
+ );
45
+
46
+ // Primary query pattern index (descending periodStartDate for latest data first)
47
+ samplesSchema.index({ siteId: 1, periodStartDate: -1 });
48
+ // Pollutant-based queries
49
+ samplesSchema.index({ siteId: 1, parameterName: 1 });
50
+
51
+ // Pre-hook to log single document deletions
52
+ samplesSchema.pre("findOneAndDelete", async function () {
53
+ const docToDelete = await this.model.findOne(this.getFilter()).lean();
54
+ if (docToDelete) {
55
+ console.log("Logging findOneAndDelete to audit", docToDelete);
56
+ const auditLog = new SamplesAudit({
57
+ siteId: docToDelete.siteId,
58
+ orgId: docToDelete.orgId,
59
+ siteCode: docToDelete.siteCode,
60
+ periodStartDate: docToDelete.periodStartDate,
61
+ periodEndDate: docToDelete.periodEndDate,
62
+ parameterName: docToDelete.parameterName,
63
+ parameterValue: docToDelete.parameterValue,
64
+ parameterUnit: docToDelete.parameterUnit,
65
+ upperDetectionLimit: docToDelete.upperDetectionLimit,
66
+ lowerDetectionLimit: docToDelete.lowerDetectionLimit,
67
+ deletedAt: new Date(),
68
+ });
69
+ await auditLog.save();
70
+ }
71
+ });
72
+
73
+ // Pre-hook to log multiple document deletions
74
+ samplesSchema.pre("deleteMany", async function () {
75
+ console.log("deleteMany pre-hook triggered");
76
+ const docsToDelete = await this.model.find(this.getFilter()).lean();
77
+ if (docsToDelete.length) {
78
+ console.log(`Logging ${docsToDelete.length} documents to audit`);
79
+ const auditLogs = docsToDelete.map((doc) => ({
80
+ siteId: doc.siteId,
81
+ orgId: doc.orgId,
82
+ siteCode: doc.siteCode,
83
+ periodStartDate: doc.periodStartDate,
84
+ periodEndDate: doc.periodEndDate,
85
+ parameterName: doc.parameterName,
86
+ parameterValue: doc.parameterValue,
87
+ parameterUnit: doc.parameterUnit,
88
+ upperDetectionLimit: doc.upperDetectionLimit,
89
+ lowerDetectionLimit: doc.lowerDetectionLimit,
90
+ deletedAt: new Date(),
91
+ }));
92
+ await SamplesAudit.insertMany(auditLogs);
93
+ }
94
+ });
95
+
96
+ // Pre-hook to log a single document deletion (for deleteOne)
97
+ samplesSchema.pre("deleteOne", async function () {
98
+ console.log("deleteOne pre-hook triggered");
99
+ const docToDelete = await this.model.findOne(this.getFilter()).lean();
100
+ if (docToDelete) {
101
+ console.log("Logging deleteOne to audit", docToDelete);
102
+ const auditLog = new SamplesAudit({
103
+ siteId: docToDelete.siteId,
104
+ orgId: docToDelete.orgId,
105
+ siteCode: docToDelete.siteCode,
106
+ periodStartDate: docToDelete.periodStartDate,
107
+ periodEndDate: docToDelete.periodEndDate,
108
+ parameterName: docToDelete.parameterName,
109
+ parameterValue: docToDelete.parameterValue,
110
+ parameterUnit: docToDelete.parameterUnit,
111
+ upperDetectionLimit: docToDelete.upperDetectionLimit,
112
+ lowerDetectionLimit: docToDelete.lowerDetectionLimit,
113
+ deletedAt: new Date(),
114
+ });
115
+ await auditLog.save();
116
+ }
117
+ });
118
+
119
+ // Pre-hook to log single document updates
120
+ samplesSchema.pre("findOneAndUpdate", async function () {
121
+ const docToUpdate = await this.model.findOne(this.getFilter()).lean();
122
+ if (docToUpdate) {
123
+ console.log("Logging findOneAndUpdate to audit", docToUpdate);
124
+ const auditLog = new SamplesAudit({
125
+ siteId: docToUpdate.siteId,
126
+ orgId: docToUpdate.orgId,
127
+ siteCode: docToUpdate.siteCode,
128
+ periodStartDate: docToUpdate.periodStartDate,
129
+ periodEndDate: docToUpdate.periodEndDate,
130
+ parameterName: docToUpdate.parameterName,
131
+ parameterValue: docToUpdate.parameterValue,
132
+ parameterUnit: docToUpdate.parameterUnit,
133
+ upperDetectionLimit: docToUpdate.upperDetectionLimit,
134
+ lowerDetectionLimit: docToUpdate.lowerDetectionLimit,
135
+ deletedAt: null,
136
+ });
137
+ await auditLog.save();
138
+ }
139
+ })
140
+
141
+ // Pre-hook to log multiple document updates
142
+ samplesSchema.pre("updateMany", async function () {
143
+ const docsToUpdate = await this.model.find(this.getFilter()).lean();
144
+ if (docsToUpdate.length) {
145
+ console.log(`Logging ${docsToUpdate.length} documents to audit`);
146
+ const auditLogs = docsToUpdate.map((doc) => ({
147
+ siteId: doc.siteId,
148
+ orgId: doc.orgId,
149
+ siteCode: doc.siteCode,
150
+ periodStartDate: doc.periodStartDate,
151
+ periodEndDate: doc.periodEndDate,
152
+ parameterName: doc.parameterName,
153
+ parameterValue: doc.parameterValue,
154
+ parameterUnit: doc.parameterUnit,
155
+ upperDetectionLimit: doc.upperDetectionLimit,
156
+ lowerDetectionLimit: doc.lowerDetectionLimit,
157
+ deletedAt: null,
158
+ }));
159
+ await SamplesAudit.insertMany(auditLogs);
160
+ }
161
+ });
162
+
163
+ // Create the SampleSites model
164
+ const Samples = mongoose.model("Samples", samplesSchema);
165
+ export { samplesSchema, Samples, SamplesAudit, samplesAuditSchema };