MisbahKhan0009 commited on
Commit
43d9b90
·
1 Parent(s): 5bb64a4

feat(backend): add firebase push notification delivery

Browse files
.env.example CHANGED
@@ -22,3 +22,13 @@ MAILCHIMP_TRANSACTIONAL_API_KEY=
22
  MAILCHIMP_FROM_EMAIL=
23
  MAILCHIMP_FROM_NAME=Care People
24
  MAILCHIMP_REPLY_TO=
 
 
 
 
 
 
 
 
 
 
 
22
  MAILCHIMP_FROM_EMAIL=
23
  MAILCHIMP_FROM_NAME=Care People
24
  MAILCHIMP_REPLY_TO=
25
+
26
+ FIREBASE_ENABLED=false
27
+ FIREBASE_PROJECT_ID=
28
+ FIREBASE_CLIENT_EMAIL=
29
+ FIREBASE_PRIVATE_KEY=
30
+ FIREBASE_SERVICE_ACCOUNT_JSON=
31
+ FIREBASE_SERVICE_ACCOUNT_PATH=
32
+
33
+ PUSH_REMINDER_WORKER_ENABLED=true
34
+ PUSH_REMINDER_WORKER_INTERVAL_SECONDS=60
README.md CHANGED
@@ -33,6 +33,17 @@ Groq AI settings:
33
  - `GROQ_API_KEY`
34
  - `GROQ_MODEL`
35
 
 
 
 
 
 
 
 
 
 
 
 
36
  For OTP, use `Mailchimp Transactional` (Mandrill API), not the regular Mailchimp marketing campaign API.
37
 
38
  Full setup guide:
@@ -142,6 +153,14 @@ Invoke-RestMethod http://localhost:3000/health
142
 
143
  - `GET /api/v1/audit-logs?eventType=&actorType=&entityType=&entityId=&from=&to=&limit=100`
144
 
 
 
 
 
 
 
 
 
145
  ## Example request JSON
146
 
147
  ### Send patient OTP
 
33
  - `GROQ_API_KEY`
34
  - `GROQ_MODEL`
35
 
36
+ Push notification (Firebase Cloud Messaging) settings:
37
+
38
+ - `FIREBASE_ENABLED`
39
+ - `FIREBASE_PROJECT_ID`
40
+ - `FIREBASE_CLIENT_EMAIL`
41
+ - `FIREBASE_PRIVATE_KEY`
42
+ - `FIREBASE_SERVICE_ACCOUNT_JSON`
43
+ - `FIREBASE_SERVICE_ACCOUNT_PATH`
44
+ - `PUSH_REMINDER_WORKER_ENABLED`
45
+ - `PUSH_REMINDER_WORKER_INTERVAL_SECONDS`
46
+
47
  For OTP, use `Mailchimp Transactional` (Mandrill API), not the regular Mailchimp marketing campaign API.
48
 
49
  Full setup guide:
 
153
 
154
  - `GET /api/v1/audit-logs?eventType=&actorType=&entityType=&entityId=&from=&to=&limit=100`
155
 
156
+ ### Notifications
157
+
158
+ - `GET /api/v1/notifications/me`
159
+ - `PATCH /api/v1/notifications/:notificationId/read`
160
+ - `GET /api/v1/notifications/devices/me`
161
+ - `POST /api/v1/notifications/devices/register`
162
+ - `DELETE /api/v1/notifications/devices/unregister`
163
+
164
  ## Example request JSON
165
 
166
  ### Send patient OTP
database/schema.sql CHANGED
@@ -312,6 +312,27 @@ CREATE TABLE IF NOT EXISTS in_app_notifications (
312
  INDEX idx_in_app_notifications_unread (user_type, user_identifier, read_at)
313
  ) ENGINE=InnoDB;
314
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  CREATE TABLE IF NOT EXISTS admissions (
316
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
317
  patient_phone VARCHAR(15) NOT NULL,
 
312
  INDEX idx_in_app_notifications_unread (user_type, user_identifier, read_at)
313
  ) ENGINE=InnoDB;
314
 
315
+ CREATE TABLE IF NOT EXISTS push_device_tokens (
316
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
317
+ user_type ENUM('patient', 'doctor', 'administrator') NOT NULL,
318
+ user_identifier VARCHAR(32) NOT NULL,
319
+ platform ENUM('android', 'ios', 'web') NOT NULL DEFAULT 'android',
320
+ device_id VARCHAR(128) NOT NULL,
321
+ token VARCHAR(255) NOT NULL,
322
+ app_version VARCHAR(30) NULL,
323
+ device_model VARCHAR(120) NULL,
324
+ locale VARCHAR(20) NULL,
325
+ timezone VARCHAR(60) NULL,
326
+ is_active TINYINT(1) NOT NULL DEFAULT 1,
327
+ last_seen_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
328
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
329
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
330
+ UNIQUE KEY uniq_push_device_token (token),
331
+ UNIQUE KEY uniq_push_user_device (user_type, user_identifier, platform, device_id),
332
+ INDEX idx_push_tokens_user_active (user_type, user_identifier, is_active),
333
+ INDEX idx_push_tokens_last_seen (last_seen_at)
334
+ ) ENGINE=InnoDB;
335
+
336
  CREATE TABLE IF NOT EXISTS admissions (
337
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
338
  patient_phone VARCHAR(15) NOT NULL,
package-lock.json CHANGED
@@ -13,6 +13,7 @@
13
  "cors": "^2.8.6",
14
  "dotenv": "^17.3.1",
15
  "express": "^5.2.1",
 
16
  "jsonwebtoken": "^9.0.3",
17
  "mysql2": "^3.20.0"
18
  },
@@ -20,16 +21,473 @@
20
  "nodemon": "^3.1.14"
21
  }
22
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  "node_modules/@types/node": {
24
  "version": "25.5.0",
25
  "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
26
  "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
27
  "license": "MIT",
28
- "peer": true,
29
  "dependencies": {
30
  "undici-types": "~7.18.0"
31
  }
32
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  "node_modules/accepts": {
34
  "version": "2.0.0",
35
  "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
@@ -43,6 +501,41 @@
43
  "node": ">= 0.6"
44
  }
45
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  "node_modules/anymatch": {
47
  "version": "3.1.3",
48
  "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
@@ -57,6 +550,33 @@
57
  "node": ">= 8"
58
  }
59
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  "node_modules/aws-ssl-profiles": {
61
  "version": "1.1.2",
62
  "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
@@ -76,6 +596,26 @@
76
  "node": "18 || 20 || >=22"
77
  }
78
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  "node_modules/bcryptjs": {
80
  "version": "3.0.3",
81
  "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
@@ -85,6 +625,15 @@
85
  "bcrypt": "bin/bcrypt"
86
  }
87
  },
 
 
 
 
 
 
 
 
 
88
  "node_modules/binary-extensions": {
89
  "version": "2.3.0",
90
  "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -217,6 +766,54 @@
217
  "fsevents": "~2.3.2"
218
  }
219
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  "node_modules/content-disposition": {
221
  "version": "1.0.1",
222
  "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
@@ -274,6 +871,15 @@
274
  "url": "https://opencollective.com/express"
275
  }
276
  },
 
 
 
 
 
 
 
 
 
277
  "node_modules/debug": {
278
  "version": "4.4.3",
279
  "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -291,6 +897,16 @@
291
  }
292
  }
293
  },
 
 
 
 
 
 
 
 
 
 
294
  "node_modules/denque": {
295
  "version": "2.1.0",
296
  "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
@@ -335,6 +951,19 @@
335
  "node": ">= 0.4"
336
  }
337
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
338
  "node_modules/ecdsa-sig-formatter": {
339
  "version": "1.0.11",
340
  "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
@@ -350,6 +979,13 @@
350
  "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
351
  "license": "MIT"
352
  },
 
 
 
 
 
 
 
353
  "node_modules/encodeurl": {
354
  "version": "2.0.0",
355
  "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
@@ -359,6 +995,16 @@
359
  "node": ">= 0.8"
360
  }
361
  },
 
 
 
 
 
 
 
 
 
 
362
  "node_modules/es-define-property": {
363
  "version": "1.0.1",
364
  "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -389,6 +1035,32 @@
389
  "node": ">= 0.4"
390
  }
391
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392
  "node_modules/escape-html": {
393
  "version": "1.0.3",
394
  "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@@ -404,6 +1076,16 @@
404
  "node": ">= 0.6"
405
  }
406
  },
 
 
 
 
 
 
 
 
 
 
407
  "node_modules/express": {
408
  "version": "5.2.1",
409
  "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
@@ -440,11 +1122,104 @@
440
  "vary": "^1.1.2"
441
  },
442
  "engines": {
443
- "node": ">= 18"
444
- },
445
- "funding": {
446
- "type": "opencollective",
447
- "url": "https://opencollective.com/express"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
448
  }
449
  },
450
  "node_modules/fill-range": {
@@ -481,6 +1256,84 @@
481
  "url": "https://opencollective.com/express"
482
  }
483
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
  "node_modules/forwarded": {
485
  "version": "0.2.0",
486
  "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -523,6 +1376,90 @@
523
  "url": "https://github.com/sponsors/ljharb"
524
  }
525
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
526
  "node_modules/generate-function": {
527
  "version": "2.3.1",
528
  "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
@@ -532,6 +1469,16 @@
532
  "is-property": "^1.0.2"
533
  }
534
  },
 
 
 
 
 
 
 
 
 
 
535
  "node_modules/get-intrinsic": {
536
  "version": "1.3.0",
537
  "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -582,6 +1529,145 @@
582
  "node": ">= 6"
583
  }
584
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
585
  "node_modules/gopd": {
586
  "version": "1.2.0",
587
  "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -594,6 +1680,20 @@
594
  "url": "https://github.com/sponsors/ljharb"
595
  }
596
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
597
  "node_modules/has-flag": {
598
  "version": "3.0.0",
599
  "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -616,6 +1716,22 @@
616
  "url": "https://github.com/sponsors/ljharb"
617
  }
618
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
619
  "node_modules/hasown": {
620
  "version": "2.0.2",
621
  "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@@ -628,6 +1744,23 @@
628
  "node": ">= 0.4"
629
  }
630
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
631
  "node_modules/http-errors": {
632
  "version": "2.0.1",
633
  "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
@@ -648,6 +1781,53 @@
648
  "url": "https://opencollective.com/express"
649
  }
650
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
651
  "node_modules/iconv-lite": {
652
  "version": "0.7.2",
653
  "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
@@ -709,6 +1889,16 @@
709
  "node": ">=0.10.0"
710
  }
711
  },
 
 
 
 
 
 
 
 
 
 
712
  "node_modules/is-glob": {
713
  "version": "4.0.3",
714
  "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -744,6 +1934,37 @@
744
  "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
745
  "license": "MIT"
746
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
747
  "node_modules/jsonwebtoken": {
748
  "version": "9.0.3",
749
  "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
@@ -777,6 +1998,22 @@
777
  "safe-buffer": "^5.0.1"
778
  }
779
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
780
  "node_modules/jws": {
781
  "version": "4.0.1",
782
  "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
@@ -787,6 +2024,24 @@
787
  "safe-buffer": "^5.0.1"
788
  }
789
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
790
  "node_modules/lodash.includes": {
791
  "version": "4.3.0",
792
  "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
@@ -835,6 +2090,28 @@
835
  "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
836
  "license": "Apache-2.0"
837
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
838
  "node_modules/lru.min": {
839
  "version": "1.1.4",
840
  "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
@@ -880,6 +2157,19 @@
880
  "url": "https://github.com/sponsors/sindresorhus"
881
  }
882
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
883
  "node_modules/mime-db": {
884
  "version": "1.54.0",
885
  "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
@@ -970,6 +2260,56 @@
970
  "node": ">= 0.6"
971
  }
972
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
973
  "node_modules/nodemon": {
974
  "version": "3.1.14",
975
  "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
@@ -1018,6 +2358,16 @@
1018
  "node": ">=0.10.0"
1019
  }
1020
  },
 
 
 
 
 
 
 
 
 
 
1021
  "node_modules/object-inspect": {
1022
  "version": "1.13.4",
1023
  "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -1051,6 +2401,22 @@
1051
  "wrappy": "1"
1052
  }
1053
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1054
  "node_modules/parseurl": {
1055
  "version": "1.3.3",
1056
  "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -1060,6 +2426,22 @@
1060
  "node": ">= 0.8"
1061
  }
1062
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1063
  "node_modules/path-to-regexp": {
1064
  "version": "8.4.1",
1065
  "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.1.tgz",
@@ -1083,6 +2465,44 @@
1083
  "url": "https://github.com/sponsors/jonschlinkert"
1084
  }
1085
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1086
  "node_modules/proxy-addr": {
1087
  "version": "2.0.7",
1088
  "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -1142,6 +2562,21 @@
1142
  "node": ">= 0.10"
1143
  }
1144
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1145
  "node_modules/readdirp": {
1146
  "version": "3.6.0",
1147
  "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -1155,6 +2590,41 @@
1155
  "node": ">=8.10.0"
1156
  }
1157
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1158
  "node_modules/router": {
1159
  "version": "2.2.0",
1160
  "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
@@ -1369,6 +2839,81 @@
1369
  "node": ">= 0.8"
1370
  }
1371
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1372
  "node_modules/supports-color": {
1373
  "version": "5.5.0",
1374
  "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -1382,6 +2927,64 @@
1382
  "node": ">=4"
1383
  }
1384
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1385
  "node_modules/to-regex-range": {
1386
  "version": "5.0.1",
1387
  "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -1414,6 +3017,19 @@
1414
  "nodetouch": "bin/nodetouch.js"
1415
  }
1416
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
1417
  "node_modules/type-is": {
1418
  "version": "2.0.1",
1419
  "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
@@ -1439,8 +3055,7 @@
1439
  "version": "7.18.2",
1440
  "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
1441
  "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
1442
- "license": "MIT",
1443
- "peer": true
1444
  },
1445
  "node_modules/unpipe": {
1446
  "version": "1.0.0",
@@ -1451,6 +3066,26 @@
1451
  "node": ">= 0.8"
1452
  }
1453
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1454
  "node_modules/vary": {
1455
  "version": "1.1.2",
1456
  "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -1460,11 +3095,137 @@
1460
  "node": ">= 0.8"
1461
  }
1462
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1463
  "node_modules/wrappy": {
1464
  "version": "1.0.2",
1465
  "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1466
  "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
1467
  "license": "ISC"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1468
  }
1469
  }
