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 +111 -31
- package/models/test_model.js +13 -12
- package/package.json +2 -2
- package/test/test.js +531 -440
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;
|
|
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
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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");
|
|
682
702
|
}
|
|
683
703
|
}
|
|
684
704
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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
|
-
|
|
697
|
-
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
return parsedFilter;
|
|
777
|
+
};
|
|
698
778
|
|
|
699
779
|
const _deSerialize = (data) => {
|
|
700
780
|
function assign(obj, keyPath, value) {
|
package/models/test_model.js
CHANGED
|
@@ -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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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.
|
|
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
|
+
}
|