@drax/crud-back 2.8.0 → 2.11.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 +1 -1
- package/dist/regexs/QueryFilterRegex.js +1 -1
- package/dist/repository/AbstractMongoRepository.js +40 -15
- package/dist/repository/AbstractSqliteRepository.js +102 -51
- package/package.json +4 -4
- package/src/controllers/AbstractFastifyController.ts +1 -1
- package/src/regexs/QueryFilterRegex.ts +1 -1
- package/src/repository/AbstractMongoRepository.ts +45 -16
- package/src/repository/AbstractSqliteRepository.ts +144 -57
- package/tsconfig.tsbuildinfo +1 -1
- package/types/regexs/QueryFilterRegex.d.ts.map +1 -1
- package/types/repository/AbstractMongoRepository.d.ts.map +1 -1
- package/types/repository/AbstractSqliteRepository.d.ts +2 -2
- package/types/repository/AbstractSqliteRepository.d.ts.map +1 -1
|
@@ -43,7 +43,7 @@ class AbstractFastifyController extends CommonController {
|
|
|
43
43
|
const filters = [];
|
|
44
44
|
filterArray.forEach((filter) => {
|
|
45
45
|
const [field, operator, value] = filter.split(";");
|
|
46
|
-
if (field && operator && (value !== undefined && value !== '')) {
|
|
46
|
+
if (field && operator && (operator === 'empty' || (value !== undefined && value !== ''))) {
|
|
47
47
|
filters.push({ field, operator, value });
|
|
48
48
|
}
|
|
49
49
|
});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
const QueryFilterRegex = /^(?:[a-zA-Z0-9_.\-]+;(?:eq|like|ne|in|nin|gt|gte|lt|lte);[a-zA-Z0-9_.\-:\., áéíóúÁÉÍÓÚ]*)(?:\|[a-zA-Z0-9_.\-]+;(?:eq|like|ne|in|nin|gt|gte|lt|lte);[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_.\-]+;(?:eq|like|ne|in|nin|gt|gte|lt|lte|empty);[a-zA-Z0-9_.\-:\., áéíóúÁÉÍÓÚ]*)*$/;
|
|
2
2
|
export default QueryFilterRegex;
|
|
3
3
|
export { QueryFilterRegex };
|
|
@@ -133,7 +133,7 @@ class AbstractMongoRepository {
|
|
|
133
133
|
else if (value) {
|
|
134
134
|
query['$or'] = this._searchFields.map(field => ({ [field]: new RegExp(value.toString(), 'i') }));
|
|
135
135
|
}
|
|
136
|
-
MongooseQueryFilter.applyFilters(query, filters);
|
|
136
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model);
|
|
137
137
|
const items = await this._model
|
|
138
138
|
.find(query)
|
|
139
139
|
.limit(limit)
|
|
@@ -152,7 +152,7 @@ class AbstractMongoRepository {
|
|
|
152
152
|
query['$or'] = this._searchFields.map(field => ({ [field]: new RegExp(search.toString(), 'i') }));
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
-
MongooseQueryFilter.applyFilters(query, filters);
|
|
155
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model);
|
|
156
156
|
// console.log("Paginate Query", query)
|
|
157
157
|
const sort = MongooseSort.applySort(orderBy, order);
|
|
158
158
|
const populate = this._populateFields;
|
|
@@ -176,7 +176,7 @@ class AbstractMongoRepository {
|
|
|
176
176
|
query['$or'] = this._searchFields.map(field => ({ [field]: new RegExp(search.toString(), 'i') }));
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
|
-
MongooseQueryFilter.applyFilters(query, filters);
|
|
179
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model);
|
|
180
180
|
const item = this._model
|
|
181
181
|
.findOne(query)
|
|
182
182
|
.populate(this._populateFields)
|
|
@@ -194,7 +194,7 @@ class AbstractMongoRepository {
|
|
|
194
194
|
query['$or'] = this._searchFields.map(field => ({ [field]: new RegExp(search.toString(), 'i') }));
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
|
-
MongooseQueryFilter.applyFilters(query, filters);
|
|
197
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model);
|
|
198
198
|
const sort = MongooseSort.applySort(orderBy, order);
|
|
199
199
|
const items = await this._model
|
|
200
200
|
.find(query)
|
|
@@ -212,14 +212,14 @@ class AbstractMongoRepository {
|
|
|
212
212
|
{ name: new RegExp(search, 'i') },
|
|
213
213
|
];
|
|
214
214
|
}
|
|
215
|
-
MongooseQueryFilter.applyFilters(query, filters);
|
|
215
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model);
|
|
216
216
|
const sort = MongooseSort.applySort(orderBy, order);
|
|
217
217
|
return this._model.find(query).limit(limit).sort(sort).cursor();
|
|
218
218
|
}
|
|
219
219
|
async groupBy({ fields = [], filters = [], dateFormat = 'day' }) {
|
|
220
220
|
const query = {};
|
|
221
|
-
MongooseQueryFilter.applyFilters(query, filters);
|
|
222
|
-
//
|
|
221
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model);
|
|
222
|
+
// console.log("groupBy Query", query)
|
|
223
223
|
// Obtener el schema para identificar campos de referencia y fechas
|
|
224
224
|
const schema = this._model.schema;
|
|
225
225
|
// Construir el objeto de agrupación dinámicamente
|
|
@@ -228,6 +228,13 @@ class AbstractMongoRepository {
|
|
|
228
228
|
const finalProjectFields = { count: 1, _id: 0 };
|
|
229
229
|
const refFields = new Set();
|
|
230
230
|
const dateFields = new Set();
|
|
231
|
+
const numericFields = new Set();
|
|
232
|
+
const groupFields = [];
|
|
233
|
+
const numericInstances = new Set(['Number', 'Decimal128', 'Double', 'Int32', 'Long', 'BigInt']);
|
|
234
|
+
const totalGroupFields = fields.filter(field => {
|
|
235
|
+
const schemaPath = schema.path(field);
|
|
236
|
+
return !(schemaPath && numericInstances.has(schemaPath.instance));
|
|
237
|
+
}).length;
|
|
231
238
|
// Función para obtener el formato de fecha según el nivel de granularidad
|
|
232
239
|
const getDateFormat = (field, format) => {
|
|
233
240
|
const formats = {
|
|
@@ -284,6 +291,13 @@ class AbstractMongoRepository {
|
|
|
284
291
|
};
|
|
285
292
|
fields.forEach(field => {
|
|
286
293
|
const schemaPath = schema.path(field);
|
|
294
|
+
// Verificar si el campo es numérico: se agregará con $sum y no formará parte de la clave de agrupación
|
|
295
|
+
if (schemaPath && numericInstances.has(schemaPath.instance)) {
|
|
296
|
+
numericFields.add(field);
|
|
297
|
+
finalProjectFields[field] = 1;
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
groupFields.push(field);
|
|
287
301
|
// Verificar si el campo es de tipo Date
|
|
288
302
|
if (schemaPath && schemaPath.instance === 'Date') {
|
|
289
303
|
dateFields.add(field);
|
|
@@ -298,7 +312,7 @@ class AbstractMongoRepository {
|
|
|
298
312
|
const refModelInstance = mongoose.model(refModel);
|
|
299
313
|
const collectionName = refModelInstance.collection.name;
|
|
300
314
|
// Determinar el campo local correcto según si es un solo campo o múltiples
|
|
301
|
-
const localField =
|
|
315
|
+
const localField = totalGroupFields === 1 ? '_id' : `_id.${fieldName}`;
|
|
302
316
|
lookupStages.push({
|
|
303
317
|
$lookup: {
|
|
304
318
|
from: collectionName,
|
|
@@ -324,9 +338,9 @@ class AbstractMongoRepository {
|
|
|
324
338
|
}
|
|
325
339
|
});
|
|
326
340
|
// Construir la proyección final para campos de fecha
|
|
327
|
-
|
|
341
|
+
groupFields.forEach(field => {
|
|
328
342
|
if (dateFields.has(field)) {
|
|
329
|
-
if (
|
|
343
|
+
if (groupFields.length === 1) {
|
|
330
344
|
finalProjectFields[field] = `$_id`;
|
|
331
345
|
}
|
|
332
346
|
else {
|
|
@@ -334,7 +348,7 @@ class AbstractMongoRepository {
|
|
|
334
348
|
}
|
|
335
349
|
}
|
|
336
350
|
else if (!refFields.has(field)) {
|
|
337
|
-
if (
|
|
351
|
+
if (groupFields.length === 1) {
|
|
338
352
|
finalProjectFields[field] = `$_id`;
|
|
339
353
|
}
|
|
340
354
|
else {
|
|
@@ -342,13 +356,24 @@ class AbstractMongoRepository {
|
|
|
342
356
|
}
|
|
343
357
|
}
|
|
344
358
|
});
|
|
359
|
+
const groupStage = {
|
|
360
|
+
_id: null,
|
|
361
|
+
count: { $sum: 1 }
|
|
362
|
+
};
|
|
363
|
+
numericFields.forEach(field => {
|
|
364
|
+
groupStage[field] = { $sum: { $ifNull: [`$${field}`, 0] } };
|
|
365
|
+
});
|
|
366
|
+
if (groupFields.length === 1) {
|
|
367
|
+
const field = groupFields[0];
|
|
368
|
+
groupStage._id = dateFields.has(field) ? getDateFormat(field, dateFormat) : groupId[field];
|
|
369
|
+
}
|
|
370
|
+
else if (groupFields.length > 1) {
|
|
371
|
+
groupStage._id = groupId;
|
|
372
|
+
}
|
|
345
373
|
const pipeline = [
|
|
346
374
|
{ $match: query },
|
|
347
375
|
{
|
|
348
|
-
$group:
|
|
349
|
-
_id: fields.length === 1 ? (dateFields.has(fields[0]) ? getDateFormat(fields[0], dateFormat) : `$${fields[0]}`) : groupId,
|
|
350
|
-
count: { $sum: 1 }
|
|
351
|
-
}
|
|
376
|
+
$group: groupStage
|
|
352
377
|
}
|
|
353
378
|
];
|
|
354
379
|
// Solo agregar lookups si hay campos de referencia
|
|
@@ -132,41 +132,61 @@ class AbstractSqliteRepository {
|
|
|
132
132
|
async paginate({ page = 1, limit = 5, orderBy = '', order = 'desc', search = '', filters = [] }) {
|
|
133
133
|
const offset = page > 1 ? (page - 1) * limit : 0;
|
|
134
134
|
let where = "";
|
|
135
|
+
let params = [];
|
|
136
|
+
// SEARCH
|
|
135
137
|
if (search && this.searchFields.length > 0) {
|
|
136
|
-
|
|
138
|
+
const searchConditions = this.searchFields
|
|
139
|
+
.map(field => `${field} LIKE ?`)
|
|
140
|
+
.join(" OR ");
|
|
141
|
+
where = ` WHERE (${searchConditions})`;
|
|
142
|
+
params.push(...this.searchFields.map(() => `%${search}%`));
|
|
137
143
|
}
|
|
144
|
+
// FILTERS
|
|
138
145
|
if (filters.length > 0) {
|
|
139
|
-
|
|
146
|
+
const result = SqlQueryFilter.applyFilters(where, filters);
|
|
147
|
+
where = result.where;
|
|
148
|
+
params.push(...result.params);
|
|
140
149
|
}
|
|
141
150
|
const sort = SqlSort.applySort(orderBy, order);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
151
|
+
// COUNT
|
|
152
|
+
const rCount = this.db
|
|
153
|
+
.prepare(`SELECT COUNT(*) as count FROM ${this.tableName} ${where}`)
|
|
154
|
+
.get(params);
|
|
155
|
+
// DATA
|
|
156
|
+
const items = this.db
|
|
157
|
+
.prepare(`SELECT * FROM ${this.tableName} ${where} ${sort} LIMIT ? OFFSET ?`)
|
|
158
|
+
.all([...params, limit, offset]);
|
|
147
159
|
for (const item of items) {
|
|
148
160
|
await this.decorate(item);
|
|
149
161
|
}
|
|
150
162
|
return {
|
|
151
|
-
page
|
|
152
|
-
limit
|
|
163
|
+
page,
|
|
164
|
+
limit,
|
|
153
165
|
total: rCount.count,
|
|
154
|
-
items
|
|
166
|
+
items
|
|
155
167
|
};
|
|
156
168
|
}
|
|
157
169
|
async find({ limit = 5, orderBy = '', order = 'desc', search = '', filters = [] }) {
|
|
158
170
|
let where = "";
|
|
171
|
+
let params = [];
|
|
172
|
+
// SEARCH
|
|
159
173
|
if (search && this.searchFields.length > 0) {
|
|
160
|
-
|
|
174
|
+
const searchConditions = this.searchFields
|
|
175
|
+
.map(field => `${field} LIKE ?`)
|
|
176
|
+
.join(" OR ");
|
|
177
|
+
where = ` WHERE (${searchConditions})`;
|
|
178
|
+
params.push(...this.searchFields.map(() => `%${search}%`));
|
|
161
179
|
}
|
|
180
|
+
// FILTERS
|
|
162
181
|
if (filters.length > 0) {
|
|
163
|
-
|
|
182
|
+
const result = SqlQueryFilter.applyFilters(where, filters);
|
|
183
|
+
where = result.where;
|
|
184
|
+
params.push(...result.params);
|
|
164
185
|
}
|
|
165
186
|
const sort = SqlSort.applySort(orderBy, order);
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
FROM ${this.tableName} ${where} ${sort} LIMIT ? `).all([limit]);
|
|
187
|
+
const items = this.db
|
|
188
|
+
.prepare(`SELECT * FROM ${this.tableName} ${where} ${sort} LIMIT ?`)
|
|
189
|
+
.all([...params, limit]);
|
|
170
190
|
for (const item of items) {
|
|
171
191
|
await this.decorate(item);
|
|
172
192
|
}
|
|
@@ -182,11 +202,17 @@ class AbstractSqliteRepository {
|
|
|
182
202
|
}
|
|
183
203
|
async search(value, limit = 1000) {
|
|
184
204
|
let where = "";
|
|
205
|
+
let params = [];
|
|
185
206
|
if (value && this.searchFields.length > 0) {
|
|
186
|
-
|
|
207
|
+
const searchConditions = this.searchFields
|
|
208
|
+
.map(field => `${field} LIKE ?`)
|
|
209
|
+
.join(" OR ");
|
|
210
|
+
where = ` WHERE (${searchConditions})`;
|
|
211
|
+
params.push(...this.searchFields.map(() => `%${value}%`));
|
|
187
212
|
}
|
|
188
|
-
const items = this.db
|
|
189
|
-
|
|
213
|
+
const items = this.db
|
|
214
|
+
.prepare(`SELECT * FROM ${this.tableName} ${where} LIMIT ?`)
|
|
215
|
+
.all([...params, limit]);
|
|
190
216
|
for (const item of items) {
|
|
191
217
|
await this.decorate(item);
|
|
192
218
|
}
|
|
@@ -217,14 +243,24 @@ class AbstractSqliteRepository {
|
|
|
217
243
|
}
|
|
218
244
|
async findOne({ search = '', filters = [] }) {
|
|
219
245
|
let where = "";
|
|
246
|
+
let params = [];
|
|
247
|
+
// SEARCH
|
|
220
248
|
if (search && this.searchFields.length > 0) {
|
|
221
|
-
|
|
249
|
+
const searchConditions = this.searchFields
|
|
250
|
+
.map(field => `${field} LIKE ?`)
|
|
251
|
+
.join(" OR ");
|
|
252
|
+
where = ` WHERE (${searchConditions})`;
|
|
253
|
+
params.push(...this.searchFields.map(() => `%${search}%`));
|
|
222
254
|
}
|
|
255
|
+
// FILTERS
|
|
223
256
|
if (filters.length > 0) {
|
|
224
|
-
|
|
257
|
+
const result = SqlQueryFilter.applyFilters(where, filters);
|
|
258
|
+
where = result.where;
|
|
259
|
+
params.push(...result.params);
|
|
225
260
|
}
|
|
226
|
-
const item = this.db
|
|
227
|
-
|
|
261
|
+
const item = this.db
|
|
262
|
+
.prepare(`SELECT * FROM ${this.tableName} ${where} LIMIT 1`)
|
|
263
|
+
.get(params);
|
|
228
264
|
if (item) {
|
|
229
265
|
await this.decorate(item);
|
|
230
266
|
}
|
|
@@ -234,12 +270,13 @@ class AbstractSqliteRepository {
|
|
|
234
270
|
if (fields.length === 0) {
|
|
235
271
|
throw new Error("At least one field is required for groupBy");
|
|
236
272
|
}
|
|
237
|
-
// Construir la cláusula WHERE con los filtros
|
|
238
273
|
let where = "";
|
|
274
|
+
let params = [];
|
|
239
275
|
if (filters.length > 0) {
|
|
240
|
-
|
|
276
|
+
const result = SqlQueryFilter.applyFilters(where, filters);
|
|
277
|
+
where = result.where;
|
|
278
|
+
params.push(...result.params);
|
|
241
279
|
}
|
|
242
|
-
// Función para obtener el formato de fecha según SQLite
|
|
243
280
|
const getDateFormatSQL = (field, format) => {
|
|
244
281
|
const formats = {
|
|
245
282
|
'year': `strftime('%Y', ${field})`,
|
|
@@ -251,39 +288,53 @@ class AbstractSqliteRepository {
|
|
|
251
288
|
};
|
|
252
289
|
return formats[format] || formats['day'];
|
|
253
290
|
};
|
|
254
|
-
// Determinar si cada campo es de fecha
|
|
255
291
|
const isDateField = (field) => {
|
|
256
292
|
const tableField = this.tableFields.find(tf => tf.name === field);
|
|
257
|
-
return tableField
|
|
293
|
+
return tableField
|
|
294
|
+
? tableField.type === 'TEXT' && (field.includes('Date') || field.includes('date'))
|
|
295
|
+
: false;
|
|
258
296
|
};
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (
|
|
262
|
-
return
|
|
297
|
+
const isNumericField = (field) => {
|
|
298
|
+
const tableField = this.tableFields.find(tf => tf.name === field);
|
|
299
|
+
if (!tableField)
|
|
300
|
+
return false;
|
|
301
|
+
const type = tableField.type.toUpperCase();
|
|
302
|
+
return ['INTEGER', 'REAL', 'NUMERIC'].includes(type);
|
|
303
|
+
};
|
|
304
|
+
const selectParts = [];
|
|
305
|
+
const groupParts = [];
|
|
306
|
+
for (const field of fields) {
|
|
307
|
+
const tableField = this.tableFields.find(tf => tf.name === field);
|
|
308
|
+
if (!tableField) {
|
|
309
|
+
throw new Error(`Invalid field ${field}`);
|
|
310
|
+
}
|
|
311
|
+
if (isNumericField(field)) {
|
|
312
|
+
selectParts.push(`SUM(${field}) as ${field}`);
|
|
313
|
+
continue;
|
|
263
314
|
}
|
|
264
|
-
return field;
|
|
265
|
-
}).join(', ');
|
|
266
|
-
// Construir la cláusula GROUP BY
|
|
267
|
-
const groupByFields = fields.map(field => {
|
|
268
315
|
if (isDateField(field)) {
|
|
269
|
-
|
|
316
|
+
const formatted = getDateFormatSQL(field, dateFormat);
|
|
317
|
+
selectParts.push(`${formatted} as ${field}`);
|
|
318
|
+
groupParts.push(formatted);
|
|
319
|
+
continue;
|
|
270
320
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
321
|
+
selectParts.push(field);
|
|
322
|
+
groupParts.push(field);
|
|
323
|
+
}
|
|
324
|
+
const selectFields = selectParts.join(", ");
|
|
325
|
+
const groupByFields = groupParts.join(", ");
|
|
274
326
|
const query = `
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
327
|
+
SELECT ${selectFields}, COUNT(*) as count
|
|
328
|
+
FROM ${this.tableName}
|
|
329
|
+
${where}
|
|
330
|
+
${groupByFields ? `GROUP BY ${groupByFields}` : ''}
|
|
331
|
+
ORDER BY count DESC
|
|
332
|
+
`;
|
|
281
333
|
try {
|
|
282
|
-
const result = this.db.prepare(query).all();
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
334
|
+
const result = this.db.prepare(query).all(params);
|
|
335
|
+
// for (const item of result) {
|
|
336
|
+
// await this.decorate(item)
|
|
337
|
+
// }
|
|
287
338
|
return result;
|
|
288
339
|
}
|
|
289
340
|
catch (e) {
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.11.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": "^2.
|
|
25
|
+
"@drax/common-back": "^2.11.0",
|
|
26
26
|
"@drax/common-share": "^2.0.0",
|
|
27
27
|
"@drax/identity-share": "^2.0.0",
|
|
28
|
-
"@drax/media-back": "^2.
|
|
28
|
+
"@drax/media-back": "^2.11.0",
|
|
29
29
|
"@graphql-tools/load-files": "^7.0.0",
|
|
30
30
|
"@graphql-tools/merge": "^9.0.4",
|
|
31
31
|
"mongoose": "^8.23.0",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"tsc-alias": "^1.8.10",
|
|
47
47
|
"typescript": "^5.9.3"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "8919d31d4d9512e48ac461b0876dc85a5849daea"
|
|
50
50
|
}
|
|
@@ -114,7 +114,7 @@ class AbstractFastifyController<T, C, U> extends CommonController {
|
|
|
114
114
|
filterArray.forEach((filter) => {
|
|
115
115
|
const [field, operator, value] = filter.split(";")
|
|
116
116
|
|
|
117
|
-
if(field && operator && (value !== undefined && value !== '') ) {
|
|
117
|
+
if(field && operator && ( operator === 'empty' || (value !== undefined && value !== '')) ) {
|
|
118
118
|
filters.push({field, operator, value})
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const QueryFilterRegex = /^(?:[a-zA-Z0-9_.\-]+;(?:eq|like|ne|in|nin|gt|gte|lt|lte);[a-zA-Z0-9_.\-:\., áéíóúÁÉÍÓÚ]*)(?:\|[a-zA-Z0-9_.\-]+;(?:eq|like|ne|in|nin|gt|gte|lt|lte);[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_.\-]+;(?:eq|like|ne|in|nin|gt|gte|lt|lte|empty);[a-zA-Z0-9_.\-:\., áéíóúÁÉÍÓÚ]*)*$/
|
|
2
2
|
|
|
3
3
|
export default QueryFilterRegex
|
|
4
4
|
export {QueryFilterRegex}
|
|
@@ -184,7 +184,7 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
|
|
|
184
184
|
query['$or'] = this._searchFields.map(field => ({[field]: new RegExp(value.toString(), 'i')}))
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
MongooseQueryFilter.applyFilters(query, filters)
|
|
187
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model)
|
|
188
188
|
|
|
189
189
|
const items = await this._model
|
|
190
190
|
.find(query)
|
|
@@ -217,7 +217,7 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
MongooseQueryFilter.applyFilters(query, filters)
|
|
220
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model)
|
|
221
221
|
|
|
222
222
|
// console.log("Paginate Query", query)
|
|
223
223
|
|
|
@@ -249,7 +249,7 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
|
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
MongooseQueryFilter.applyFilters(query, filters)
|
|
252
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model)
|
|
253
253
|
|
|
254
254
|
const item = this._model
|
|
255
255
|
.findOne(query)
|
|
@@ -278,7 +278,7 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
|
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
MongooseQueryFilter.applyFilters(query, filters)
|
|
281
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model)
|
|
282
282
|
|
|
283
283
|
const sort = MongooseSort.applySort(orderBy, order)
|
|
284
284
|
const items = await this._model
|
|
@@ -309,9 +309,9 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
|
|
|
309
309
|
]
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
-
MongooseQueryFilter.applyFilters(query, filters)
|
|
312
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model)
|
|
313
313
|
|
|
314
|
-
const sort = MongooseSort.applySort(orderBy, order)
|
|
314
|
+
const sort = MongooseSort.applySort(orderBy, order,)
|
|
315
315
|
|
|
316
316
|
return this._model.find(query).limit(limit).sort(sort).cursor() as Cursor<T>;
|
|
317
317
|
}
|
|
@@ -320,8 +320,8 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
|
|
|
320
320
|
|
|
321
321
|
const query = {}
|
|
322
322
|
|
|
323
|
-
MongooseQueryFilter.applyFilters(query, filters)
|
|
324
|
-
|
|
323
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model)
|
|
324
|
+
// console.log("groupBy Query", query)
|
|
325
325
|
|
|
326
326
|
// Obtener el schema para identificar campos de referencia y fechas
|
|
327
327
|
const schema = this._model.schema
|
|
@@ -332,6 +332,13 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
|
|
|
332
332
|
const finalProjectFields: any = {count: 1, _id: 0}
|
|
333
333
|
const refFields = new Set<string>()
|
|
334
334
|
const dateFields = new Set<string>()
|
|
335
|
+
const numericFields = new Set<string>()
|
|
336
|
+
const groupFields: string[] = []
|
|
337
|
+
const numericInstances = new Set(['Number', 'Decimal128', 'Double', 'Int32', 'Long', 'BigInt'])
|
|
338
|
+
const totalGroupFields = fields.filter(field => {
|
|
339
|
+
const schemaPath = schema.path(field)
|
|
340
|
+
return !(schemaPath && numericInstances.has(schemaPath.instance))
|
|
341
|
+
}).length
|
|
335
342
|
|
|
336
343
|
// Función para obtener el formato de fecha según el nivel de granularidad
|
|
337
344
|
const getDateFormat = (field: string, format: string) => {
|
|
@@ -391,6 +398,15 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
|
|
|
391
398
|
fields.forEach(field => {
|
|
392
399
|
const schemaPath = schema.path(field)
|
|
393
400
|
|
|
401
|
+
// Verificar si el campo es numérico: se agregará con $sum y no formará parte de la clave de agrupación
|
|
402
|
+
if (schemaPath && numericInstances.has(schemaPath.instance)) {
|
|
403
|
+
numericFields.add(field)
|
|
404
|
+
finalProjectFields[field] = 1
|
|
405
|
+
return
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
groupFields.push(field)
|
|
409
|
+
|
|
394
410
|
// Verificar si el campo es de tipo Date
|
|
395
411
|
if (schemaPath && schemaPath.instance === 'Date') {
|
|
396
412
|
dateFields.add(field)
|
|
@@ -408,7 +424,7 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
|
|
|
408
424
|
const collectionName = refModelInstance.collection.name
|
|
409
425
|
|
|
410
426
|
// Determinar el campo local correcto según si es un solo campo o múltiples
|
|
411
|
-
const localField =
|
|
427
|
+
const localField = totalGroupFields === 1 ? '_id' : `_id.${fieldName}`
|
|
412
428
|
|
|
413
429
|
lookupStages.push({
|
|
414
430
|
$lookup: {
|
|
@@ -437,15 +453,15 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
|
|
|
437
453
|
})
|
|
438
454
|
|
|
439
455
|
// Construir la proyección final para campos de fecha
|
|
440
|
-
|
|
456
|
+
groupFields.forEach(field => {
|
|
441
457
|
if (dateFields.has(field)) {
|
|
442
|
-
if (
|
|
458
|
+
if (groupFields.length === 1) {
|
|
443
459
|
finalProjectFields[field] = `$_id`
|
|
444
460
|
} else {
|
|
445
461
|
finalProjectFields[field] = `$_id.${field}`
|
|
446
462
|
}
|
|
447
463
|
} else if (!refFields.has(field)) {
|
|
448
|
-
if (
|
|
464
|
+
if (groupFields.length === 1) {
|
|
449
465
|
finalProjectFields[field] = `$_id`
|
|
450
466
|
} else {
|
|
451
467
|
finalProjectFields[field] = `$_id.${field}`
|
|
@@ -453,13 +469,26 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
|
|
|
453
469
|
}
|
|
454
470
|
})
|
|
455
471
|
|
|
472
|
+
const groupStage: any = {
|
|
473
|
+
_id: null,
|
|
474
|
+
count: {$sum: 1}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
numericFields.forEach(field => {
|
|
478
|
+
groupStage[field] = {$sum: {$ifNull: [`$${field}`, 0]}}
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
if (groupFields.length === 1) {
|
|
482
|
+
const field = groupFields[0]
|
|
483
|
+
groupStage._id = dateFields.has(field) ? getDateFormat(field, dateFormat) : groupId[field]
|
|
484
|
+
} else if (groupFields.length > 1) {
|
|
485
|
+
groupStage._id = groupId
|
|
486
|
+
}
|
|
487
|
+
|
|
456
488
|
const pipeline: any[] = [
|
|
457
489
|
{$match: query},
|
|
458
490
|
{
|
|
459
|
-
$group:
|
|
460
|
-
_id: fields.length === 1 ? (dateFields.has(fields[0]) ? getDateFormat(fields[0], dateFormat) : `$${fields[0]}`) : groupId,
|
|
461
|
-
count: {$sum: 1}
|
|
462
|
-
}
|
|
491
|
+
$group: groupStage
|
|
463
492
|
}
|
|
464
493
|
]
|
|
465
494
|
|