1470
  }
 
13
  "cors": "^2.8.6",
14
  "dotenv": "^17.3.1",
15
  "express": "^5.2.1",
16
+ "firebase-admin": "^13.7.0",
17
  "jsonwebtoken": "^9.0.3",
18
  "mysql2": "^3.20.0"
19
  },
 
21
  "nodemon": "^3.1.14"
22
  }
23
  },
24
+ "node_modules/@fastify/busboy": {
25
+ "version": "3.2.0",
26
+ "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.2.0.tgz",
27
+ "integrity": "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==",
28
+ "license": "MIT"
29
+ },
30
+ "node_modules/@firebase/app-check-interop-types": {
31
+ "version": "0.3.3",
32
+ "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz",
33
+ "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==",
34
+ "license": "Apache-2.0"
35
+ },
36
+ "node_modules/@firebase/app-types": {
37
+ "version": "0.9.3",
38
+ "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz",
39
+ "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==",
40
+ "license": "Apache-2.0"
41
+ },
42
+ "node_modules/@firebase/auth-interop-types": {
43
+ "version": "0.2.4",
44
+ "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz",
45
+ "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==",
46
+ "license": "Apache-2.0"
47
+ },
48
+ "node_modules/@firebase/component": {
49
+ "version": "0.7.2",
50
+ "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.2.tgz",
51
+ "integrity": "sha512-iyVDGc6Vjx7Rm0cAdccLH/NG6fADsgJak/XW9IA2lPf8AjIlsemOpFGKczYyPHxm4rnKdR8z6sK4+KEC7NwmEg==",
52
+ "license": "Apache-2.0",
53
+ "dependencies": {
54
+ "@firebase/util": "1.15.0",
55
+ "tslib": "^2.1.0"
56
+ },
57
+ "engines": {
58
+ "node": ">=20.0.0"
59
+ }
60
+ },
61
+ "node_modules/@firebase/database": {
62
+ "version": "1.1.2",
63
+ "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.2.tgz",
64
+ "integrity": "sha512-lP96CMjMPy/+d1d9qaaHjHHdzdwvEOuyyLq9ehX89e2XMKwS1jHNzYBO+42bdSumuj5ukPbmnFtViZu8YOMT+w==",
65
+ "license": "Apache-2.0",
66
+ "dependencies": {
67
+ "@firebase/app-check-interop-types": "0.3.3",
68
+ "@firebase/auth-interop-types": "0.2.4",
69
+ "@firebase/component": "0.7.2",
70
+ "@firebase/logger": "0.5.0",
71
+ "@firebase/util": "1.15.0",
72
+ "faye-websocket": "0.11.4",
73
+ "tslib": "^2.1.0"
74
+ },
75
+ "engines": {
76
+ "node": ">=20.0.0"
77
+ }
78
+ },
79
+ "node_modules/@firebase/database-compat": {
80
+ "version": "2.1.2",
81
+ "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.2.tgz",
82
+ "integrity": "sha512-j4A6IhVZbgxAzT6gJJC2PfOxYCK9SrDrUO7nTM4EscTYtKkAkzsbKoCnDdjFapQfnsncvPWjqVTr/0PffUwg3g==",
83
+ "license": "Apache-2.0",
84
+ "dependencies": {
85
+ "@firebase/component": "0.7.2",
86
+ "@firebase/database": "1.1.2",
87
+ "@firebase/database-types": "1.0.18",
88
+ "@firebase/logger": "0.5.0",
89
+ "@firebase/util": "1.15.0",
90
+ "tslib": "^2.1.0"
91
+ },
92
+ "engines": {
93
+ "node": ">=20.0.0"
94
+ }
95
+ },
96
+ "node_modules/@firebase/database-types": {
97
+ "version": "1.0.18",
98
+ "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.18.tgz",
99
+ "integrity": "sha512-yOY8IC2go9lfbVDMiy2ATun4EB2AFwocPaQADwMN/RHRUAZSM4rlAV7PGbWPSG/YhkJ2A9xQAiAENgSua9G5Fg==",
100
+ "license": "Apache-2.0",
101
+ "dependencies": {
102
+ "@firebase/app-types": "0.9.3",
103
+ "@firebase/util": "1.15.0"
104
+ }
105
+ },
106
+ "node_modules/@firebase/logger": {
107
+ "version": "0.5.0",
108
+ "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz",
109
+ "integrity": "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==",
110
+ "license": "Apache-2.0",
111
+ "dependencies": {
112
+ "tslib": "^2.1.0"
113
+ },
114
+ "engines": {
115
+ "node": ">=20.0.0"
116
+ }
117
+ },
118
+ "node_modules/@firebase/util": {
119
+ "version": "1.15.0",
120
+ "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.15.0.tgz",
121
+ "integrity": "sha512-AmWf3cHAOMbrCPG4xdPKQaj5iHnyYfyLKZxwz+Xf55bqKbpAmcYifB4jQinT2W9XhDRHISOoPyBOariJpCG6FA==",
122
+ "hasInstallScript": true,
123
+ "license": "Apache-2.0",
124
+ "dependencies": {
125
+ "tslib": "^2.1.0"
126
+ },
127
+ "engines": {
128
+ "node": ">=20.0.0"
129
+ }
130
+ },
131
+ "node_modules/@google-cloud/firestore": {
132
+ "version": "7.11.6",
133
+ "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.6.tgz",
134
+ "integrity": "sha512-EW/O8ktzwLfyWBOsNuhRoMi8lrC3clHM5LVFhGvO1HCsLozCOOXRAlHrYBoE6HL42Sc8yYMuCb2XqcnJ4OOEpw==",
135
+ "license": "Apache-2.0",
136
+ "optional": true,
137
+ "dependencies": {
138
+ "@opentelemetry/api": "^1.3.0",
139
+ "fast-deep-equal": "^3.1.1",
140
+ "functional-red-black-tree": "^1.0.1",
141
+ "google-gax": "^4.3.3",
142
+ "protobufjs": "^7.2.6"
143
+ },
144
+ "engines": {
145
+ "node": ">=14.0.0"
146
+ }
147
+ },
148
+ "node_modules/@google-cloud/paginator": {
149
+ "version": "5.0.2",
150
+ "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz",
151
+ "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==",
152
+ "license": "Apache-2.0",
153
+ "optional": true,
154
+ "dependencies": {
155
+ "arrify": "^2.0.0",
156
+ "extend": "^3.0.2"
157
+ },
158
+ "engines": {
159
+ "node": ">=14.0.0"
160
+ }
161
+ },
162
+ "node_modules/@google-cloud/projectify": {
163
+ "version": "4.0.0",
164
+ "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz",
165
+ "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==",
166
+ "license": "Apache-2.0",
167
+ "optional": true,
168
+ "engines": {
169
+ "node": ">=14.0.0"
170
+ }
171
+ },
172
+ "node_modules/@google-cloud/promisify": {
173
+ "version": "4.0.0",
174
+ "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz",
175
+ "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==",
176
+ "license": "Apache-2.0",
177
+ "optional": true,
178
+ "engines": {
179
+ "node": ">=14"
180
+ }
181
+ },
182
+ "node_modules/@google-cloud/storage": {
183
+ "version": "7.19.0",
184
+ "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.19.0.tgz",
185
+ "integrity": "sha512-n2FjE7NAOYyshogdc7KQOl/VZb4sneqPjWouSyia9CMDdMhRX5+RIbqalNmC7LOLzuLAN89VlF2HvG8na9G+zQ==",
186
+ "license": "Apache-2.0",
187
+ "optional": true,
188
+ "dependencies": {
189
+ "@google-cloud/paginator": "^5.0.0",
190
+ "@google-cloud/projectify": "^4.0.0",
191
+ "@google-cloud/promisify": "<4.1.0",
192
+ "abort-controller": "^3.0.0",
193
+ "async-retry": "^1.3.3",
194
+ "duplexify": "^4.1.3",
195
+ "fast-xml-parser": "^5.3.4",
196
+ "gaxios": "^6.0.2",
197
+ "google-auth-library": "^9.6.3",
198
+ "html-entities": "^2.5.2",
199
+ "mime": "^3.0.0",
200
+ "p-limit": "^3.0.1",
201
+ "retry-request": "^7.0.0",
202
+ "teeny-request": "^9.0.0",
203
+ "uuid": "^8.0.0"
204
+ },
205
+ "engines": {
206
+ "node": ">=14"
207
+ }
208
+ },
209
+ "node_modules/@google-cloud/storage/node_modules/gcp-metadata": {
210
+ "version": "6.1.1",
211
+ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz",
212
+ "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==",
213
+ "license": "Apache-2.0",
214
+ "optional": true,
215
+ "dependencies": {
216
+ "gaxios": "^6.1.1",
217
+ "google-logging-utils": "^0.0.2",
218
+ "json-bigint": "^1.0.0"
219
+ },
220
+ "engines": {
221
+ "node": ">=14"
222
+ }
223
+ },
224
+ "node_modules/@google-cloud/storage/node_modules/google-auth-library": {
225
+ "version": "9.15.1",
226
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz",
227
+ "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==",
228
+ "license": "Apache-2.0",
229
+ "optional": true,
230
+ "dependencies": {
231
+ "base64-js": "^1.3.0",
232
+ "ecdsa-sig-formatter": "^1.0.11",
233
+ "gaxios": "^6.1.1",
234
+ "gcp-metadata": "^6.1.0",
235
+ "gtoken": "^7.0.0",
236
+ "jws": "^4.0.0"
237
+ },
238
+ "engines": {
239
+ "node": ">=14"
240
+ }
241
+ },
242
+ "node_modules/@google-cloud/storage/node_modules/google-logging-utils": {
243
+ "version": "0.0.2",
244
+ "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz",
245
+ "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==",
246
+ "license": "Apache-2.0",
247
+ "optional": true,
248
+ "engines": {
249
+ "node": ">=14"
250
+ }
251
+ },
252
+ "node_modules/@google-cloud/storage/node_modules/uuid": {
253
+ "version": "8.3.2",
254
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
255
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
256
+ "license": "MIT",
257
+ "optional": true,
258
+ "bin": {
259
+ "uuid": "dist/bin/uuid"
260
+ }
261
+ },
262
+ "node_modules/@grpc/grpc-js": {
263
+ "version": "1.14.3",
264
+ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz",
265
+ "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==",
266
+ "license": "Apache-2.0",
267
+ "optional": true,
268
+ "dependencies": {
269
+ "@grpc/proto-loader": "^0.8.0",
270
+ "@js-sdsl/ordered-map": "^4.4.2"
271
+ },
272
+ "engines": {
273
+ "node": ">=12.10.0"
274
+ }
275
+ },
276
+ "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": {
277
+ "version": "0.8.0",
278
+ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz",
279
+ "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==",
280
+ "license": "Apache-2.0",
281
+ "optional": true,
282
+ "dependencies": {
283
+ "lodash.camelcase": "^4.3.0",
284
+ "long": "^5.0.0",
285
+ "protobufjs": "^7.5.3",
286
+ "yargs": "^17.7.2"
287
+ },
288
+ "bin": {
289
+ "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
290
+ },
291
+ "engines": {
292
+ "node": ">=6"
293
+ }
294
+ },
295
+ "node_modules/@grpc/proto-loader": {
296
+ "version": "0.7.15",
297
+ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz",
298
+ "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==",
299
+ "license": "Apache-2.0",
300
+ "optional": true,
301
+ "dependencies": {
302
+ "lodash.camelcase": "^4.3.0",
303
+ "long": "^5.0.0",
304
+ "protobufjs": "^7.2.5",
305
+ "yargs": "^17.7.2"
306
+ },
307
+ "bin": {
308
+ "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
309
+ },
310
+ "engines": {
311
+ "node": ">=6"
312
+ }
313
+ },
314
+ "node_modules/@js-sdsl/ordered-map": {
315
+ "version": "4.4.2",
316
+ "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
317
+ "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==",
318
+ "license": "MIT",
319
+ "optional": true,
320
+ "funding": {
321
+ "type": "opencollective",
322
+ "url": "https://opencollective.com/js-sdsl"
323
+ }
324
+ },
325
+ "node_modules/@opentelemetry/api": {
326
+ "version": "1.9.1",
327
+ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz",
328
+ "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==",
329
+ "license": "Apache-2.0",
330
+ "optional": true,
331
+ "engines": {
332
+ "node": ">=8.0.0"
333
+ }
334
+ },
335
+ "node_modules/@protobufjs/aspromise": {
336
+ "version": "1.1.2",
337
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
338
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
339
+ "license": "BSD-3-Clause",
340
+ "optional": true
341
+ },
342
+ "node_modules/@protobufjs/base64": {
343
+ "version": "1.1.2",
344
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
345
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
346
+ "license": "BSD-3-Clause",
347
+ "optional": true
348
+ },
349
+ "node_modules/@protobufjs/codegen": {
350
+ "version": "2.0.4",
351
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
352
+ "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
353
+ "license": "BSD-3-Clause",
354
+ "optional": true
355
+ },
356
+ "node_modules/@protobufjs/eventemitter": {
357
+ "version": "1.1.0",
358
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
359
+ "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
360
+ "license": "BSD-3-Clause",
361
+ "optional": true
362
+ },
363
+ "node_modules/@protobufjs/fetch": {
364
+ "version": "1.1.0",
365
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
366
+ "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
367
+ "license": "BSD-3-Clause",
368
+ "optional": true,
369
+ "dependencies": {
370
+ "@protobufjs/aspromise": "^1.1.1",
371
+ "@protobufjs/inquire": "^1.1.0"
372
+ }
373
+ },
374
+ "node_modules/@protobufjs/float": {
375
+ "version": "1.0.2",
376
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
377
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
378
+ "license": "BSD-3-Clause",
379
+ "optional": true
380
+ },
381
+ "node_modules/@protobufjs/inquire": {
382
+ "version": "1.1.0",
383
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
384
+ "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
385
+ "license": "BSD-3-Clause",
386
+ "optional": true
387
+ },
388
+ "node_modules/@protobufjs/path": {
389
+ "version": "1.1.2",
390
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
391
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
392
+ "license": "BSD-3-Clause",
393
+ "optional": true
394
+ },
395
+ "node_modules/@protobufjs/pool": {
396
+ "version": "1.1.0",
397
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
398
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
399
+ "license": "BSD-3-Clause",
400
+ "optional": true
401
+ },
402
+ "node_modules/@protobufjs/utf8": {
403
+ "version": "1.1.0",
404
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
405
+ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
406
+ "license": "BSD-3-Clause",
407
+ "optional": true
408
+ },
409
+ "node_modules/@tootallnate/once": {
410
+ "version": "2.0.0",
411
+ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
412
+ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
413
+ "license": "MIT",
414
+ "optional": true,
415
+ "engines": {
416
+ "node": ">= 10"
417
+ }
418
+ },
419
+ "node_modules/@types/caseless": {
420
+ "version": "0.12.5",
421
+ "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz",
422
+ "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==",
423
+ "license": "MIT",
424
+ "optional": true
425
+ },
426
+ "node_modules/@types/jsonwebtoken": {
427
+ "version": "9.0.10",
428
+ "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
429
+ "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
430
+ "license": "MIT",
431
+ "dependencies": {
432
+ "@types/ms": "*",
433
+ "@types/node": "*"
434
+ }
435
+ },
436
+ "node_modules/@types/long": {
437
+ "version": "4.0.2",
438
+ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
439
+ "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
440
+ "license": "MIT",
441
+ "optional": true
442
+ },
443
+ "node_modules/@types/ms": {
444
+ "version": "2.1.0",
445
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
446
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
447
+ "license": "MIT"
448
+ },
449
  "node_modules/@types/node": {
450
  "version": "25.5.0",
451
  "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
452
  "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
453
  "license": "MIT",
 
454
  "dependencies": {
455
  "undici-types": "~7.18.0"
456
  }
457
  },
