@drax/crud-back 0.34.0 → 0.37.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/builders/CrudSchemaBuilder.js +25 -1
- package/dist/controllers/AbstractFastifyController.js +27 -2
- package/dist/index.js +2 -1
- package/dist/regexs/QueryFilterRegex.js +1 -1
- package/dist/repository/AbstractMongoRepository.js +145 -0
- package/dist/schemas/GroupBySchema.js +7 -0
- package/dist/services/AbstractService.js +3 -0
- package/package.json +6 -6
- package/src/builders/CrudSchemaBuilder.ts +34 -1
- package/src/controllers/AbstractFastifyController.ts +37 -2
- package/src/index.ts +2 -0
- package/src/regexs/QueryFilterRegex.ts +1 -1
- package/src/repository/AbstractMongoRepository.ts +196 -26
- package/src/schemas/GroupBySchema.ts +10 -0
- package/src/services/AbstractService.ts +5 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/types/builders/CrudSchemaBuilder.d.ts +177 -0
- package/types/builders/CrudSchemaBuilder.d.ts.map +1 -1
- package/types/controllers/AbstractFastifyController.d.ts +3 -0
- package/types/controllers/AbstractFastifyController.d.ts.map +1 -1
- package/types/index.d.ts +2 -1
- package/types/index.d.ts.map +1 -1
- package/types/repository/AbstractMongoRepository.d.ts +2 -1
- package/types/repository/AbstractMongoRepository.d.ts.map +1 -1
- package/types/schemas/GroupBySchema.d.ts +13 -0
- package/types/schemas/GroupBySchema.d.ts.map +1 -0
- package/types/services/AbstractService.d.ts +2 -1
- package/types/services/AbstractService.d.ts.map +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import z from 'zod';
|
|
2
2
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
3
|
-
import { IdParamSchema, DeleteBodyResponseSchema, PaginateQuerySchema, PaginateBodyResponseSchema, FindQuerySchema, SearchQuerySchema, FindByParamSchema, ErrorBodyResponseSchema, ValidationErrorBodyResponseSchema, ExportBodyResponseSchema } from '../index.js';
|
|
3
|
+
import { IdParamSchema, DeleteBodyResponseSchema, PaginateQuerySchema, PaginateBodyResponseSchema, FindQuerySchema, SearchQuerySchema, FindByParamSchema, ErrorBodyResponseSchema, ValidationErrorBodyResponseSchema, ExportBodyResponseSchema, GroupByQuerySchema } from '../index.js';
|
|
4
4
|
export class CrudSchemaBuilder {
|
|
5
5
|
constructor(entitySchema, entityCreateSchema, entityUpdateSchema, entityName, target = 'openApi3', tags = []) {
|
|
6
6
|
this.target = 'openApi3'; //"jsonSchema7" | "jsonSchema2019-09" | "openApi3" | "openAi"
|
|
@@ -29,6 +29,11 @@ export class CrudSchemaBuilder {
|
|
|
29
29
|
get jsonEntityArraySchema() {
|
|
30
30
|
return zodToJsonSchema(z.array(this.entitySchema), { target: this.target });
|
|
31
31
|
}
|
|
32
|
+
get jsonEntityGroupBySchema() {
|
|
33
|
+
return zodToJsonSchema(z.array(z.object({
|
|
34
|
+
count: z.number()
|
|
35
|
+
}).catchall(z.any())), { target: this.target });
|
|
36
|
+
}
|
|
32
37
|
get jsonExportBodyResponse() {
|
|
33
38
|
return zodToJsonSchema(ExportBodyResponseSchema, { target: this.target });
|
|
34
39
|
}
|
|
@@ -41,6 +46,9 @@ export class CrudSchemaBuilder {
|
|
|
41
46
|
get jsonFindQuerySchema() {
|
|
42
47
|
return zodToJsonSchema(FindQuerySchema, { target: this.target });
|
|
43
48
|
}
|
|
49
|
+
get jsonGroupByQuerySchema() {
|
|
50
|
+
return zodToJsonSchema(GroupByQuerySchema, { target: this.target });
|
|
51
|
+
}
|
|
44
52
|
get jsonSearchQuerySchema() {
|
|
45
53
|
return zodToJsonSchema(SearchQuerySchema, { target: this.target });
|
|
46
54
|
}
|
|
@@ -127,6 +135,22 @@ export class CrudSchemaBuilder {
|
|
|
127
135
|
}
|
|
128
136
|
};
|
|
129
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Get JSON schema for find entities
|
|
140
|
+
*/
|
|
141
|
+
get groupBySchema() {
|
|
142
|
+
return {
|
|
143
|
+
...(this.getTags),
|
|
144
|
+
query: this.jsonGroupByQuerySchema,
|
|
145
|
+
response: {
|
|
146
|
+
200: this.jsonEntityGroupBySchema,
|
|
147
|
+
400: this.jsonErrorBodyResponse,
|
|
148
|
+
401: this.jsonErrorBodyResponse,
|
|
149
|
+
403: this.jsonErrorBodyResponse,
|
|
150
|
+
500: this.jsonErrorBodyResponse
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
130
154
|
/**
|
|
131
155
|
* Get JSON schema for find entities
|
|
132
156
|
*/
|
|
@@ -41,7 +41,9 @@ class AbstractFastifyController extends CommonController {
|
|
|
41
41
|
const filters = [];
|
|
42
42
|
filterArray.forEach((filter) => {
|
|
43
43
|
const [field, operator, value] = filter.split(";");
|
|
44
|
-
|
|
44
|
+
if (field && operator && (value !== undefined && value !== '')) {
|
|
45
|
+
filters.push({ field, operator, value });
|
|
46
|
+
}
|
|
45
47
|
});
|
|
46
48
|
return filters;
|
|
47
49
|
}
|
|
@@ -330,7 +332,7 @@ class AbstractFastifyController extends CommonController {
|
|
|
330
332
|
const search = request.query.search;
|
|
331
333
|
const filters = this.parseFilters(request.query.filters);
|
|
332
334
|
this.applyUserAndTenantFilters(filters, request.rbac);
|
|
333
|
-
//console.log("
|
|
335
|
+
// console.log("paginate filters",filters)
|
|
334
336
|
let paginateResult = await this.service.paginate({ page, limit, orderBy, order, search, filters });
|
|
335
337
|
return paginateResult;
|
|
336
338
|
}
|
|
@@ -380,6 +382,29 @@ class AbstractFastifyController extends CommonController {
|
|
|
380
382
|
this.handleError(e, reply);
|
|
381
383
|
}
|
|
382
384
|
}
|
|
385
|
+
async groupBy(request, reply) {
|
|
386
|
+
try {
|
|
387
|
+
request.rbac.assertPermission(this.permission.View);
|
|
388
|
+
const fields = request.query.fields ?
|
|
389
|
+
request.query.fields.split(',').map(f => f.trim()).filter(f => f.length > 0) :
|
|
390
|
+
[];
|
|
391
|
+
const dateFormat = request.query.dateFormat ? request.query.dateFormat : 'day';
|
|
392
|
+
if (fields.length === 0) {
|
|
393
|
+
throw new BadRequestError('At least one field is required for grouping');
|
|
394
|
+
}
|
|
395
|
+
const filters = this.parseFilters(request.query.filters);
|
|
396
|
+
this.applyUserAndTenantFilters(filters, request.rbac);
|
|
397
|
+
const result = await this.service.groupBy({ fields, filters, dateFormat });
|
|
398
|
+
// console.log("groupby fields",fields)
|
|
399
|
+
// console.log("groupby dateFormat",dateFormat)
|
|
400
|
+
// console.log("groupby filters",filters)
|
|
401
|
+
// console.log("groupby result",result)
|
|
402
|
+
return result;
|
|
403
|
+
}
|
|
404
|
+
catch (e) {
|
|
405
|
+
this.handleError(e, reply);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
383
408
|
}
|
|
384
409
|
export default AbstractFastifyController;
|
|
385
410
|
export { AbstractFastifyController };
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { PaginateBodyResponseSchema, PaginateQuerySchema } from "./schemas/Pagin
|
|
|
10
10
|
import { FindQuerySchema } from "./schemas/FindSchema.js";
|
|
11
11
|
import { SearchQuerySchema } from "./schemas/SearchSchema.js";
|
|
12
12
|
import { FindByParamSchema } from "./schemas/FindBySchema.js";
|
|
13
|
+
import { GroupByQuerySchema } from "./schemas/GroupBySchema.js";
|
|
13
14
|
import { ExportBodyResponseSchema } from "./schemas/ExportBodyResponseSchema.js";
|
|
14
15
|
import { ErrorBodyResponseSchema, ValidationErrorBodyResponseSchema } from "./schemas/ErrorBodyResponseSchema.js";
|
|
15
16
|
import { CrudSchemaBuilder } from "./builders/CrudSchemaBuilder.js";
|
|
@@ -17,6 +18,6 @@ export {
|
|
|
17
18
|
//CRUD
|
|
18
19
|
AbstractMongoRepository, AbstractSqliteRepository, AbstractService, AbstractFastifyController,
|
|
19
20
|
//Schemas
|
|
20
|
-
IdParamSchema, DeleteBodyResponseSchema, PaginateBodyResponseSchema, PaginateQuerySchema, FindQuerySchema, SearchQuerySchema, FindByParamSchema, ErrorBodyResponseSchema, ValidationErrorBodyResponseSchema, ExportBodyResponseSchema,
|
|
21
|
+
IdParamSchema, DeleteBodyResponseSchema, PaginateBodyResponseSchema, PaginateQuerySchema, FindQuerySchema, GroupByQuerySchema, SearchQuerySchema, FindByParamSchema, ErrorBodyResponseSchema, ValidationErrorBodyResponseSchema, ExportBodyResponseSchema,
|
|
21
22
|
//Builder
|
|
22
23
|
CrudSchemaBuilder, };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
const QueryFilterRegex = /^(?:[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);[a-zA-Z0-9_.\-:\., áéíóúÁÉÍÓÚ]*)(?:\|[a-zA-Z0-9_.\-]+;(?:eq|like|ne|in|nin|gt|gte|lt|lte);[a-zA-Z0-9_.\-:\., áéíóúÁÉÍÓÚ]*)*$/;
|
|
2
2
|
export default QueryFilterRegex;
|
|
3
3
|
export { QueryFilterRegex };
|
|
@@ -215,6 +215,151 @@ class AbstractMongoRepository {
|
|
|
215
215
|
const sort = MongooseSort.applySort(orderBy, order);
|
|
216
216
|
return this._model.find(query).limit(limit).sort(sort).cursor();
|
|
217
217
|
}
|
|
218
|
+
async groupBy({ fields = [], filters = [], dateFormat = 'day' }) {
|
|
219
|
+
const query = {};
|
|
220
|
+
MongooseQueryFilter.applyFilters(query, filters);
|
|
221
|
+
// Obtener el schema para identificar campos de referencia y fechas
|
|
222
|
+
const schema = this._model.schema;
|
|
223
|
+
// Construir el objeto de agrupación dinámicamente
|
|
224
|
+
const groupId = {};
|
|
225
|
+
const lookupStages = [];
|
|
226
|
+
const finalProjectFields = { count: 1, _id: 0 };
|
|
227
|
+
const refFields = new Set();
|
|
228
|
+
const dateFields = new Set();
|
|
229
|
+
// Función para obtener el formato de fecha según el nivel de granularidad
|
|
230
|
+
const getDateFormat = (field, format) => {
|
|
231
|
+
const formats = {
|
|
232
|
+
'year': {
|
|
233
|
+
$dateFromParts: {
|
|
234
|
+
year: { $year: `$${field}` },
|
|
235
|
+
month: 1,
|
|
236
|
+
day: 1
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
'month': {
|
|
240
|
+
$dateFromParts: {
|
|
241
|
+
year: { $year: `$${field}` },
|
|
242
|
+
month: { $month: `$${field}` },
|
|
243
|
+
day: 1
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
'day': {
|
|
247
|
+
$dateFromParts: {
|
|
248
|
+
year: { $year: `$${field}` },
|
|
249
|
+
month: { $month: `$${field}` },
|
|
250
|
+
day: { $dayOfMonth: `$${field}` }
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
'hour': {
|
|
254
|
+
$dateFromParts: {
|
|
255
|
+
year: { $year: `$${field}` },
|
|
256
|
+
month: { $month: `$${field}` },
|
|
257
|
+
day: { $dayOfMonth: `$${field}` },
|
|
258
|
+
hour: { $hour: `$${field}` }
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
'minute': {
|
|
262
|
+
$dateFromParts: {
|
|
263
|
+
year: { $year: `$${field}` },
|
|
264
|
+
month: { $month: `$${field}` },
|
|
265
|
+
day: { $dayOfMonth: `$${field}` },
|
|
266
|
+
hour: { $hour: `$${field}` },
|
|
267
|
+
minute: { $minute: `$${field}` }
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
'second': {
|
|
271
|
+
$dateFromParts: {
|
|
272
|
+
year: { $year: `$${field}` },
|
|
273
|
+
month: { $month: `$${field}` },
|
|
274
|
+
day: { $dayOfMonth: `$${field}` },
|
|
275
|
+
hour: { $hour: `$${field}` },
|
|
276
|
+
minute: { $minute: `$${field}` },
|
|
277
|
+
second: { $second: `$${field}` }
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
return formats[format] || formats['day'];
|
|
282
|
+
};
|
|
283
|
+
fields.forEach(field => {
|
|
284
|
+
const schemaPath = schema.path(field);
|
|
285
|
+
// Verificar si el campo es de tipo Date
|
|
286
|
+
if (schemaPath && schemaPath.instance === 'Date') {
|
|
287
|
+
dateFields.add(field);
|
|
288
|
+
groupId[field] = getDateFormat(field, dateFormat);
|
|
289
|
+
}
|
|
290
|
+
// Verificar si el campo es una referencia
|
|
291
|
+
else if (schemaPath && schemaPath.options && schemaPath.options.ref) {
|
|
292
|
+
const refModel = schemaPath.options.ref;
|
|
293
|
+
const fieldName = field;
|
|
294
|
+
refFields.add(field);
|
|
295
|
+
// Obtener el modelo referenciado y su nombre de colección real
|
|
296
|
+
const refModelInstance = mongoose.model(refModel);
|
|
297
|
+
const collectionName = refModelInstance.collection.name;
|
|
298
|
+
// Determinar el campo local correcto según si es un solo campo o múltiples
|
|
299
|
+
const localField = fields.length === 1 ? '_id' : `_id.${fieldName}`;
|
|
300
|
+
lookupStages.push({
|
|
301
|
+
$lookup: {
|
|
302
|
+
from: collectionName,
|
|
303
|
+
localField: localField,
|
|
304
|
+
foreignField: '_id',
|
|
305
|
+
as: `${fieldName}_populated`
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
// Unwind para convertir el array en objeto único
|
|
309
|
+
lookupStages.push({
|
|
310
|
+
$unwind: {
|
|
311
|
+
path: `$${fieldName}_populated`,
|
|
312
|
+
preserveNullAndEmptyArrays: true
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
// En la proyección final, usar el objeto poblado
|
|
316
|
+
finalProjectFields[field] = `$${fieldName}_populated`;
|
|
317
|
+
groupId[field] = `$${field}`;
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
// Si no es una referencia ni fecha, usar el valor directo
|
|
321
|
+
groupId[field] = `$${field}`;
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
// Construir la proyección final para campos de fecha
|
|
325
|
+
fields.forEach(field => {
|
|
326
|
+
if (dateFields.has(field)) {
|
|
327
|
+
if (fields.length === 1) {
|
|
328
|
+
finalProjectFields[field] = `$_id`;
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
finalProjectFields[field] = `$_id.${field}`;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
else if (!refFields.has(field)) {
|
|
335
|
+
if (fields.length === 1) {
|
|
336
|
+
finalProjectFields[field] = `$_id`;
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
finalProjectFields[field] = `$_id.${field}`;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
const pipeline = [
|
|
344
|
+
{ $match: query },
|
|
345
|
+
{
|
|
346
|
+
$group: {
|
|
347
|
+
_id: fields.length === 1 ? (dateFields.has(fields[0]) ? getDateFormat(fields[0], dateFormat) : `$${fields[0]}`) : groupId,
|
|
348
|
+
count: { $sum: 1 }
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
];
|
|
352
|
+
// Solo agregar lookups si hay campos de referencia
|
|
353
|
+
if (lookupStages.length > 0) {
|
|
354
|
+
pipeline.push(...lookupStages);
|
|
355
|
+
}
|
|
356
|
+
pipeline.push({
|
|
357
|
+
$project: finalProjectFields
|
|
358
|
+
}, { $sort: { count: -1 } });
|
|
359
|
+
console.log("pipeline", JSON.stringify(pipeline, null, 2));
|
|
360
|
+
const result = await this._model.aggregate(pipeline).exec();
|
|
361
|
+
return result;
|
|
362
|
+
}
|
|
218
363
|
}
|
|
219
364
|
export default AbstractMongoRepository;
|
|
220
365
|
export { AbstractMongoRepository };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import QueryFilterRegex from "../regexs/QueryFilterRegex.js";
|
|
3
|
+
const GroupByQuerySchema = z.object({
|
|
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|..."),
|
|
6
|
+
});
|
|
7
|
+
export { GroupByQuerySchema };
|
|
@@ -200,6 +200,9 @@ class AbstractService {
|
|
|
200
200
|
throw e;
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
|
+
async groupBy({ fields = [], filters = [], dateFormat = 'day' }) {
|
|
204
|
+
return await this._repository.groupBy({ fields, filters, dateFormat });
|
|
205
|
+
}
|
|
203
206
|
async export({ format = 'JSON', headers = [], headersTranslate = [], separator = ';', fileName = 'export', orderBy = '', order = false, search = '', filters = [] }, destinationPath) {
|
|
204
207
|
try {
|
|
205
208
|
let cursor;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.37.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": "^0.
|
|
26
|
-
"@drax/common-share": "^0.
|
|
27
|
-
"@drax/identity-share": "^0.
|
|
28
|
-
"@drax/media-back": "^0.
|
|
25
|
+
"@drax/common-back": "^0.37.0",
|
|
26
|
+
"@drax/common-share": "^0.37.0",
|
|
27
|
+
"@drax/identity-share": "^0.37.0",
|
|
28
|
+
"@drax/media-back": "^0.37.0",
|
|
29
29
|
"@graphql-tools/load-files": "^7.0.0",
|
|
30
30
|
"@graphql-tools/merge": "^9.0.4",
|
|
31
31
|
"mongoose": "^8.6.3",
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
"tsc-alias": "^1.8.10",
|
|
46
46
|
"typescript": "^5.6.2"
|
|
47
47
|
},
|
|
48
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "0bca48d4b686fd9536a78d84f5befe6801238000"
|
|
49
49
|
}
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
FindByParamSchema,
|
|
12
12
|
ErrorBodyResponseSchema,
|
|
13
13
|
ValidationErrorBodyResponseSchema,
|
|
14
|
-
ExportBodyResponseSchema
|
|
14
|
+
ExportBodyResponseSchema,
|
|
15
|
+
GroupByQuerySchema
|
|
15
16
|
} from '../index.js';
|
|
16
17
|
|
|
17
18
|
export class CrudSchemaBuilder<T extends z.ZodObject<z.ZodRawShape>, TCreate extends z.ZodObject<z.ZodRawShape>, TUpdate extends z.ZodObject<z.ZodRawShape>> {
|
|
@@ -54,6 +55,17 @@ export class CrudSchemaBuilder<T extends z.ZodObject<z.ZodRawShape>, TCreate ext
|
|
|
54
55
|
return zodToJsonSchema(z.array(this.entitySchema), {target: this.target})
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
get jsonEntityGroupBySchema() {
|
|
59
|
+
return zodToJsonSchema(
|
|
60
|
+
z.array(
|
|
61
|
+
z.object({
|
|
62
|
+
count: z.number()
|
|
63
|
+
}).catchall(z.any())
|
|
64
|
+
),
|
|
65
|
+
{target: this.target}
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
57
69
|
get jsonExportBodyResponse() {
|
|
58
70
|
return zodToJsonSchema(ExportBodyResponseSchema, {target: this.target})
|
|
59
71
|
}
|
|
@@ -70,6 +82,10 @@ export class CrudSchemaBuilder<T extends z.ZodObject<z.ZodRawShape>, TCreate ext
|
|
|
70
82
|
return zodToJsonSchema(FindQuerySchema, {target: this.target})
|
|
71
83
|
}
|
|
72
84
|
|
|
85
|
+
get jsonGroupByQuerySchema(){
|
|
86
|
+
return zodToJsonSchema(GroupByQuerySchema, {target: this.target})
|
|
87
|
+
}
|
|
88
|
+
|
|
73
89
|
get jsonSearchQuerySchema(){
|
|
74
90
|
return zodToJsonSchema(SearchQuerySchema, {target: this.target})
|
|
75
91
|
}
|
|
@@ -166,6 +182,23 @@ export class CrudSchemaBuilder<T extends z.ZodObject<z.ZodRawShape>, TCreate ext
|
|
|
166
182
|
}
|
|
167
183
|
}
|
|
168
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Get JSON schema for find entities
|
|
187
|
+
*/
|
|
188
|
+
get groupBySchema() {
|
|
189
|
+
return {
|
|
190
|
+
...(this.getTags),
|
|
191
|
+
query: this.jsonGroupByQuerySchema,
|
|
192
|
+
response: {
|
|
193
|
+
200: this.jsonEntityGroupBySchema,
|
|
194
|
+
400: this.jsonErrorBodyResponse,
|
|
195
|
+
401: this.jsonErrorBodyResponse,
|
|
196
|
+
403: this.jsonErrorBodyResponse,
|
|
197
|
+
500: this.jsonErrorBodyResponse
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
169
202
|
/**
|
|
170
203
|
* Get JSON schema for find entities
|
|
171
204
|
*/
|
|
@@ -39,6 +39,8 @@ type CustomRequest = FastifyRequest<{
|
|
|
39
39
|
headersTranslate?: string
|
|
40
40
|
separator?: string
|
|
41
41
|
fileName?: string
|
|
42
|
+
fields?: string
|
|
43
|
+
dateFormat?: 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second'
|
|
42
44
|
}
|
|
43
45
|
}>
|
|
44
46
|
|
|
@@ -97,7 +99,11 @@ class AbstractFastifyController<T, C, U> extends CommonController {
|
|
|
97
99
|
const filters: IDraxFieldFilter[] = []
|
|
98
100
|
filterArray.forEach((filter) => {
|
|
99
101
|
const [field, operator, value] = filter.split(";")
|
|
100
|
-
|
|
102
|
+
|
|
103
|
+
if(field && operator && (value !== undefined && value !== '') ) {
|
|
104
|
+
filters.push({field, operator, value})
|
|
105
|
+
}
|
|
106
|
+
|
|
101
107
|
})
|
|
102
108
|
return filters
|
|
103
109
|
} catch (e) {
|
|
@@ -449,7 +455,7 @@ class AbstractFastifyController<T, C, U> extends CommonController {
|
|
|
449
455
|
const filters: IDraxFieldFilter[] = this.parseFilters(request.query.filters)
|
|
450
456
|
this.applyUserAndTenantFilters(filters, request.rbac);
|
|
451
457
|
|
|
452
|
-
//console.log("
|
|
458
|
+
// console.log("paginate filters",filters)
|
|
453
459
|
|
|
454
460
|
let paginateResult = await this.service.paginate({page, limit, orderBy, order, search, filters})
|
|
455
461
|
return paginateResult
|
|
@@ -509,6 +515,35 @@ class AbstractFastifyController<T, C, U> extends CommonController {
|
|
|
509
515
|
this.handleError(e, reply)
|
|
510
516
|
}
|
|
511
517
|
}
|
|
518
|
+
|
|
519
|
+
async groupBy(request: CustomRequest, reply: FastifyReply) {
|
|
520
|
+
try {
|
|
521
|
+
request.rbac.assertPermission(this.permission.View)
|
|
522
|
+
|
|
523
|
+
const fields: string[] = request.query.fields ?
|
|
524
|
+
request.query.fields.split(',').map(f => f.trim()).filter(f => f.length > 0) :
|
|
525
|
+
[]
|
|
526
|
+
|
|
527
|
+
const dateFormat = request.query.dateFormat ? request.query.dateFormat : 'day'
|
|
528
|
+
|
|
529
|
+
if (fields.length === 0) {
|
|
530
|
+
throw new BadRequestError('At least one field is required for grouping')
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const filters: IDraxFieldFilter[] = this.parseFilters(request.query.filters)
|
|
534
|
+
this.applyUserAndTenantFilters(filters, request.rbac)
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
const result = await this.service.groupBy({fields, filters, dateFormat})
|
|
538
|
+
// console.log("groupby fields",fields)
|
|
539
|
+
// console.log("groupby dateFormat",dateFormat)
|
|
540
|
+
// console.log("groupby filters",filters)
|
|
541
|
+
// console.log("groupby result",result)
|
|
542
|
+
return result
|
|
543
|
+
} catch (e) {
|
|
544
|
+
this.handleError(e, reply)
|
|
545
|
+
}
|
|
546
|
+
}
|
|
512
547
|
}
|
|
513
548
|
|
|
514
549
|
export default AbstractFastifyController;
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {PaginateBodyResponseSchema, PaginateQuerySchema} from "./schemas/Paginat
|
|
|
12
12
|
import {FindQuerySchema} from "./schemas/FindSchema.js"
|
|
13
13
|
import {SearchQuerySchema} from "./schemas/SearchSchema.js"
|
|
14
14
|
import {FindByParamSchema} from "./schemas/FindBySchema.js"
|
|
15
|
+
import {GroupByQuerySchema} from "./schemas/GroupBySchema.js"
|
|
15
16
|
import {ExportBodyResponseSchema} from "./schemas/ExportBodyResponseSchema.js"
|
|
16
17
|
import {ErrorBodyResponseSchema, ValidationErrorBodyResponseSchema} from "./schemas/ErrorBodyResponseSchema.js"
|
|
17
18
|
import {CrudSchemaBuilder} from "./builders/CrudSchemaBuilder.js";
|
|
@@ -35,6 +36,7 @@ export {
|
|
|
35
36
|
PaginateBodyResponseSchema,
|
|
36
37
|
PaginateQuerySchema,
|
|
37
38
|
FindQuerySchema,
|
|
39
|
+
GroupByQuerySchema,
|
|
38
40
|
SearchQuerySchema,
|
|
39
41
|
FindByParamSchema,
|
|
40
42
|
ErrorBodyResponseSchema,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const QueryFilterRegex = /^(?:[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);[a-zA-Z0-9_.\-:\., áéíóúÁÉÍÓÚ]*)(?:\|[a-zA-Z0-9_.\-]+;(?:eq|like|ne|in|nin|gt|gte|lt|lte);[a-zA-Z0-9_.\-:\., áéíóúÁÉÍÓÚ]*)*$/
|
|
2
2
|
|
|
3
3
|
export default QueryFilterRegex
|
|
4
4
|
export {QueryFilterRegex}
|