@drax/crud-back 2.9.0 → 3.0.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 +48 -25
- package/dist/regexs/QueryFilterRegex.js +1 -1
- package/dist/repository/AbstractMongoRepository.js +19 -15
- package/dist/repository/AbstractSqliteRepository.js +142 -60
- package/dist/services/AbstractService.js +9 -6
- package/package.json +8 -7
- package/src/controllers/AbstractFastifyController.ts +57 -29
- package/src/regexs/QueryFilterRegex.ts +1 -1
- package/src/repository/AbstractMongoRepository.ts +25 -16
- package/src/repository/AbstractSqliteRepository.ts +204 -66
- package/src/services/AbstractService.ts +12 -6
- package/test/_mocks/MockRepository.ts +1 -1
- package/test/controllers/PersonController.test.ts +547 -0
- package/test/people/controllers/CountryController.ts +40 -0
- package/test/people/controllers/LanguageController.ts +29 -0
- package/test/people/controllers/PersonController.ts +29 -0
- package/test/people/factory/services/CountryServiceFactory.ts +41 -0
- package/test/people/factory/services/LanguageServiceFactory.ts +41 -0
- package/test/people/factory/services/PersonServiceFactory.ts +41 -0
- package/test/people/interfaces/ICountry.ts +28 -0
- package/test/people/interfaces/ICountryRepository.ts +11 -0
- package/test/people/interfaces/ILanguage.ts +32 -0
- package/test/people/interfaces/ILanguageRepository.ts +11 -0
- package/test/people/interfaces/IPerson.ts +58 -0
- package/test/people/interfaces/IPersonRepository.ts +11 -0
- package/test/people/models/CountryModel.ts +38 -0
- package/test/people/models/LanguageModel.ts +40 -0
- package/test/people/models/PersonModel.ts +61 -0
- package/test/people/permissions/CountryPermissions.ts +14 -0
- package/test/people/permissions/LanguagePermissions.ts +14 -0
- package/test/people/permissions/PersonPermissions.ts +18 -0
- package/test/people/repository/mongo/CountryMongoRepository.ts +22 -0
- package/test/people/repository/mongo/LanguageMongoRepository.ts +22 -0
- package/test/people/repository/mongo/PersonMongoRepository.ts +22 -0
- package/test/people/repository/sqlite/CountrySqliteRepository.ts +33 -0
- package/test/people/repository/sqlite/LanguageSqliteRepository.ts +37 -0
- package/test/people/repository/sqlite/PersonSqliteRepository.ts +42 -0
- package/test/people/routes/CountryRoutes.ts +34 -0
- package/test/people/routes/LanguageRoutes.ts +34 -0
- package/test/people/routes/PersonRoutes.ts +40 -0
- package/test/people/schemas/CountrySchema.ts +22 -0
- package/test/people/schemas/LanguageSchema.ts +22 -0
- package/test/people/schemas/PersonSchema.ts +42 -0
- package/test/people/services/CountryService.ts +20 -0
- package/test/people/services/LanguageService.ts +20 -0
- package/test/people/services/PersonService.ts +20 -0
- package/test/services/AbstractService.test.ts +11 -7
- package/test/setup/MongoInMemory.ts +56 -0
- package/test/setup/TestSetup.ts +344 -0
- package/test/setup/data/admin-role.ts +13 -0
- package/test/setup/data/basic-user.ts +14 -0
- package/test/setup/data/one-tenant.ts +6 -0
- package/test/setup/data/restricted-role.ts +16 -0
- package/test/setup/data/root-user.ts +14 -0
- package/test/setup/data/tenant-one-user.ts +13 -0
- package/test/setup/data/tenant-two-user.ts +14 -0
- package/test/setup/data/two-tenant.ts +6 -0
- package/test/workers/ExportCsvWorker.test.ts +1 -1
- package/tsconfig.json +2 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/types/controllers/AbstractFastifyController.d.ts.map +1 -1
- package/types/regexs/QueryFilterRegex.d.ts.map +1 -1
- package/types/repository/AbstractMongoRepository.d.ts +3 -3
- package/types/repository/AbstractMongoRepository.d.ts.map +1 -1
- package/types/repository/AbstractSqliteRepository.d.ts +6 -5
- package/types/repository/AbstractSqliteRepository.d.ts.map +1 -1
- package/types/services/AbstractService.d.ts +4 -2
- package/types/services/AbstractService.d.ts.map +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CommonConfig, DraxConfig, LimitError, NotFoundError, BadRequestError, CommonController } from "@drax/common-back";
|
|
1
|
+
import { CommonConfig, DraxConfig, LimitError, NotFoundError, BadRequestError, CommonController, setNestedValue } from "@drax/common-back";
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import QueryFilterRegex from "../regexs/QueryFilterRegex.js";
|
|
4
4
|
import CrudEventEmitter from "../events/CrudEventEmitter.js";
|
|
@@ -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
|
});
|
|
@@ -72,15 +72,26 @@ class AbstractFastifyController extends CommonController {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
assertTenant(item, rbac) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
//Si tenantAssert esta habilitado y si ademas el usuario pertenece a un tenant
|
|
76
|
+
if (this.tenantAssert && rbac.hasTenant) {
|
|
77
|
+
//Si esta populado
|
|
78
|
+
if (item[this.tenantField]?._id) {
|
|
79
|
+
rbac.assertTenantId(item[this.tenantField]._id.toString()); //
|
|
80
|
+
//Si esta crudo
|
|
81
|
+
}
|
|
82
|
+
else if (item[this.tenantField]) {
|
|
83
|
+
rbac.assertTenantId(item[this.tenantField].toString());
|
|
84
|
+
}
|
|
78
85
|
}
|
|
79
86
|
}
|
|
80
87
|
assertUser(item, rbac) {
|
|
81
88
|
if (this.userAssert) {
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
if (item[this.userField]?._id) {
|
|
90
|
+
rbac.assertUserId(item[this.userField]._id.toString());
|
|
91
|
+
}
|
|
92
|
+
else if (item[this.userField]) {
|
|
93
|
+
rbac.assertUserId(item[this.userField].toString());
|
|
94
|
+
}
|
|
84
95
|
}
|
|
85
96
|
}
|
|
86
97
|
assertUserAndTenant(item, rbac) {
|
|
@@ -89,10 +100,10 @@ class AbstractFastifyController extends CommonController {
|
|
|
89
100
|
}
|
|
90
101
|
applyUserAndTenantSetters(payload, rbac) {
|
|
91
102
|
if (this.tenantSetter && rbac.tenantId) {
|
|
92
|
-
payload
|
|
103
|
+
setNestedValue(payload, this.tenantField, rbac.tenantId);
|
|
93
104
|
}
|
|
94
105
|
if (this.userSetter && rbac.userId) {
|
|
95
|
-
payload
|
|
106
|
+
setNestedValue(payload, this.userField, rbac.userId);
|
|
96
107
|
}
|
|
97
108
|
}
|
|
98
109
|
extractRequestData(request) {
|
|
@@ -212,17 +223,21 @@ class AbstractFastifyController extends CommonController {
|
|
|
212
223
|
const id = request.params.id;
|
|
213
224
|
const payload = request.body;
|
|
214
225
|
let preItem = await this.service.findById(id);
|
|
226
|
+
if (!preItem) {
|
|
227
|
+
reply.statusCode = 404;
|
|
228
|
+
reply.send({ error: 'NOT_FOUND' });
|
|
229
|
+
}
|
|
215
230
|
if (!request.rbac.hasSomePermission([this.permission.All, this.permission.UpdateAll])) {
|
|
216
|
-
|
|
217
|
-
reply.statusCode = 404;
|
|
218
|
-
reply.send({ error: 'NOT_FOUND' });
|
|
219
|
-
}
|
|
231
|
+
//Si assertUser habilitado y si el usuario no tiene UpdateAll/All, solo puede modificar sus propios registros
|
|
220
232
|
this.assertUser(preItem, request.rbac);
|
|
221
233
|
}
|
|
222
|
-
//
|
|
234
|
+
//Si assertTenant habilitado y si usuario tiene tenant, solo puede modificar registros de su tenant
|
|
235
|
+
this.assertTenant(preItem, request.rbac);
|
|
236
|
+
//Definido el tenant en create no debe modificarse en un update
|
|
223
237
|
if (this.tenantSetter) {
|
|
224
238
|
delete payload[this.tenantField];
|
|
225
239
|
}
|
|
240
|
+
//Definido el user en create no debe modificarse en un update
|
|
226
241
|
if (this.userSetter) {
|
|
227
242
|
delete payload[this.userField];
|
|
228
243
|
}
|
|
@@ -255,17 +270,21 @@ class AbstractFastifyController extends CommonController {
|
|
|
255
270
|
const id = request.params.id;
|
|
256
271
|
const payload = request.body;
|
|
257
272
|
let preItem = await this.service.findById(id);
|
|
273
|
+
if (!preItem) {
|
|
274
|
+
reply.statusCode = 404;
|
|
275
|
+
reply.send({ error: 'NOT_FOUND' });
|
|
276
|
+
}
|
|
258
277
|
if (!request.rbac.hasSomePermission([this.permission.All, this.permission.UpdateAll])) {
|
|
259
|
-
|
|
260
|
-
reply.statusCode = 404;
|
|
261
|
-
reply.send({ error: 'NOT_FOUND' });
|
|
262
|
-
}
|
|
278
|
+
//Si assertUser habilitado y si el usuario no tiene UpdateAll/All, solo puede modificar sus propios registros
|
|
263
279
|
this.assertUser(preItem, request.rbac);
|
|
264
280
|
}
|
|
265
|
-
//
|
|
281
|
+
//Si assertTenant habilitado y si usuario tiene tenant, solo puede modificar registros de su tenant
|
|
282
|
+
this.assertTenant(preItem, request.rbac);
|
|
283
|
+
//Definido el tenant en el create no debe modificarse en un update
|
|
266
284
|
if (this.tenantSetter) {
|
|
267
285
|
delete payload[this.tenantField];
|
|
268
286
|
}
|
|
287
|
+
//Definido el user en el create no debe modificarse en un update
|
|
269
288
|
if (this.userSetter) {
|
|
270
289
|
delete payload[this.userField];
|
|
271
290
|
}
|
|
@@ -404,10 +423,12 @@ class AbstractFastifyController extends CommonController {
|
|
|
404
423
|
const limit = this.defaultLimit;
|
|
405
424
|
const field = request.params.field;
|
|
406
425
|
const value = request.params.value;
|
|
407
|
-
let
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
426
|
+
let filters = [];
|
|
427
|
+
this.applyUserAndTenantFilters(filters, request.rbac);
|
|
428
|
+
let items = await this.service.findBy(field, value, limit, filters);
|
|
429
|
+
// for (let item of items) {
|
|
430
|
+
// this.assertUserAndTenant(item, request.rbac)
|
|
431
|
+
// }
|
|
411
432
|
return items;
|
|
412
433
|
}
|
|
413
434
|
catch (e) {
|
|
@@ -423,8 +444,10 @@ class AbstractFastifyController extends CommonController {
|
|
|
423
444
|
}
|
|
424
445
|
const field = request.params.field;
|
|
425
446
|
const value = request.params.value;
|
|
426
|
-
let
|
|
427
|
-
this.
|
|
447
|
+
let filters = [];
|
|
448
|
+
this.applyUserAndTenantFilters(filters, request.rbac);
|
|
449
|
+
let item = await this.service.findOneBy(field, value, filters);
|
|
450
|
+
// this.assertUserAndTenant(item, request.rbac);
|
|
428
451
|
return item;
|
|
429
452
|
}
|
|
430
453
|
catch (e) {
|
|
@@ -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 };
|
|
@@ -89,28 +89,32 @@ class AbstractMongoRepository {
|
|
|
89
89
|
.exec();
|
|
90
90
|
return item;
|
|
91
91
|
}
|
|
92
|
-
async findByIds(ids) {
|
|
92
|
+
async findByIds(ids, filters = []) {
|
|
93
93
|
ids.map(id => this.assertId(id));
|
|
94
|
+
const query = { _id: { $in: ids } };
|
|
95
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model);
|
|
94
96
|
const items = await this._model
|
|
95
|
-
.find(
|
|
97
|
+
.find(query)
|
|
96
98
|
.populate(this._populateFields)
|
|
97
99
|
.lean(this._lean)
|
|
98
100
|
.exec();
|
|
99
101
|
return items;
|
|
100
102
|
}
|
|
101
|
-
async findOneBy(field, value) {
|
|
102
|
-
const
|
|
103
|
+
async findOneBy(field, value, filters = []) {
|
|
104
|
+
const query = { [field]: value };
|
|
105
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model);
|
|
103
106
|
const item = await this._model
|
|
104
|
-
.findOne(
|
|
107
|
+
.findOne(query)
|
|
105
108
|
.populate(this._populateFields)
|
|
106
109
|
.lean(this._lean)
|
|
107
110
|
.exec();
|
|
108
111
|
return item;
|
|
109
112
|
}
|
|
110
|
-
async findBy(field, value, limit = 0) {
|
|
111
|
-
const
|
|
113
|
+
async findBy(field, value, limit = 0, filters = []) {
|
|
114
|
+
const query = { [field]: value };
|
|
115
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model);
|
|
112
116
|
const items = await this._model
|
|
113
|
-
.find(
|
|
117
|
+
.find(query)
|
|
114
118
|
.limit(limit)
|
|
115
119
|
.populate(this._populateFields)
|
|
116
120
|
.lean(this._lean)
|
|
@@ -133,7 +137,7 @@ class AbstractMongoRepository {
|
|
|
133
137
|
else if (value) {
|
|
134
138
|
query['$or'] = this._searchFields.map(field => ({ [field]: new RegExp(value.toString(), 'i') }));
|
|
135
139
|
}
|
|
136
|
-
MongooseQueryFilter.applyFilters(query, filters);
|
|
140
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model);
|
|
137
141
|
const items = await this._model
|
|
138
142
|
.find(query)
|
|
139
143
|
.limit(limit)
|
|
@@ -152,7 +156,7 @@ class AbstractMongoRepository {
|
|
|
152
156
|
query['$or'] = this._searchFields.map(field => ({ [field]: new RegExp(search.toString(), 'i') }));
|
|
153
157
|
}
|
|
154
158
|
}
|
|
155
|
-
MongooseQueryFilter.applyFilters(query, filters);
|
|
159
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model);
|
|
156
160
|
// console.log("Paginate Query", query)
|
|
157
161
|
const sort = MongooseSort.applySort(orderBy, order);
|
|
158
162
|
const populate = this._populateFields;
|
|
@@ -176,7 +180,7 @@ class AbstractMongoRepository {
|
|
|
176
180
|
query['$or'] = this._searchFields.map(field => ({ [field]: new RegExp(search.toString(), 'i') }));
|
|
177
181
|
}
|
|
178
182
|
}
|
|
179
|
-
MongooseQueryFilter.applyFilters(query, filters);
|
|
183
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model);
|
|
180
184
|
const item = this._model
|
|
181
185
|
.findOne(query)
|
|
182
186
|
.populate(this._populateFields)
|
|
@@ -194,7 +198,7 @@ class AbstractMongoRepository {
|
|
|
194
198
|
query['$or'] = this._searchFields.map(field => ({ [field]: new RegExp(search.toString(), 'i') }));
|
|
195
199
|
}
|
|
196
200
|
}
|
|
197
|
-
MongooseQueryFilter.applyFilters(query, filters);
|
|
201
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model);
|
|
198
202
|
const sort = MongooseSort.applySort(orderBy, order);
|
|
199
203
|
const items = await this._model
|
|
200
204
|
.find(query)
|
|
@@ -212,14 +216,14 @@ class AbstractMongoRepository {
|
|
|
212
216
|
{ name: new RegExp(search, 'i') },
|
|
213
217
|
];
|
|
214
218
|
}
|
|
215
|
-
MongooseQueryFilter.applyFilters(query, filters);
|
|
219
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model);
|
|
216
220
|
const sort = MongooseSort.applySort(orderBy, order);
|
|
217
221
|
return this._model.find(query).limit(limit).sort(sort).cursor();
|
|
218
222
|
}
|
|
219
223
|
async groupBy({ fields = [], filters = [], dateFormat = 'day' }) {
|
|
220
224
|
const query = {};
|
|
221
|
-
MongooseQueryFilter.applyFilters(query, filters);
|
|
222
|
-
//
|
|
225
|
+
MongooseQueryFilter.applyFilters(query, filters, this._model);
|
|
226
|
+
// console.log("groupBy Query", query)
|
|
223
227
|
// Obtener el schema para identificar campos de referencia y fechas
|
|
224
228
|
const schema = this._model.schema;
|
|
225
229
|
// Construir el objeto de agrupación dinámicamente
|
|
@@ -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
|
}
|
|
@@ -180,13 +200,19 @@ class AbstractSqliteRepository {
|
|
|
180
200
|
}
|
|
181
201
|
return items;
|
|
182
202
|
}
|
|
183
|
-
async search(value, limit = 1000) {
|
|
203
|
+
async search(value, limit = 1000, filters = []) {
|
|
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
|
}
|
|
@@ -199,32 +225,73 @@ class AbstractSqliteRepository {
|
|
|
199
225
|
await this.decorate(item);
|
|
200
226
|
return item;
|
|
201
227
|
}
|
|
202
|
-
async
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
228
|
+
async findByIds(ids, filters = []) {
|
|
229
|
+
const inPlaceholders = ids.map(() => '?').join(',');
|
|
230
|
+
let where = `WHERE ID IN(${inPlaceholders})`;
|
|
231
|
+
let params = [ids];
|
|
232
|
+
if (filters.length > 0) {
|
|
233
|
+
const result = SqlQueryFilter.applyFilters(where, filters);
|
|
234
|
+
where = result.where;
|
|
235
|
+
params.push(...result.params);
|
|
236
|
+
}
|
|
237
|
+
const items = this.db
|
|
238
|
+
.prepare(`SELECT * FROM ${this.tableName} ${where}`)
|
|
239
|
+
.all(params);
|
|
206
240
|
for (const item of items) {
|
|
207
241
|
await this.decorate(item);
|
|
208
242
|
}
|
|
209
243
|
return items;
|
|
210
244
|
}
|
|
211
|
-
async
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
245
|
+
async findBy(field, value, limit = 0, filters = []) {
|
|
246
|
+
let where = `WHERE ${field} = ?`;
|
|
247
|
+
let params = [value];
|
|
248
|
+
if (filters.length > 0) {
|
|
249
|
+
const result = SqlQueryFilter.applyFilters(where, filters);
|
|
250
|
+
where = result.where;
|
|
251
|
+
params.push(...result.params);
|
|
252
|
+
}
|
|
253
|
+
const items = this.db
|
|
254
|
+
.prepare(`SELECT * FROM ${this.tableName} ${where} LIMIT ?`)
|
|
255
|
+
.all([...params, limit]);
|
|
256
|
+
for (const item of items) {
|
|
257
|
+
await this.decorate(item);
|
|
258
|
+
}
|
|
259
|
+
return items;
|
|
260
|
+
}
|
|
261
|
+
async findOneBy(field, value, filters = []) {
|
|
262
|
+
let where = `WHERE ${field} = ?`;
|
|
263
|
+
let params = [value];
|
|
264
|
+
if (filters.length > 0) {
|
|
265
|
+
const result = SqlQueryFilter.applyFilters(where, filters);
|
|
266
|
+
where = result.where;
|
|
267
|
+
params.push(...result.params);
|
|
268
|
+
}
|
|
269
|
+
const item = this.db
|
|
270
|
+
.prepare(`SELECT * FROM ${this.tableName} ${where} LIMIT 1`)
|
|
271
|
+
.get(params);
|
|
215
272
|
await this.decorate(item);
|
|
216
273
|
return item;
|
|
217
274
|
}
|
|
218
275
|
async findOne({ search = '', filters = [] }) {
|
|
219
276
|
let where = "";
|
|
277
|
+
let params = [];
|
|
278
|
+
// SEARCH
|
|
220
279
|
if (search && this.searchFields.length > 0) {
|
|
221
|
-
|
|
280
|
+
const searchConditions = this.searchFields
|
|
281
|
+
.map(field => `${field} LIKE ?`)
|
|
282
|
+
.join(" OR ");
|
|
283
|
+
where = ` WHERE (${searchConditions})`;
|
|
284
|
+
params.push(...this.searchFields.map(() => `%${search}%`));
|
|
222
285
|
}
|
|
286
|
+
// FILTERS
|
|
223
287
|
if (filters.length > 0) {
|
|
224
|
-
|
|
288
|
+
const result = SqlQueryFilter.applyFilters(where, filters);
|
|
289
|
+
where = result.where;
|
|
290
|
+
params.push(...result.params);
|
|
225
291
|
}
|
|
226
|
-
const item = this.db
|
|
227
|
-
|
|
292
|
+
const item = this.db
|
|
293
|
+
.prepare(`SELECT * FROM ${this.tableName} ${where} LIMIT 1`)
|
|
294
|
+
.get(params);
|
|
228
295
|
if (item) {
|
|
229
296
|
await this.decorate(item);
|
|
230
297
|
}
|
|
@@ -234,12 +301,13 @@ class AbstractSqliteRepository {
|
|
|
234
301
|
if (fields.length === 0) {
|
|
235
302
|
throw new Error("At least one field is required for groupBy");
|
|
236
303
|
}
|
|
237
|
-
// Construir la cláusula WHERE con los filtros
|
|
238
304
|
let where = "";
|
|
305
|
+
let params = [];
|
|
239
306
|
if (filters.length > 0) {
|
|
240
|
-
|
|
307
|
+
const result = SqlQueryFilter.applyFilters(where, filters);
|
|
308
|
+
where = result.where;
|
|
309
|
+
params.push(...result.params);
|
|
241
310
|
}
|
|
242
|
-
// Función para obtener el formato de fecha según SQLite
|
|
243
311
|
const getDateFormatSQL = (field, format) => {
|
|
244
312
|
const formats = {
|
|
245
313
|
'year': `strftime('%Y', ${field})`,
|
|
@@ -251,39 +319,53 @@ class AbstractSqliteRepository {
|
|
|
251
319
|
};
|
|
252
320
|
return formats[format] || formats['day'];
|
|
253
321
|
};
|
|
254
|
-
// Determinar si cada campo es de fecha
|
|
255
322
|
const isDateField = (field) => {
|
|
256
323
|
const tableField = this.tableFields.find(tf => tf.name === field);
|
|
257
|
-
return tableField
|
|
324
|
+
return tableField
|
|
325
|
+
? tableField.type === 'TEXT' && (field.includes('Date') || field.includes('date'))
|
|
326
|
+
: false;
|
|
258
327
|
};
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (
|
|
262
|
-
return
|
|
328
|
+
const isNumericField = (field) => {
|
|
329
|
+
const tableField = this.tableFields.find(tf => tf.name === field);
|
|
330
|
+
if (!tableField)
|
|
331
|
+
return false;
|
|
332
|
+
const type = tableField.type.toUpperCase();
|
|
333
|
+
return ['INTEGER', 'REAL', 'NUMERIC'].includes(type);
|
|
334
|
+
};
|
|
335
|
+
const selectParts = [];
|
|
336
|
+
const groupParts = [];
|
|
337
|
+
for (const field of fields) {
|
|
338
|
+
const tableField = this.tableFields.find(tf => tf.name === field);
|
|
339
|
+
if (!tableField) {
|
|
340
|
+
throw new Error(`Invalid field ${field}`);
|
|
341
|
+
}
|
|
342
|
+
if (isNumericField(field)) {
|
|
343
|
+
selectParts.push(`SUM(${field}) as ${field}`);
|
|
344
|
+
continue;
|
|
263
345
|
}
|
|
264
|
-
return field;
|
|
265
|
-
}).join(', ');
|
|
266
|
-
// Construir la cláusula GROUP BY
|
|
267
|
-
const groupByFields = fields.map(field => {
|
|
268
346
|
if (isDateField(field)) {
|
|
269
|
-
|
|
347
|
+
const formatted = getDateFormatSQL(field, dateFormat);
|
|
348
|
+
selectParts.push(`${formatted} as ${field}`);
|
|
349
|
+
groupParts.push(formatted);
|
|
350
|
+
continue;
|
|
270
351
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
352
|
+
selectParts.push(field);
|
|
353
|
+
groupParts.push(field);
|
|
354
|
+
}
|
|
355
|
+
const selectFields = selectParts.join(", ");
|
|
356
|
+
const groupByFields = groupParts.join(", ");
|
|
274
357
|
const query = `
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
358
|
+
SELECT ${selectFields}, COUNT(*) as count
|
|
359
|
+
FROM ${this.tableName}
|
|
360
|
+
${where}
|
|
361
|
+
${groupByFields ? `GROUP BY ${groupByFields}` : ''}
|
|
362
|
+
ORDER BY count DESC
|
|
363
|
+
`;
|
|
281
364
|
try {
|
|
282
|
-
const result = this.db.prepare(query).all();
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
365
|
+
const result = this.db.prepare(query).all(params);
|
|
366
|
+
// for (const item of result) {
|
|
367
|
+
// await this.decorate(item)
|
|
368
|
+
// }
|
|
287
369
|
return result;
|
|
288
370
|
}
|
|
289
371
|
catch (e) {
|
|
@@ -110,9 +110,12 @@ class AbstractService {
|
|
|
110
110
|
}
|
|
111
111
|
async updatePartial(id, data) {
|
|
112
112
|
data = await this.validateInputUpdatePartial(data);
|
|
113
|
+
if (this.transformUpdatePartial) {
|
|
114
|
+
data = await this.transformUpdatePartial(data);
|
|
115
|
+
}
|
|
113
116
|
let item = await this._repository.updatePartial(id, data);
|
|
114
|
-
if (this.
|
|
115
|
-
await this.
|
|
117
|
+
if (this.onUpdatedPartial) {
|
|
118
|
+
await this.onUpdatedPartial(item);
|
|
116
119
|
}
|
|
117
120
|
item = await this.validateOutput(item);
|
|
118
121
|
return item;
|
|
@@ -155,9 +158,9 @@ class AbstractService {
|
|
|
155
158
|
throw e;
|
|
156
159
|
}
|
|
157
160
|
}
|
|
158
|
-
async findOneBy(field, value) {
|
|
161
|
+
async findOneBy(field, value, filters = []) {
|
|
159
162
|
try {
|
|
160
|
-
let item = await this._repository.findOneBy(field, value);
|
|
163
|
+
let item = await this._repository.findOneBy(field, value, filters);
|
|
161
164
|
if (item && this.transformRead) {
|
|
162
165
|
item = await this.transformRead(item);
|
|
163
166
|
}
|
|
@@ -211,9 +214,9 @@ class AbstractService {
|
|
|
211
214
|
throw e;
|
|
212
215
|
}
|
|
213
216
|
}
|
|
214
|
-
async findBy(field, value, limit = 1000) {
|
|
217
|
+
async findBy(field, value, limit = 1000, filters = []) {
|
|
215
218
|
try {
|
|
216
|
-
let items = await this._repository.findBy(field, value, limit);
|
|
219
|
+
let items = await this._repository.findBy(field, value, limit, filters);
|
|
217
220
|
if (this.transformRead) {
|
|
218
221
|
items = await Promise.all(items.map(item => this.transformRead(item)));
|
|
219
222
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "3.0.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": "^
|
|
26
|
-
"@drax/common-share": "^
|
|
27
|
-
"@drax/identity-share": "^
|
|
28
|
-
"@drax/media-back": "^
|
|
25
|
+
"@drax/common-back": "^3.0.0",
|
|
26
|
+
"@drax/common-share": "^3.0.0",
|
|
27
|
+
"@drax/identity-share": "^3.0.0",
|
|
28
|
+
"@drax/media-back": "^3.0.0",
|
|
29
29
|
"@graphql-tools/load-files": "^7.0.0",
|
|
30
30
|
"@graphql-tools/merge": "^9.0.4",
|
|
31
31
|
"mongoose": "^8.23.0",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"nodemon": "^3.1.0",
|
|
45
45
|
"ts-node": "^10.9.2",
|
|
46
46
|
"tsc-alias": "^1.8.10",
|
|
47
|
-
"typescript": "^5.9.3"
|
|
47
|
+
"typescript": "^5.9.3",
|
|
48
|
+
"vitest": "^3.2.4"
|
|
48
49
|
},
|
|
49
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "63ae718b24ea25ae80b1a9a5dfb84a3abbb95199"
|
|
50
51
|
}
|