@justair/justair-library 7.2.0-alpha.a00effb → 7.2.0-alpha.a4979c6

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/index.d.ts CHANGED
@@ -77,8 +77,6 @@ import { passwordResetTokenSchema } from "./models/passwordResetToken.js";
77
77
  import { PasswordResetToken } from "./models/passwordResetToken.js";
78
78
  import { monitorEpisodesSchema } from "./models/monitorEpisodes.js";
79
79
  import { MonitorEpisode } from "./models/monitorEpisodes.js";
80
- import { dataDownloadSchema } from "./models/dataDownloads.js";
81
- import { DataDownloads } from "./models/dataDownloads.js";
82
80
  import { userNotificationsSchema } from "./models/userNotifications.js";
83
81
  import { UserNotification } from "./models/userNotifications.js";
84
82
  import { AqiAlertNotification } from "./models/userNotifications.js";
@@ -88,6 +86,10 @@ import { AccountNotification } from "./models/userNotifications.js";
88
86
  import { NOTIFICATION_KINDS } from "./models/userNotifications.js";
89
87
  import { NOTIFICATION_CHANNELS } from "./models/userNotifications.js";
90
88
  import { DELIVERY_STATUSES } from "./models/userNotifications.js";
89
+ import { dataDownloadSchema } from "./models/dataDownloads.js";
90
+ import { DataDownloads } from "./models/dataDownloads.js";
91
+ import { AqiDataDownload } from "./models/dataDownloads.js";
92
+ import { SampleDataDownload } from "./models/dataDownloads.js";
91
93
  import { PARAMETERS } from "./constants/pollutants.js";
92
94
  import { HEAVY_METALS } from "./constants/pollutants.js";
93
95
  import { ALERT_LEVEL_WEIGHTS } from "./constants/alertLevels.js";
@@ -102,5 +104,5 @@ import { validateQuietHours } from "./utils/quietHours.js";
102
104
  import { getZoneMinutes } from "./utils/quietHours.js";
103
105
  import { isInQuietWindow } from "./utils/quietHours.js";
104
106
  import { resolveQuietHours } from "./utils/quietHours.js";
