jxp 2.14.6 → 2.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/libs/jxp.js CHANGED
@@ -72,7 +72,7 @@ const outputCSV = (req, res, next) => {
72
72
  try {
73
73
  const data = res.result.data.map(row => row._doc);
74
74
  if (!data.length) {
75
- throw("")
75
+ throw ("")
76
76
  }
77
77
  res.writeHead(200, {
78
78
  'Content-Type': 'text/csv',
@@ -91,7 +91,7 @@ const outputCSV = (req, res, next) => {
91
91
  const actionGet = async (req, res) => {
92
92
  const opname = `get ${req.modelname} ${ops++}`;
93
93
  console.time(opname);
94
- const parseSearch = function(search) {
94
+ const parseSearch = function (search) {
95
95
  let result = {};
96
96
  for (let i in search) {
97
97
  result[i] = new RegExp(search[i], "i");
@@ -103,6 +103,10 @@ const actionGet = async (req, res) => {
103
103
  filters = parseFilter(req.query.filter);
104
104
  } catch (err) {
105
105
  console.trace(new Date(), err);
106
+ // Preserve BadRequestError, convert others to InternalServerError
107
+ if (err instanceof errors.BadRequestError) {
108
+ throw err;
109
+ }
106
110
  throw new errors.InternalServerError(err.toString());
107
111
  }
108
112
  let search = parseSearch(req.query.search);
@@ -120,9 +124,9 @@ const actionGet = async (req, res) => {
120
124
  }
121
125
  if (req.query.search) {
122
126
  // console.log({ search: req.query.search });
123
- q = req.Model.find({ $text: { $search: req.query.search }}, { score : { $meta: "textScore" } }).sort( { score: { $meta : "textScore" } } );
124
- countquery = Object.assign({ $text: { $search: req.query.search }}, countquery);
125
- qcount = req.Model.find({ $text: { $search: req.query.search }});
127
+ q = req.Model.find({ $text: { $search: req.query.search } }, { score: { $meta: "textScore" } }).sort({ score: { $meta: "textScore" } });
128
+ countquery = Object.assign({ $text: { $search: req.query.search } }, countquery);
129
+ qcount = req.Model.find({ $text: { $search: req.query.search } });
126
130
  }
127
131
  if (res.user) {
128
132
  q.options = ({ user: res.user });
@@ -164,7 +168,7 @@ const actionGet = async (req, res) => {
164
168
  } else {
165
169
  q.populate(req.query.populate);
166
170
  }
167
- result.populate = req.query.populate;
171
+ result.populate = req.query.populate;
168
172
  }
169
173
  if (req.query.autopopulate) {
170
174
  for (let key in req.Model.schema.paths) {
@@ -189,9 +193,13 @@ const actionGet = async (req, res) => {
189
193
  result.data = await q.exec();
190
194
  res.result = result;
191
195
  if (debug) console.timeEnd(opname);
192
- } catch(err) {
196
+ } catch (err) {
193
197
  console.error(new Date(), err);
194
198
  if (debug) console.timeEnd(opname);
199
+ // Preserve BadRequestError, convert others to InternalServerError
200
+ if (err instanceof errors.BadRequestError) {
201
+ throw err;
202
+ }
195
203
  if (err.code) throw err;
196
204
  throw new errors.InternalServerError(err.toString());
197
205
  }
@@ -204,7 +212,7 @@ const actionGetOne = async (req, res) => {
204
212
  const data = await getOne(req.Model, req.params.item_id, req.query, { user: res.user });
205
213
  res.result = { data };
206
214
  if (debug) console.timeEnd(opname);
207
- } catch(err) {
215
+ } catch (err) {
208
216
  console.error(new Date(), err);
209
217
  if (debug) console.timeEnd(opname);
210
218
  if (err.code) throw err;
@@ -262,7 +270,7 @@ const actionPut = async (req, res) => {
262
270
  let silence = req.params._silence;
263
271
  if (req.body && req.body._silence) silence = true;
264
272
  if (!silence) {
265
- req.config.callbacks.put.call(null, req.modelname, item, res.user );
273
+ req.config.callbacks.put.call(null, req.modelname, item, res.user);
266
274
  ws.putHook.call(null, req.modelname, item, res.user);
267
275
  }
268
276
  res.json({
@@ -283,12 +291,12 @@ const actionUpdate = async (req, res) => {
283
291
  const opname = `update ${req.modelname}/${req.params.item_id} ${ops++}`;
284
292
  console.time(opname);
285
293
  try {
286
- let body_data = datamunging.deserialize(req.body);
294
+ let body_data = datamunging.deserialize(req.body);
287
295
  const data = await req.Model.update({ _id: req.params.item_id }, body_data);
288
296
  let silence = req.params._silence;
289
297
  if (req.body && req.body._silence) silence = true;
290
298
  if (!silence) {
291
- req.config.callbacks.put.call(null, req.modelname, data, res.user );
299
+ req.config.callbacks.put.call(null, req.modelname, data, res.user);
292
300
  ws.putHook.call(null, req.modelname, data, res.user);
293
301
  }
294
302
  res.json({
@@ -370,10 +378,10 @@ const actionDelete = async (req, res) => {
370
378
  }
371
379
  res.json({
372
380
  status: "ok",
373
- message: `${req.modelname}/${ req.params.item_id } deleted`
381
+ message: `${req.modelname}/${req.params.item_id} deleted`
374
382
  });
375
383
  if (debug) console.timeEnd(opname);
376
- } catch(err) {
384
+ } catch (err) {
377
385
  console.error(new Date(), err);
378
386
  if (debug) console.timeEnd(opname);
379
387
  if (err.code) throw err;
@@ -384,7 +392,7 @@ const actionDelete = async (req, res) => {
384
392
  const actionCount = async (req, res) => {
385
393
  const opname = `count ${req.modelname} ${ops++}`;
386
394
  console.time(opname);
387
- const parseSearch = function(search) {
395
+ const parseSearch = function (search) {
388
396
  let result = {};
389
397
  for (let i in search) {
390
398
  result[i] = new RegExp(search[i], "i");
@@ -409,7 +417,7 @@ const actionCount = async (req, res) => {
409
417
  const count = await req.Model.countDocuments(filters).exec();
410
418
  res.result = { count };
411
419
  if (debug) console.timeEnd(opname);
412
- } catch(err) {
420
+ } catch (err) {
413
421
  console.error(new Date(), err);
414
422
  if (debug) console.timeEnd(opname);
415
423
  if (err.code) throw err;
@@ -424,7 +432,7 @@ const actionCall = async (req, res) => {
424
432
  try {
425
433
  const result = await req.Model[req.params.method_name](req.body);
426
434
  res.json(result);
427
- } catch(err) {
435
+ } catch (err) {
428
436
  console.error(new Date(), err);
429
437
  if (err.code) throw err;
430
438
  throw new errors.InternalServerError(err.toString());
@@ -440,7 +448,7 @@ const actionCallItem = async (req, res) => {
440
448
  req.params.__user = res.user || null;
441
449
  const result = await req.Model[req.params.method_name](item);
442
450
  res.json(result);
443
- } catch(err) {
451
+ } catch (err) {
444
452
  console.trace(err);
445
453
  if (err.code) throw err;
446
454
  throw new errors.InternalServerError(err.toString());
@@ -493,7 +501,7 @@ const actionQuery = async (req, res) => {
493
501
  } else {
494
502
  q.populate(req.query.populate);
495
503
  }
496
- result.populate = req.query.populate;
504
+ result.populate = req.query.populate;
497
505
  }
498
506
  if (req.query.autopopulate) {
499
507
  for (let key in req.Model.schema.paths) {
@@ -516,7 +524,7 @@ const actionQuery = async (req, res) => {
516
524
  res.result = result;
517
525
  if (debug) console.timeEnd(opname);
518
526
  res.json(result);
519
- } catch(err) {
527
+ } catch (err) {
520
528
  console.error(new Date(), err);
521
529
  if (debug) console.timeEnd(opname);
522
530
  if (err.code) throw err;
@@ -611,7 +619,7 @@ const actionBulkWrite = async (req, res) => {
611
619
  const getOne = async (Model, item_id, params, options) => {
612
620
  const query = Model.findById(item_id, {}, options);
613
621
  if (params.populate) {
614
- if ((typeof params.populate === "object") && !Array.isArray(params.populate)) {
622
+ if ((typeof params.populate === "object") && !Array.isArray(params.populate)) {
615
623
  for (let i in params.populate) {
616
624
  query.populate(i, params.populate[i].replace(/,/g, " "));
617
625
  }
@@ -641,56 +649,143 @@ const getOne = async (Model, item_id, params, options) => {
641
649
  //Don't ever return passwords
642
650
  delete item.password;
643
651
  return item;
644
- } catch(err) {
652
+ } catch (err) {
645
653
  console.error(err);
646
654
  if (err.code) throw err;
647
655
  throw new errors.InternalServerError(err.toString());
648
656
  }
649
657
  };
650
658
 
651
- const parseFilter = (filter) => {
652
- if (!filter)
653
- return {};
654
- if (typeof filter == "object") {
655
- Object.keys(filter).forEach(function(key) {
656
- var val = filter[key];
657
- if (filter[key] === "false") filter[key] = false;
658
- if (filter[key] === "true") filter[key] = true;
659
- if (val.indexOf) {
660
- if (val.indexOf(":") !== -1) {
661
- let tmp = val.split(":");
662
- filter[key] = {};
663
- let tmpkey = tmp.shift();
664
- let tmpval = tmp.join(":");
665
- if ((tmpval[0] === "[") && (tmpval[tmpval.length - 1] === "]")) { // Could be an array for a $in or similar
666
- let arr = tmpval.slice(1, tmpval.length - 1).split(",");
667
- tmpval = arr;
668
- }
669
- filter[key][tmpkey] = tmpval;
670
- if (tmpkey === "$regex" && tmpval[0] === "/") {
671
- let match = tmpval.match(new RegExp('^/(.*?)/([gimy]*)$'));
672
- let regex = new RegExp(match[1], match[2]);
673
- filter[key][tmpkey] = regex;
659
+ // Helper function to check if a string is an ISO date string
660
+ function isISODateString(str) {
661
+ if (typeof str !== 'string') return false;
662
+ const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
663
+ if (!isoDateRegex.test(str)) return false;
664
+ const date = new Date(str);
665
+ if (isNaN(date.getTime())) {
666
+ throw new errors.BadRequestError("Invalid date format");
667
+ }
668
+ return true;
669
+ }
670
+
671
+ const parseFilter = (filter, depth = 0) => {
672
+ const MAX_DEPTH = 10;
673
+
674
+ if (!filter) return {};
675
+ if (depth > MAX_DEPTH) {
676
+ console.warn('Maximum filter depth exceeded');
677
+ return filter;
678
+ }
679
+
680
+ if (typeof filter !== "object" || filter === null) return filter;
681
+
682
+ // Handle arrays by merging their operators
683
+ if (Array.isArray(filter)) {
684
+ const result = {};
685
+ filter.forEach(item => {
686
+ if (typeof item === "string" && item.includes(":")) {
687
+ const parts = item.split(":");
688
+ const key = parts[0];
689
+ const value = parts.slice(1).join(":");
690
+ if (key.startsWith("$")) {
691
+ try {
692
+ if (isISODateString(value)) {
693
+ result[key] = new Date(value);
694
+ } else {
695
+ result[key] = value;
696
+ }
697
+ } catch (err) {
698
+ if (err instanceof errors.BadRequestError) {
699
+ throw err;
700
+ }
701
+ throw new errors.BadRequestError("Invalid date format");
674
702
  }
675
703
  }
676
- if (typeof val == "object") {
677
- let result = parseFilter(val);
678
- filter[key] = {};
679
- for (let x = 0; x < result.length; x++) {
680
- filter[key][Object.keys(result[x])[0]] =
681
- result[x][Object.keys(result[x])[0]];
704
+ }
705
+ });
706
+ return result;
707
+ }
708
+
709
+ // Create a new object to avoid modifying the input
710
+ const parsedFilter = {};
711
+
712
+ for (let i in filter) {
713
+ if (filter[i] === "false") {
714
+ parsedFilter[i] = false;
715
+ continue;
716
+ }
717
+ if (filter[i] === "true") {
718
+ parsedFilter[i] = true;
719
+ continue;
720
+ }
721
+ if (typeof filter[i] === "string") {
722
+ try {
723
+ if (isISODateString(filter[i])) {
724
+ parsedFilter[i] = new Date(filter[i]);
725
+ continue;
726
+ }
727
+ } catch (err) {
728
+ if (err instanceof errors.BadRequestError) {
729
+ throw err;
730
+ }
731
+ throw new errors.BadRequestError("Invalid date format");
732
+ }
733
+ if (filter[i].includes(":")) {
734
+ const parts = filter[i].split(":");
735
+ const key = parts[0];
736
+ const value = parts.slice(1).join(":");
737
+ if (key.startsWith("$")) {
738
+ try {
739
+ if (isISODateString(value)) {
740
+ if (!parsedFilter[i]) parsedFilter[i] = {};
741
+ parsedFilter[i][key] = new Date(value);
742
+ } else if (value.startsWith("[") && value.endsWith("]")) {
743
+ if (!parsedFilter[i]) parsedFilter[i] = {};
744
+ parsedFilter[i][key] = value.slice(1, -1).split(",");
745
+ } else if (key === "$regex" && value.startsWith("/")) {
746
+ if (!parsedFilter[i]) parsedFilter[i] = {};
747
+ const match = value.match(/^\/(.+?)\/([gimy]*)$/);
748
+ if (match) {
749
+ parsedFilter[i][key] = new RegExp(match[1], match[2]);
750
+ }
751
+ } else {
752
+ if (!parsedFilter[i]) parsedFilter[i] = {};
753
+ parsedFilter[i][key] = value;
754
+ }
755
+ } catch (err) {
756
+ if (err instanceof errors.BadRequestError) {
757
+ throw err;
758
+ }
759
+ throw new errors.BadRequestError("Invalid date format");
682
760
  }
761
+ } else {
762
+ parsedFilter[i] = filter[i];
683
763
  }
764
+ } else {
765
+ parsedFilter[i] = filter[i];
684
766
  }
685
- });
767
+ } else if (Array.isArray(filter[i])) {
768
+ parsedFilter[i] = parseFilter(filter[i], depth + 1);
769
+ } else if (typeof filter[i] === "object") {
770
+ parsedFilter[i] = parseFilter(filter[i], depth + 1);
771
+ } else {
772
+ parsedFilter[i] = filter[i];
773
+ }
686
774
  }
687
- return filter;
688
- }
775
+
776
+ return parsedFilter;
777
+ };
689
778
 
690
779
  const _deSerialize = (data) => {
691
780
  function assign(obj, keyPath, value) {
692
- // http://stackoverflow.com/questions/5484673/javascript-how-to-dynamically-create-nested-objects-using-object-names-given-by
781
+ const MAX_DEPTH = 20; // Prevent excessive nesting
693
782
  const lastKeyIndex = keyPath.length - 1;
783
+
784
+ if (lastKeyIndex >= MAX_DEPTH) {
785
+ console.warn('Maximum nesting depth exceeded in _deSerialize');
786
+ return;
787
+ }
788
+
694
789
  for (let i = 0; i < lastKeyIndex; ++i) {
695
790
  let key = keyPath[i];
696
791
  if (!(key in obj)) obj[key] = {};
@@ -698,10 +793,13 @@ const _deSerialize = (data) => {
698
793
  }
699
794
  obj[keyPath[lastKeyIndex]] = value;
700
795
  }
796
+
797
+ if (!data || typeof data !== 'object') return;
798
+
701
799
  for (let datum in data) {
702
800
  const matches = datum.match(/\[(.+?)\]/g);
703
801
  if (matches) {
704
- const params = matches.map(function(match) {
802
+ const params = matches.map(function (match) {
705
803
  return match.replace(/[[\]]/g, "");
706
804
  });
707
805
  if (isNaN(params[0])) {
@@ -765,7 +863,7 @@ const changeUrlParams = (req, key, val) => {
765
863
  return req.config.url + req.path() + "?" + querystring.stringify(q);
766
864
  };
767
865
 
768
- const JXP = function(options) {
866
+ const JXP = function (options) {
769
867
  const server = restify.createServer();
770
868
  const model_dir = options.model_dir || modeldir.findModelDir(path.dirname(process.argv[1]));
771
869
  //Set up config with default
@@ -773,12 +871,12 @@ const JXP = function(options) {
773
871
  model_dir: path.join(model_dir),
774
872
  mongo: options.mongo,
775
873
  callbacks: {
776
- put: function() {},
777
- post: function() {},
778
- delete: function() {},
779
- get: function() {},
780
- getOne: function() {},
781
- update: function() {},
874
+ put: function () { },
875
+ post: function () { },
876
+ delete: function () { },
877
+ get: function () { },
878
+ getOne: function () { },
879
+ update: function () { },
782
880
  },
783
881
  log: "access.log",
784
882
  pre_hooks: {
@@ -841,13 +939,13 @@ const JXP = function(options) {
841
939
  global.apikey = config.apikey;
842
940
  global.server = config.server;
843
941
  global.model_dir = model_dir;
844
-
942
+
845
943
  // Pre-load models
846
944
  var files = fs.readdirSync(config.model_dir);
847
- let modelnames = files.filter(function(fname) {
945
+ let modelnames = files.filter(function (fname) {
848
946
  return fname.indexOf("_model.js") !== -1;
849
947
  });
850
- modelnames.forEach(function(fname) {
948
+ modelnames.forEach(function (fname) {
851
949
  var modelname = fname.replace("_model.js", "");
852
950
  models[modelname] = require(path.join(config.model_dir, fname));
853
951
  });
@@ -856,9 +954,9 @@ const JXP = function(options) {
856
954
  security.init(config);
857
955
  login.init(config);
858
956
  groups.init(config);
859
- ws.init({models});
957
+ ws.init({ models });
860
958
  cache.init(config);
861
- const docs = new Docs({config, models});
959
+ const docs = new Docs({ config, models });
862
960
 
863
961
  // Set up our API server
864
962
 
@@ -877,7 +975,7 @@ const JXP = function(options) {
877
975
  const cors = corsMiddleware({
878
976
  preflightMaxAge: 5, //Optional
879
977
  origins: ['*'],
880
- allowHeaders: ['X-Requested-With','Authorization'],
978
+ allowHeaders: ['X-Requested-With', 'Authorization'],
881
979
  exposeHeaders: ['Authorization']
882
980
  });
883
981
 
@@ -1,40 +1,87 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
+ const mongoose = require("mongoose");
3
4
 
4
5
  const getModelFileContents = (model_dir) => {
5
6
  return new Promise((resolve, reject) => {
6
7
  try {
7
- fs.readdir(model_dir, function(err, files) {
8
+ fs.readdir(model_dir, function (err, files) {
8
9
  if (err) {
9
10
  return reject(err);
10
11
  }
12
+
13
+ // Filter for only model files first
14
+ const modelFiles = files.filter(file =>
15
+ file.endsWith('_model.js') &&
16
+ fs.statSync(path.join(model_dir, file)).isFile()
17
+ );
18
+
19
+ // Limit number of files processed at once
20
+ const MAX_FILES = 1000;
21
+ if (modelFiles.length > MAX_FILES) {
22
+ return reject(new Error(`Too many model files (${modelFiles.length}). Maximum allowed is ${MAX_FILES}`));
23
+ }
24
+
11
25
  let models = [];
12
- for (const file of files) {
26
+ let errors = [];
27
+
28
+ for (const file of modelFiles) {
13
29
  const modelname = path.basename(file, ".js").replace("_model", "");
14
30
  try {
15
- const modelobj = require(path.join(model_dir, file));
16
- if (
17
- modelobj.schema &&
18
- modelobj.schema.get("_perms") &&
19
- (modelobj.schema.get("_perms").admin ||
20
- modelobj.schema.get("_perms").user ||
21
- modelobj.schema.get("_perms").owner ||
22
- modelobj.schema.get("_perms").all)
23
- ) {
24
- let model = {
25
- model: modelname,
26
- file: file,
27
- perms: modelobj.schema.get("_perms")
28
- };
29
- models.push(model);
31
+ // Try to get the model from mongoose first
32
+ let modelobj;
33
+ try {
34
+ // Convert modelname to proper case for mongoose (first letter uppercase)
35
+ const modelKey = modelname.charAt(0).toUpperCase() + modelname.slice(1);
36
+ modelobj = mongoose.models[modelKey];
37
+
38
+ // If model doesn't exist in mongoose, try to load it from file
39
+ if (!modelobj) {
40
+ const filePath = path.join(model_dir, file);
41
+ delete require.cache[require.resolve(filePath)];
42
+ modelobj = require(filePath);
43
+ }
44
+ } catch (e) {
45
+ // If getting from mongoose fails, try loading from file
46
+ const filePath = path.join(model_dir, file);
47
+ delete require.cache[require.resolve(filePath)];
48
+ modelobj = require(filePath);
49
+ }
50
+
51
+ // Check if we have a valid model with schema and permissions
52
+ if (modelobj && modelobj.schema) {
53
+ const perms = modelobj.schema.get("_perms");
54
+ if (perms && (perms.admin || perms.user || perms.owner || perms.all)) {
55
+ models.push({
56
+ model: modelname,
57
+ file: file,
58
+ perms: perms
59
+ });
60
+ continue; // Skip error handling if successful
61
+ }
30
62
  }
63
+
64
+ // If we get here, the model was loaded but didn't have proper schema/perms
65
+ errors.push(`Invalid model structure for ${modelname}`);
66
+
31
67
  } catch (error) {
32
- console.error("Error with model " + modelname, error);
68
+ // Only add to errors if it's not an OverwriteModelError
69
+ if (!error.message.includes('Cannot overwrite')) {
70
+ errors.push(`Error with model ${modelname}: ${error.message}`);
71
+ console.error(`Error processing model ${modelname}:`, error);
72
+ }
33
73
  }
34
74
  }
75
+
76
+ // If we have errors but also some valid models, just log the errors
77
+ if (errors.length > 0 && models.length > 0) {
78
+ console.warn('Some models failed to load:', errors);
79
+ }
80
+
81
+ // Return empty array if no models found, but don't treat it as an error
35
82
  return resolve(models);
36
83
  });
37
- } catch(err) {
84
+ } catch (err) {
38
85
  return reject(err);
39
86
  }
40
87
  });
@@ -11,30 +11,31 @@ const TestSchema = new JXPSchema({
11
11
  fulltext: { type: String, index: { text: true } },
12
12
  link_id: { type: ObjectId, link: "Link", }, // We can populate these links during a query
13
13
  other_link_id: { type: ObjectId, link: "Link", map_to: "other_link" },
14
- array_link_id: [{ type: ObjectId, link: "Link", map_to: "array_link", justOne: false } ],
15
- composite_array: [
14
+ array_link_id: [{ type: ObjectId, link: "Link", map_to: "array_link", justOne: false }],
15
+ composite_array: [
16
16
  { afoo: String, abar: Number }
17
- ]
17
+ ],
18
+ date_field: { type: Date, index: true } // Added date field for testing date filtering
18
19
  },
19
- {
20
- perms: {
21
- admin: "crud", // CRUD = Create, Retrieve, Update and Delete
22
- owner: "crud",
23
- user: "cr",
24
- all: "r" // Unauthenticated users will be able to read from test, but that is all
20
+ {
21
+ perms: {
22
+ admin: "crud", // CRUD = Create, Retrieve, Update and Delete
23
+ owner: "crud",
24
+ user: "cr",
25
+ all: "r" // Unauthenticated users will be able to read from test, but that is all
26
+ }
25
27
  }
26
- }
27
28
  );
28
29
 
29
30
  // Full text index
30
31
  // TestSchema.index( { "$**": "text" } );
31
32
 
32
33
  // We can define useful functions that we can call through the API using our /call endpoint
33
- TestSchema.statics.test = function() {
34
+ TestSchema.statics.test = function () {
34
35
  return "Testing OKAY!";
35
36
  };
36
37
 
37
- TestSchema.pre("save", function(next) {
38
+ TestSchema.pre("save", function (next) {
38
39
  // If we have the setting "error" set to true, we will throw an error
39
40
  if (this.bar == "Throw an error") {
40
41
  throw new errors.ImATeapotError("I'm a teapot");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jxp",
3
3
  "description:": "An opinionated RESTful API library based on Mongoose and Restify. Make an API by just writing Mongoose models.",
4
- "version": "2.14.6",
4
+ "version": "2.15.1",
5
5
  "private": false,
6
6
  "main": "libs/jxp.js",
7
7
  "scripts": {
@@ -29,12 +29,12 @@
29
29
  "url": "https://github.com/WorkSpaceMan/jxp/issues"
30
30
  },
31
31
  "dependencies": {
32
- "axios": "^1.6.5",
33
- "bcryptjs": "^2.4.3",
34
- "commander": "^11.1.0",
35
- "config": "^3.3.10",
36
- "dotenv": "^16.4.0",
37
- "glob": "10.3.10",
32
+ "axios": "^1.8.4",
33
+ "bcryptjs": "^3.0.2",
34
+ "commander": "^13.1.0",
35
+ "config": "^3.3.12",
36
+ "dotenv": "^16.4.7",
37
+ "glob": "11.0.1",
38
38
  "js-yaml": "4.1.0",
39
39
  "json2csv": "^5.0.7",
40
40
  "jsonwebtoken": "^9.0.2",
@@ -46,25 +46,25 @@
46
46
  "mongoose-friendly": "^0.1.4",
47
47
  "morgan": "^1.10.0",
48
48
  "node-cache": "^5.1.2",
49
- "nodemailer": "^6.9.8",
49
+ "nodemailer": "^6.10.0",
50
50
  "nodemailer-smtp-transport": "^2.7.4",
51
51
  "path": "^0.12.7",
52
- "pug": "^3.0.2",
52
+ "pug": "^3.0.3",
53
53
  "querystring": "^0.2.1",
54
54
  "rand-token": "^1.0.1",
55
55
  "readline-sync": "^1.4.10",
56
56
  "restify": "^11.1.0",
57
57
  "restify-cors-middleware2": "^2.2.1",
58
58
  "restify-errors": "^8.0.2",
59
- "traverse": "^0.6.8",
60
- "underscore": "^1.13.6",
61
- "ws": "^8.16.0"
59
+ "traverse": "^0.6.11",
60
+ "underscore": "^1.13.7",
61
+ "ws": "^8.18.1"
62
62
  },
63
63
  "devDependencies": {
64
- "chai": "^4.3.7",
64
+ "chai": "^4.3.10",
65
65
  "chai-http": "^4.4.0",
66
66
  "mocha": "^10.2.0",
67
67
  "moment": "^2.29.4",
68
68
  "should": "^13.2.3"
69
69
  }
70
- }
70
+ }