Skip to content

Commit f6e2ce6

Browse files
committed
fix(mongoose): parse and format search, filter and or
- Implements most filter conditions with transform functions to mongoose - Follows order of important when or and filter conditions exist together - TODO: nested queries
1 parent a99b394 commit f6e2ce6

File tree

4 files changed

+114
-26
lines changed

4 files changed

+114
-26
lines changed

packages/crud-mongoose/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "nest-crud-mongoose",
2+
"name": "@nestjsx/crud-mongoose",
33
"description": "NestJs CRUD for RESTful APIs - Mongoose",
44
"version": "0.0.5",
55
"license": "MIT",

packages/crud-mongoose/src/mongoose-crud.service.ts

+76-10
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ import {
1818
isArrayFull,
1919
isNil,
2020
isObject,
21+
isObjectFull,
2122
isUndefined,
2223
objKeys,
2324
} from '@nestjsx/util';
2425
/**
2526
* mongoose imports
2627
*/
2728
import { Document, DocumentQuery, Model, Schema, Types } from 'mongoose';
29+
import { MONGOOSE_OPERATOR_MAP } from 'nest-crud-mongoose/mongoose-operator-map';
2830
import { DeepPartial, ObjectLiteral } from 'typeorm';
2931

3032
/**
@@ -421,23 +423,87 @@ export class MongooseCrudService<T extends Document> extends CrudService<T> {
421423
options: CrudRequestOptions,
422424
parsed: ParsedRequestParams,
423425
): any {
424-
const filter = this.queryFilterToSearch(options.query.filter);
425426
const paramsFilter = this.queryFilterToSearch(parsed.paramsFilter);
426427

427-
return { ...filter, ...paramsFilter };
428+
const hasSearch = isObjectFull(parsed.search);
429+
430+
if (hasSearch) {
431+
const search = this.queryFilterToSearch(parsed.search);
432+
return {
433+
...search,
434+
...paramsFilter,
435+
};
436+
}
437+
438+
const filters = parsed.filter.filter(
439+
(condition) => !!condition && isObjectFull(condition),
440+
);
441+
const ors = parsed.or.filter((condition) => !!condition && isObjectFull(condition));
442+
const filter = this.buildIndividualFilter(filters);
443+
const or = this.buildIndividualFilter(ors);
444+
445+
if (filters.length === 0 && ors.length === 0) {
446+
return {
447+
...paramsFilter,
448+
};
449+
} else if (filters.length === 1 && ors.length === 0) {
450+
return {
451+
$and: filters,
452+
...paramsFilter,
453+
};
454+
} else if (ors.length === 1 && filters.length === 0) {
455+
return {
456+
$or: ors,
457+
...paramsFilter,
458+
};
459+
} else if (filters.length === 1 && ors.length === 1) {
460+
return {
461+
$or: [...filter, ...or],
462+
...paramsFilter,
463+
};
464+
} else {
465+
return {
466+
$or: [
467+
{
468+
$and: ors,
469+
},
470+
{
471+
$and: filters,
472+
},
473+
],
474+
...paramsFilter,
475+
};
476+
}
477+
}
478+
479+
private buildIndividualFilter(filters: any[]): any[] {
480+
return filters
481+
.filter((filter) => !!MONGOOSE_OPERATOR_MAP[filter.operator])
482+
.map((filter) => MONGOOSE_OPERATOR_MAP[filter.operator](filter.value));
428483
}
429484

430485
private queryFilterToSearch(filter: any): any {
431486
return isArrayFull(filter)
432-
? filter.reduce(
433-
(prev, item) => ({
434-
...prev,
435-
[item.field]: { [item.operator]: item.value },
436-
}),
437-
{},
438-
)
439-
: isObject(filter)
440487
? filter
488+
.filter((item) => !!MONGOOSE_OPERATOR_MAP[item.operator])
489+
.reduce(
490+
(prev, item) => ({
491+
...prev,
492+
[item.field]: MONGOOSE_OPERATOR_MAP[item.operator](item.value),
493+
}),
494+
{},
495+
)
496+
: isObject(filter)
497+
? Object.keys(filter).reduce((prev, key) => {
498+
const conditions = isArrayFull(filter[key])
499+
? filter[key].filter((condition) => !!condition && isObjectFull(condition))
500+
: [];
501+
502+
return {
503+
...prev,
504+
...(conditions.length > 0 ? { [key]: conditions } : {}),
505+
};
506+
}, {})
441507
: {};
442508
}
443509

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export const MONGOOSE_OPERATOR_MAP: { [key: string]: (value?: any) => any } = {
2+
$eq: (value) => ({
3+
$eq: value,
4+
}),
5+
$ne: (value) => ({
6+
$ne: value,
7+
}),
8+
$gt: (value) => ({ $gt: value }),
9+
$lt: (value) => ({ $lt: value }),
10+
$gte: (value) => ({ $gte: value }),
11+
$lte: (value) => ({ $lte: value }),
12+
$in: (value) => ({ $in: value }),
13+
$notin: (value) => ({ $nin: value }),
14+
$isnull: () => ({ $eq: null }),
15+
$notnull: () => ({ $ne: null }),
16+
$between: (value: any[]) => ({ $gt: Math.min(...value), $lt: Math.max(...value) }),
17+
$starts: (value) => ({ $regex: `/^${value}.*$/` }),
18+
$end: (value) => ({ $regex: `/^.*${value}$/` }),
19+
$cont: (value) => ({ $regex: `/^.*${value}.*$/` }),
20+
$excl: (value) => ({ $regex: `/^((?!${value}).)*$/` }),
21+
};

packages/crud-mongoose/test/b.query-params.spec.ts

+16-15
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
MONGO_URI,
1616
} from '../../../integration/crud-mongoose/mongoose.config';
1717
import {
18+
Post,
1819
PostDocument,
1920
postSchema,
2021
PostsService,
@@ -125,21 +126,21 @@ describe('#crud-typeorm', () => {
125126
// });
126127
// });
127128
//
128-
// describe('#query filter', () => {
129-
// it('should return data with limit', (done) => {
130-
// const query = qb.setLimit(4).query();
131-
// return request(server)
132-
// .get('/companies')
133-
// .query(query)
134-
// .end((_, res) => {
135-
// expect(res.status).toBe(200);
136-
// expect(res.body.length).toBe(4);
137-
// res.body.forEach((e: Company) => {
138-
// expect(e.id).not.toBe(1);
139-
// });
140-
// done();
141-
// });
142-
// });
129+
describe('#query filter', () => {
130+
it('should return one post', (done) => {
131+
const query = qb
132+
.setFilter({ field: 'name', operator: '$eq', value: 'john2' })
133+
.query();
134+
return request(server)
135+
.get('/users')
136+
.query(query)
137+
.end((_, res) => {
138+
expect(res.status).toBe(200);
139+
expect(res.body.length).toBe(1);
140+
done();
141+
});
142+
});
143+
});
143144
// it('should return with maxLimit', (done) => {
144145
// const query = qb.setLimit(7).query();
145146
// return request(server)

0 commit comments

Comments
 (0)