105
- export { Database, adminSchema, Admin, configurationsSchema, Configurations, measurementsSchema, Measurements, monitorRequestsSchema, MonitorRequests, monitorsSchema, Monitors, organizationsSchema, Organizations, usersSchema, Users, COOKIE_CONSENT_CATEGORIES, COOKIE_CONSENT_VALUES, validateCookieConsent, eventsSchema, Events, lightMonitorSchema, LightMonitors, monitorSuppliersSchema, MonitorSuppliers, contextsSchema, Contexts, parametersSchema, Parameters, announcementSchema, Announcements, jobsSchema, Jobs, apiKeySchema, ApiKey, UsageMetrics, usageMetricsSchema, Audit, auditSchema, EventsAudit, eventsAuditSchema, MonitorAudit, monitorAuditSchema, parametersEnum, deploymentTypesEnum, AlertsAudit, Alerts, alertsSchema, alertsAuditSchema, Features, featuresSchema, dataCompletenessSchema, DataCompleteness, networkMetricsSchema, NetworkMetrics, rateOfChangeSchema, RateOfChange, changeStreamCheckpointsSchema, ChangeStreamCheckpoints, sitesSchema, Sites, sampleSitesSchema, SampleSites, sampleSiteAuditSchema, SampleSiteAudit, sampleParametersEnum, samplesSchema, Samples, samplesAuditSchema, SamplesAudit, sampleParameterReferenceConcentrations, passwordResetTokenSchema, PasswordResetToken, monitorEpisodesSchema, MonitorEpisode, dataDownloadSchema, DataDownloads, userNotificationsSchema, UserNotification, AqiAlertNotification, DeviceOfflineNotification, WeeklyDigestNotification, AccountNotification, NOTIFICATION_KINDS, NOTIFICATION_CHANNELS, DELIVERY_STATUSES, PARAMETERS, HEAVY_METALS, ALERT_LEVEL_WEIGHTS, getAlertWeight, splitName, DEFAULT_QUIET_HOURS, DEFAULT_TIMEZONE, QUIET_HOURS_ERRORS, isValidTimeString, isValidIANATimezone, validateQuietHours, getZoneMinutes, isInQuietWindow, resolveQuietHours };
107
+ export { Database, adminSchema, Admin, configurationsSchema, Configurations, measurementsSchema, Measurements, monitorRequestsSchema, MonitorRequests, monitorsSchema, Monitors, organizationsSchema, Organizations, usersSchema, Users, COOKIE_CONSENT_CATEGORIES, COOKIE_CONSENT_VALUES, validateCookieConsent, eventsSchema, Events, lightMonitorSchema, LightMonitors, monitorSuppliersSchema, MonitorSuppliers, contextsSchema, Contexts, parametersSchema, Parameters, announcementSchema, Announcements, jobsSchema, Jobs, apiKeySchema, ApiKey, UsageMetrics, usageMetricsSchema, Audit, auditSchema, EventsAudit, eventsAuditSchema, MonitorAudit, monitorAuditSchema, parametersEnum, deploymentTypesEnum, AlertsAudit, Alerts, alertsSchema, alertsAuditSchema, Features, featuresSchema, dataCompletenessSchema, DataCompleteness, networkMetricsSchema, NetworkMetrics, rateOfChangeSchema, RateOfChange, changeStreamCheckpointsSchema, ChangeStreamCheckpoints, sitesSchema, Sites, sampleSitesSchema, SampleSites, sampleSiteAuditSchema, SampleSiteAudit, sampleParametersEnum, samplesSchema, Samples, samplesAuditSchema, SamplesAudit, sampleParameterReferenceConcentrations, passwordResetTokenSchema, PasswordResetToken, monitorEpisodesSchema, MonitorEpisode, userNotificationsSchema, UserNotification, AqiAlertNotification, DeviceOfflineNotification, WeeklyDigestNotification, AccountNotification, NOTIFICATION_KINDS, NOTIFICATION_CHANNELS, DELIVERY_STATUSES, dataDownloadSchema, DataDownloads, AqiDataDownload, SampleDataDownload, PARAMETERS, HEAVY_METALS, ALERT_LEVEL_WEIGHTS, getAlertWeight, splitName, DEFAULT_QUIET_HOURS, DEFAULT_TIMEZONE, QUIET_HOURS_ERRORS, isValidTimeString, isValidIANATimezone, validateQuietHours, getZoneMinutes, isInQuietWindow, resolveQuietHours };
106
108
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAwHA;;;iBAEC;yBAxDwB,oBAAoB;qBADxB,gBAAgB;4BAjEF,mBAAmB;sBAAnB,mBAAmB;qCAI/C,4BAA4B;+BAA5B,4BAA4B;mCAM5B,0BAA0B;6BAA1B,0BAA0B;sCAI1B,6BAA6B;gCAA7B,6BAA6B;+BAQ7B,sBAAsB;yBAAtB,sBAAsB;oCACsB,2BAA2B;8BAA3B,2BAA2B;4BAcvE,mBAAmB;sBAAnB,mBAAmB;0CAAnB,mBAAmB;sCAAnB,mBAAmB;sCAAnB,mBAAmB;6BAMnB,oBAAoB;uBAApB,oBAAoB;mCAnBuB,2BAA2B;8BAA3B,2BAA2B;uCAItE,8BAA8B;iCAA9B,8BAA8B;+BACI,sBAAsB;yBAAtB,sBAAsB;iCAClB,wBAAwB;2BAAxB,wBAAwB;mCAcnB,2BAA2B;8BAA3B,2BAA2B;2BAC5C,kBAAkB;qBAAlB,kBAAkB;6BACd,oBAAoB;uBAApB,oBAAoB;6BACR,0BAA0B;mCAA1B,0BAA0B;sBArCpE,0BAA0B;4BAA1B,0BAA0B;4BAiC1B,oBAAoB;kCAApB,oBAAoB;6BArBpB,sBAAsB;mCAAtB,sBAAsB;+BAAtB,sBAAsB;oCAAtB,sBAAsB;4BA+BtB,oBAAoB;uBAApB,oBAAoB;6BAApB,oBAAoB;kCAApB,oBAAoB;yBACc,sBAAsB;+BAAtB,sBAAsB;uCAIxD,8BAA8B;iCAA9B,8BAA8B;qCACgB,4BAA4B;+BAA5B,4BAA4B;mCAChC,0BAA0B;6BAA1B,0BAA0B;8CAIpE,qCAAqC;wCAArC,qCAAqC;4BAkCT,mBAAmB;sBAAnB,mBAAmB;kCAS/C,yBAAyB;4BAAzB,yBAAyB;sCAAzB,yBAAyB;gCAAzB,yBAAyB;qCAAzB,yBAAyB;8BASzB,qBAAqB;wBAArB,qBAAqB;mCAArB,qBAAqB;6BAArB,qBAAqB;uDAArB,qBAAqB;yCACiC,gCAAgC;mCAAhC,gCAAgC;sCA7CtF,6BAA6B;+BAA7B,6BAA6B;mCA8Cc,2BAA2B;8BAA3B,2BAA2B;wCAnCtE,+BAA+B;iCAA/B,+BAA+B;qCAA/B,+BAA+B;0CAA/B,+BAA+B;yCAA/B,+BAA+B;oCAA/B,+BAA+B;mCAA/B,+BAA+B;sCAA/B,+BAA+B;kCAA/B,+BAA+B;2BAhBG,2BAA2B;6BAA3B,2BAA2B;oCAChB,4BAA4B;+BAA5B,4BAA4B;0BAgBtD,sBAAsB;oCAWzC,uBAAuB;iCAAvB,uBAAuB;mCAAvB,uBAAuB;kCAAvB,uBAAuB;oCAAvB,uBAAuB;mCAAvB,uBAAuB;+BAAvB,uBAAuB;gCAAvB,uBAAuB;kCAAvB,uBAAuB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AA6HA;;;iBAEC;yBA7DwB,oBAAoB;qBADxB,gBAAgB;4BAjEF,mBAAmB;sBAAnB,mBAAmB;qCAI/C,4BAA4B;+BAA5B,4BAA4B;mCAM5B,0BAA0B;6BAA1B,0BAA0B;sCAI1B,6BAA6B;gCAA7B,6BAA6B;+BAQ7B,sBAAsB;yBAAtB,sBAAsB;oCACsB,2BAA2B;8BAA3B,2BAA2B;4BAcvE,mBAAmB;sBAAnB,mBAAmB;0CAAnB,mBAAmB;sCAAnB,mBAAmB;sCAAnB,mBAAmB;6BAMnB,oBAAoB;uBAApB,oBAAoB;mCAnBuB,2BAA2B;8BAA3B,2BAA2B;uCAItE,8BAA8B;iCAA9B,8BAA8B;+BACI,sBAAsB;yBAAtB,sBAAsB;iCAClB,wBAAwB;2BAAxB,wBAAwB;mCAcnB,2BAA2B;8BAA3B,2BAA2B;2BAC5C,kBAAkB;qBAAlB,kBAAkB;6BACd,oBAAoB;uBAApB,oBAAoB;6BACR,0BAA0B;mCAA1B,0BAA0B;sBArCpE,0BAA0B;4BAA1B,0BAA0B;4BAiC1B,oBAAoB;kCAApB,oBAAoB;6BArBpB,sBAAsB;mCAAtB,sBAAsB;+BAAtB,sBAAsB;oCAAtB,sBAAsB;4BA+BtB,oBAAoB;uBAApB,oBAAoB;6BAApB,oBAAoB;kCAApB,oBAAoB;yBACc,sBAAsB;+BAAtB,sBAAsB;uCAIxD,8BAA8B;iCAA9B,8BAA8B;qCACgB,4BAA4B;+BAA5B,4BAA4B;mCAChC,0BAA0B;6BAA1B,0BAA0B;8CAIpE,qCAAqC;wCAArC,qCAAqC;4BAkCT,mBAAmB;sBAAnB,mBAAmB;kCAS/C,yBAAyB;4BAAzB,yBAAyB;sCAAzB,yBAAyB;gCAAzB,yBAAyB;qCAAzB,yBAAyB;8BASzB,qBAAqB;wBAArB,qBAAqB;mCAArB,qBAAqB;6BAArB,qBAAqB;uDAArB,qBAAqB;yCACiC,gCAAgC;mCAAhC,gCAAgC;sCA7CtF,6BAA6B;+BAA7B,6BAA6B;wCAW7B,+BAA+B;iCAA/B,+BAA+B;qCAA/B,+BAA+B;0CAA/B,+BAA+B;yCAA/B,+BAA+B;oCAA/B,+BAA+B;mCAA/B,+BAA+B;sCAA/B,+BAA+B;kCAA/B,+BAA+B;mCAwC/B,2BAA2B;8BAA3B,2BAA2B;gCAA3B,2BAA2B;mCAA3B,2BAA2B;2BAxDO,2BAA2B;6BAA3B,2BAA2B;oCAChB,4BAA4B;+BAA5B,4BAA4B;0BAgBtD,sBAAsB;oCAWzC,uBAAuB;iCAAvB,uBAAuB;mCAAvB,uBAAuB;kCAAvB,uBAAuB;oCAAvB,uBAAuB;mCAAvB,uBAAuB;+BAAvB,uBAAuB;gCAAvB,uBAAuB;kCAAvB,uBAAuB"}
@@ -1,3 +1,5 @@
1
1
  export const dataDownloadSchema: any;