458
+ "node_modules/@types/request": {
459
+ "version": "2.48.13",
460
+ "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.13.tgz",
461
+ "integrity": "sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==",
462
+ "license": "MIT",
463
+ "optional": true,
464
+ "dependencies": {
465
+ "@types/caseless": "*",
466
+ "@types/node": "*",
467
+ "@types/tough-cookie": "*",
468
+ "form-data": "^2.5.5"
469
+ }
470
+ },
471
+ "node_modules/@types/tough-cookie": {
472
+ "version": "4.0.5",
473
+ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
474
+ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
475
+ "license": "MIT",
476
+ "optional": true
477
+ },
478
+ "node_modules/abort-controller": {
479
+ "version": "3.0.0",
480
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
481
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
482
+ "license": "MIT",
483
+ "optional": true,
484
+ "dependencies": {
485
+ "event-target-shim": "^5.0.0"
486
+ },
487
+ "engines": {
488
+ "node": ">=6.5"
489
+ }
490
+ },
491
  "node_modules/accepts": {
492
  "version": "2.0.0",
493
  "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
 
501
  "node": ">= 0.6"
502
  }
503
  },
504
+ "node_modules/agent-base": {
505
+ "version": "7.1.4",
506
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
507
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
508
+ "license": "MIT",
509
+ "engines": {
510
+ "node": ">= 14"
511
+ }
512
+ },
513
+ "node_modules/ansi-regex": {
514
+ "version": "5.0.1",
515
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
516
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
517
+ "license": "MIT",
518
+ "optional": true,
519
+ "engines": {
520
+ "node": ">=8"
521
+ }
522
+ },
523
+ "node_modules/ansi-styles": {
524
+ "version": "4.3.0",
525
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
526
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
527
+ "license": "MIT",
528
+ "optional": true,
529
+ "dependencies": {
530
+ "color-convert": "^2.0.1"
531
+ },
532
+ "engines": {
533
+ "node": ">=8"
534
+ },
535
+ "funding": {
536
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
537
+ }
538
+ },
539
  "node_modules/anymatch": {
540
  "version": "3.1.3",
541
  "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
 
550
  "node": ">= 8"
551
  }
552
  },
553
+ "node_modules/arrify": {
554
+ "version": "2.0.1",
555
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
556
+ "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
557
+ "license": "MIT",
558
+ "optional": true,
559
+ "engines": {
560
+ "node": ">=8"
561
+ }
562
+ },
563
+ "node_modules/async-retry": {
564
+ "version": "1.3.3",
565
+ "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz",
566
+ "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==",
567
+ "license": "MIT",
568
+ "optional": true,
569
+ "dependencies": {
570
+ "retry": "0.13.1"
571
+ }
572
+ },
573
+ "node_modules/asynckit": {
574
+ "version": "0.4.0",
575
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
576
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
577
+ "license": "MIT",
578
+ "optional": true
579
+ },
580
  "node_modules/aws-ssl-profiles": {
581
  "version": "1.1.2",
582
  "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
 
596
  "node": "18 || 20 || >=22"
597
  }
598
  },
599
+ "node_modules/base64-js": {
600
+ "version": "1.5.1",
601
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
602
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
603
+ "funding": [
604
+ {
605
+ "type": "github",
606
+ "url": "https://github.com/sponsors/feross"
607
+ },
608
+ {
609
+ "type": "patreon",
610
+ "url": "https://www.patreon.com/feross"
611
+ },
612
+ {
613
+ "type": "consulting",
614
+ "url": "https://feross.org/support"
615
+ }
616
+ ],
617
+ "license": "MIT"
618
+ },
619
  "node_modules/bcryptjs": {
620
  "version": "3.0.3",
621
  "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
 
625
  "bcrypt": "bin/bcrypt"
626
  }
627
  },
628
+ "node_modules/bignumber.js": {
629
+ "version": "9.3.1",
630
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
631
+ "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
632
+ "license": "MIT",
633
+ "engines": {
634
+ "node": "*"
635
+ }
636
+ },
637
  "node_modules/binary-extensions": {
638
  "version": "2.3.0",
639
  "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
 
766
  "fsevents": "~2.3.2"
767
  }
768
  },
769
+ "node_modules/cliui": {
770
+ "version": "8.0.1",
771
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
772
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
773
+ "license": "ISC",
774
+ "optional": true,
775
+ "dependencies": {
776
+ "string-width": "^4.2.0",
777
+ "strip-ansi": "^6.0.1",
778
+ "wrap-ansi": "^7.0.0"
779
+ },
780
+ "engines": {
781
+ "node": ">=12"
782
+ }
783
+ },
784
+ "node_modules/color-convert": {
785
+ "version": "2.0.1",
786
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
787
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
788
+ "license": "MIT",
789
+ "optional": true,
790
+ "dependencies": {
791
+ "color-name": "~1.1.4"
792
+ },
793
+ "engines": {
794
+ "node": ">=7.0.0"
795
+ }
796
+ },
797
+ "node_modules/color-name": {
798
+ "version": "1.1.4",
799
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
800
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
801
+ "license": "MIT",
802
+ "optional": true
803
+ },
804
+ "node_modules/combined-stream": {
805
+ "version": "1.0.8",
806
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
807
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
808
+ "license": "MIT",
809
+ "optional": true,
810
+ "dependencies": {
811
+ "delayed-stream": "~1.0.0"
812
+ },
813
+ "engines": {
814
+ "node": ">= 0.8"
815
+ }
816
+ },
817
  "node_modules/content-disposition": {
818
  "version": "1.0.1",
819
  "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
 
871
  "url": "https://opencollective.com/express"
872
  }
873
  },
874
+ "node_modules/data-uri-to-buffer": {
875
+ "version": "4.0.1",
876
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
877
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
878
+ "license": "MIT",
879
+ "engines": {
880
+ "node": ">= 12"
881
+ }
882
+ },
883
  "node_modules/debug": {
884
  "version": "4.4.3",
885
  "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
 
897
  }
898
  }
899
  },
900
+ "node_modules/delayed-stream": {
901
+ "version": "1.0.0",
902
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
903
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
904
+ "license": "MIT",
905
+ "optional": true,
906
+ "engines": {
907
+ "node": ">=0.4.0"
908
+ }
909
+ },
910
  "node_modules/denque": {
911
  "version": "2.1.0",
912
  "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
 
951
  "node": ">= 0.4"
952
  }
953
  },
954
+ "node_modules/duplexify": {
955
+ "version": "4.1.3",
956
+ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz",
957
+ "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==",
958
+ "license": "MIT",
959
+ "optional": true,
960
+ "dependencies": {
961
+ "end-of-stream": "^1.4.1",
962
+ "inherits": "^2.0.3",
963
+ "readable-stream": "^3.1.1",
964
+ "stream-shift": "^1.0.2"
965
+ }
966
+ },
967
  "node_modules/ecdsa-sig-formatter": {
968
  "version": "1.0.11",
969
  "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
 
979
  "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
980
  "license": "MIT"
981
  },
982
+ "node_modules/emoji-regex": {
983
+ "version": "8.0.0",
984
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
985
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
986
+ "license": "MIT",
987
+ "optional": true
988
+ },
989
  "node_modules/encodeurl": {
990
  "version": "2.0.0",
991
  "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
 
995
  "node": ">= 0.8"
996
  }
997
  },
998
+ "node_modules/end-of-stream": {
999
+ "version": "1.4.5",
1000
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
1001
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
1002
+ "license": "MIT",
1003
+ "optional": true,
1004
+ "dependencies": {
1005
+ "once": "^1.4.0"
1006
+ }
1007
+ },
1008
  "node_modules/es-define-property": {
1009
  "version": "1.0.1",
1010
  "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
 
1035
  "node": ">= 0.4"
1036
  }
1037
  },
1038
+ "node_modules/es-set-tostringtag": {
1039
+ "version": "2.1.0",
1040
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
1041
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
1042
+ "license": "MIT",
1043
+ "optional": true,
1044
+ "dependencies": {
1045
+ "es-errors": "^1.3.0",
1046
+ "get-intrinsic": "^1.2.6",
1047
+ "has-tostringtag": "^1.0.2",
1048
+ "hasown": "^2.0.2"
1049
+ },
1050
+ "engines": {
1051
+ "node": ">= 0.4"
1052
+ }
1053
+ },
1054
+ "node_modules/escalade": {
1055
+ "version": "3.2.0",
1056
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
1057
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
1058
+ "license": "MIT",
1059
+ "optional": true,
1060
+ "engines": {
1061
+ "node": ">=6"
1062
+ }
1063
+ },
1064
  "node_modules/escape-html": {
1065
  "version": "1.0.3",
1066
  "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
 
1076
  "node": ">= 0.6"
1077
  }
1078
  },
1079
+ "node_modules/event-target-shim": {
1080
+ "version": "5.0.1",
1081
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
1082
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
1083
+ "license": "MIT",
1084
+ "optional": true,
1085
+ "engines": {
1086
+ "node": ">=6"
1087
+ }
1088
+ },
1089
  "node_modules/express": {
1090
  "version": "5.2.1",
1091
  "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
 
1122
  "vary": "^1.1.2"
1123
  },
1124
  "engines": {
1125
+ "node": ">= 18"
1126
+ },
1127
+ "funding": {
1128
+ "type": "opencollective",
1129
+ "url": "https://opencollective.com/express"
1130
+ }
1131
+ },
1132
+ "node_modules/extend": {
1133
+ "version": "3.0.2",
1134
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
1135
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
1136
+ "license": "MIT"
1137
+ },
1138
+ "node_modules/farmhash-modern": {
1139
+ "version": "1.1.0",
1140
+ "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz",
1141
+ "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==",
1142
+ "license": "MIT",
1143
+ "engines": {
1144
+ "node": ">=18.0.0"
1145
+ }
1146
+ },
1147
+ "node_modules/fast-deep-equal": {
1148
+ "version": "3.1.3",
1149
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
1150
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
1151
+ "license": "MIT"
1152
+ },
1153
+ "node_modules/fast-xml-builder": {
1154
+ "version": "1.1.4",
1155
+ "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz",
1156
+ "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==",
1157
+ "funding": [
1158
+ {
1159
+ "type": "github",
1160
+ "url": "https://github.com/sponsors/NaturalIntelligence"
1161
+ }
1162
+ ],
1163
+ "license": "MIT",
1164
+ "optional": true,
1165
+ "dependencies": {
1166
+ "path-expression-matcher": "^1.1.3"
1167
+ }
1168
+ },
1169
+ "node_modules/fast-xml-parser": {
1170
+ "version": "5.5.10",
1171
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.10.tgz",
1172
+ "integrity": "sha512-go2J2xODMc32hT+4Xr/bBGXMaIoiCwrwp2mMtAvKyvEFW6S/v5Gn2pBmE4nvbwNjGhpcAiOwEv7R6/GZ6XRa9w==",
1173
+ "funding": [
1174
+ {
1175
+ "type": "github",
1176
+ "url": "https://github.com/sponsors/NaturalIntelligence"
1177
+ }
1178
+ ],
1179
+ "license": "MIT",
1180
+ "optional": true,
1181
+ "dependencies": {
1182
+ "fast-xml-builder": "^1.1.4",
1183
+ "path-expression-matcher": "^1.2.1",
1184
+ "strnum": "^2.2.2"
1185
+ },
1186
+ "bin": {
1187
+ "fxparser": "src/cli/cli.js"
1188
+ }
1189
+ },
1190
+ "node_modules/faye-websocket": {
1191
+ "version": "0.11.4",
1192
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
1193
+ "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
1194
+ "license": "Apache-2.0",
1195
+ "dependencies": {
1196
+ "websocket-driver": ">=0.5.1"
1197
+ },
1198
+ "engines": {
1199
+ "node": ">=0.8.0"
1200
+ }
1201
+ },
1202
+ "node_modules/fetch-blob": {
1203
+ "version": "3.2.0",
1204
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
1205
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
1206
+ "funding": [
1207
+ {
1208
+ "type": "github",
1209
+ "url": "https://github.com/sponsors/jimmywarting"
1210
+ },
1211
+ {
1212
+ "type": "paypal",
1213
+ "url": "https://paypal.me/jimmywarting"
1214
+ }
1215
+ ],
1216
+ "license": "MIT",
1217
+ "dependencies": {
1218
+ "node-domexception": "^1.0.0",
1219
+ "web-streams-polyfill": "^3.0.3"
1220
+ },
1221
+ "engines": {
1222
+ "node": "^12.20 || >= 14.13"
1223
  }
1224
  },
