@justair/justair-library 6.14.0 → 7.0.0
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/dist/config/db.d.ts +3 -1
- package/dist/config/db.d.ts.map +1 -1
- package/dist/config/tests/db.test.d.ts +2 -0
- package/dist/config/tests/db.test.d.ts.map +1 -0
- package/dist/models/measurements.d.ts +31 -36
- package/dist/models/measurements.d.ts.map +1 -1
- package/dist/models/monitorEpisodes.d.ts +122 -222
- package/dist/models/monitorEpisodes.d.ts.map +1 -1
- package/dist/models/organizations.d.ts.map +1 -1
- package/dist/models/passwordResetToken.d.ts +82 -122
- package/dist/models/passwordResetToken.d.ts.map +1 -1
- package/dist/models/sampleSites.d.ts.map +1 -1
- package/dist/models/sites.d.ts.map +1 -1
- package/dist/models/userNotifications.d.ts +260 -122
- package/dist/models/userNotifications.d.ts.map +1 -1
- package/dist/models/users.d.ts.map +1 -1
- package/dist/modules/audit/schema.d.ts +122 -195
- package/dist/modules/audit/schema.d.ts.map +1 -1
- package/package.json +8 -2
- package/src/config/db.js +6 -1
- package/src/config/tests/db.test.js +46 -0
- package/src/models/organizations.js +1 -3
- package/src/models/sampleSites.js +269 -260
- package/src/models/sites.js +65 -55
- package/src/models/users.js +1 -2
|
@@ -1,261 +1,270 @@
|
|
|
1
|
-
import mongoose from "mongoose";
|
|
2
|
-
import { customAlphabet } from 'nanoid';
|
|
3
|
-
import { HEAVY_METALS } from "../constants/pollutants.js";
|
|
4
|
-
|
|
5
|
-
const generateSiteCode = customAlphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 8);
|
|
6
|
-
|
|
7
|
-
const sampleParametersEnum = Object.keys(HEAVY_METALS);
|
|
8
|
-
|
|
9
|
-
const noteSchema = mongoose.Schema({
|
|
10
|
-
note: {
|
|
11
|
-
type: String,
|
|
12
|
-
required: true,
|
|
13
|
-
},
|
|
14
|
-
type: {
|
|
15
|
-
type: String,
|
|
16
|
-
required: true,
|
|
17
|
-
},
|
|
18
|
-
adminId: {
|
|
19
|
-
type: mongoose.Types.ObjectId,
|
|
20
|
-
ref: "Admin",
|
|
21
|
-
required: true,
|
|
22
|
-
},
|
|
23
|
-
adminName: {
|
|
24
|
-
type: String,
|
|
25
|
-
},
|
|
26
|
-
date: {
|
|
27
|
-
type: Date,
|
|
28
|
-
default: Date.now,
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
// Sample Site Audit Schema
|
|
33
|
-
const sampleSiteAuditSchema = mongoose.Schema(
|
|
34
|
-
{
|
|
35
|
-
sampleSiteId: { type: mongoose.Types.ObjectId, ref: "SampleSites" },
|
|
36
|
-
sampleSiteCode: { type: String }, // reference to SampleSites.code
|
|
37
|
-
orgId: { type: mongoose.Types.ObjectId, ref: "Organizations" },
|
|
38
|
-
timeUpdated: Date,
|
|
39
|
-
deletedAt: { type: Date, default: Date.now }, // Only populated on delete
|
|
40
|
-
sampleSiteLocation: {
|
|
41
|
-
// Sample Site GPS location (lat, lon)
|
|
42
|
-
type: { type: String, enum: ["Point"], required: true },
|
|
43
|
-
coordinates: { type: [Number], required: true },
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
timestamps: true,
|
|
48
|
-
}
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
// Create the SampleSiteAudit model
|
|
52
|
-
const SampleSiteAudit = mongoose.model("SampleSiteAudit", sampleSiteAuditSchema);
|
|
53
|
-
|
|
54
|
-
// Sample Sites Schema
|
|
55
|
-
const sampleSitesSchema = mongoose.Schema(
|
|
56
|
-
{
|
|
57
|
-
name: { type: String },
|
|
58
|
-
code: { type: String, unique: true },
|
|
59
|
-
managedBy: { type: String },
|
|
60
|
-
sponsoredBy: { type: String },
|
|
61
|
-
lastSamplePeriodStartDate: { type: Date },
|
|
62
|
-
lastSamplePeriodEndDate: { type: Date },
|
|
63
|
-
lastSampleUploadDate: { type: Date },
|
|
64
|
-
isPrivate: { type: Boolean, default: false, required: true },
|
|
65
|
-
isActive: { type: Boolean, default: true, required: true },
|
|
66
|
-
sponsor: { type: mongoose.Types.ObjectId, ref: "Organizations" },
|
|
67
|
-
sponsorName: { type: String },
|
|
68
|
-
/*
|
|
69
|
-
* Location object: unified structure for all address and geospatial data.
|
|
70
|
-
* - monitorLatitude and monitorLongitude are deprecated and no longer used.
|
|
71
|
-
* - The location object (including location.gps) is now built in config/google.js.
|
|
72
|
-
* - location.gps is a GeoJSON Point used for all geospatial queries (replaces gpsLocation).
|
|
73
|
-
*/
|
|
74
|
-
location: {
|
|
75
|
-
city: { type: String },
|
|
76
|
-
state: { type: String },
|
|
77
|
-
county: { type: String },
|
|
78
|
-
street: { type: String },
|
|
79
|
-
zip_code: { type: String },
|
|
80
|
-
neighborhood: { type: String },
|
|
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
|
-
sampleSitesSchema.index({
|
|
135
|
-
|
|
136
|
-
//
|
|
137
|
-
sampleSitesSchema.
|
|
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
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
import { customAlphabet } from 'nanoid';
|
|
3
|
+
import { HEAVY_METALS } from "../constants/pollutants.js";
|
|
4
|
+
|
|
5
|
+
const generateSiteCode = customAlphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 8);
|
|
6
|
+
|
|
7
|
+
const sampleParametersEnum = Object.keys(HEAVY_METALS);
|
|
8
|
+
|
|
9
|
+
const noteSchema = mongoose.Schema({
|
|
10
|
+
note: {
|
|
11
|
+
type: String,
|
|
12
|
+
required: true,
|
|
13
|
+
},
|
|
14
|
+
type: {
|
|
15
|
+
type: String,
|
|
16
|
+
required: true,
|
|
17
|
+
},
|
|
18
|
+
adminId: {
|
|
19
|
+
type: mongoose.Types.ObjectId,
|
|
20
|
+
ref: "Admin",
|
|
21
|
+
required: true,
|
|
22
|
+
},
|
|
23
|
+
adminName: {
|
|
24
|
+
type: String,
|
|
25
|
+
},
|
|
26
|
+
date: {
|
|
27
|
+
type: Date,
|
|
28
|
+
default: Date.now,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Sample Site Audit Schema
|
|
33
|
+
const sampleSiteAuditSchema = mongoose.Schema(
|
|
34
|
+
{
|
|
35
|
+
sampleSiteId: { type: mongoose.Types.ObjectId, ref: "SampleSites" },
|
|
36
|
+
sampleSiteCode: { type: String }, // reference to SampleSites.code
|
|
37
|
+
orgId: { type: mongoose.Types.ObjectId, ref: "Organizations" },
|
|
38
|
+
timeUpdated: Date,
|
|
39
|
+
deletedAt: { type: Date, default: Date.now }, // Only populated on delete
|
|
40
|
+
sampleSiteLocation: {
|
|
41
|
+
// Sample Site GPS location (lat, lon)
|
|
42
|
+
type: { type: String, enum: ["Point"], required: true },
|
|
43
|
+
coordinates: { type: [Number], required: true },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
timestamps: true,
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Create the SampleSiteAudit model
|
|
52
|
+
const SampleSiteAudit = mongoose.model("SampleSiteAudit", sampleSiteAuditSchema);
|
|
53
|
+
|
|
54
|
+
// Sample Sites Schema
|
|
55
|
+
const sampleSitesSchema = mongoose.Schema(
|
|
56
|
+
{
|
|
57
|
+
name: { type: String },
|
|
58
|
+
code: { type: String, unique: true },
|
|
59
|
+
managedBy: { type: String },
|
|
60
|
+
sponsoredBy: { type: String },
|
|
61
|
+
lastSamplePeriodStartDate: { type: Date },
|
|
62
|
+
lastSamplePeriodEndDate: { type: Date },
|
|
63
|
+
lastSampleUploadDate: { type: Date },
|
|
64
|
+
isPrivate: { type: Boolean, default: false, required: true },
|
|
65
|
+
isActive: { type: Boolean, default: true, required: true },
|
|
66
|
+
sponsor: { type: mongoose.Types.ObjectId, ref: "Organizations" },
|
|
67
|
+
sponsorName: { type: String },
|
|
68
|
+
/*
|
|
69
|
+
* Location object: unified structure for all address and geospatial data.
|
|
70
|
+
* - monitorLatitude and monitorLongitude are deprecated and no longer used.
|
|
71
|
+
* - The location object (including location.gps) is now built in config/google.js.
|
|
72
|
+
* - location.gps is a GeoJSON Point used for all geospatial queries (replaces gpsLocation).
|
|
73
|
+
*/
|
|
74
|
+
location: {
|
|
75
|
+
city: { type: String },
|
|
76
|
+
state: { type: String },
|
|
77
|
+
county: { type: String },
|
|
78
|
+
street: { type: String },
|
|
79
|
+
zip_code: { type: String },
|
|
80
|
+
neighborhood: { type: String },
|
|
81
|
+
// GPS is optional (legacy docs predate it — D14), but a partial Point is
|
|
82
|
+
// invalid: providing either half makes both halves required.
|
|
83
|
+
gps: {
|
|
84
|
+
type: {
|
|
85
|
+
type: String,
|
|
86
|
+
enum: ["Point"],
|
|
87
|
+
required: function () {
|
|
88
|
+
const gps = this.location?.gps;
|
|
89
|
+
return Array.isArray(gps?.coordinates) && gps.coordinates.length > 0;
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
coordinates: {
|
|
93
|
+
type: [Number],
|
|
94
|
+
// no [] default — an empty coordinates array is invalid GeoJSON and
|
|
95
|
+
// would poison the 2dsphere index
|
|
96
|
+
default: undefined,
|
|
97
|
+
required: function () {
|
|
98
|
+
return this.location?.gps?.type != null;
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
context: { type: [String] },
|
|
104
|
+
parameters: [
|
|
105
|
+
{
|
|
106
|
+
type: String,
|
|
107
|
+
enum: sampleParametersEnum,
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
notes: { type: [noteSchema] },
|
|
111
|
+
image: { type: String },
|
|
112
|
+
siteId: {
|
|
113
|
+
type: mongoose.Types.ObjectId,
|
|
114
|
+
ref: "Sites",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
timestamps: true,
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Update pre-save hook
|
|
123
|
+
sampleSitesSchema.pre('save', function () {
|
|
124
|
+
if (!this.code) {
|
|
125
|
+
this.code = generateSiteCode();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
// Geographic queries - already exists
|
|
131
|
+
sampleSitesSchema.index({ "location.gps": "2dsphere" }); // use location.gps for geospatial queries
|
|
132
|
+
|
|
133
|
+
// Sponsor-based queries
|
|
134
|
+
sampleSitesSchema.index({ sponsor: 1, isPrivate: 1 });
|
|
135
|
+
|
|
136
|
+
// Location-based filtering
|
|
137
|
+
sampleSitesSchema.index({ "location.city": 1, "location.state": 1 });
|
|
138
|
+
sampleSitesSchema.index({ "location.neighborhood": 1 });
|
|
139
|
+
sampleSitesSchema.index({ "location.county": 1 });
|
|
140
|
+
|
|
141
|
+
// Query parameter filtering
|
|
142
|
+
sampleSitesSchema.index({ context: 1 });
|
|
143
|
+
sampleSitesSchema.index({ parameters: 1 });
|
|
144
|
+
|
|
145
|
+
// Pre-hook to log single document deletions
|
|
146
|
+
sampleSitesSchema.pre("findOneAndDelete", async function () {
|
|
147
|
+
const docToDelete = await this.model.findOne(this.getFilter()).lean();
|
|
148
|
+
if (docToDelete) {
|
|
149
|
+
let coordinates = null;
|
|
150
|
+
if (docToDelete.location && docToDelete.location.gps && Array.isArray(docToDelete.location.gps.coordinates)) {
|
|
151
|
+
coordinates = docToDelete.location.gps.coordinates;
|
|
152
|
+
} else if (docToDelete.gpsLocation && Array.isArray(docToDelete.gpsLocation.coordinates)) {
|
|
153
|
+
coordinates = docToDelete.gpsLocation.coordinates;
|
|
154
|
+
}
|
|
155
|
+
if (coordinates) {
|
|
156
|
+
const auditLog = new SampleSiteAudit({
|
|
157
|
+
sampleSiteId: docToDelete._id,
|
|
158
|
+
sampleSiteCode: docToDelete.code,
|
|
159
|
+
orgId: docToDelete.sponsor,
|
|
160
|
+
timeUpdated: docToDelete.updatedAt,
|
|
161
|
+
sampleSiteLocation: {
|
|
162
|
+
type: "Point",
|
|
163
|
+
coordinates,
|
|
164
|
+
},
|
|
165
|
+
deletedAt: new Date(),
|
|
166
|
+
});
|
|
167
|
+
await auditLog.save();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Pre-hook to log multiple document deletions
|
|
173
|
+
sampleSitesSchema.pre("deleteMany", async function () {
|
|
174
|
+
const docsToDelete = await this.model.find(this.getFilter()).lean();
|
|
175
|
+
|
|
176
|
+
if (docsToDelete.length) {
|
|
177
|
+
const auditLogs = docsToDelete.map((doc) => {
|
|
178
|
+
let coordinates = null;
|
|
179
|
+
if (doc.location && doc.location.gps && Array.isArray(doc.location.gps.coordinates)) {
|
|
180
|
+
coordinates = doc.location.gps.coordinates;
|
|
181
|
+
} else if (doc.gpsLocation && Array.isArray(doc.gpsLocation.coordinates)) {
|
|
182
|
+
coordinates = doc.gpsLocation.coordinates;
|
|
183
|
+
}
|
|
184
|
+
if (coordinates) {
|
|
185
|
+
return {
|
|
186
|
+
sampleSiteId: doc._id,
|
|
187
|
+
sampleSiteCode: doc.code,
|
|
188
|
+
orgId: doc.sponsor,
|
|
189
|
+
timeUpdated: doc.updatedAt,
|
|
190
|
+
sampleSiteLocation: {
|
|
191
|
+
type: "Point",
|
|
192
|
+
coordinates,
|
|
193
|
+
},
|
|
194
|
+
deletedAt: new Date(),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}).filter(Boolean);
|
|
199
|
+
|
|
200
|
+
if (auditLogs.length) {
|
|
201
|
+
await SampleSiteAudit.insertMany(auditLogs);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Pre-hook to log a single document deletion (for deleteOne)
|
|
207
|
+
sampleSitesSchema.pre("deleteOne", async function () {
|
|
208
|
+
const docToDelete = await this.model.findOne(this.getFilter()).lean();
|
|
209
|
+
|
|
210
|
+
if (docToDelete) {
|
|
211
|
+
let coordinates = null;
|
|
212
|
+
if (docToDelete.location && docToDelete.location.gps && Array.isArray(docToDelete.location.gps.coordinates)) {
|
|
213
|
+
coordinates = docToDelete.location.gps.coordinates;
|
|
214
|
+
} else if (docToDelete.gpsLocation && Array.isArray(docToDelete.gpsLocation.coordinates)) {
|
|
215
|
+
coordinates = docToDelete.gpsLocation.coordinates;
|
|
216
|
+
}
|
|
217
|
+
if (coordinates) {
|
|
218
|
+
const auditLog = new SampleSiteAudit({
|
|
219
|
+
sampleSiteId: docToDelete._id,
|
|
220
|
+
sampleSiteCode: docToDelete.code,
|
|
221
|
+
orgId: docToDelete.sponsor,
|
|
222
|
+
timeUpdated: docToDelete.updatedAt,
|
|
223
|
+
sampleSiteLocation: {
|
|
224
|
+
type: "Point",
|
|
225
|
+
coordinates,
|
|
226
|
+
},
|
|
227
|
+
deletedAt: new Date(),
|
|
228
|
+
});
|
|
229
|
+
await auditLog.save();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Pre-hook to log multiple document updates
|
|
235
|
+
sampleSitesSchema.pre("updateMany", async function () {
|
|
236
|
+
const docsToUpdate = await this.model.find(this.getFilter()).lean();
|
|
237
|
+
if (docsToUpdate.length) {
|
|
238
|
+
const auditLogs = docsToUpdate.map((doc) => {
|
|
239
|
+
let coordinates = null;
|
|
240
|
+
if (doc.location && doc.location.gps && Array.isArray(doc.location.gps.coordinates)) {
|
|
241
|
+
coordinates = doc.location.gps.coordinates;
|
|
242
|
+
} else if (doc.gpsLocation && Array.isArray(doc.gpsLocation.coordinates)) {
|
|
243
|
+
coordinates = doc.gpsLocation.coordinates;
|
|
244
|
+
}
|
|
245
|
+
if (coordinates) {
|
|
246
|
+
return {
|
|
247
|
+
sampleSiteId: doc._id,
|
|
248
|
+
sampleSiteCode: doc.code,
|
|
249
|
+
orgId: doc.sponsor,
|
|
250
|
+
timeUpdated: doc.updatedAt,
|
|
251
|
+
sampleSiteLocation: {
|
|
252
|
+
type: "Point",
|
|
253
|
+
coordinates,
|
|
254
|
+
},
|
|
255
|
+
deletedAt: null, // Not a deletion, so this field is null
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
return null;
|
|
259
|
+
}).filter(Boolean);
|
|
260
|
+
|
|
261
|
+
if (auditLogs.length) {
|
|
262
|
+
await SampleSiteAudit.insertMany(auditLogs);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Create the SampleSites model
|
|
268
|
+
const SampleSites = mongoose.model("SampleSites", sampleSitesSchema);
|
|
269
|
+
|
|
261
270
|
export { sampleSitesSchema, SampleSites, sampleSiteAuditSchema, SampleSiteAudit, sampleParametersEnum };
|
package/src/models/sites.js
CHANGED
|
@@ -1,55 +1,65 @@
|
|
|
1
|
-
import mongoose from "mongoose";
|
|
2
|
-
|
|
3
|
-
// Sites Schema
|
|
4
|
-
const sitesSchema = mongoose.Schema(
|
|
5
|
-
{
|
|
6
|
-
name: { type: String },
|
|
7
|
-
sampleSiteId: {
|
|
8
|
-
type: mongoose.Types.ObjectId,
|
|
9
|
-
ref: "SampleSites",
|
|
10
|
-
},
|
|
11
|
-
monitorIds: {
|
|
12
|
-
type: [{ type: mongoose.Types.ObjectId, ref: "Monitors" }],
|
|
13
|
-
default: undefined,
|
|
14
|
-
},
|
|
15
|
-
sponsor: { type: mongoose.Types.ObjectId, ref: "Organizations" },
|
|
16
|
-
location: {
|
|
17
|
-
city: { type: String },
|
|
18
|
-
state: { type: String },
|
|
19
|
-
county: { type: String },
|
|
20
|
-
street: { type: String },
|
|
21
|
-
zip_code: { type: String },
|
|
22
|
-
neighborhood: { type: String },
|
|
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
|
-
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
|
|
3
|
+
// Sites Schema
|
|
4
|
+
const sitesSchema = mongoose.Schema(
|
|
5
|
+
{
|
|
6
|
+
name: { type: String },
|
|
7
|
+
sampleSiteId: {
|
|
8
|
+
type: mongoose.Types.ObjectId,
|
|
9
|
+
ref: "SampleSites",
|
|
10
|
+
},
|
|
11
|
+
monitorIds: {
|
|
12
|
+
type: [{ type: mongoose.Types.ObjectId, ref: "Monitors" }],
|
|
13
|
+
default: undefined,
|
|
14
|
+
},
|
|
15
|
+
sponsor: { type: mongoose.Types.ObjectId, ref: "Organizations" },
|
|
16
|
+
location: {
|
|
17
|
+
city: { type: String },
|
|
18
|
+
state: { type: String },
|
|
19
|
+
county: { type: String },
|
|
20
|
+
street: { type: String },
|
|
21
|
+
zip_code: { type: String },
|
|
22
|
+
neighborhood: { type: String },
|
|
23
|
+
// GPS is optional (legacy docs predate it — D14), but a partial Point is
|
|
24
|
+
// invalid: providing either half makes both halves required.
|
|
25
|
+
gps: {
|
|
26
|
+
type: {
|
|
27
|
+
type: String,
|
|
28
|
+
enum: ["Point"],
|
|
29
|
+
required: function () {
|
|
30
|
+
const gps = this.location?.gps;
|
|
31
|
+
return Array.isArray(gps?.coordinates) && gps.coordinates.length > 0;
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
coordinates: {
|
|
35
|
+
type: [Number],
|
|
36
|
+
// no [] default — an empty coordinates array is invalid GeoJSON and
|
|
37
|
+
// would poison the 2dsphere index
|
|
38
|
+
default: undefined,
|
|
39
|
+
required: function () {
|
|
40
|
+
return this.location?.gps?.type != null;
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
isActive: { type: Boolean, default: true },
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
timestamps: true,
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Geographic queries
|
|
53
|
+
sitesSchema.index({ "location.gps": "2dsphere" });
|
|
54
|
+
|
|
55
|
+
// Sponsor-based queries
|
|
56
|
+
sitesSchema.index({ sponsor: 1, isActive: 1 });
|
|
57
|
+
|
|
58
|
+
// Back-reference lookups
|
|
59
|
+
sitesSchema.index({ sampleSiteId: 1 });
|
|
60
|
+
sitesSchema.index({ monitorIds: 1 });
|
|
61
|
+
|
|
62
|
+
// Create the Sites model
|
|
63
|
+
const Sites = mongoose.model("Sites", sitesSchema);
|
|
64
|
+
|
|
65
|
+
export { sitesSchema, Sites };
|