jxp 2.15.0 → 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
@@ -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);
@@ -192,6 +196,10 @@ const actionGet = async (req, res) => {
192
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
  }
@@ -648,8 +656,20 @@ const getOne = async (Model, item_id, params, options) => {
648
656
  }
649
657
  };
650
658
 
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
+
651
671
  const parseFilter = (filter, depth = 0) => {
652
- const MAX_DEPTH = 10; // Prevent infinite recursion
672
+ const MAX_DEPTH = 10;
653
673
 
654
674
  if (!filter) return {};
655
675
  if (depth > MAX_DEPTH) {
@@ -659,42 +679,102 @@ const parseFilter = (filter, depth = 0) => {
659
679
 
660
680
  if (typeof filter !== "object" || filter === null) return filter;
661
681
 
662
- Object.keys(filter).forEach(function (key) {
663
- var val = filter[key];
664
- if (filter[key] === "false") filter[key] = false;
665
- if (filter[key] === "true") filter[key] = true;
666
- if (val && val.indexOf) {
667
- if (val.indexOf(":") !== -1) {
668
- let tmp = val.split(":");
669
- filter[key] = {};
670
- let tmpkey = tmp.shift();
671
- let tmpval = tmp.join(":");
672
- if ((tmpval[0] === "[") && (tmpval[tmpval.length - 1] === "]")) {
673
- let arr = tmpval.slice(1, tmpval.length - 1).split(",");
674
- tmpval = arr;
675
- }
676
- filter[key][tmpkey] = tmpval;
677
- if (tmpkey === "$regex" && tmpval[0] === "/") {
678
- let match = tmpval.match(new RegExp('^/(.*?)/([gimy]*)$'));
679
- if (match) {
680
- let regex = new RegExp(match[1], match[2]);
681
- filter[key][tmpkey] = regex;
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");
682
702
  }
683
703
  }
684
704
  }
685
- if (typeof val == "object") {
686
- let result = parseFilter(val, depth + 1);
687
- if (result && typeof result === 'object') {
688
- filter[key] = {};
689
- Object.keys(result).forEach(resultKey => {
690
- filter[key][resultKey] = result[resultKey];
691
- });
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;
692
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");
760
+ }
761
+ } else {
762
+ parsedFilter[i] = filter[i];
763
+ }
764
+ } else {
765
+ parsedFilter[i] = filter[i];
693
766
  }
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];
694
773
  }
695
- });
696
- return filter;
697
- }
774
+ }
775
+
776
+ return parsedFilter;
777
+ };
698
778
 
699
779
  const _deSerialize = (data) => {
700
780
  function assign(obj, keyPath, value) {
@@ -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.15.0",
4
+ "version": "2.15.1",
5
5
  "private": false,
6
6
  "main": "libs/jxp.js",
7
7
  "scripts": {
@@ -67,4 +67,4 @@
67
67
  "moment": "^2.29.4",
68
68
  "should": "^13.2.3"
69
69
  }
70
- }
70
+ }