2
2
  export const DataDownloads: any;
3
+ export const AqiDataDownload: any;
4
+ export const SampleDataDownload: any;
3
5
  //# sourceMappingURL=dataDownloads.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dataDownloads.d.ts","sourceRoot":"","sources":["../../src/models/dataDownloads.js"],"names":[],"mappings":"AAEA,qCAwBE;AAEF,gCAA0E"}
1
+ {"version":3,"file":"dataDownloads.d.ts","sourceRoot":"","sources":["../../src/models/dataDownloads.js"],"names":[],"mappings":"AAWA,qCAiBE;AAuBF,gCAA0E;AAE1E,kCASE;AAGF,qCASE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justair/justair-library",
3
- "version": "7.2.0-alpha.a00effb",
3
+ "version": "7.2.0-alpha.a4979c6",
4
4
  "description": "JustAir Internal Library",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -116,7 +116,12 @@ import {
116
116
  sampleParameterReferenceConcentrations,
117
117
  } from "./models/samples.js";
118
118
  import { passwordResetTokenSchema, PasswordResetToken } from "./models/passwordResetToken.js";
119
- import { dataDownloadSchema, DataDownloads } from "./models/dataDownloads.js";
119
+ import {
120
+ dataDownloadSchema,
121
+ DataDownloads,
122
+ AqiDataDownload,
123
+ SampleDataDownload,
124
+ } from "./models/dataDownloads.js";
120
125
 
121
126
  export function createLoggerInstance({ DATADOG_API_KEY, APPLICATION_NAME }) {
122
127
  return new CustomLogger({ DATADOG_API_KEY, APPLICATION_NAME });
@@ -202,9 +207,6 @@ export {
202
207
  // Notification episodes + send log (JA-2658 / JA-2691 stack)
203
208
  monitorEpisodesSchema,
204
209
  MonitorEpisode,
205
- // Data downloads (JA-3080)
206
- dataDownloadSchema,
207
- DataDownloads,
208
210
  userNotificationsSchema,
209
211
  UserNotification,
210
212
  AqiAlertNotification,
@@ -214,6 +216,11 @@ export {
214
216
  NOTIFICATION_KINDS,
215
217
  NOTIFICATION_CHANNELS,
216
218
  DELIVERY_STATUSES,
219
+ // Data downloads (JA-3080)
220
+ dataDownloadSchema,
221
+ DataDownloads,
222
+ AqiDataDownload,
223
+ SampleDataDownload,
217
224
  // Pollutant constants
218
225
  PARAMETERS,
219
226
  HEAVY_METALS,
@@ -1,31 +1,78 @@
1
1
  import mongoose from "mongoose";
2
+ import { DEFAULT_TIMEZONE } from "../utils/quietHours.js";
2
3
 
4
+ const MAX_RANGE_DAYS = 31;
5
+
6
+ // DataDownloads — one doc per data download request (JA-3080). Written by
7
+ // JustAirService on download (JA-3081); read by the org-level admin
8
+ // communication screen. AQI downloads are monitor-scoped, Sample downloads
9
+ // are site-scoped (samples.js keys samples to SampleSites, not Monitors) —
10
+ // modeled as discriminators per CLAUDE.md's multi-kind pattern rather than
11
+ // a flat schema with a monitorId/siteId pair that's nullable either way.
3
12
  const dataDownloadSchema = mongoose.Schema(
4
13
  {
5
- monitorId: {
6
- type: mongoose.Types.ObjectId,
7
- ref: "Monitors",
8
- required: [true, "monitorId is required"],
9
- },
10
14
  orgId: {
11
15
  type: mongoose.Types.ObjectId,
12
16
  ref: "Organizations",
13
17
  required: [true, "orgId is required"],
14
18
  },
15
- dataType: {
16
- type: String,
17
- enum: ["AQI", "Sample"],
18
- required: [true, "dataType is required"],
19
- },
20
- startDate: { type: Date, required: [true, "startDate is required"] },
21
- endDate: { type: Date, required: [true, "endDate is required"] },
22
- email: { type: String, default: null },
19
+ dateRangeStart: { type: Date, required: [true, "dateRangeStart is required"] },
20
+ dateRangeEnd: { type: Date, required: [true, "dateRangeEnd is required"] },
21
+ timezone: { type: String, default: DEFAULT_TIMEZONE },
22
+ downloadedAt: { type: Date, required: true, default: Date.now },
23
+ email: { type: String, trim: true, lowercase: true, default: null },
23
24
  },
24
25
  {
25
26
  timestamps: true,
27
+ discriminatorKey: "dataType",
26
28
  }
27
29
  );
28
30
 
31
+ // Cross-field rule (quiet-hours shape, CLAUDE.md): end must not precede
32
+ // start, and the product rule caps a single download at 31 days of hourly
33
+ // data. Lives in pre('validate') so create()/save() can't persist an
34
+ // invalid range even if a caller forgets to check.
35
+ dataDownloadSchema.pre("validate", async function () {
36
+ if (this.dateRangeStart && this.dateRangeEnd) {
37
+ if (this.dateRangeEnd < this.dateRangeStart) {
38
+ this.invalidate("dateRangeEnd", "dateRangeEnd must not be before dateRangeStart");
39
+ } else {
40
+ const rangeMs = this.dateRangeEnd - this.dateRangeStart;
41
+ const maxRangeMs = MAX_RANGE_DAYS * 24 * 60 * 60 * 1000;
42
+ if (rangeMs > maxRangeMs) {
43
+ this.invalidate("dateRangeEnd", `date range cannot exceed ${MAX_RANGE_DAYS} days`);
44
+ }
45
+ }
46
+ }
47
+ });
48
+
49
+ // Org-level admin screen lists a given org's downloads, most recent first.
50
+ dataDownloadSchema.index({ orgId: 1, createdAt: -1 });
51
+
29
52
  const DataDownloads = mongoose.model("DataDownloads", dataDownloadSchema);
30
53
 
31
- export { dataDownloadSchema, DataDownloads };
54
+ const AqiDataDownload = DataDownloads.discriminator(
55
+ "AQI",
56
+ new mongoose.Schema({
57
+ monitorId: {
58
+ type: mongoose.Types.ObjectId,
59
+ ref: "Monitors",
60
+ required: [true, "monitorId is required"],
61
+ },
62
+ })
63
+ );
64
+ AqiDataDownload.schema.index({ monitorId: 1, createdAt: -1 });
65
+
66
+ const SampleDataDownload = DataDownloads.discriminator(
67
+ "Sample",
68
+ new mongoose.Schema({
69
+ siteId: {
70
+ type: mongoose.Types.ObjectId,
71
+ ref: "SampleSites",
72
+ required: [true, "siteId is required"],
73
+ },
74
+ })
75
+ );
76
+ SampleDataDownload.schema.index({ siteId: 1, createdAt: -1 });
77
+
78
+ export { dataDownloadSchema, DataDownloads, AqiDataDownload, SampleDataDownload };