1225
  "node_modules/fill-range": {
 
1256
  "url": "https://opencollective.com/express"
1257
  }
1258
  },
1259
+ "node_modules/firebase-admin": {
1260
+ "version": "13.7.0",
1261
+ "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.7.0.tgz",
1262
+ "integrity": "sha512-o3qS8zCJbApe7aKzkO2Pa380t9cHISqeSd3blqYTtOuUUUua3qZTLwNWgGUOss3td6wbzrZhiHIj3c8+fC046Q==",
1263
+ "license": "Apache-2.0",
1264
+ "dependencies": {
1265
+ "@fastify/busboy": "^3.0.0",
1266
+ "@firebase/database-compat": "^2.0.0",
1267
+ "@firebase/database-types": "^1.0.6",
1268
+ "farmhash-modern": "^1.1.0",
1269
+ "fast-deep-equal": "^3.1.1",
1270
+ "google-auth-library": "^10.6.1",
1271
+ "jsonwebtoken": "^9.0.0",
1272
+ "jwks-rsa": "^3.1.0",
1273
+ "node-forge": "^1.3.1",
1274
+ "uuid": "^11.0.2"
1275
+ },
1276
+ "engines": {
1277
+ "node": ">=18"
1278
+ },
1279
+ "optionalDependencies": {
1280
+ "@google-cloud/firestore": "^7.11.0",
1281
+ "@google-cloud/storage": "^7.19.0"
1282
+ }
1283
+ },
1284
+ "node_modules/form-data": {
1285
+ "version": "2.5.5",
1286
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz",
1287
+ "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==",
1288
+ "license": "MIT",
1289
+ "optional": true,
1290
+ "dependencies": {
1291
+ "asynckit": "^0.4.0",
1292
+ "combined-stream": "^1.0.8",
1293
+ "es-set-tostringtag": "^2.1.0",
1294
+ "hasown": "^2.0.2",
1295
+ "mime-types": "^2.1.35",
1296
+ "safe-buffer": "^5.2.1"
1297
+ },
1298
+ "engines": {
1299
+ "node": ">= 0.12"
1300
+ }
1301
+ },
1302
+ "node_modules/form-data/node_modules/mime-db": {
1303
+ "version": "1.52.0",
1304
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
1305
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
1306
+ "license": "MIT",
1307
+ "optional": true,
1308
+ "engines": {
1309
+ "node": ">= 0.6"
1310
+ }
1311
+ },
1312
+ "node_modules/form-data/node_modules/mime-types": {
1313
+ "version": "2.1.35",
1314
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
1315
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
1316
+ "license": "MIT",
1317
+ "optional": true,
1318
+ "dependencies": {
1319
+ "mime-db": "1.52.0"
1320
+ },
1321
+ "engines": {
1322
+ "node": ">= 0.6"
1323
+ }
1324
+ },
1325
+ "node_modules/formdata-polyfill": {
1326
+ "version": "4.0.10",
1327
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
1328
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
1329
+ "license": "MIT",
1330
+ "dependencies": {
1331
+ "fetch-blob": "^3.1.2"
1332
+ },
1333
+ "engines": {
1334
+ "node": ">=12.20.0"
1335
+ }
1336
+ },
1337
  "node_modules/forwarded": {
1338
  "version": "0.2.0",
1339
  "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
 
1376
  "url": "https://github.com/sponsors/ljharb"
1377
  }
1378
  },
1379
+ "node_modules/functional-red-black-tree": {
1380
+ "version": "1.0.1",
1381
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
1382
+ "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
1383
+ "license": "MIT",
1384
+ "optional": true
1385
+ },
1386
+ "node_modules/gaxios": {
1387
+ "version": "6.7.1",
1388
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
1389
+ "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==",
1390
+ "license": "Apache-2.0",
1391
+ "optional": true,
1392
+ "dependencies": {
1393
+ "extend": "^3.0.2",
1394
+ "https-proxy-agent": "^7.0.1",
1395
+ "is-stream": "^2.0.0",
1396
+ "node-fetch": "^2.6.9",
1397
+ "uuid": "^9.0.1"
1398
+ },
1399
+ "engines": {
1400
+ "node": ">=14"
1401
+ }
1402
+ },
1403
+ "node_modules/gaxios/node_modules/uuid": {
1404
+ "version": "9.0.1",
1405
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
1406
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
1407
+ "funding": [
1408
+ "https://github.com/sponsors/broofa",
1409
+ "https://github.com/sponsors/ctavan"
1410
+ ],
1411
+ "license": "MIT",
1412
+ "optional": true,
1413
+ "bin": {
1414
+ "uuid": "dist/bin/uuid"
1415
+ }
1416
+ },
1417
+ "node_modules/gcp-metadata": {
1418
+ "version": "8.1.2",
1419
+ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz",
1420
+ "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==",
1421
+ "license": "Apache-2.0",
1422
+ "dependencies": {
1423
+ "gaxios": "^7.0.0",
1424
+ "google-logging-utils": "^1.0.0",
1425
+ "json-bigint": "^1.0.0"
1426
+ },
1427
+ "engines": {
1428
+ "node": ">=18"
1429
+ }
1430
+ },
1431
+ "node_modules/gcp-metadata/node_modules/gaxios": {
1432
+ "version": "7.1.4",
1433
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz",
1434
+ "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==",
1435
+ "license": "Apache-2.0",
1436
+ "dependencies": {
1437
+ "extend": "^3.0.2",
1438
+ "https-proxy-agent": "^7.0.1",
1439
+ "node-fetch": "^3.3.2"
1440
+ },
1441
+ "engines": {
1442
+ "node": ">=18"
1443
+ }
1444
+ },
1445
+ "node_modules/gcp-metadata/node_modules/node-fetch": {
1446
+ "version": "3.3.2",
1447
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
1448
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
1449
+ "license": "MIT",
1450
+ "dependencies": {
1451
+ "data-uri-to-buffer": "^4.0.0",
1452
+ "fetch-blob": "^3.1.4",
1453
+ "formdata-polyfill": "^4.0.10"
1454
+ },
1455
+ "engines": {
1456
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
1457
+ },
1458
+ "funding": {
1459
+ "type": "opencollective",
1460
+ "url": "https://opencollective.com/node-fetch"
1461
+ }
1462
+ },
1463
  "node_modules/generate-function": {
1464
  "version": "2.3.1",
1465
  "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
 
1469
  "is-property": "^1.0.2"
1470
  }
1471
  },
1472
+ "node_modules/get-caller-file": {
1473
+ "version": "2.0.5",
1474
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
1475
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
1476
+ "license": "ISC",
1477
+ "optional": true,
1478
+ "engines": {
1479
+ "node": "6.* || 8.* || >= 10.*"
1480
+ }
1481
+ },
1482
  "node_modules/get-intrinsic": {
1483
  "version": "1.3.0",
1484
  "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
 
1529
  "node": ">= 6"
1530
  }
1531
  },
1532
+ "node_modules/google-auth-library": {
1533
+ "version": "10.6.2",
1534
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz",
1535
+ "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==",
1536
+ "license": "Apache-2.0",
1537
+ "dependencies": {
1538
+ "base64-js": "^1.3.0",
1539
+ "ecdsa-sig-formatter": "^1.0.11",
1540
+ "gaxios": "^7.1.4",
1541
+ "gcp-metadata": "8.1.2",
1542
+ "google-logging-utils": "1.1.3",
1543
+ "jws": "^4.0.0"
1544
+ },
1545
+ "engines": {
1546
+ "node": ">=18"
1547
+ }
1548
+ },
1549
+ "node_modules/google-auth-library/node_modules/gaxios": {
1550
+ "version": "7.1.4",
1551
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz",
1552
+ "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==",
1553
+ "license": "Apache-2.0",
1554
+ "dependencies": {
1555
+ "extend": "^3.0.2",
1556
+ "https-proxy-agent": "^7.0.1",
1557
+ "node-fetch": "^3.3.2"
1558
+ },
1559
+ "engines": {
1560
+ "node": ">=18"
1561
+ }
1562
+ },
1563
+ "node_modules/google-auth-library/node_modules/node-fetch": {
1564
+ "version": "3.3.2",
1565
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
1566
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
1567
+ "license": "MIT",
1568
+ "dependencies": {
1569
+ "data-uri-to-buffer": "^4.0.0",
1570
+ "fetch-blob": "^3.1.4",
1571
+ "formdata-polyfill": "^4.0.10"
1572
+ },
1573
+ "engines": {
1574
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
1575
+ },
1576
+ "funding": {
1577
+ "type": "opencollective",
1578
+ "url": "https://opencollective.com/node-fetch"
1579
+ }
1580
+ },
1581
+ "node_modules/google-gax": {
1582
+ "version": "4.6.1",
1583
+ "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.6.1.tgz",
1584
+ "integrity": "sha512-V6eky/xz2mcKfAd1Ioxyd6nmA61gao3n01C+YeuIwu3vzM9EDR6wcVzMSIbLMDXWeoi9SHYctXuKYC5uJUT3eQ==",
1585
+ "license": "Apache-2.0",
1586
+ "optional": true,
1587
+ "dependencies": {
1588
+ "@grpc/grpc-js": "^1.10.9",
1589
+ "@grpc/proto-loader": "^0.7.13",
1590
+ "@types/long": "^4.0.0",
1591
+ "abort-controller": "^3.0.0",
1592
+ "duplexify": "^4.0.0",
1593
+ "google-auth-library": "^9.3.0",
1594
+ "node-fetch": "^2.7.0",
1595
+ "object-hash": "^3.0.0",
1596
+ "proto3-json-serializer": "^2.0.2",
1597
+ "protobufjs": "^7.3.2",
1598
+ "retry-request": "^7.0.0",
1599
+ "uuid": "^9.0.1"
1600
+ },
1601
+ "engines": {
1602
+ "node": ">=14"
1603
+ }
1604
+ },
1605
+ "node_modules/google-gax/node_modules/gcp-metadata": {
1606
+ "version": "6.1.1",
1607
+ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz",
1608
+ "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==",
1609
+ "license": "Apache-2.0",
1610
+ "optional": true,
1611
+ "dependencies": {
1612
+ "gaxios": "^6.1.1",
1613
+ "google-logging-utils": "^0.0.2",
1614
+ "json-bigint": "^1.0.0"
1615
+ },
1616
+ "engines": {
1617
+ "node": ">=14"
1618
+ }
1619
+ },
1620
+ "node_modules/google-gax/node_modules/google-auth-library": {
1621
+ "version": "9.15.1",
1622
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz",
1623
+ "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==",
1624
+ "license": "Apache-2.0",
1625
+ "optional": true,
1626
+ "dependencies": {
1627
+ "base64-js": "^1.3.0",
1628
+ "ecdsa-sig-formatter": "^1.0.11",
1629
+ "gaxios": "^6.1.1",
1630
+ "gcp-metadata": "^6.1.0",
1631
+ "gtoken": "^7.0.0",
1632
+ "jws": "^4.0.0"
1633
+ },
1634
+ "engines": {
1635
+ "node": ">=14"
1636
+ }
1637
+ },
1638
+ "node_modules/google-gax/node_modules/google-logging-utils": {
1639
+ "version": "0.0.2",
1640
+ "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz",
1641
+ "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==",
1642
+ "license": "Apache-2.0",
1643
+ "optional": true,
1644
+ "engines": {
1645
+ "node": ">=14"
1646
+ }
1647
+ },
1648
+ "node_modules/google-gax/node_modules/uuid": {
1649
+ "version": "9.0.1",
1650
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
1651
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
1652
+ "funding": [
1653
+ "https://github.com/sponsors/broofa",
1654
+ "https://github.com/sponsors/ctavan"
1655
+ ],
1656
+ "license": "MIT",
1657
+ "optional": true,
1658
+ "bin": {
1659
+ "uuid": "dist/bin/uuid"
1660
+ }
1661
+ },
1662
+ "node_modules/google-logging-utils": {
1663
+ "version": "1.1.3",
1664
+ "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz",
1665
+ "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==",
1666
+ "license": "Apache-2.0",
1667
+ "engines": {
1668
+ "node": ">=14"
1669
+ }
1670
+ },
1671
  "node_modules/gopd": {
1672
  "version": "1.2.0",
1673
  "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
 
1680
  "url": "https://github.com/sponsors/ljharb"
1681
  }
1682
  },
1683
+ "node_modules/gtoken": {
1684
+ "version": "7.1.0",
1685
+ "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
1686
+ "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==",
1687
+ "license": "MIT",
1688
+ "optional": true,
1689
+ "dependencies": {
1690
+ "gaxios": "^6.0.0",
1691
+ "jws": "^4.0.0"
1692
+ },
1693
+ "engines": {
1694
+ "node": ">=14.0.0"
1695
+ }
1696
+ },
1697
  "node_modules/has-flag": {
1698
  "version": "3.0.0",
1699
  "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
 
1716
  "url": "https://github.com/sponsors/ljharb"
1717
  }
1718
  },
1719
+ "node_modules/has-tostringtag": {
1720
+ "version": "1.0.2",
1721
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
1722
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
1723
+ "license": "MIT",
1724
+ "optional": true,
1725
+ "dependencies": {
1726
+ "has-symbols": "^1.0.3"
1727
+ },
1728
+ "engines": {
1729
+ "node": ">= 0.4"
1730
+ },
1731
+ "funding": {
1732
+ "url": "https://github.com/sponsors/ljharb"
1733
+ }
1734
+ },
1735
  "node_modules/hasown": {
1736
  "version": "2.0.2",
1737
  "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
 
1744
  "node": ">= 0.4"
1745
  }
1746
  },
