@drax/crud-back 3.9.0 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/AbstractFastifyController.js +2 -2
- package/dist/regexs/QueryFilterRegex.js +1 -1
- package/dist/schemas/FindSchema.js +1 -1
- package/dist/schemas/GroupBySchema.js +1 -1
- package/dist/schemas/PaginateSchema.js +1 -1
- package/package.json +4 -4
- package/src/controllers/AbstractFastifyController.ts +2 -2
- package/src/regexs/QueryFilterRegex.ts +1 -1
- package/src/schemas/FindSchema.ts +1 -1
- package/src/schemas/GroupBySchema.ts +1 -1
- package/src/schemas/PaginateSchema.ts +1 -1
- package/test/controllers/PersonController.test.ts +64 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/types/regexs/QueryFilterRegex.d.ts.map +1 -1
|
@@ -42,9 +42,9 @@ class AbstractFastifyController extends CommonController {
|
|
|
42
42
|
const filterArray = stringFilters.split("|");
|
|
43
43
|
const filters = [];
|
|
44
44
|
filterArray.forEach((filter) => {
|
|
45
|
-
const [field, operator, value] = filter.split(";");
|
|
45
|
+
const [field, operator, value, orGroup] = filter.split(";");
|
|
46
46
|
if (field && operator && (operator === 'empty' || (value !== undefined && value !== ''))) {
|
|
47
|
-
filters.push({ field, operator, value });
|
|
47
|
+
filters.push({ field, operator, value, orGroup: orGroup || undefined });
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
return filters;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
const QueryFilterRegex = /^(?:[a-zA-Z0-9_.\-]+;(?:eq|like|ne|in|nin|gt|gte|lt|lte|empty);[a-zA-Z0-9_.\-:\., áéíóúÁÉÍÓÚ]*)(?:\|[a-zA-Z0-9_.\-]+;(?:eq|like|ne|in|nin|gt|gte|lt|lte|empty);[a-zA-Z0-9_.\-:\., áéíóúÁÉÍÓÚ]*)*$/;
|
|
1
|
+
const QueryFilterRegex = /^(?:[a-zA-Z0-9_.\-]+;(?:eq|like|ne|in|nin|gt|gte|lt|lte|empty);[a-zA-Z0-9_.\-:\., áéíóúÁÉÍÓÚ]*(?:;[a-zA-Z0-9_.\-]+)?)(?:\|[a-zA-Z0-9_.\-]+;(?:eq|like|ne|in|nin|gt|gte|lt|lte|empty);[a-zA-Z0-9_.\-:\., áéíóúÁÉÍÓÚ]*(?:;[a-zA-Z0-9_.\-]+)?)*$/;
|
|
2
2
|
export default QueryFilterRegex;
|
|
3
3
|
export { QueryFilterRegex };
|
|
@@ -4,6 +4,6 @@ const FindQuerySchema = z.object({
|
|
|
4
4
|
orderBy: z.string().optional(),
|
|
5
5
|
order: z.enum(["asc", "desc"]).optional(),
|
|
6
6
|
search: z.string().optional(),
|
|
7
|
-
filters: z.string().regex(QueryFilterRegex).optional().describe("Format: field;operator;value|field;operator;value|..."),
|
|
7
|
+
filters: z.string().regex(QueryFilterRegex).optional().describe("Format: field;operator;value[;orGroup]|field;operator;value[;orGroup]|..."),
|
|
8
8
|
});
|
|
9
9
|
export { FindQuerySchema };
|
|
@@ -2,6 +2,6 @@ import z from "zod";
|
|
|
2
2
|
import QueryFilterRegex from "../regexs/QueryFilterRegex.js";
|
|
3
3
|
const GroupByQuerySchema = z.object({
|
|
4
4
|
fields: z.array(z.string()).min(1).max(10),
|
|
5
|
-
filters: z.string().regex(QueryFilterRegex).optional().describe("Format: field;operator;value|field;operator;value|..."),
|
|
5
|
+
filters: z.string().regex(QueryFilterRegex).optional().describe("Format: field;operator;value[;orGroup]|field;operator;value[;orGroup]|..."),
|
|
6
6
|
});
|
|
7
7
|
export { GroupByQuerySchema };
|
|
@@ -6,7 +6,7 @@ const PaginateQuerySchema = z.object({
|
|
|
6
6
|
orderBy: z.string().optional(),
|
|
7
7
|
order: z.enum(["asc", "desc"]).optional(),
|
|
8
8
|
search: z.string().optional(),
|
|
9
|
-
filters: z.string().regex(QueryFilterRegex).optional().describe("Format: field;operator;value|field;operator;value|..."),
|
|
9
|
+
filters: z.string().regex(QueryFilterRegex).optional().describe("Format: field;operator;value[;orGroup]|field;operator;value[;orGroup]|..."),
|
|
10
10
|
});
|
|
11
11
|
const PaginateBodyResponseSchema = z.object({
|
|
12
12
|
page: z.number(),
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "3.
|
|
6
|
+
"version": "3.10.0",
|
|
7
7
|
"description": "Crud utils across modules",
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"types": "types/index.d.ts",
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
"author": "Cristian Incarnato & Drax Team",
|
|
23
23
|
"license": "ISC",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@drax/common-back": "^3.
|
|
25
|
+
"@drax/common-back": "^3.10.0",
|
|
26
26
|
"@drax/common-share": "^3.0.0",
|
|
27
27
|
"@drax/identity-share": "^3.0.0",
|
|
28
|
-
"@drax/media-back": "^3.
|
|
28
|
+
"@drax/media-back": "^3.10.0",
|
|
29
29
|
"@graphql-tools/load-files": "^7.0.0",
|
|
30
30
|
"@graphql-tools/merge": "^9.0.4",
|
|
31
31
|
"mongoose": "^8.23.0",
|
|
@@ -47,5 +47,5 @@
|
|
|
47
47
|
"typescript": "^5.9.3",
|
|
48
48
|
"vitest": "^3.2.4"
|
|
49
49
|
},
|
|
50
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "063b6aba11e891c5374c2d3de6546a3ff120b4c9"
|
|
51
51
|
}
|
|
@@ -112,10 +112,10 @@ class AbstractFastifyController<T, C, U> extends CommonController {
|
|
|
112
112
|
const filterArray = stringFilters.split("|")
|
|
113
113
|
const filters: IDraxFieldFilter[] = []
|
|
114
114
|
filterArray.forEach((filter) => {
|
|
115
|
-
const [field, operator, value] = filter.split(";")
|
|
115
|
+
const [field, operator, value, orGroup] = filter.split(";")
|
|
116
116
|
|
|
117
117
|
if (field && operator && (operator === 'empty' || (value !== undefined && value !== ''))) {
|
|
118
|
-
filters.push({field, operator, value})
|
|
118
|
+
filters.push({field, operator, value, orGroup: orGroup || undefined})
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const QueryFilterRegex = /^(?:[a-zA-Z0-9_.\-]+;(?:eq|like|ne|in|nin|gt|gte|lt|lte|empty);[a-zA-Z0-9_.\-:\., áéíóúÁÉÍÓÚ]*)(?:\|[a-zA-Z0-9_.\-]+;(?:eq|like|ne|in|nin|gt|gte|lt|lte|empty);[a-zA-Z0-9_.\-:\., áéíóúÁÉÍÓÚ]*)*$/
|
|
1
|
+
const QueryFilterRegex = /^(?:[a-zA-Z0-9_.\-]+;(?:eq|like|ne|in|nin|gt|gte|lt|lte|empty);[a-zA-Z0-9_.\-:\., áéíóúÁÉÍÓÚ]*(?:;[a-zA-Z0-9_.\-]+)?)(?:\|[a-zA-Z0-9_.\-]+;(?:eq|like|ne|in|nin|gt|gte|lt|lte|empty);[a-zA-Z0-9_.\-:\., áéíóúÁÉÍÓÚ]*(?:;[a-zA-Z0-9_.\-]+)?)*$/
|
|
2
2
|
|
|
3
3
|
export default QueryFilterRegex
|
|
4
4
|
export {QueryFilterRegex}
|
|
@@ -6,7 +6,7 @@ const FindQuerySchema = z.object({
|
|
|
6
6
|
orderBy: z.string().optional(),
|
|
7
7
|
order: z.enum(["asc", "desc"]).optional(),
|
|
8
8
|
search: z.string().optional(),
|
|
9
|
-
filters: z.string().regex(QueryFilterRegex).optional().describe("Format: field;operator;value|field;operator;value|..."),
|
|
9
|
+
filters: z.string().regex(QueryFilterRegex).optional().describe("Format: field;operator;value[;orGroup]|field;operator;value[;orGroup]|..."),
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
|
|
@@ -3,7 +3,7 @@ import QueryFilterRegex from "../regexs/QueryFilterRegex.js";
|
|
|
3
3
|
|
|
4
4
|
const GroupByQuerySchema = z.object({
|
|
5
5
|
fields: z.array(z.string()).min(1).max(10),
|
|
6
|
-
filters: z.string().regex(QueryFilterRegex).optional().describe("Format: field;operator;value|field;operator;value|..."),
|
|
6
|
+
filters: z.string().regex(QueryFilterRegex).optional().describe("Format: field;operator;value[;orGroup]|field;operator;value[;orGroup]|..."),
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
|
|
@@ -7,7 +7,7 @@ const PaginateQuerySchema = z.object({
|
|
|
7
7
|
orderBy: z.string().optional(),
|
|
8
8
|
order: z.enum(["asc", "desc"]).optional(),
|
|
9
9
|
search: z.string().optional(),
|
|
10
|
-
filters: z.string().regex(QueryFilterRegex).optional().describe("Format: field;operator;value|field;operator;value|..."),
|
|
10
|
+
filters: z.string().regex(QueryFilterRegex).optional().describe("Format: field;operator;value[;orGroup]|field;operator;value[;orGroup]|..."),
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
|
|
@@ -466,6 +466,70 @@ describe("Person Controller Test", function () {
|
|
|
466
466
|
expect(findByResult[0].fullname).toBe("Active Person")
|
|
467
467
|
})
|
|
468
468
|
|
|
469
|
+
it("should create and find people with filters grouped by orGroup", async () => {
|
|
470
|
+
const { accessToken } = await testSetup.rootUserLogin()
|
|
471
|
+
await testSetup.dropCollection('Person')
|
|
472
|
+
|
|
473
|
+
const entityData = [
|
|
474
|
+
{ fullname: "Hero Person", race: "human", live: true, address: defaultAddress },
|
|
475
|
+
{ fullname: "Mage Person", race: "elf", live: true, address: defaultAddress },
|
|
476
|
+
{ fullname: "Hidden Hero", race: "orc", live: false, address: defaultAddress },
|
|
477
|
+
{ fullname: "Hidden Rogue", race: "human", live: false, address: defaultAddress }
|
|
478
|
+
]
|
|
479
|
+
|
|
480
|
+
for (const data of entityData) {
|
|
481
|
+
await testSetup.fastifyInstance.inject({
|
|
482
|
+
method: 'POST',
|
|
483
|
+
url: '/api/person',
|
|
484
|
+
payload: data,
|
|
485
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
486
|
+
})
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const findByResp = await testSetup.fastifyInstance.inject({
|
|
490
|
+
method: 'GET',
|
|
491
|
+
url: '/api/person/find?filters=fullname;like;Hero;group1|race;eq;elf;group1|live;eq;true',
|
|
492
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
const findByResult = await findByResp.json()
|
|
496
|
+
expect(findByResp.statusCode).toBe(200)
|
|
497
|
+
expect(findByResult.length).toBe(2)
|
|
498
|
+
expect(findByResult.map((item: any) => item.fullname).sort()).toEqual(["Hero Person", "Mage Person"])
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
it("should combine search with orGroup filters without overriding either condition", async () => {
|
|
502
|
+
const { accessToken } = await testSetup.rootUserLogin()
|
|
503
|
+
await testSetup.dropCollection('Person')
|
|
504
|
+
|
|
505
|
+
const entityData = [
|
|
506
|
+
{ fullname: "Searchable Hero", race: "human", live: true, address: defaultAddress },
|
|
507
|
+
{ fullname: "Searchable Elf", race: "elf", live: true, address: defaultAddress },
|
|
508
|
+
{ fullname: "Hidden Elf", race: "elf", live: true, address: defaultAddress },
|
|
509
|
+
{ fullname: "Searchable Rogue", race: "human", live: false, address: defaultAddress }
|
|
510
|
+
]
|
|
511
|
+
|
|
512
|
+
for (const data of entityData) {
|
|
513
|
+
await testSetup.fastifyInstance.inject({
|
|
514
|
+
method: 'POST',
|
|
515
|
+
url: '/api/person',
|
|
516
|
+
payload: data,
|
|
517
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
518
|
+
})
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const paginateResp = await testSetup.fastifyInstance.inject({
|
|
522
|
+
method: 'GET',
|
|
523
|
+
url: '/api/person?search=Searchable&filters=fullname;like;Hero;group1|race;eq;elf;group1|live;eq;true',
|
|
524
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
const paginateResult = await paginateResp.json()
|
|
528
|
+
expect(paginateResp.statusCode).toBe(200)
|
|
529
|
+
expect(paginateResult.total).toBe(2)
|
|
530
|
+
expect(paginateResult.items.map((item: any) => item.fullname).sort()).toEqual(["Searchable Elf", "Searchable Hero"])
|
|
531
|
+
})
|
|
532
|
+
|
|
469
533
|
// 8. Create and Group By
|
|
470
534
|
it("should create and groupBy for people", async () => {
|
|
471
535
|
const { accessToken } = await testSetup.rootUserLogin()
|