c2-mongoose 2.1.214 → 2.1.216
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/flow/SearcherFlow.d.ts +27 -0
- package/dist/flow/SearcherFlow.js +278 -0
- package/dist/flow/item/BuildPopulateSingleFlowItem.d.ts +8 -0
- package/dist/flow/item/BuildPopulateSingleFlowItem.js +63 -0
- package/dist/flow/item/BuildSelectPopulateFlowItem.js +1 -1
- package/package.json +1 -1
- package/src/flow/SearchFlow.ts +0 -19
- package/src/flow/SearcherFlow.ts +261 -0
- package/src/flow/item/BuildPopulateSingleFlowItem.ts +55 -0
- package/src/flow/item/BuildSelectPopulateFlowItem.ts +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
import { SearchOptions, SearchResponse } from "../types/SearchResponse";
|
|
3
|
+
import SearchFlow from "./SearchFlow";
|
|
4
|
+
export interface IPopulate {
|
|
5
|
+
[key: string]: any;
|
|
6
|
+
path: string;
|
|
7
|
+
select: string;
|
|
8
|
+
populate: IPopulate;
|
|
9
|
+
}
|
|
10
|
+
declare class SearcherFlow<D> {
|
|
11
|
+
private model;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
private searchText;
|
|
14
|
+
private orderSense;
|
|
15
|
+
private orderBy;
|
|
16
|
+
private page;
|
|
17
|
+
private limit;
|
|
18
|
+
private pageable;
|
|
19
|
+
private selection;
|
|
20
|
+
private populate;
|
|
21
|
+
constructor(repository: mongoose.Model<any>, search?: SearchFlow);
|
|
22
|
+
prepareSearch(search: any): void;
|
|
23
|
+
search(model: mongoose.Model<any>, options: SearchOptions): Promise<SearchResponse<D>>;
|
|
24
|
+
transformObject(originalObject: any): any;
|
|
25
|
+
buildDefaultFilters(objectSearch: any): any;
|
|
26
|
+
}
|
|
27
|
+
export default SearcherFlow;
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
14
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
15
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
16
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
17
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
18
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
19
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
23
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
24
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
25
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
26
|
+
function step(op) {
|
|
27
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
28
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
29
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
30
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
31
|
+
switch (op[0]) {
|
|
32
|
+
case 0: case 1: t = op; break;
|
|
33
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
34
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
35
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
36
|
+
default:
|
|
37
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
38
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
39
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
40
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
41
|
+
if (t[2]) _.ops.pop();
|
|
42
|
+
_.trys.pop(); continue;
|
|
43
|
+
}
|
|
44
|
+
op = body.call(thisArg, _);
|
|
45
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
46
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
50
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
51
|
+
};
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
var mongoose_1 = require("mongoose");
|
|
54
|
+
var Utils_1 = require("../utils/Utils");
|
|
55
|
+
var BuildSelectSingleFlowItem_1 = __importDefault(require("./item/BuildSelectSingleFlowItem"));
|
|
56
|
+
var BuildPopulateSingleFlowItem_1 = __importDefault(require("./item/BuildPopulateSingleFlowItem"));
|
|
57
|
+
var moment_1 = __importDefault(require("moment"));
|
|
58
|
+
var SearcherFlow = /** @class */ (function () {
|
|
59
|
+
function SearcherFlow(repository, search) {
|
|
60
|
+
this.searchText = "";
|
|
61
|
+
this.orderSense = "desc";
|
|
62
|
+
this.orderBy = "_id";
|
|
63
|
+
this.page = 1;
|
|
64
|
+
this.limit = 50;
|
|
65
|
+
this.pageable = true;
|
|
66
|
+
this.selection = [""];
|
|
67
|
+
this.populate = [""];
|
|
68
|
+
this.model = repository;
|
|
69
|
+
for (var key in search) {
|
|
70
|
+
this[key] = search[key];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
SearcherFlow.prototype.prepareSearch = function (search) {
|
|
74
|
+
for (var key in search) {
|
|
75
|
+
this[key] = search[key];
|
|
76
|
+
}
|
|
77
|
+
// this.projection = this.buildProjections()
|
|
78
|
+
// this.populate = this.buildPopulate()
|
|
79
|
+
// this.buildOrdenation()
|
|
80
|
+
};
|
|
81
|
+
SearcherFlow.prototype.search = function (model, options) {
|
|
82
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
83
|
+
var stagesItems, stagesPaging, stagesMetadata, facet, _i, stagesMetadata_1, metadata, result, _a, resultAux;
|
|
84
|
+
return __generator(this, function (_b) {
|
|
85
|
+
switch (_b.label) {
|
|
86
|
+
case 0:
|
|
87
|
+
stagesItems = [];
|
|
88
|
+
if ((0, Utils_1.isNotEmpty)(options.pipelines)) {
|
|
89
|
+
stagesItems.push.apply(stagesItems, options.pipelines);
|
|
90
|
+
}
|
|
91
|
+
if ((0, Utils_1.isNotEmpty)(options.unions)) {
|
|
92
|
+
stagesItems.push.apply(stagesItems, options.unions);
|
|
93
|
+
}
|
|
94
|
+
stagesItems.push({ $match: this.filters });
|
|
95
|
+
if ((0, Utils_1.isNotEmpty)(this.projection)) {
|
|
96
|
+
stagesItems.push({ $project: BuildSelectSingleFlowItem_1.default.build(model, this.select) });
|
|
97
|
+
}
|
|
98
|
+
// stagesItems.push({ $sort: this.sort })
|
|
99
|
+
if (this.pageable === true) {
|
|
100
|
+
stagesItems.push({ $skip: ((this.page - 1) * this.limit) || 0 });
|
|
101
|
+
stagesItems.push({ $limit: this.limit });
|
|
102
|
+
}
|
|
103
|
+
stagesPaging = [];
|
|
104
|
+
if ((0, Utils_1.isNotEmpty)(options.pipelines)) {
|
|
105
|
+
stagesPaging.push.apply(stagesPaging, options.pipelines);
|
|
106
|
+
}
|
|
107
|
+
if ((0, Utils_1.isNotEmpty)(options.unions)) {
|
|
108
|
+
stagesPaging.push.apply(stagesPaging, options.unions);
|
|
109
|
+
}
|
|
110
|
+
stagesPaging.push({ $match: this.filters });
|
|
111
|
+
stagesPaging.push({ $count: "total" });
|
|
112
|
+
stagesPaging.push({
|
|
113
|
+
$addFields: {
|
|
114
|
+
page: this.pageable === true ? this.page : 1,
|
|
115
|
+
limit: this.pageable === true ? this.limit : '$total',
|
|
116
|
+
totalPages: this.pageable === true ? {
|
|
117
|
+
$cond: {
|
|
118
|
+
if: { $eq: ['$total', 0] },
|
|
119
|
+
then: 0,
|
|
120
|
+
else: { $ceil: { $divide: ['$total', this.limit] } }
|
|
121
|
+
}
|
|
122
|
+
} : 1,
|
|
123
|
+
startIndex: this.pageable === true ? { $subtract: [{ $multiply: [this.page, this.limit] }, this.limit - 1] } : 1,
|
|
124
|
+
endIndex: this.pageable === true ? { $multiply: [this.page, this.limit] } : '$total'
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
stagesMetadata = [];
|
|
128
|
+
if ((0, Utils_1.isNotEmpty)(options.metadata)) {
|
|
129
|
+
stagesMetadata.push.apply(stagesMetadata, options.metadata);
|
|
130
|
+
}
|
|
131
|
+
facet = {};
|
|
132
|
+
if (this.onlyMetadata === false || this.onlyMetadata === undefined) {
|
|
133
|
+
facet.items = stagesItems;
|
|
134
|
+
facet.paging = stagesPaging;
|
|
135
|
+
}
|
|
136
|
+
for (_i = 0, stagesMetadata_1 = stagesMetadata; _i < stagesMetadata_1.length; _i++) {
|
|
137
|
+
metadata = stagesMetadata_1[_i];
|
|
138
|
+
facet[metadata.id] = metadata.conditions;
|
|
139
|
+
}
|
|
140
|
+
return [4 /*yield*/, model.aggregate([
|
|
141
|
+
{
|
|
142
|
+
$facet: facet
|
|
143
|
+
},
|
|
144
|
+
], {
|
|
145
|
+
collation: {
|
|
146
|
+
locale: "pt",
|
|
147
|
+
numericOrdering: true
|
|
148
|
+
}
|
|
149
|
+
}).session(options === null || options === void 0 ? void 0 : options.session)];
|
|
150
|
+
case 1:
|
|
151
|
+
result = _b.sent();
|
|
152
|
+
if (!(0, Utils_1.isNotEmpty)(this.populate)) return [3 /*break*/, 3];
|
|
153
|
+
_a = result[0];
|
|
154
|
+
return [4 /*yield*/, model.populate(result[0].items, BuildPopulateSingleFlowItem_1.default.buildPopulate(model, this.populate, this.selection))];
|
|
155
|
+
case 2:
|
|
156
|
+
_a.items = _b.sent();
|
|
157
|
+
_b.label = 3;
|
|
158
|
+
case 3:
|
|
159
|
+
resultAux = this.transformObject(result[0]);
|
|
160
|
+
return [2 /*return*/, resultAux];
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
SearcherFlow.prototype.transformObject = function (originalObject) {
|
|
166
|
+
var metadata = {};
|
|
167
|
+
var transformedObject = {};
|
|
168
|
+
for (var key in originalObject) {
|
|
169
|
+
if (key.startsWith('metadata-')) {
|
|
170
|
+
if (originalObject[key].length === 1) {
|
|
171
|
+
metadata[key.replace("metadata-", "")] = originalObject[key][0];
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
metadata[key.replace("metadata-", "")] = originalObject[key];
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
transformedObject[key] = originalObject[key];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
var hasPagination = (originalObject === null || originalObject === void 0 ? void 0 : originalObject.paging) ? true : false;
|
|
181
|
+
return __assign(__assign({}, transformedObject), { metadata: metadata, paging: hasPagination ? (originalObject === null || originalObject === void 0 ? void 0 : originalObject.paging[0]) || { total: 0, page: 1, limit: this.limit } : undefined });
|
|
182
|
+
};
|
|
183
|
+
SearcherFlow.prototype.buildDefaultFilters = function (objectSearch) {
|
|
184
|
+
var _this = this;
|
|
185
|
+
var filters = { $and: [] };
|
|
186
|
+
Object.entries(objectSearch).forEach(function (_a) {
|
|
187
|
+
var key = _a[0], value = _a[1];
|
|
188
|
+
if ((0, Utils_1.isNotEmpty)(value)) {
|
|
189
|
+
var condition = {};
|
|
190
|
+
if ([
|
|
191
|
+
'onlyMetadata',
|
|
192
|
+
'projection',
|
|
193
|
+
'pageable',
|
|
194
|
+
'orderSense',
|
|
195
|
+
'orderBy',
|
|
196
|
+
'properties',
|
|
197
|
+
'populate',
|
|
198
|
+
'page',
|
|
199
|
+
'limit',
|
|
200
|
+
'model',
|
|
201
|
+
'select',
|
|
202
|
+
'searchText',
|
|
203
|
+
'sort',
|
|
204
|
+
'isPageable',
|
|
205
|
+
'searchPageable'
|
|
206
|
+
].includes(key)) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (typeof value === 'string' && _this.isValidObjectId(value)) {
|
|
210
|
+
value = new mongoose_1.Types.ObjectId(value);
|
|
211
|
+
}
|
|
212
|
+
if (value === 'true') {
|
|
213
|
+
value = true;
|
|
214
|
+
}
|
|
215
|
+
if (value === 'false') {
|
|
216
|
+
value = false;
|
|
217
|
+
}
|
|
218
|
+
if (key.endsWith('DateRange') || key.endsWith('DateTimeRange')) {
|
|
219
|
+
var fieldName = key.replace('Range', '');
|
|
220
|
+
var values = value;
|
|
221
|
+
if ((0, Utils_1.isNotEmpty)(values[0])) {
|
|
222
|
+
var momentValue = (0, moment_1.default)(values[0]);
|
|
223
|
+
momentValue.hour(0);
|
|
224
|
+
momentValue.minute(0);
|
|
225
|
+
momentValue.second(0);
|
|
226
|
+
condition[fieldName] = __assign(__assign({}, condition[fieldName]), { $gte: momentValue.tz('America/Sao_Paulo', true).toDate() });
|
|
227
|
+
}
|
|
228
|
+
if ((0, Utils_1.isNotEmpty)(values[1])) {
|
|
229
|
+
var momentValue = (0, moment_1.default)(values[1]);
|
|
230
|
+
momentValue.hour(23);
|
|
231
|
+
momentValue.minute(59);
|
|
232
|
+
momentValue.second(59);
|
|
233
|
+
condition[fieldName] = __assign(__assign({}, condition[fieldName]), { $lte: momentValue.tz('America/Sao_Paulo', true).toDate() });
|
|
234
|
+
}
|
|
235
|
+
filters.$and.push(condition);
|
|
236
|
+
}
|
|
237
|
+
else if (key.endsWith('Like')) {
|
|
238
|
+
var fieldName = key.replace('Like', '');
|
|
239
|
+
condition[fieldName] = _this.buildRegex(value); //{ $regex: value as any, $options: 'i' }
|
|
240
|
+
filters.$and.push(condition);
|
|
241
|
+
}
|
|
242
|
+
else if (key.endsWith('Exists')) {
|
|
243
|
+
var fieldName = key.replace('Exists', '');
|
|
244
|
+
condition[fieldName] = { $exists: value };
|
|
245
|
+
filters.$and.push(condition);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
if (!Array.isArray(value)) {
|
|
249
|
+
condition[key] = value;
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
var arr = [];
|
|
253
|
+
for (var _i = 0, value_1 = value; _i < value_1.length; _i++) {
|
|
254
|
+
var val = value_1[_i];
|
|
255
|
+
if (typeof val === 'string' && _this.isValidObjectId(val)) {
|
|
256
|
+
val = new mongoose_1.Types.ObjectId(val);
|
|
257
|
+
}
|
|
258
|
+
arr.push(val);
|
|
259
|
+
}
|
|
260
|
+
if (key.startsWith("notIn")) {
|
|
261
|
+
key = key.replace("notIn", "");
|
|
262
|
+
condition[key] = { $nin: arr };
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
condition[key] = { $in: arr };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
filters.$and.push(condition);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
if (filters.$and.length === 0)
|
|
273
|
+
delete filters['$and'];
|
|
274
|
+
return filters;
|
|
275
|
+
};
|
|
276
|
+
return SearcherFlow;
|
|
277
|
+
}());
|
|
278
|
+
exports.default = SearcherFlow;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
import { IPopulate } from "../SearcherFlow";
|
|
3
|
+
declare class BuildPopulateSingleFlowItem {
|
|
4
|
+
buildPopulate(model: mongoose.Model<any>, populatesStr: string[], selectsStr: string[]): IPopulate[];
|
|
5
|
+
buildPath(target: string, nested?: string): IPopulate;
|
|
6
|
+
}
|
|
7
|
+
declare const _default: BuildPopulateSingleFlowItem;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
var Utils_1 = require("../../utils/Utils");
|
|
15
|
+
var BuildPopulateSingleFlowItem = /** @class */ (function () {
|
|
16
|
+
function BuildPopulateSingleFlowItem() {
|
|
17
|
+
}
|
|
18
|
+
BuildPopulateSingleFlowItem.prototype.buildPopulate = function (model, populatesStr, selectsStr) {
|
|
19
|
+
var _a, _b, _c, _d;
|
|
20
|
+
var populate = [];
|
|
21
|
+
if ((0, Utils_1.isEmpty)(populatesStr)) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
var _loop_1 = function () {
|
|
25
|
+
var _e = property.split('.'), first = _e[0], rest = _e.slice(1);
|
|
26
|
+
var nested = rest.join('.');
|
|
27
|
+
var populateLocal = this_1.buildPath(first, nested);
|
|
28
|
+
if (Array.isArray(selectsStr)) {
|
|
29
|
+
var selects = selectsStr.filter(function (sel) { return sel.startsWith("".concat(first, ".")); });
|
|
30
|
+
var select = selects.map(function (sel) { return sel.split(".")[1]; }).join(" ");
|
|
31
|
+
if ((0, Utils_1.isNotEmpty)(select)) {
|
|
32
|
+
populateLocal.select = select;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
var existing = populate.find(function (populateAux) { return populateAux.path === populateLocal.path; });
|
|
36
|
+
if (existing) {
|
|
37
|
+
var populateAux = __assign(__assign({}, existing === null || existing === void 0 ? void 0 : existing.populate), { path: "".concat((_b = (_a = existing.populate) === null || _a === void 0 ? void 0 : _a.path) !== null && _b !== void 0 ? _b : "", " ").concat((_d = (_c = populateLocal.populate) === null || _c === void 0 ? void 0 : _c.path) !== null && _d !== void 0 ? _d : "").trim() });
|
|
38
|
+
existing.populate = populateAux;
|
|
39
|
+
return "continue";
|
|
40
|
+
}
|
|
41
|
+
populate.push(populateLocal);
|
|
42
|
+
};
|
|
43
|
+
var this_1 = this;
|
|
44
|
+
for (var _i = 0, populatesStr_1 = populatesStr; _i < populatesStr_1.length; _i++) {
|
|
45
|
+
var property = populatesStr_1[_i];
|
|
46
|
+
_loop_1();
|
|
47
|
+
}
|
|
48
|
+
return populate;
|
|
49
|
+
};
|
|
50
|
+
BuildPopulateSingleFlowItem.prototype.buildPath = function (target, nested) {
|
|
51
|
+
if (nested === void 0) { nested = ""; }
|
|
52
|
+
var populate = {};
|
|
53
|
+
populate.path = target;
|
|
54
|
+
if ((0, Utils_1.isNotEmpty)(nested)) {
|
|
55
|
+
var _a = nested.split('.'), first = _a[0], rest = _a.slice(1);
|
|
56
|
+
var nested2 = rest.join('.');
|
|
57
|
+
populate.populate = this.buildPath(first, nested2);
|
|
58
|
+
}
|
|
59
|
+
return populate;
|
|
60
|
+
};
|
|
61
|
+
return BuildPopulateSingleFlowItem;
|
|
62
|
+
}());
|
|
63
|
+
exports.default = new BuildPopulateSingleFlowItem;
|
|
@@ -10,7 +10,7 @@ var BuildSelectPopulateFlowItem = /** @class */ (function () {
|
|
|
10
10
|
if (campoInfo.instance === "ObjectID") {
|
|
11
11
|
return undefined;
|
|
12
12
|
}
|
|
13
|
-
if ((0, Utils_1.isEmpty)(projection[path]) || isNaN(projection[path]) === false) {
|
|
13
|
+
if ((0, Utils_1.isEmpty)(projection) || (0, Utils_1.isEmpty)(projection[path]) || isNaN(projection[path]) === false) {
|
|
14
14
|
return undefined;
|
|
15
15
|
}
|
|
16
16
|
return projection[path];
|
package/package.json
CHANGED
package/src/flow/SearchFlow.ts
CHANGED
|
@@ -520,25 +520,6 @@ abstract class SearchFlow {
|
|
|
520
520
|
|
|
521
521
|
return filters
|
|
522
522
|
}
|
|
523
|
-
|
|
524
|
-
// public addFilterModel(model: any, filters: any) {
|
|
525
|
-
// Object.entries(model).forEach(([key, value]) => {
|
|
526
|
-
// if (key.endsWith('DateRange')) {
|
|
527
|
-
// return
|
|
528
|
-
// }
|
|
529
|
-
// if (key.endsWith('Like')) {
|
|
530
|
-
// return
|
|
531
|
-
// }
|
|
532
|
-
// if (['onlyMetadata', 'projection', 'pageable', 'orderSense', 'orderBy', 'properties', 'populate', 'page', 'limit', 'model', 'select', 'searchText', 'isPageable', 'searchPageable'].includes(key)) {
|
|
533
|
-
// return
|
|
534
|
-
// }
|
|
535
|
-
// if (isNotEmpty(value)) {
|
|
536
|
-
// let condition = {} as any
|
|
537
|
-
// condition[key] = value
|
|
538
|
-
// filters.$and.push(condition)
|
|
539
|
-
// }
|
|
540
|
-
// })
|
|
541
|
-
// }
|
|
542
523
|
}
|
|
543
524
|
|
|
544
525
|
export default SearchFlow
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import mongoose, { ClientSession, Types } from "mongoose"
|
|
2
|
+
import { SearchOptions, SearchResponse } from "../types/SearchResponse"
|
|
3
|
+
import { isEmpty, isNotEmpty } from "../utils/Utils"
|
|
4
|
+
import SearchFlow from "./SearchFlow"
|
|
5
|
+
import BuildSelectSingleFlowItem from "./item/BuildSelectSingleFlowItem"
|
|
6
|
+
import BuildPopulateSingleFlowItem from "./item/BuildPopulateSingleFlowItem"
|
|
7
|
+
import moment from "moment"
|
|
8
|
+
export interface IPopulate {
|
|
9
|
+
[key: string]: any
|
|
10
|
+
path: string
|
|
11
|
+
select: string
|
|
12
|
+
populate: IPopulate
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class SearcherFlow<D> {
|
|
16
|
+
|
|
17
|
+
private model: mongoose.Model<any>
|
|
18
|
+
|
|
19
|
+
[key: string]: any
|
|
20
|
+
private searchText: string = ""
|
|
21
|
+
private orderSense: string = "desc"
|
|
22
|
+
private orderBy: string = "_id"
|
|
23
|
+
private page: number = 1
|
|
24
|
+
private limit: number = 50
|
|
25
|
+
private pageable: boolean = true
|
|
26
|
+
|
|
27
|
+
private selection: string[] = [""]
|
|
28
|
+
private populate: string[] = [""]
|
|
29
|
+
|
|
30
|
+
constructor(repository: mongoose.Model<any>, search?: SearchFlow) {
|
|
31
|
+
this.model = repository
|
|
32
|
+
for (const key in search) {
|
|
33
|
+
this[key] = search[key];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public prepareSearch(search: any) {
|
|
38
|
+
for (const key in search) {
|
|
39
|
+
this[key] = search[key];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// this.projection = this.buildProjections()
|
|
43
|
+
// this.populate = this.buildPopulate()
|
|
44
|
+
// this.buildOrdenation()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async search(model: mongoose.Model<any>, options: SearchOptions): Promise<SearchResponse<D>> {
|
|
48
|
+
|
|
49
|
+
let stagesItems: any[] = []
|
|
50
|
+
|
|
51
|
+
if (isNotEmpty(options.pipelines)) {
|
|
52
|
+
stagesItems.push(...options.pipelines)
|
|
53
|
+
}
|
|
54
|
+
if (isNotEmpty(options.unions)) {
|
|
55
|
+
stagesItems.push(...options.unions)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
stagesItems.push({ $match: this.filters })
|
|
59
|
+
|
|
60
|
+
if (isNotEmpty(this.projection)) {
|
|
61
|
+
stagesItems.push({ $project: BuildSelectSingleFlowItem.build(model, this.select) })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// stagesItems.push({ $sort: this.sort })
|
|
65
|
+
if (this.pageable === true) {
|
|
66
|
+
stagesItems.push({ $skip: ((this.page - 1) * this.limit) || 0 })
|
|
67
|
+
stagesItems.push({ $limit: this.limit })
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
let stagesPaging: any[] = []
|
|
72
|
+
if (isNotEmpty(options.pipelines)) {
|
|
73
|
+
stagesPaging.push(...options.pipelines)
|
|
74
|
+
}
|
|
75
|
+
if (isNotEmpty(options.unions)) {
|
|
76
|
+
stagesPaging.push(...options.unions)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
stagesPaging.push({ $match: this.filters })
|
|
80
|
+
stagesPaging.push({ $count: "total" })
|
|
81
|
+
stagesPaging.push({
|
|
82
|
+
$addFields: {
|
|
83
|
+
page: this.pageable === true ? this.page : 1,
|
|
84
|
+
limit: this.pageable === true ? this.limit : '$total',
|
|
85
|
+
totalPages: this.pageable === true ? {
|
|
86
|
+
$cond: {
|
|
87
|
+
if: { $eq: ['$total', 0] },
|
|
88
|
+
then: 0,
|
|
89
|
+
else: { $ceil: { $divide: ['$total', this.limit] } }
|
|
90
|
+
}
|
|
91
|
+
} : 1,
|
|
92
|
+
startIndex: this.pageable === true ? { $subtract: [{ $multiply: [this.page, this.limit] }, this.limit - 1] } : 1,
|
|
93
|
+
endIndex: this.pageable === true ? { $multiply: [this.page, this.limit] } : '$total'
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
let stagesMetadata: any[] = []
|
|
98
|
+
if (isNotEmpty(options.metadata)) {
|
|
99
|
+
stagesMetadata.push(...options.metadata)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const facet: { [key: string]: any } = {}
|
|
103
|
+
if (this.onlyMetadata === false || this.onlyMetadata === undefined) {
|
|
104
|
+
facet.items = stagesItems
|
|
105
|
+
facet.paging = stagesPaging
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (let metadata of stagesMetadata) {
|
|
109
|
+
facet[metadata.id] = metadata.conditions
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const result = await model.aggregate(
|
|
113
|
+
[
|
|
114
|
+
{
|
|
115
|
+
$facet: facet
|
|
116
|
+
},
|
|
117
|
+
], {
|
|
118
|
+
collation: {
|
|
119
|
+
locale: "pt",
|
|
120
|
+
numericOrdering: true
|
|
121
|
+
}
|
|
122
|
+
}).session(options?.session as ClientSession)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
if (isNotEmpty(this.populate)) {
|
|
126
|
+
result[0].items = await model.populate(result[0].items, BuildPopulateSingleFlowItem.buildPopulate(model, this.populate, this.selection))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let resultAux = this.transformObject(result[0])
|
|
130
|
+
return resultAux
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
transformObject(originalObject: any): any {
|
|
134
|
+
const metadata: any = {};
|
|
135
|
+
const transformedObject: any = {};
|
|
136
|
+
|
|
137
|
+
for (const key in originalObject) {
|
|
138
|
+
if (key.startsWith('metadata-')) {
|
|
139
|
+
if (originalObject[key].length === 1) {
|
|
140
|
+
metadata[key.replace("metadata-", "")] = originalObject[key][0]
|
|
141
|
+
continue
|
|
142
|
+
}
|
|
143
|
+
metadata[key.replace("metadata-", "")] = originalObject[key]
|
|
144
|
+
} else {
|
|
145
|
+
transformedObject[key] = originalObject[key];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let hasPagination = originalObject?.paging ? true : false
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
...transformedObject,
|
|
153
|
+
metadata,
|
|
154
|
+
paging: hasPagination ? originalObject?.paging[0] || { total: 0, page: 1, limit: this.limit } : undefined,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
public buildDefaultFilters(objectSearch: any) {
|
|
159
|
+
let filters = { $and: [] } as any
|
|
160
|
+
Object.entries(objectSearch).forEach(([key, value]) => {
|
|
161
|
+
if (isNotEmpty(value)) {
|
|
162
|
+
let condition = {} as any
|
|
163
|
+
if ([
|
|
164
|
+
'onlyMetadata',
|
|
165
|
+
'projection',
|
|
166
|
+
'pageable',
|
|
167
|
+
'orderSense',
|
|
168
|
+
'orderBy',
|
|
169
|
+
'properties',
|
|
170
|
+
'populate',
|
|
171
|
+
'page',
|
|
172
|
+
'limit',
|
|
173
|
+
'model',
|
|
174
|
+
'select',
|
|
175
|
+
'searchText',
|
|
176
|
+
'sort',
|
|
177
|
+
'isPageable',
|
|
178
|
+
'searchPageable'].includes(key)) {
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (typeof value === 'string' && this.isValidObjectId(value as string)) {
|
|
183
|
+
value = new Types.ObjectId(value as string)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (value === 'true') {
|
|
187
|
+
value = true
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (value === 'false') {
|
|
191
|
+
value = false
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (key.endsWith('DateRange') || key.endsWith('DateTimeRange')) {
|
|
195
|
+
var fieldName = key.replace('Range', '')
|
|
196
|
+
var values = value as any
|
|
197
|
+
if (isNotEmpty(values[0])) {
|
|
198
|
+
var momentValue = moment(values[0])
|
|
199
|
+
momentValue.hour(0)
|
|
200
|
+
momentValue.minute(0)
|
|
201
|
+
momentValue.second(0)
|
|
202
|
+
condition[fieldName] = {
|
|
203
|
+
...condition[fieldName],
|
|
204
|
+
$gte: momentValue.tz('America/Sao_Paulo', true).toDate()
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (isNotEmpty(values[1])) {
|
|
209
|
+
var momentValue = moment(values[1])
|
|
210
|
+
momentValue.hour(23)
|
|
211
|
+
momentValue.minute(59)
|
|
212
|
+
momentValue.second(59)
|
|
213
|
+
condition[fieldName] = {
|
|
214
|
+
...condition[fieldName],
|
|
215
|
+
$lte: momentValue.tz('America/Sao_Paulo', true).toDate()
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
filters.$and.push(condition)
|
|
219
|
+
} else if (key.endsWith('Like')) {
|
|
220
|
+
var fieldName = key.replace('Like', '')
|
|
221
|
+
condition[fieldName] = this.buildRegex(value as string) //{ $regex: value as any, $options: 'i' }
|
|
222
|
+
filters.$and.push(condition)
|
|
223
|
+
} else if (key.endsWith('Exists')) {
|
|
224
|
+
var fieldName = key.replace('Exists', '')
|
|
225
|
+
condition[fieldName] = { $exists: value as boolean }
|
|
226
|
+
filters.$and.push(condition)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
else {
|
|
230
|
+
if (!Array.isArray(value)) {
|
|
231
|
+
condition[key] = value
|
|
232
|
+
} else {
|
|
233
|
+
let arr = []
|
|
234
|
+
for (let val of value) {
|
|
235
|
+
if (typeof val === 'string' && this.isValidObjectId(val as string)) {
|
|
236
|
+
val = new Types.ObjectId(val as string)
|
|
237
|
+
}
|
|
238
|
+
arr.push(val)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (key.startsWith("notIn")) {
|
|
242
|
+
key = key.replace("notIn", "")
|
|
243
|
+
condition[key] = { $nin: arr }
|
|
244
|
+
} else {
|
|
245
|
+
condition[key] = { $in: arr }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
}
|
|
249
|
+
filters.$and.push(condition)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
if (filters.$and.length === 0)
|
|
255
|
+
delete filters['$and']
|
|
256
|
+
|
|
257
|
+
return filters
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export default SearcherFlow
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
import { isEmpty, isNotEmpty } from "../../utils/Utils";
|
|
3
|
+
import { IPopulate } from "../SearcherFlow";
|
|
4
|
+
|
|
5
|
+
class BuildPopulateSingleFlowItem {
|
|
6
|
+
|
|
7
|
+
public buildPopulate(model: mongoose.Model<any>, populatesStr: string[], selectsStr: string[]): IPopulate[] {
|
|
8
|
+
var populate = [] as IPopulate[]
|
|
9
|
+
if (isEmpty(populatesStr)) {
|
|
10
|
+
return []
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
for (var property of populatesStr) {
|
|
14
|
+
let [first, ...rest] = property.split('.')
|
|
15
|
+
let nested = rest.join('.')
|
|
16
|
+
|
|
17
|
+
let populateLocal = this.buildPath(first, nested)
|
|
18
|
+
|
|
19
|
+
if (Array.isArray(selectsStr)) {
|
|
20
|
+
let selects = (selectsStr as []).filter((sel: string) => sel.startsWith(`${first}.`))
|
|
21
|
+
let select = selects.map((sel: string) => sel.split(".")[1]).join(" ")
|
|
22
|
+
if (isNotEmpty(select)) {
|
|
23
|
+
populateLocal.select = select
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const existing = populate.find((populateAux: IPopulate) => populateAux.path === populateLocal.path)
|
|
28
|
+
if (existing) {
|
|
29
|
+
const populateAux = {
|
|
30
|
+
...existing?.populate,
|
|
31
|
+
path: `${existing.populate?.path ?? ``} ${populateLocal.populate?.path ?? ``}`.trim()
|
|
32
|
+
}
|
|
33
|
+
existing.populate = populateAux
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
populate.push(populateLocal)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return populate
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public buildPath(target: string, nested: string = ""): IPopulate {
|
|
44
|
+
var populate = {} as IPopulate
|
|
45
|
+
populate.path = target
|
|
46
|
+
if (isNotEmpty(nested)) {
|
|
47
|
+
let [first, ...rest] = nested.split('.')
|
|
48
|
+
let nested2 = rest.join('.')
|
|
49
|
+
populate.populate = this.buildPath(first, nested2)
|
|
50
|
+
}
|
|
51
|
+
return populate
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default new BuildPopulateSingleFlowItem
|
|
@@ -12,7 +12,7 @@ class BuildSelectPopulateFlowItem {
|
|
|
12
12
|
return undefined
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
if (isEmpty(projection[path]) || isNaN(projection[path]) === false) {
|
|
15
|
+
if (isEmpty(projection) || isEmpty(projection[path]) || isNaN(projection[path]) === false) {
|
|
16
16
|
return undefined
|
|
17
17
|
}
|
|
18
18
|
return projection[path];
|