1747
+ "node_modules/html-entities": {
1748
+ "version": "2.6.0",
1749
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
1750
+ "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==",
1751
+ "funding": [
1752
+ {
1753
+ "type": "github",
1754
+ "url": "https://github.com/sponsors/mdevils"
1755
+ },
1756
+ {
1757
+ "type": "patreon",
1758
+ "url": "https://patreon.com/mdevils"
1759
+ }
1760
+ ],
1761
+ "license": "MIT",
1762
+ "optional": true
1763
+ },
1764
  "node_modules/http-errors": {
1765
  "version": "2.0.1",
1766
  "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
 
1781
  "url": "https://opencollective.com/express"
1782
  }
1783
  },
1784
+ "node_modules/http-parser-js": {
1785
+ "version": "0.5.10",
1786
+ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz",
1787
+ "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==",
1788
+ "license": "MIT"
1789
+ },
1790
+ "node_modules/http-proxy-agent": {
1791
+ "version": "5.0.0",
1792
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
1793
+ "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
1794
+ "license": "MIT",
1795
+ "optional": true,
1796
+ "dependencies": {
1797
+ "@tootallnate/once": "2",
1798
+ "agent-base": "6",
1799
+ "debug": "4"
1800
+ },
1801
+ "engines": {
1802
+ "node": ">= 6"
1803
+ }
1804
+ },
1805
+ "node_modules/http-proxy-agent/node_modules/agent-base": {
1806
+ "version": "6.0.2",
1807
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
1808
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
1809
+ "license": "MIT",
1810
+ "optional": true,
1811
+ "dependencies": {
1812
+ "debug": "4"
1813
+ },
1814
+ "engines": {
1815
+ "node": ">= 6.0.0"
1816
+ }
1817
+ },
1818
+ "node_modules/https-proxy-agent": {
1819
+ "version": "7.0.6",
1820
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
1821
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
1822
+ "license": "MIT",
1823
+ "dependencies": {
1824
+ "agent-base": "^7.1.2",
1825
+ "debug": "4"
1826
+ },
1827
+ "engines": {
1828
+ "node": ">= 14"
1829
+ }
1830
+ },
1831
  "node_modules/iconv-lite": {
1832
  "version": "0.7.2",
1833
  "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
 
1889
  "node": ">=0.10.0"
1890
  }
1891
  },
1892
+ "node_modules/is-fullwidth-code-point": {
1893
+ "version": "3.0.0",
1894
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
1895
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
1896
+ "license": "MIT",
1897
+ "optional": true,
1898
+ "engines": {
1899
+ "node": ">=8"
1900
+ }
1901
+ },
1902
  "node_modules/is-glob": {
1903
  "version": "4.0.3",
1904
  "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
 
1934
  "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
1935
  "license": "MIT"
1936
  },
1937
+ "node_modules/is-stream": {
1938
+ "version": "2.0.1",
1939
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
1940
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
1941
+ "license": "MIT",
1942
+ "optional": true,
1943
+ "engines": {
1944
+ "node": ">=8"
1945
+ },
1946
+ "funding": {
1947
+ "url": "https://github.com/sponsors/sindresorhus"
1948
+ }
1949
+ },
1950
+ "node_modules/jose": {
1951
+ "version": "4.15.9",
1952
+ "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
1953
+ "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
1954
+ "license": "MIT",
1955
+ "funding": {
1956
+ "url": "https://github.com/sponsors/panva"
1957
+ }
1958
+ },
1959
+ "node_modules/json-bigint": {
1960
+ "version": "1.0.0",
1961
+ "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
1962
+ "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
1963
+ "license": "MIT",
1964
+ "dependencies": {
1965
+ "bignumber.js": "^9.0.0"
1966
+ }
1967
+ },
1968
  "node_modules/jsonwebtoken": {
1969
  "version": "9.0.3",
1970
  "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
 
1998
  "safe-buffer": "^5.0.1"
1999
  }
2000
  },
2001
+ "node_modules/jwks-rsa": {
2002
+ "version": "3.2.2",
2003
+ "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.2.tgz",
2004
+ "integrity": "sha512-BqTyEDV+lS8F2trk3A+qJnxV5Q9EqKCBJOPti3W97r7qTympCZjb7h2X6f2kc+0K3rsSTY1/6YG2eaXKoj497w==",
2005
+ "license": "MIT",
2006
+ "dependencies": {
2007
+ "@types/jsonwebtoken": "^9.0.4",
2008
+ "debug": "^4.3.4",
2009
+ "jose": "^4.15.4",
2010
+ "limiter": "^1.1.5",
2011
+ "lru-memoizer": "^2.2.0"
2012
+ },
2013
+ "engines": {
2014
+ "node": ">=14"
2015
+ }
2016
+ },
2017
  "node_modules/jws": {
2018
  "version": "4.0.1",
2019
  "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
 
2024
  "safe-buffer": "^5.0.1"
2025
  }
2026
  },
2027
+ "node_modules/limiter": {
2028
+ "version": "1.1.5",
2029
+ "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
2030
+ "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA=="
2031
+ },
2032
+ "node_modules/lodash.camelcase": {
2033
+ "version": "4.3.0",
2034
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
2035
+ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
2036
+ "license": "MIT",
2037
+ "optional": true
2038
+ },
2039
+ "node_modules/lodash.clonedeep": {
2040
+ "version": "4.5.0",
2041
+ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
2042
+ "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
2043
+ "license": "MIT"
2044
+ },
2045
  "node_modules/lodash.includes": {
2046
  "version": "4.3.0",
2047
  "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
 
2090
  "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
2091
  "license": "Apache-2.0"
2092
  },
2093
+ "node_modules/lru-cache": {
2094
+ "version": "6.0.0",
2095
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
2096
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
2097
+ "license": "ISC",
2098
+ "dependencies": {
2099
+ "yallist": "^4.0.0"
2100
+ },
2101
+ "engines": {
2102
+ "node": ">=10"
2103
+ }
2104
+ },
2105
+ "node_modules/lru-memoizer": {
2106
+ "version": "2.3.0",
2107
+ "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz",
2108
+ "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==",
2109
+ "license": "MIT",
2110
+ "dependencies": {
2111
+ "lodash.clonedeep": "^4.5.0",
2112
+ "lru-cache": "6.0.0"
2113
+ }
2114
+ },
2115
  "node_modules/lru.min": {
2116
  "version": "1.1.4",
2117
  "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
 
2157
  "url": "https://github.com/sponsors/sindresorhus"
2158
  }
2159
  },
2160
+ "node_modules/mime": {
2161
+ "version": "3.0.0",
2162
+ "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
2163
+ "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
2164
+ "license": "MIT",
2165
+ "optional": true,
2166
+ "bin": {
2167
+ "mime": "cli.js"
2168
+ },
2169
+ "engines": {
2170
+ "node": ">=10.0.0"
2171
+ }
2172
+ },
2173
  "node_modules/mime-db": {
2174
  "version": "1.54.0",
2175
  "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
 
2260
  "node": ">= 0.6"
2261
  }
2262
  },
2263
+ "node_modules/node-domexception": {
2264
+ "version": "1.0.0",
2265
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
2266
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
2267
+ "deprecated": "Use your platform's native DOMException instead",
2268
+ "funding": [
2269
+ {
2270
+ "type": "github",
2271
+ "url": "https://github.com/sponsors/jimmywarting"
2272
+ },
2273
+ {
2274
+ "type": "github",
2275
+ "url": "https://paypal.me/jimmywarting"
2276
+ }
2277
+ ],
2278
+ "license": "MIT",
2279
+ "engines": {
2280
+ "node": ">=10.5.0"
2281
+ }
2282
+ },
2283
+ "node_modules/node-fetch": {
2284
+ "version": "2.7.0",
2285
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
2286
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
2287
+ "license": "MIT",
2288
+ "optional": true,
2289
+ "dependencies": {
2290
+ "whatwg-url": "^5.0.0"
2291
+ },
2292
+ "engines": {
2293
+ "node": "4.x || >=6.0.0"
2294
+ },
2295
+ "peerDependencies": {
2296
+ "encoding": "^0.1.0"
2297
+ },
2298
+ "peerDependenciesMeta": {
2299
+ "encoding": {
2300
+ "optional": true
2301
+ }
2302
+ }
2303
+ },
2304
+ "node_modules/node-forge": {
2305
+ "version": "1.4.0",
2306
+ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz",
2307
+ "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==",
2308
+ "license": "(BSD-3-Clause OR GPL-2.0)",
2309
+ "engines": {
2310
+ "node": ">= 6.13.0"
2311
+ }
2312
+ },
2313
  "node_modules/nodemon": {
2314
  "version": "3.1.14",
2315
  "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
 
2358
  "node": ">=0.10.0"
2359
  }
2360
  },
2361
+ "node_modules/object-hash": {
2362
+ "version": "3.0.0",
2363
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
2364
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
2365
+ "license": "MIT",
2366
+ "optional": true,
2367
+ "engines": {
2368
+ "node": ">= 6"
2369
+ }
2370
+ },
2371
  "node_modules/object-inspect": {
2372
  "version": "1.13.4",
2373
  "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
 
2401
  "wrappy": "1"
2402
  }
2403
  },
2404
+ "node_modules/p-limit": {
2405
+ "version": "3.1.0",
2406
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
2407
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
2408
+ "license": "MIT",
2409
+ "optional": true,
2410
+ "dependencies": {
2411
+ "yocto-queue": "^0.1.0"
2412
+ },
2413
+ "engines": {
2414
+ "node": ">=10"
2415
+ },
2416
+ "funding": {
2417
+ "url": "https://github.com/sponsors/sindresorhus"
2418
+ }
2419
+ },
2420
  "node_modules/parseurl": {
2421
  "version": "1.3.3",
2422
  "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
 
2426
  "node": ">= 0.8"
2427
  }
2428
  },
2429
+ "node_modules/path-expression-matcher": {
2430
+ "version": "1.2.1",
2431
+ "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.1.tgz",
2432
+ "integrity": "sha512-d7gQQmLvAKXKXE2GeP9apIGbMYKz88zWdsn/BN2HRWVQsDFdUY36WSLTY0Jvd4HWi7Fb30gQ62oAOzdgJA6fZw==",
2433
+ "funding": [
2434
+ {
2435
+ "type": "github",
2436
+ "url": "https://github.com/sponsors/NaturalIntelligence"
2437
+ }
2438
+ ],
2439
+ "license": "MIT",
2440
+ "optional": true,
2441
+ "engines": {
2442
+ "node": ">=14.0.0"
2443
+ }
2444
+ },
2445
  "node_modules/path-to-regexp": {
2446
  "version": "8.4.1",
2447
  "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.1.tgz",
 
2465
  "url": "https://github.com/sponsors/jonschlinkert"
2466
  }
2467
  },
2468
+ "node_modules/proto3-json-serializer": {
2469
+ "version": "2.0.2",
2470
+ "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz",
2471
+ "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==",
2472
+ "license": "Apache-2.0",
2473
+ "optional": true,
2474
+ "dependencies": {
2475
+ "protobufjs": "^7.2.5"
2476
+ },
2477
+ "engines": {
2478
+ "node": ">=14.0.0"
2479
+ }
2480
+ },
2481
+ "node_modules/protobufjs": {
2482
+ "version": "7.5.4",
2483
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
2484
+ "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
2485
+ "hasInstallScript": true,
2486
+ "license": "BSD-3-Clause",
2487
+ "optional": true,
2488
+ "dependencies": {
2489
+ "@protobufjs/aspromise": "^1.1.2",
2490
+ "@protobufjs/base64": "^1.1.2",
2491
+ "@protobufjs/codegen": "^2.0.4",
2492
+ "@protobufjs/eventemitter": "^1.1.0",
2493
+ "@protobufjs/fetch": "^1.1.0",
2494
+ "@protobufjs/float": "^1.0.2",
2495
+ "@protobufjs/inquire": "^1.1.0",
2496
+ "@protobufjs/path": "^1.1.2",
2497
+ "@protobufjs/pool": "^1.1.0",
2498
+ "@protobufjs/utf8": "^1.1.0",
2499
+ "@types/node": ">=13.7.0",
2500
+ "long": "^5.0.0"
2501
+ },
2502
+ "engines": {
2503
+ "node": ">=12.0.0"
2504
+ }
2505
+ },
2506
  "node_modules/proxy-addr": {
2507
  "version": "2.0.7",
2508
  "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
 
2562
  "node": ">= 0.10"
2563
  }
2564
  },
2565
+ "node_modules/readable-stream": {
2566
+ "version": "3.6.2",
2567
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
2568
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
2569
+ "license": "MIT",
2570
+ "optional": true,
2571
+ "dependencies": {
2572
+ "inherits": "^2.0.3",
2573
+ "string_decoder": "^1.1.1",
2574
+ "util-deprecate": "^1.0.1"
2575
+ },
2576
+ "engines": {
2577
+ "node": ">= 6"
2578
+ }
2579
+ },
2580
  "node_modules/readdirp": {
2581
  "version": "3.6.0",
2582
  "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
 
2590
  "node": ">=8.10.0"
2591
  }
2592
  },
2593
+ "node_modules/require-directory": {
2594
+ "version": "2.1.1",
2595
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
2596
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
2597
+ "license": "MIT",
2598
+ "optional": true,
2599
+ "engines": {
2600
+ "node": ">=0.10.0"
2601
+ }
2602
+ },
2603
+ "node_modules/retry": {
2604
+ "version": "0.13.1",
2605
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
2606
+ "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
2607
+ "license": "MIT",
2608
+ "optional": true,
2609
+ "engines": {
2610
+ "node": ">= 4"
2611
+ }
2612
+ },
2613
+ "node_modules/retry-request": {
2614
+ "version": "7.0.2",
2615
+ "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz",
2616
+ "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==",
2617
+ "license": "MIT",
2618
+ "optional": true,
2619
+ "dependencies": {
2620
+ "@types/request": "^2.48.8",
2621
+ "extend": "^3.0.2",
2622
+ "teeny-request": "^9.0.0"
2623
+ },
2624
+ "engines": {
2625
+ "node": ">=14"
2626
+ }
2627
+ },
2628
  "node_modules/router": {
2629
  "version": "2.2.0",
2630
  "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
 
2839
  "node": ">= 0.8"
2840
  }
2841
  },
2842
+ "node_modules/stream-events": {
2843
+ "version": "1.0.5",
2844
+ "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz",
2845
+ "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==",
2846
+ "license": "MIT",
2847
+ "optional": true,
2848
+ "dependencies": {
2849
+ "stubs": "^3.0.0"
2850
+ }
2851
+ },
2852
+ "node_modules/stream-shift": {
2853
+ "version": "1.0.3",
2854
+ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz",
2855
+ "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==",
2856
+ "license": "MIT",
2857
+ "optional": true
2858
+ },
2859
+ "node_modules/string_decoder": {
2860
+ "version": "1.3.0",
2861
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
2862
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
2863
+ "license": "MIT",
2864
+ "optional": true,
2865
+ "dependencies": {
2866
+ "safe-buffer": "~5.2.0"
2867
+ }
2868
+ },
2869
+ "node_modules/string-width": {
2870
+ "version": "4.2.3",
2871
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
2872
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
2873
+ "license": "MIT",
2874
+ "optional": true,
2875
+ "dependencies": {
2876
+ "emoji-regex": "^8.0.0",
2877
+ "is-fullwidth-code-point": "^3.0.0",
2878
+ "strip-ansi": "^6.0.1"
2879
+ },
2880
+ "engines": {
2881
+ "node": ">=8"
2882
+ }
2883
+ },
2884
+ "node_modules/strip-ansi": {
2885
+ "version": "6.0.1",
2886
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
2887
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
2888
+ "license": "MIT",
2889
+ "optional": true,
2890
+ "dependencies": {
2891
+ "ansi-regex": "^5.0.1"
2892
+ },
2893
+ "engines": {
2894
+ "node": ">=8"
2895
+ }
2896
+ },
2897
+ "node_modules/strnum": {
2898
+ "version": "2.2.2",
2899
+ "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.2.tgz",
2900
+ "integrity": "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==",
2901
+ "funding": [
2902
+ {
2903
+ "type": "github",
2904
+ "url": "https://github.com/sponsors/NaturalIntelligence"
2905
+ }
2906
+ ],
2907
+ "license": "MIT",
2908
+ "optional": true
2909
+ },
2910
+ "node_modules/stubs": {
2911
+ "version": "3.0.0",
2912
+ "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz",
2913
+ "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==",
2914
+ "license": "MIT",
2915
+ "optional": true
2916
+ },
2917
  "node_modules/supports-color": {
2918
  "version": "5.5.0",
2919
  "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
 
2927
  "node": ">=4"
2928
  }
2929
  },
2930
+ "node_modules/teeny-request": {
2931
+ "version": "9.0.0",
2932
+ "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz",
2933
+ "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==",
2934
+ "license": "Apache-2.0",
2935
+ "optional": true,
2936
+ "dependencies": {
2937
+ "http-proxy-agent": "^5.0.0",
2938
+ "https-proxy-agent": "^5.0.0",
2939
+ "node-fetch": "^2.6.9",
2940
+ "stream-events": "^1.0.5",
2941
+ "uuid": "^9.0.0"
2942
+ },
2943
+ "engines": {
2944
+ "node": ">=14"
2945
+ }
2946
+ },
2947
+ "node_modules/teeny-request/node_modules/agent-base": {
2948
+ "version": "6.0.2",
2949
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
2950
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
2951
+ "license": "MIT",
2952
+ "optional": true,
2953
+ "dependencies": {
2954
+ "debug": "4"
2955
+ },
2956
+ "engines": {
2957
+ "node": ">= 6.0.0"
2958
+ }
2959
+ },
2960
+ "node_modules/teeny-request/node_modules/https-proxy-agent": {
2961
+ "version": "5.0.1",
2962
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
2963
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
2964
+ "license": "MIT",
2965
+ "optional": true,
2966
+ "dependencies": {
2967
+ "agent-base": "6",
2968
+ "debug": "4"
2969
+ },
2970
+ "engines": {
2971
+ "node": ">= 6"
2972
+ }
2973
+ },
2974
+ "node_modules/teeny-request/node_modules/uuid": {
2975
+ "version": "9.0.1",
2976
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
2977
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
2978
+ "funding": [
2979
+ "https://github.com/sponsors/broofa",
2980
+ "https://github.com/sponsors/ctavan"
2981
+ ],
2982
+ "license": "MIT",
2983
+ "optional": true,
2984
+ "bin": {
2985
+ "uuid": "dist/bin/uuid"
2986
+ }
2987
+ },
2988
  "node_modules/to-regex-range": {
2989
  "version": "5.0.1",
2990
  "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
 
3017
  "nodetouch": "bin/nodetouch.js"
3018
  }
3019
  },
3020
+ "node_modules/tr46": {
3021
+ "version": "0.0.3",
3022
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
3023
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
3024
+ "license": "MIT",
3025
+ "optional": true
3026
+ },
3027
+ "node_modules/tslib": {
3028
+ "version": "2.8.1",
3029
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
3030
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
3031
+ "license": "0BSD"
3032
+ },
3033
  "node_modules/type-is": {
3034
  "version": "2.0.1",
3035
  "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
 
3055
  "version": "7.18.2",
3056
  "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
3057
  "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
3058
+ "license": "MIT"
 
3059
  },
3060
  "node_modules/unpipe": {
3061
  "version": "1.0.0",
 
3066
  "node": ">= 0.8"
3067
  }
3068
  },
3069
+ "node_modules/util-deprecate": {
3070
+ "version": "1.0.2",
3071
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
3072
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
3073
+ "license": "MIT",
3074
+ "optional": true
3075
+ },
3076
+ "node_modules/uuid": {
3077
+ "version": "11.1.0",
3078
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
3079
+ "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
3080
+ "funding": [
3081
+ "https://github.com/sponsors/broofa",
3082
+ "https://github.com/sponsors/ctavan"
3083
+ ],
3084
+ "license": "MIT",
3085
+ "bin": {
3086
+ "uuid": "dist/esm/bin/uuid"
3087
+ }
3088
+ },
3089
  "node_modules/vary": {
3090
  "version": "1.1.2",
3091
  "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
 
3095
  "node": ">= 0.8"
3096
  }
3097
  },
3098
+ "node_modules/web-streams-polyfill": {
3099
+ "version": "3.3.3",
3100
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
3101
+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
3102
+ "license": "MIT",
3103
+ "engines": {
3104
+ "node": ">= 8"
3105
+ }
3106
+ },
3107
+ "node_modules/webidl-conversions": {
3108
+ "version": "3.0.1",
3109
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
3110
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
3111
+ "license": "BSD-2-Clause",
3112
+ "optional": true
3113
+ },
3114
+ "node_modules/websocket-driver": {
3115
+ "version": "0.7.4",
3116
+ "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
3117
+ "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
3118
+ "license": "Apache-2.0",
3119
+ "dependencies": {
3120
+ "http-parser-js": ">=0.5.1",
3121
+ "safe-buffer": ">=5.1.0",
3122
+ "websocket-extensions": ">=0.1.1"
3123
+ },
3124
+ "engines": {
3125
+ "node": ">=0.8.0"
3126
+ }
3127
+ },
3128
+ "node_modules/websocket-extensions": {
3129
+ "version": "0.1.4",
3130
+ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
3131
+ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
3132
+ "license": "Apache-2.0",
3133
+ "engines": {
3134
+ "node": ">=0.8.0"
3135
+ }
3136
+ },
3137
+ "node_modules/whatwg-url": {
3138
+ "version": "5.0.0",
3139
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
3140
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
3141
+ "license": "MIT",
3142
+ "optional": true,
3143
+ "dependencies": {
3144
+ "tr46": "~0.0.3",
3145
+ "webidl-conversions": "^3.0.0"
3146
+ }
3147
+ },
3148
+ "node_modules/wrap-ansi": {
3149
+ "version": "7.0.0",
3150
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
3151
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
3152
+ "license": "MIT",
3153
+ "optional": true,
3154
+ "dependencies": {
3155
+ "ansi-styles": "^4.0.0",
3156
+ "string-width": "^4.1.0",
3157
+ "strip-ansi": "^6.0.0"
3158
+ },
3159
+ "engines": {
3160
+ "node": ">=10"
3161
+ },
3162
+ "funding": {
3163
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
3164
+ }
3165
+ },
3166
  "node_modules/wrappy": {
3167
  "version": "1.0.2",
3168
  "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
3169
  "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
3170
  "license": "ISC"
3171
+ },
3172
+ "node_modules/y18n": {
3173
+ "version": "5.0.8",
3174
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
3175
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
3176
+ "license": "ISC",
3177
+ "optional": true,
3178
+ "engines": {
3179
+ "node": ">=10"
3180
+ }
3181
+ },
3182
+ "node_modules/yallist": {
3183
+ "version": "4.0.0",
3184
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
3185
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
3186
+ "license": "ISC"
3187
+ },
3188
+ "node_modules/yargs": {
3189
+ "version": "17.7.2",
3190
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
3191
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
3192
+ "license": "MIT",
3193
+ "optional": true,
3194
+ "dependencies": {
3195
+ "cliui": "^8.0.1",
3196
+ "escalade": "^3.1.1",
3197
+ "get-caller-file": "^2.0.5",
3198
+ "require-directory": "^2.1.1",
3199
+ "string-width": "^4.2.3",
3200
+ "y18n": "^5.0.5",
3201
+ "yargs-parser": "^21.1.1"
3202
+ },
3203
+ "engines": {
3204
+ "node": ">=12"
3205
+ }
3206
+ },
3207
+ "node_modules/yargs-parser": {
3208
+ "version": "21.1.1",
3209
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
3210
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
3211
+ "license": "ISC",
3212
+ "optional": true,
3213
+ "engines": {
3214
+ "node": ">=12"
3215
+ }
3216
+ },
3217
+ "node_modules/yocto-queue": {
3218
+ "version": "0.1.0",
3219
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
3220
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
3221
+ "license": "MIT",
3222
+ "optional": true,
3223
+ "engines": {
3224
+ "node": ">=10"
3225
+ },
3226
+ "funding": {
3227
+ "url": "https://github.com/sponsors/sindresorhus"
3228
+ }
3229
  }
3230
  }
3231
  }
package.json CHANGED
@@ -29,6 +29,7 @@
29
  "cors": "^2.8.6",
30
  "dotenv": "^17.3.1",
31
  "express": "^5.2.1",
 
32
  "jsonwebtoken": "^9.0.3",
33
  "mysql2": "^3.20.0"
34
  },
 
29
  "cors": "^2.8.6",
30
  "dotenv": "^17.3.1",
31
  "express": "^5.2.1",
32
+ "firebase-admin": "^13.7.0",
33
  "jsonwebtoken": "^9.0.3",
34
  "mysql2": "^3.20.0"
35
  },
src/config/env.js CHANGED
@@ -47,6 +47,17 @@ const env = {
47
  mailchimpReplyTo: process.env.MAILCHIMP_REPLY_TO || '',
48
  groqApiKey: process.env.GROQ_API_KEY || '',
49
  groqModel: process.env.GROQ_MODEL || 'llama-3.1-8b-instant',
 
 
 
 
 
 
 
 
 
 
 
50
  };
51
 
52
  module.exports = env;
 
47
  mailchimpReplyTo: process.env.MAILCHIMP_REPLY_TO || '',
48
  groqApiKey: process.env.GROQ_API_KEY || '',
49
  groqModel: process.env.GROQ_MODEL || 'llama-3.1-8b-instant',
50
+ firebaseEnabled: toBoolean(process.env.FIREBASE_ENABLED, false),
51
+ firebaseProjectId: process.env.FIREBASE_PROJECT_ID || '',
52
+ firebaseClientEmail: process.env.FIREBASE_CLIENT_EMAIL || '',
53
+ firebasePrivateKey: process.env.FIREBASE_PRIVATE_KEY || '',
54
+ firebaseServiceAccountJson: process.env.FIREBASE_SERVICE_ACCOUNT_JSON || '',
55
+ firebaseServiceAccountPath: process.env.FIREBASE_SERVICE_ACCOUNT_PATH || '',
56
+ pushReminderWorkerEnabled: toBoolean(process.env.PUSH_REMINDER_WORKER_ENABLED, true),
57
+ pushReminderWorkerIntervalSeconds: toNumber(
58
+ process.env.PUSH_REMINDER_WORKER_INTERVAL_SECONDS,
59
+ 60,
60
+ ),
61
  };
62
 
63
  module.exports = env;
src/config/firebaseAdmin.js ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const admin = require('firebase-admin');
4
+
5
+ const env = require('./env');
6
+
7
+ let initialized = false;
8
+ let initializationError = null;
9
+
10
+ function parseServiceAccountFromEnv() {
11
+ if (env.firebaseServiceAccountJson) {
12
+ try {
13
+ return JSON.parse(env.firebaseServiceAccountJson);
14
+ } catch (_error) {
15
+ initializationError = 'FIREBASE_SERVICE_ACCOUNT_JSON is not valid JSON.';
16
+ return null;
17
+ }
18
+ }
19
+
20
+ if (env.firebaseServiceAccountPath) {
21
+ try {
22
+ const absolutePath = path.resolve(
23
+ __dirname,
24
+ '..',
25
+ '..',
26
+ env.firebaseServiceAccountPath,
27
+ );
28
+ const raw = fs.readFileSync(absolutePath, 'utf8');
29
+ return JSON.parse(raw);
30
+ } catch (_error) {
31
+ initializationError = 'FIREBASE_SERVICE_ACCOUNT_PATH could not be read.';
32
+ return null;
33
+ }
34
+ }
35
+
36
+ if (env.firebaseProjectId && env.firebaseClientEmail && env.firebasePrivateKey) {
37
+ return {
38
+ project_id: env.firebaseProjectId,
39
+ client_email: env.firebaseClientEmail,
40
+ private_key: env.firebasePrivateKey.replace(/\\n/g, '\n'),
41
+ };
42
+ }
43
+
44
+ return null;
45
+ }
46
+
47
+ function initializeFirebaseAdmin() {
48
+ if (initialized || admin.apps.length > 0) {
49
+ initialized = true;
50
+ return;
51
+ }
52
+
53
+ if (!env.firebaseEnabled) {
54
+ initializationError = 'Firebase push is disabled.';
55
+ return;
56
+ }
57
+
58
+ const serviceAccount = parseServiceAccountFromEnv();
59
+ if (!serviceAccount) {
60
+ if (!initializationError) {
61
+ initializationError = 'Missing Firebase service account credentials.';
62
+ }
63
+ return;
64
+ }
65
+
66
+ try {
67
+ admin.initializeApp({
68
+ credential: admin.credential.cert(serviceAccount),
69
+ projectId: serviceAccount.project_id || env.firebaseProjectId || undefined,
70
+ });
71
+ initialized = true;
72
+ initializationError = null;
73
+ } catch (_error) {
74
+ initializationError = 'Failed to initialize Firebase Admin SDK.';
75
+ initialized = false;
76
+ }
77
+ }
78
+
79
+ function getFirebaseMessaging() {
80
+ initializeFirebaseAdmin();
81
+
82
+ if (!initialized) {
83
+ return null;
84
+ }
85
+
86
+ return admin.messaging();
87
+ }
88
+
89
+ function isFirebaseMessagingEnabled() {
90
+ initializeFirebaseAdmin();
91
+ return initialized;
92
+ }
93
+
94
+ function getFirebaseInitializationError() {
95
+ return initializationError;
96
+ }
97
+
98
+ module.exports = {
99
+ getFirebaseMessaging,
100
+ isFirebaseMessagingEnabled,
101
+ getFirebaseInitializationError,
102
+ };
src/routes/notifications.routes.js CHANGED
@@ -2,8 +2,11 @@ const express = require('express');
2
 
3
  const { requireRole } = require('../middleware/auth');
4
  const {
 
5
  listInAppNotifications,
6
  markNotificationAsRead,
 
 
7
  } = require('../services/notifications.service');
8
  const { sendSuccess } = require('../utils/apiResponse');
9
 
@@ -28,6 +31,58 @@ router.get('/me', async (req, res) => {
28
  });
29
  });
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  router.patch('/:notificationId/read', async (req, res) => {
32
  const notification = await markNotificationAsRead({
33
  notificationId: Number(req.params.notificationId),
 
2
 
3
  const { requireRole } = require('../middleware/auth');
4
  const {
5
+ listPushDevicesForUser,
6
  listInAppNotifications,
7
  markNotificationAsRead,
8
+ registerPushDeviceToken,
9
+ unregisterPushDeviceToken,
10
  } = require('../services/notifications.service');
11
  const { sendSuccess } = require('../utils/apiResponse');
12
 
 
31
  });
32
  });
33
 
34
+ router.get('/devices/me', async (req, res) => {
35
+ const devices = await listPushDevicesForUser({
36
+ userType: req.auth.userType,
37
+ userIdentifier: req.auth.userIdentifier,
38
+ });
39
+
40
+ return sendSuccess(res, {
41
+ message: 'Registered push devices fetched successfully.',
42
+ data: {
43
+ devices,
44
+ total: devices.length,
45
+ },
46
+ });
47
+ });
48
+
49
+ router.post('/devices/register', async (req, res) => {
50
+ const device = await registerPushDeviceToken({
51
+ userType: req.auth.userType,
52
+ userIdentifier: req.auth.userIdentifier,
53
+ token: req.body.token,
54
+ platform: req.body.platform,
55
+ deviceId: req.body.deviceId,
56
+ appVersion: req.body.appVersion,
57
+ deviceModel: req.body.deviceModel,
58
+ locale: req.body.locale,
59
+ timezone: req.body.timezone,
60
+ });
61
+
62
+ return sendSuccess(res, {
63
+ status: 201,
64
+ message: 'Push device registered successfully.',
65
+ data: device,
66
+ });
67
+ });
68
+
69
+ router.delete('/devices/unregister', async (req, res) => {
70
+ const removed = await unregisterPushDeviceToken({
71
+ userType: req.auth.userType,
72
+ userIdentifier: req.auth.userIdentifier,
73
+ token: req.body.token,
74
+ });
75
+
76
+ return sendSuccess(res, {
77
+ message: removed
78
+ ? 'Push device unregistered successfully.'
79
+ : 'Push device was already unregistered.',
80
+ data: {
81
+ removed,
82
+ },
83
+ });
84
+ });
85
+
86
  router.patch('/:notificationId/read', async (req, res) => {
87
  const notification = await markNotificationAsRead({
88
  notificationId: Number(req.params.notificationId),
src/server.js CHANGED
@@ -1,6 +1,7 @@
1
  const app = require('./app');
2
  const env = require('./config/env');
3
  const { query } = require('./config/db');
 
4
 
5
  async function startServer() {
6
  await query('SELECT 1 AS db_ok');
@@ -9,6 +10,8 @@ async function startServer() {
9
  console.log(
10
  `Care People backend listening on http://localhost:${env.port}`,
11
  );
 
 
12
  });
13
  }
14
 
 
1
  const app = require('./app');
2
  const env = require('./config/env');
3
  const { query } = require('./config/db');
4
+ const { startReminderPushWorker } = require('./workers/reminderPushWorker');
5
 
6
  async function startServer() {
7
  await query('SELECT 1 AS db_ok');
 
10
  console.log(
11
  `Care People backend listening on http://localhost:${env.port}`,
12
  );
13
+
14
+ startReminderPushWorker();
15
  });
16
  }
17
 
src/services/notifications.service.js CHANGED
@@ -1,4 +1,7 @@
 
 
1
  const { query } = require('../config/db');
 
2
  const HttpError = require('../utils/httpError');
3
 
4
  function normalizeUserType(userType) {
@@ -9,6 +12,59 @@ function normalizeUserType(userType) {
9
  return normalized;
10
  }
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  function parseMetadata(value) {
13
  if (!value) {
14
  return null;
@@ -25,6 +81,380 @@ function parseMetadata(value) {
25
  }
26
  }
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  async function createInAppNotification({
29
  userType,
30
  userIdentifier,
@@ -33,6 +463,7 @@ async function createInAppNotification({
33
  body,
34
  metadata = null,
35
  connection = null,
 
36
  }) {
37
  const normalizedUserType = normalizeUserType(userType);
38
  const normalizedUserIdentifier = String(userIdentifier || '').trim();
@@ -104,7 +535,23 @@ async function createInAppNotification({
104
  },
105
  );
106
 
107
- return Number(result.insertId || 0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  }
109
 
110
  async function listInAppNotifications({
@@ -226,4 +673,8 @@ module.exports = {
226
  createInAppNotification,
227
  listInAppNotifications,
228
  markNotificationAsRead,
 
 
 
 
229
  };
 
1
+ const crypto = require('crypto');
2
+
3
  const { query } = require('../config/db');
4
+ const { getFirebaseMessaging, isFirebaseMessagingEnabled } = require('../config/firebaseAdmin');
5
  const HttpError = require('../utils/httpError');
6
 
7
  function normalizeUserType(userType) {
 
12
  return normalized;
13
  }
14
 
15
+ function normalizePlatform(platform) {
16
+ const normalized = String(platform || '').trim().toLowerCase();
17
+ if (!normalized) {
18
+ return 'android';
19
+ }
20
+
21
+ if (!['android', 'ios', 'web'].includes(normalized)) {
22
+ throw new HttpError(400, 'Invalid platform value.', 'INVALID_PUSH_PLATFORM');
23
+ }
24
+
25
+ return normalized;
26
+ }
27
+
28
+ function normalizeDeviceId(deviceId, token) {
29
+ const normalized = String(deviceId || '').trim();
30
+ if (normalized) {
31
+ return normalized.slice(0, 128);
32
+ }
33
+
34
+ return crypto.createHash('sha1').update(String(token || '')).digest('hex');
35
+ }
36
+
37
+ function toNullableText(value, maxLength) {
38
+ if (value == null) {
39
+ return null;
40
+ }
41
+
42
+ const normalized = String(value).trim();
43
+ if (!normalized) {
44
+ return null;
45
+ }
46
+
47
+ return typeof maxLength === 'number' ? normalized.slice(0, maxLength) : normalized;
48
+ }
49
+
50
+ function mapPushDeviceToken(row) {
51
+ return {
52
+ id: Number(row.id),
53
+ userType: row.user_type,
54
+ userIdentifier: row.user_identifier,
55
+ platform: row.platform,
56
+ deviceId: row.device_id,
57
+ appVersion: row.app_version,
58
+ deviceModel: row.device_model,
59
+ locale: row.locale,
60
+ timezone: row.timezone,
61
+ isActive: Boolean(row.is_active),
62
+ lastSeenAt: row.last_seen_at,
63
+ createdAt: row.created_at,
64
+ updatedAt: row.updated_at,
65
+ };
66
+ }
67
+
68
  function parseMetadata(value) {
69
  if (!value) {
70
  return null;
 
81
  }
82
  }
83
 
84
+ function stringifyPushDataValue(value) {
85
+ if (value == null) {
86
+ return '';
87
+ }
88
+
89
+ if (typeof value === 'string') {
90
+ return value;
91
+ }
92
+
93
+ if (typeof value === 'number' || typeof value === 'boolean') {
94
+ return String(value);
95
+ }
96
+
97
+ try {
98
+ return JSON.stringify(value);
99
+ } catch (_error) {
100
+ return String(value);
101
+ }
102
+ }
103
+
104
+ function buildPushDataPayload(baseData = {}) {
105
+ const result = {};
106
+ Object.entries(baseData).forEach(([key, value]) => {
107
+ if (value == null) {
108
+ return;
109
+ }
110
+ result[key] = stringifyPushDataValue(value);
111
+ });
112
+ return result;
113
+ }
114
+
115
+ function getInvalidTokenErrorCodes() {
116
+ return new Set([
117
+ 'messaging/invalid-registration-token',
118
+ 'messaging/registration-token-not-registered',
119
+ ]);
120
+ }
121
+
122
+ async function registerPushDeviceToken({
123
+ userType,
124
+ userIdentifier,
125
+ token,
126
+ platform = 'android',
127
+ deviceId,
128
+ appVersion = null,
129
+ deviceModel = null,
130
+ locale = null,
131
+ timezone = null,
132
+ }) {
133
+ const normalizedUserType = normalizeUserType(userType);
134
+ const normalizedUserIdentifier = String(userIdentifier || '').trim();
135
+ const normalizedToken = String(token || '').trim();
136
+ const normalizedPlatform = normalizePlatform(platform);
137
+
138
+ if (!normalizedUserIdentifier) {
139
+ throw new HttpError(400, 'userIdentifier is required.', 'USER_IDENTIFIER_REQUIRED');
140
+ }
141
+
142
+ if (!normalizedToken || normalizedToken.length < 20) {
143
+ throw new HttpError(400, 'Valid push token is required.', 'PUSH_TOKEN_REQUIRED');
144
+ }
145
+
146
+ const normalizedDeviceId = normalizeDeviceId(deviceId, normalizedToken);
147
+
148
+ await query(
149
+ `
150
+ INSERT INTO push_device_tokens (
151
+ user_type,
152
+ user_identifier,
153
+ platform,
154
+ device_id,
155
+ token,
156
+ app_version,
157
+ device_model,
158
+ locale,
159
+ timezone,
160
+ is_active,
161
+ last_seen_at
162
+ ) VALUES (
163
+ :userType,
164
+ :userIdentifier,
165
+ :platform,
166
+ :deviceId,
167
+ :token,
168
+ :appVersion,
169
+ :deviceModel,
170
+ :locale,
171
+ :timezone,
172
+ 1,
173
+ CURRENT_TIMESTAMP
174
+ )
175
+ ON DUPLICATE KEY UPDATE
176
+ user_type = VALUES(user_type),
177
+ user_identifier = VALUES(user_identifier),
178
+ platform = VALUES(platform),
179
+ device_id = VALUES(device_id),
180
+ app_version = VALUES(app_version),
181
+ device_model = VALUES(device_model),
182
+ locale = VALUES(locale),
183
+ timezone = VALUES(timezone),
184
+ is_active = 1,
185
+ last_seen_at = CURRENT_TIMESTAMP,
186
+ updated_at = CURRENT_TIMESTAMP
187
+ `,
188
+ {
189
+ userType: normalizedUserType,
190
+ userIdentifier: normalizedUserIdentifier,
191
+ platform: normalizedPlatform,
192
+ deviceId: normalizedDeviceId,
193
+ token: normalizedToken,
194
+ appVersion: toNullableText(appVersion, 30),
195
+ deviceModel: toNullableText(deviceModel, 120),
196
+ locale: toNullableText(locale, 20),
197
+ timezone: toNullableText(timezone, 60),
198
+ },
199
+ );
200
+
201
+ const rows = await query(
202
+ `
203
+ SELECT
204
+ id,
205
+ user_type,
206
+ user_identifier,
207
+ platform,
208
+ device_id,
209
+ app_version,
210
+ device_model,
211
+ locale,
212
+ timezone,
213
+ is_active,
214
+ last_seen_at,
215
+ created_at,
216
+ updated_at
217
+ FROM push_device_tokens
218
+ WHERE token = :token
219
+ LIMIT 1
220
+ `,
221
+ { token: normalizedToken },
222
+ );
223
+
224
+ return mapPushDeviceToken(rows[0]);
225
+ }
226
+
227
+ async function unregisterPushDeviceToken({ userType, userIdentifier, token }) {
228
+ const normalizedUserType = normalizeUserType(userType);
229
+ const normalizedUserIdentifier = String(userIdentifier || '').trim();
230
+ const normalizedToken = String(token || '').trim();
231
+
232
+ if (!normalizedUserIdentifier) {
233
+ throw new HttpError(400, 'userIdentifier is required.', 'USER_IDENTIFIER_REQUIRED');
234
+ }
235
+
236
+ if (!normalizedToken) {
237
+ throw new HttpError(400, 'token is required.', 'PUSH_TOKEN_REQUIRED');
238
+ }
239
+
240
+ const result = await query(
241
+ `
242
+ UPDATE push_device_tokens
243
+ SET
244
+ is_active = 0,
245
+ updated_at = CURRENT_TIMESTAMP
246
+ WHERE
247
+ user_type = :userType
248
+ AND user_identifier = :userIdentifier
249
+ AND token = :token
250
+ `,
251
+ {
252
+ userType: normalizedUserType,
253
+ userIdentifier: normalizedUserIdentifier,
254
+ token: normalizedToken,
255
+ },
256
+ );
257
+
258
+ return Number(result.affectedRows || 0) > 0;
259
+ }
260
+
261
+ async function listPushDevicesForUser({ userType, userIdentifier }) {
262
+ const normalizedUserType = normalizeUserType(userType);
263
+ const normalizedUserIdentifier = String(userIdentifier || '').trim();
264
+
265
+ if (!normalizedUserIdentifier) {
266
+ throw new HttpError(400, 'userIdentifier is required.', 'USER_IDENTIFIER_REQUIRED');
267
+ }
268
+
269
+ const rows = await query(
270
+ `
271
+ SELECT
272
+ id,
273
+ user_type,
274
+ user_identifier,
275
+ platform,
276
+ device_id,
277
+ app_version,
278
+ device_model,
279
+ locale,
280
+ timezone,
281
+ is_active,
282
+ last_seen_at,
283
+ created_at,
284
+ updated_at
285
+ FROM push_device_tokens
286
+ WHERE
287
+ user_type = :userType
288
+ AND user_identifier = :userIdentifier
289
+ ORDER BY updated_at DESC
290
+ LIMIT 25
291
+ `,
292
+ {
293
+ userType: normalizedUserType,
294
+ userIdentifier: normalizedUserIdentifier,
295
+ },
296
+ );
297
+
298
+ return rows.map(mapPushDeviceToken);
299
+ }
300
+
301
+ async function deactivatePushTokensById(tokenIds = []) {
302
+ if (!Array.isArray(tokenIds) || tokenIds.length === 0) {
303
+ return 0;
304
+ }
305
+
306
+ const placeholders = tokenIds.map(() => '?').join(', ');
307
+ const result = await query(
308
+ `
309
+ UPDATE push_device_tokens
310
+ SET
311
+ is_active = 0,
312
+ updated_at = CURRENT_TIMESTAMP
313
+ WHERE id IN (${placeholders})
314
+ `,
315
+ tokenIds,
316
+ );
317
+
318
+ return Number(result.affectedRows || 0);
319
+ }
320
+
321
+ async function sendPushNotificationToUser({
322
+ userType,
323
+ userIdentifier,
324
+ category,
325
+ title,
326
+ body,
327
+ metadata = null,
328
+ }) {
329
+ const normalizedUserType = normalizeUserType(userType);
330
+ const normalizedUserIdentifier = String(userIdentifier || '').trim();
331
+ const normalizedTitle = String(title || '').trim();
332
+ const normalizedBody = String(body || '').trim();
333
+ const normalizedCategory = String(category || 'general').trim() || 'general';
334
+
335
+ if (!normalizedUserIdentifier || !normalizedTitle || !normalizedBody) {
336
+ return {
337
+ delivered: false,
338
+ reason: 'INVALID_INPUT',
339
+ sentCount: 0,
340
+ successCount: 0,
341
+ failureCount: 0,
342
+ invalidatedCount: 0,
343
+ };
344
+ }
345
+
346
+ if (!isFirebaseMessagingEnabled()) {
347
+ return {
348
+ delivered: false,
349
+ reason: 'PUSH_DISABLED',
350
+ sentCount: 0,
351
+ successCount: 0,
352
+ failureCount: 0,
353
+ invalidatedCount: 0,
354
+ };
355
+ }
356
+
357
+ const tokenRows = await query(
358
+ `
359
+ SELECT id, token
360
+ FROM push_device_tokens
361
+ WHERE
362
+ user_type = :userType
363
+ AND user_identifier = :userIdentifier
364
+ AND is_active = 1
365
+ ORDER BY updated_at DESC
366
+ LIMIT 20
367
+ `,
368
+ {
369
+ userType: normalizedUserType,
370
+ userIdentifier: normalizedUserIdentifier,
371
+ },
372
+ );
373
+
374
+ if (tokenRows.length === 0) {
375
+ return {
376
+ delivered: false,
377
+ reason: 'NO_ACTIVE_TOKENS',
378
+ sentCount: 0,
379
+ successCount: 0,
380
+ failureCount: 0,
381
+ invalidatedCount: 0,
382
+ };
383
+ }
384
+
385
+ const messaging = getFirebaseMessaging();
386
+ if (!messaging) {
387
+ return {
388
+ delivered: false,
389
+ reason: 'PUSH_NOT_INITIALIZED',
390
+ sentCount: 0,
391
+ successCount: 0,
392
+ failureCount: 0,
393
+ invalidatedCount: 0,
394
+ };
395
+ }
396
+
397
+ const tokens = tokenRows.map((row) => row.token);
398
+
399
+ const data = buildPushDataPayload({
400
+ category: normalizedCategory,
401
+ userType: normalizedUserType,
402
+ userIdentifier: normalizedUserIdentifier,
403
+ ...(metadata || {}),
404
+ });
405
+
406
+ try {
407
+ const response = await messaging.sendEachForMulticast({
408
+ tokens,
409
+ notification: {
410
+ title: normalizedTitle,
411
+ body: normalizedBody,
412
+ },
413
+ data,
414
+ android: {
415
+ priority: 'high',
416
+ notification: {
417
+ channelId: 'care_people_general',
418
+ clickAction: 'FLUTTER_NOTIFICATION_CLICK',
419
+ },
420
+ },
421
+ });
422
+
423
+ const invalidTokenErrorCodes = getInvalidTokenErrorCodes();
424
+ const invalidTokenIds = [];
425
+
426
+ response.responses.forEach((item, index) => {
427
+ if (item.success) {
428
+ return;
429
+ }
430
+
431
+ if (invalidTokenErrorCodes.has(item.error?.code)) {
432
+ invalidTokenIds.push(Number(tokenRows[index].id));
433
+ }
434
+ });
435
+
436
+ const invalidatedCount = await deactivatePushTokensById(invalidTokenIds);
437
+
438
+ return {
439
+ delivered: response.successCount > 0,
440
+ reason: response.successCount > 0 ? null : 'SEND_FAILED',
441
+ sentCount: tokens.length,
442
+ successCount: response.successCount,
443
+ failureCount: response.failureCount,
444
+ invalidatedCount,
445
+ };
446
+ } catch (_error) {
447
+ return {
448
+ delivered: false,
449
+ reason: 'SEND_EXCEPTION',
450
+ sentCount: tokens.length,
451
+ successCount: 0,
452
+ failureCount: tokens.length,
453
+ invalidatedCount: 0,
454
+ };
455
+ }
456
+ }
457
+
458
  async function createInAppNotification({
459
  userType,
460
  userIdentifier,
 
463
  body,
464
  metadata = null,
465
  connection = null,
466
+ sendPush = true,
467
  }) {
468
  const normalizedUserType = normalizeUserType(userType);
469
  const normalizedUserIdentifier = String(userIdentifier || '').trim();
 
535
  },
536
  );
537
 
538
+ const notificationId = Number(result.insertId || 0);
539
+
540
+ if (sendPush) {
541
+ await sendPushNotificationToUser({
542
+ userType: normalizedUserType,
543
+ userIdentifier: normalizedUserIdentifier,
544
+ category: normalizedCategory,
545
+ title: normalizedTitle,
546
+ body: normalizedBody,
547
+ metadata: {
548
+ notificationId,
549
+ ...(metadata || {}),
550
+ },
551
+ }).catch(() => null);
552
+ }
553
+
554
+ return notificationId;
555
  }
556
 
557
  async function listInAppNotifications({
 
673
  createInAppNotification,
674
  listInAppNotifications,
675
  markNotificationAsRead,
676
+ registerPushDeviceToken,
677
+ unregisterPushDeviceToken,
678
+ listPushDevicesForUser,
679
+ sendPushNotificationToUser,
680
  };
src/services/reminders.service.js CHANGED
@@ -417,8 +417,14 @@ async function listReminderOccurrencesForPatient(patientPhone, { limit = 120, on
417
  }));
418
  }
419
 
420
- async function syncDueReminderOccurrencesForPatient(patientPhone) {
421
- const normalizedPatientPhone = String(patientPhone || '').trim();
 
 
 
 
 
 
422
 
423
  const dueRows = await query(
424
  `
@@ -437,13 +443,16 @@ async function syncDueReminderOccurrencesForPatient(patientPhone) {
437
  FROM medication_reminder_occurrences o
438
  INNER JOIN medication_reminders r ON r.id = o.reminder_id
439
  WHERE
440
- o.patient_phone = :patientPhone
441
- AND o.status = 'scheduled'
442
  AND o.scheduled_for <= CURRENT_TIMESTAMP
443
  ORDER BY o.scheduled_for ASC
444
- LIMIT 30
445
  `,
446
- { patientPhone: normalizedPatientPhone },
 
 
 
447
  );
448
 
449
  const updated = [];
@@ -463,7 +472,7 @@ async function syncDueReminderOccurrencesForPatient(patientPhone) {
463
 
464
  await createInAppNotification({
465
  userType: 'patient',
466
- userIdentifier: normalizedPatientPhone,
467
  category: 'medicine_reminder',
468
  title: row.notification_title || `Medicine reminder: ${row.medicine_name}`,
469
  body: row.notification_body || `${row.medicine_name} ${row.intake_instruction || ''}`.trim(),
@@ -494,6 +503,20 @@ async function syncDueReminderOccurrencesForPatient(patientPhone) {
494
  return updated;
495
  }
496
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
  async function updateReminderOccurrenceStatus({ occurrenceId, patientPhone, status }) {
498
  const normalizedOccurrenceId = Number(occurrenceId);
499
  const normalizedPatientPhone = String(patientPhone || '').trim();
@@ -587,6 +610,7 @@ module.exports = {
587
  createRemindersForPrescription,
588
  listReminderOccurrencesForPatient,
589
  loadReminderSummariesByPrescriptionIds,
 
590
  syncDueReminderOccurrencesForPatient,
591
  updateReminderOccurrenceStatus,
592
  };
 
417
  }));
418
  }
419
 
420
+ async function syncDueReminderOccurrences({ patientPhone = null, limit = 30 } = {}) {
421
+ const normalizedPatientPhone = patientPhone == null
422
+ ? null
423
+ : String(patientPhone || '').trim();
424
+
425
+ if (patientPhone != null && !normalizedPatientPhone) {
426
+ throw new HttpError(400, 'patientPhone is required.', 'PATIENT_REQUIRED');
427
+ }
428
 
429
  const dueRows = await query(
430
  `
 
443
  FROM medication_reminder_occurrences o
444
  INNER JOIN medication_reminders r ON r.id = o.reminder_id
445
  WHERE
446
+ ${normalizedPatientPhone ? 'o.patient_phone = :patientPhone AND' : ''}
447
+ o.status = 'scheduled'
448
  AND o.scheduled_for <= CURRENT_TIMESTAMP
449
  ORDER BY o.scheduled_for ASC
450
+ LIMIT :limit
451
  `,
452
+ {
453
+ patientPhone: normalizedPatientPhone,
454
+ limit: Math.min(Math.max(Number(limit) || 30, 1), 200),
455
+ },
456
  );
457
 
458
  const updated = [];
 
472
 
473
  await createInAppNotification({
474
  userType: 'patient',
475
+ userIdentifier: row.patient_phone,
476
  category: 'medicine_reminder',
477
  title: row.notification_title || `Medicine reminder: ${row.medicine_name}`,
478
  body: row.notification_body || `${row.medicine_name} ${row.intake_instruction || ''}`.trim(),
 
503
  return updated;
504
  }
505
 
506
+ async function syncDueReminderOccurrencesForPatient(patientPhone) {
507
+ return syncDueReminderOccurrences({
508
+ patientPhone,
509
+ limit: 30,
510
+ });
511
+ }
512
+
513
+ async function syncDueReminderOccurrencesForAll({ limit = 80 } = {}) {
514
+ return syncDueReminderOccurrences({
515
+ patientPhone: null,
516
+ limit,
517
+ });
518
+ }
519
+
520
  async function updateReminderOccurrenceStatus({ occurrenceId, patientPhone, status }) {
521
  const normalizedOccurrenceId = Number(occurrenceId);
522
  const normalizedPatientPhone = String(patientPhone || '').trim();
 
610
  createRemindersForPrescription,
611
  listReminderOccurrencesForPatient,
612
  loadReminderSummariesByPrescriptionIds,
613
+ syncDueReminderOccurrencesForAll,
614
  syncDueReminderOccurrencesForPatient,
615
  updateReminderOccurrenceStatus,
616
  };
src/workers/reminderPushWorker.js ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const env = require('../config/env');
2
+ const { syncDueReminderOccurrencesForAll } = require('../services/reminders.service');
3
+
4
+ let intervalHandle = null;
5
+ let isRunning = false;
6
+
7
+ async function runOnce() {
8
+ if (isRunning) {
9
+ return;
10
+ }
11
+
12
+ isRunning = true;
13
+
14
+ try {
15
+ const synced = await syncDueReminderOccurrencesForAll({ limit: 120 });
16
+ if (synced.length > 0) {
17
+ console.log(`[push-worker] Synced due reminder occurrences: ${synced.length}`);
18
+ }
19
+ } catch (error) {
20
+ console.error('[push-worker] Failed to sync due reminders.');
21
+ console.error(error);
22
+ } finally {
23
+ isRunning = false;
24
+ }
25
+ }
26
+
27
+ function startReminderPushWorker() {
28
+ if (intervalHandle || !env.pushReminderWorkerEnabled) {
29
+ return;
30
+ }
31
+
32
+ const intervalMs = Math.max(env.pushReminderWorkerIntervalSeconds, 15) * 1000;
33
+
34
+ setTimeout(() => {
35
+ runOnce();
36
+ }, 2000);
37
+
38
+ intervalHandle = setInterval(() => {
39
+ runOnce();
40
+ }, intervalMs);
41
+
42
+ console.log(`[push-worker] Reminder push worker started (${Math.floor(intervalMs / 1000)}s interval).`);
43
+ }
44
+
45
+ module.exports = {
46
+ startReminderPushWorker,
47
+ };