@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.
Files changed (68) hide show
  1. package/dist/controllers/AbstractFastifyController.js +48 -25
  2. package/dist/regexs/QueryFilterRegex.js +1 -1
  3. package/dist/repository/AbstractMongoRepository.js +19 -15
  4. package/dist/repository/AbstractSqliteRepository.js +142 -60
  5. package/dist/services/AbstractService.js +9 -6
  6. package/package.json +8 -7
  7. package/src/controllers/AbstractFastifyController.ts +57 -29
  8. package/src/regexs/QueryFilterRegex.ts +1 -1
  9. package/src/repository/AbstractMongoRepository.ts +25 -16
  10. package/src/repository/AbstractSqliteRepository.ts +204 -66
  11. package/src/services/AbstractService.ts +12 -6
  12. package/test/_mocks/MockRepository.ts +1 -1
  13. package/test/controllers/PersonController.test.ts +547 -0
  14. package/test/people/controllers/CountryController.ts +40 -0
  15. package/test/people/controllers/LanguageController.ts +29 -0
  16. package/test/people/controllers/PersonController.ts +29 -0
  17. package/test/people/factory/services/CountryServiceFactory.ts +41 -0
  18. package/test/people/factory/services/LanguageServiceFactory.ts +41 -0
  19. package/test/people/factory/services/PersonServiceFactory.ts +41 -0
  20. package/test/people/interfaces/ICountry.ts +28 -0
  21. package/test/people/interfaces/ICountryRepository.ts +11 -0
  22. package/test/people/interfaces/ILanguage.ts +32 -0
  23. package/test/people/interfaces/ILanguageRepository.ts +11 -0
  24. package/test/people/interfaces/IPerson.ts +58 -0
  25. package/test/people/interfaces/IPersonRepository.ts +11 -0
  26. package/test/people/models/CountryModel.ts +38 -0
  27. package/test/people/models/LanguageModel.ts +40 -0
  28. package/test/people/models/PersonModel.ts +61 -0
  29. package/test/people/permissions/CountryPermissions.ts +14 -0
  30. package/test/people/permissions/LanguagePermissions.ts +14 -0
  31. package/test/people/permissions/PersonPermissions.ts +18 -0
  32. package/test/people/repository/mongo/CountryMongoRepository.ts +22 -0
  33. package/test/people/repository/mongo/LanguageMongoRepository.ts +22 -0
  34. package/test/people/repository/mongo/PersonMongoRepository.ts +22 -0
  35. package/test/people/repository/sqlite/CountrySqliteRepository.ts +33 -0
  36. package/test/people/repository/sqlite/LanguageSqliteRepository.ts +37 -0
  37. package/test/people/repository/sqlite/PersonSqliteRepository.ts +42 -0
  38. package/test/people/routes/CountryRoutes.ts +34 -0
  39. package/test/people/routes/LanguageRoutes.ts +34 -0
  40. package/test/people/routes/PersonRoutes.ts +40 -0
  41. package/test/people/schemas/CountrySchema.ts +22 -0
  42. package/test/people/schemas/LanguageSchema.ts +22 -0
  43. package/test/people/schemas/PersonSchema.ts +42 -0
  44. package/test/people/services/CountryService.ts +20 -0
  45. package/test/people/services/LanguageService.ts +20 -0
  46. package/test/people/services/PersonService.ts +20 -0
  47. package/test/services/AbstractService.test.ts +11 -7
  48. package/test/setup/MongoInMemory.ts +56 -0
  49. package/test/setup/TestSetup.ts +344 -0
  50. package/test/setup/data/admin-role.ts +13 -0
  51. package/test/setup/data/basic-user.ts +14 -0
  52. package/test/setup/data/one-tenant.ts +6 -0
  53. package/test/setup/data/restricted-role.ts +16 -0
  54. package/test/setup/data/root-user.ts +14 -0
  55. package/test/setup/data/tenant-one-user.ts +13 -0
  56. package/test/setup/data/tenant-two-user.ts +14 -0
  57. package/test/setup/data/two-tenant.ts +6 -0
  58. package/test/workers/ExportCsvWorker.test.ts +1 -1
  59. package/tsconfig.json +2 -1
  60. package/tsconfig.tsbuildinfo +1 -1
  61. package/types/controllers/AbstractFastifyController.d.ts.map +1 -1
  62. package/types/regexs/QueryFilterRegex.d.ts.map +1 -1
  63. package/types/repository/AbstractMongoRepository.d.ts +3 -3
  64. package/types/repository/AbstractMongoRepository.d.ts.map +1 -1
  65. package/types/repository/AbstractSqliteRepository.d.ts +6 -5
  66. package/types/repository/AbstractSqliteRepository.d.ts.map +1 -1
  67. package/types/services/AbstractService.d.ts +4 -2
  68. package/types/services/AbstractService.d.ts.map +1 -1
@@ -5,7 +5,7 @@ import {
5
5
  LimitError,
6
6
  NotFoundError,
7
7
  BadRequestError,
8
- CommonController
8
+ CommonController, setNestedValue
9
9
  } from "@drax/common-back";
10
10
  import {IRbac} from "@drax/identity-share";
11
11
  import type {FastifyReply, FastifyRequest} from "fastify";
@@ -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
 
@@ -150,17 +150,28 @@ class AbstractFastifyController<T, C, U> extends CommonController {
150
150
  }
151
151
 
152
152
  protected assertTenant(item: T, rbac: IRbac) {
153
- if (this.tenantAssert) {
154
- const itemTenantId = item[this.tenantField]?._id ? item[this.tenantField]._id.toString() : null
155
- rbac.assertTenantId(itemTenantId)
153
+
154
+ //Si tenantAssert esta habilitado y si ademas el usuario pertenece a un tenant
155
+ if (this.tenantAssert && rbac.hasTenant) {
156
+
157
+ //Si esta populado
158
+ if(item[this.tenantField]?._id){
159
+ rbac.assertTenantId(item[this.tenantField]._id.toString())//
160
+ //Si esta crudo
161
+ }else if(item[this.tenantField]){
162
+ rbac.assertTenantId(item[this.tenantField].toString())
163
+ }
156
164
  }
157
165
  }
158
166
 
159
167
  protected assertUser(item: T, rbac: IRbac) {
160
168
 
161
169
  if (this.userAssert) {
162
- const itemUserId = item[this.userField]?._id ? item[this.userField]._id.toString() : null
163
- rbac.assertUserId(itemUserId)
170
+ if(item[this.userField]?._id){
171
+ rbac.assertUserId(item[this.userField]._id.toString())
172
+ }else if(item[this.userField]){
173
+ rbac.assertUserId(item[this.userField].toString())
174
+ }
164
175
  }
165
176
  }
166
177
 
@@ -170,12 +181,13 @@ class AbstractFastifyController<T, C, U> extends CommonController {
170
181
  }
171
182
 
172
183
  protected applyUserAndTenantSetters(payload: any, rbac: any) {
184
+
173
185
  if (this.tenantSetter && rbac.tenantId) {
174
- payload[this.tenantField] = rbac.tenantId
186
+ setNestedValue(payload, this.tenantField, rbac.tenantId)
175
187
  }
176
188
 
177
189
  if (this.userSetter && rbac.userId) {
178
- payload[this.userField] = rbac.userId
190
+ setNestedValue(payload, this.userField, rbac.userId)
179
191
  }
180
192
  }
181
193
 
@@ -309,21 +321,24 @@ class AbstractFastifyController<T, C, U> extends CommonController {
309
321
 
310
322
  let preItem = await this.service.findById(id)
311
323
 
312
- if (!request.rbac.hasSomePermission([this.permission.All, this.permission.UpdateAll])) {
313
-
314
- if (!preItem) {
315
- reply.statusCode = 404
316
- reply.send({error: 'NOT_FOUND'})
317
- }
324
+ if (!preItem) {
325
+ reply.statusCode = 404
326
+ reply.send({error: 'NOT_FOUND'})
327
+ }
318
328
 
329
+ if (!request.rbac.hasSomePermission([this.permission.All, this.permission.UpdateAll])) {
330
+ //Si assertUser habilitado y si el usuario no tiene UpdateAll/All, solo puede modificar sus propios registros
319
331
  this.assertUser(preItem, request.rbac)
320
332
  }
333
+ //Si assertTenant habilitado y si usuario tiene tenant, solo puede modificar registros de su tenant
334
+ this.assertTenant(preItem, request.rbac)
321
335
 
322
- //Definido el tenant/user en el create no debe modificarse en un update
336
+ //Definido el tenant en create no debe modificarse en un update
323
337
  if(this.tenantSetter) {
324
338
  delete payload[this.tenantField]
325
339
  }
326
340
 
341
+ //Definido el user en create no debe modificarse en un update
327
342
  if(this.userSetter){
328
343
  delete payload[this.userField]
329
344
  }
@@ -365,21 +380,25 @@ class AbstractFastifyController<T, C, U> extends CommonController {
365
380
 
366
381
  let preItem = await this.service.findById(id)
367
382
 
368
- if (!request.rbac.hasSomePermission([this.permission.All, this.permission.UpdateAll])) {
369
-
370
- if (!preItem) {
371
- reply.statusCode = 404
372
- reply.send({error: 'NOT_FOUND'})
373
- }
383
+ if (!preItem) {
384
+ reply.statusCode = 404
385
+ reply.send({error: 'NOT_FOUND'})
386
+ }
374
387
 
388
+ if (!request.rbac.hasSomePermission([this.permission.All, this.permission.UpdateAll])) {
389
+ //Si assertUser habilitado y si el usuario no tiene UpdateAll/All, solo puede modificar sus propios registros
375
390
  this.assertUser(preItem, request.rbac)
376
391
  }
377
392
 
378
- //Definido el tenant/user en el create no debe modificarse en un update
393
+ //Si assertTenant habilitado y si usuario tiene tenant, solo puede modificar registros de su tenant
394
+ this.assertTenant(preItem, request.rbac)
395
+
396
+ //Definido el tenant en el create no debe modificarse en un update
379
397
  if(this.tenantSetter) {
380
398
  delete payload[this.tenantField]
381
399
  }
382
400
 
401
+ //Definido el user en el create no debe modificarse en un update
383
402
  if(this.userSetter){
384
403
  delete payload[this.userField]
385
404
  }
@@ -552,11 +571,15 @@ class AbstractFastifyController<T, C, U> extends CommonController {
552
571
  const limit = this.defaultLimit
553
572
  const field = request.params.field
554
573
  const value = request.params.value
555
- let items = await this.service.findBy(field, value, limit)
556
574
 
557
- for (let item of items) {
558
- this.assertUserAndTenant(item, request.rbac)
559
- }
575
+ let filters = []
576
+ this.applyUserAndTenantFilters(filters, request.rbac);
577
+
578
+ let items = await this.service.findBy(field, value, limit, filters)
579
+
580
+ // for (let item of items) {
581
+ // this.assertUserAndTenant(item, request.rbac)
582
+ // }
560
583
 
561
584
  return items
562
585
  } catch (e) {
@@ -574,8 +597,13 @@ class AbstractFastifyController<T, C, U> extends CommonController {
574
597
 
575
598
  const field = request.params.field
576
599
  const value = request.params.value
577
- let item = await this.service.findOneBy(field, value)
578
- this.assertUserAndTenant(item, request.rbac);
600
+
601
+ let filters = []
602
+ this.applyUserAndTenantFilters(filters, request.rbac);
603
+
604
+ let item = await this.service.findOneBy(field, value, filters)
605
+
606
+ // this.assertUserAndTenant(item, request.rbac);
579
607
 
580
608
  return item
581
609
  } catch (e) {
@@ -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}
@@ -122,12 +122,16 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
122
122
  return item as T;
123
123
  }
124
124
 
125
- async findByIds(ids: Array<string>): Promise<T[]> {
125
+ async findByIds(ids: Array<string>, filters: IDraxFieldFilter[] = []): Promise<T[]> {
126
126
 
127
127
  ids.map(id => this.assertId(id))
128
128
 
129
+ const query: any = {_id: {$in: ids}}
130
+
131
+ MongooseQueryFilter.applyFilters(query, filters, this._model)
132
+
129
133
  const items = await this._model
130
- .find({_id: {$in: ids}})
134
+ .find(query)
131
135
  .populate(this._populateFields)
132
136
  .lean(this._lean)
133
137
  .exec()
@@ -136,11 +140,13 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
136
140
  return items as T[]
137
141
  }
138
142
 
139
- async findOneBy(field: string, value: any): Promise<T | null> {
140
- const filter: any = {[field]: value}
143
+ async findOneBy(field: string, value: any, filters: IDraxFieldFilter[] = []): Promise<T | null> {
144
+ const query: any = {[field]: value}
145
+
146
+ MongooseQueryFilter.applyFilters(query, filters, this._model)
141
147
 
142
148
  const item = await this._model
143
- .findOne(filter)
149
+ .findOne(query)
144
150
  .populate(this._populateFields)
145
151
  .lean(this._lean)
146
152
  .exec()
@@ -149,10 +155,13 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
149
155
  return item as T
150
156
  }
151
157
 
152
- async findBy(field: string, value: any, limit: number = 0): Promise<T[]> {
153
- const filter: any = {[field]: value}
158
+ async findBy(field: string, value: any, limit: number = 0, filters: IDraxFieldFilter[] = []): Promise<T[]> {
159
+ const query: any = {[field]: value}
160
+
161
+ MongooseQueryFilter.applyFilters(query, filters, this._model)
162
+
154
163
  const items = await this._model
155
- .find(filter)
164
+ .find(query)
156
165
  .limit(limit)
157
166
  .populate(this._populateFields)
158
167
  .lean(this._lean)
@@ -184,7 +193,7 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
184
193
  query['$or'] = this._searchFields.map(field => ({[field]: new RegExp(value.toString(), 'i')}))
185
194
  }
186
195
 
187
- MongooseQueryFilter.applyFilters(query, filters)
196
+ MongooseQueryFilter.applyFilters(query, filters, this._model)
188
197
 
189
198
  const items = await this._model
190
199
  .find(query)
@@ -217,7 +226,7 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
217
226
  }
218
227
  }
219
228
 
220
- MongooseQueryFilter.applyFilters(query, filters)
229
+ MongooseQueryFilter.applyFilters(query, filters, this._model)
221
230
 
222
231
  // console.log("Paginate Query", query)
223
232
 
@@ -249,7 +258,7 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
249
258
  }
250
259
  }
251
260
 
252
- MongooseQueryFilter.applyFilters(query, filters)
261
+ MongooseQueryFilter.applyFilters(query, filters, this._model)
253
262
 
254
263
  const item = this._model
255
264
  .findOne(query)
@@ -278,7 +287,7 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
278
287
  }
279
288
  }
280
289
 
281
- MongooseQueryFilter.applyFilters(query, filters)
290
+ MongooseQueryFilter.applyFilters(query, filters, this._model)
282
291
 
283
292
  const sort = MongooseSort.applySort(orderBy, order)
284
293
  const items = await this._model
@@ -309,9 +318,9 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
309
318
  ]
310
319
  }
311
320
 
312
- MongooseQueryFilter.applyFilters(query, filters)
321
+ MongooseQueryFilter.applyFilters(query, filters, this._model)
313
322
 
314
- const sort = MongooseSort.applySort(orderBy, order)
323
+ const sort = MongooseSort.applySort(orderBy, order,)
315
324
 
316
325
  return this._model.find(query).limit(limit).sort(sort).cursor() as Cursor<T>;
317
326
  }
@@ -320,8 +329,8 @@ class AbstractMongoRepository<T, C, U> implements IDraxCrud<T, C, U> {
320
329
 
321
330
  const query = {}
322
331
 
323
- MongooseQueryFilter.applyFilters(query, filters)
324
- // console.log("groupBy Query", query)
332
+ MongooseQueryFilter.applyFilters(query, filters, this._model)
333
+ // console.log("groupBy Query", query)
325
334
 
326
335
  // Obtener el schema para identificar campos de referencia y fechas
327
336
  const schema = this._model.schema
@@ -1,6 +1,6 @@
1
1
  import sqlite from "better-sqlite3";
2
2
  import type {
3
- IDraxCrud,
3
+ IDraxCrud, IDraxFieldFilter,
4
4
  IDraxFindOptions,
5
5
  IDraxGroupByOptions,
6
6
  IDraxPaginateOptions,
@@ -8,10 +8,13 @@ import type {
8
8
  } from "@drax/crud-share";
9
9
  import {randomUUID} from "node:crypto";
10
10
  import {
11
- SqlSort, SqlQueryFilter, SqliteTableBuilder, SqliteTableField,
12
- SqliteErrorToValidationError, MongooseQueryFilter
11
+ SqlSort,
12
+ SqlQueryFilter,
13
+ SqliteTableBuilder,
14
+ SqliteTableField,
15
+ SqliteErrorToValidationError
13
16
  } from "@drax/common-back";
14
- import mongoose from "mongoose";
17
+
15
18
 
16
19
 
17
20
  class AbstractSqliteRepository<T, C, U> implements IDraxCrud<T, C, U> {
@@ -195,31 +198,50 @@ class AbstractSqliteRepository<T, C, U> implements IDraxCrud<T, C, U> {
195
198
  const offset = page > 1 ? (page - 1) * limit : 0
196
199
 
197
200
  let where = ""
201
+ let params: any[] = []
202
+
203
+ // SEARCH
198
204
  if (search && this.searchFields.length > 0) {
199
- where = ` WHERE ${this.searchFields.map(field => `${field} LIKE '%${search}%'`).join(" OR ")}`
205
+
206
+ const searchConditions = this.searchFields
207
+ .map(field => `${field} LIKE ?`)
208
+ .join(" OR ")
209
+
210
+ where = ` WHERE (${searchConditions})`
211
+
212
+ params.push(...this.searchFields.map(() => `%${search}%`))
200
213
  }
201
214
 
215
+ // FILTERS
202
216
  if (filters.length > 0) {
203
- where = SqlQueryFilter.applyFilters(where, filters)
217
+
218
+ const result = SqlQueryFilter.applyFilters(where, filters)
219
+
220
+ where = result.where
221
+ params.push(...result.params)
204
222
  }
205
223
 
206
224
  const sort = SqlSort.applySort(orderBy, order)
207
225
 
208
- const rCount = this.db.prepare(`SELECT COUNT(*) as count
209
- FROM ${this.tableName} ${where}`).get();
210
- const items = this.db.prepare(`SELECT *
211
- FROM ${this.tableName} ${where} ${sort} LIMIT ?
212
- OFFSET ? `).all([limit, offset]) as T[];
226
+ // COUNT
227
+ const rCount = this.db
228
+ .prepare(`SELECT COUNT(*) as count FROM ${this.tableName} ${where}`)
229
+ .get(params)
230
+
231
+ // DATA
232
+ const items = this.db
233
+ .prepare(`SELECT * FROM ${this.tableName} ${where} ${sort} LIMIT ? OFFSET ?`)
234
+ .all([...params, limit, offset]) as T[]
213
235
 
214
236
  for (const item of items) {
215
237
  await this.decorate(item)
216
238
  }
217
239
 
218
240
  return {
219
- page: page,
220
- limit: limit,
241
+ page,
242
+ limit,
221
243
  total: rCount.count,
222
- items: items
244
+ items
223
245
  }
224
246
  }
225
247
 
@@ -229,24 +251,37 @@ class AbstractSqliteRepository<T, C, U> implements IDraxCrud<T, C, U> {
229
251
  order = 'desc',
230
252
  search = '',
231
253
  filters = []
232
- }: IDraxFindOptions): Promise<any[]> {
233
-
254
+ }: IDraxFindOptions): Promise<T[]> {
234
255
 
235
256
  let where = ""
257
+ let params: any[] = []
258
+
259
+ // SEARCH
236
260
  if (search && this.searchFields.length > 0) {
237
- where = ` WHERE ${this.searchFields.map(field => `${field} LIKE '%${search}%'`).join(" OR ")}`
261
+
262
+ const searchConditions = this.searchFields
263
+ .map(field => `${field} LIKE ?`)
264
+ .join(" OR ")
265
+
266
+ where = ` WHERE (${searchConditions})`
267
+
268
+ params.push(...this.searchFields.map(() => `%${search}%`))
238
269
  }
239
270
 
271
+ // FILTERS
240
272
  if (filters.length > 0) {
241
- where = SqlQueryFilter.applyFilters(where, filters)
273
+
274
+ const result = SqlQueryFilter.applyFilters(where, filters)
275
+
276
+ where = result.where
277
+ params.push(...result.params)
242
278
  }
243
279
 
244
280
  const sort = SqlSort.applySort(orderBy, order)
245
281
 
246
- const rCount = this.db.prepare(`SELECT COUNT(*) as count
247
- FROM ${this.tableName} ${where}`).get();
248
- const items = this.db.prepare(`SELECT *
249
- FROM ${this.tableName} ${where} ${sort} LIMIT ? `).all([limit]) as T[];
282
+ const items = this.db
283
+ .prepare(`SELECT * FROM ${this.tableName} ${where} ${sort} LIMIT ?`)
284
+ .all([...params, limit]) as T[]
250
285
 
251
286
  for (const item of items) {
252
287
  await this.decorate(item)
@@ -264,13 +299,25 @@ class AbstractSqliteRepository<T, C, U> implements IDraxCrud<T, C, U> {
264
299
  return items
265
300
  }
266
301
 
267
- async search(value: any, limit: number = 1000): Promise<any[]> {
302
+ async search(value: any, limit: number = 1000, filters: IDraxFieldFilter[] = []): Promise<T[]> {
303
+
268
304
  let where = ""
305
+ let params: any[] = []
306
+
269
307
  if (value && this.searchFields.length > 0) {
270
- where = ` WHERE ${this.searchFields.map(field => `${field} LIKE '%${value}%'`).join(" OR ")}`
308
+
309
+ const searchConditions = this.searchFields
310
+ .map(field => `${field} LIKE ?`)
311
+ .join(" OR ")
312
+
313
+ where = ` WHERE (${searchConditions})`
314
+
315
+ params.push(...this.searchFields.map(() => `%${value}%`))
271
316
  }
272
- const items = this.db.prepare(`SELECT *
273
- FROM ${this.tableName} ${where} LIMIT ${limit}`).all();
317
+
318
+ const items = this.db
319
+ .prepare(`SELECT * FROM ${this.tableName} ${where} LIMIT ?`)
320
+ .all([...params, limit]) as T[]
274
321
 
275
322
  for (const item of items) {
276
323
  await this.decorate(item)
@@ -287,22 +334,73 @@ class AbstractSqliteRepository<T, C, U> implements IDraxCrud<T, C, U> {
287
334
  return item
288
335
  }
289
336
 
290
- async findBy(field: string, value: any, limit: number = 0): Promise<T[] | null> {
291
- const items = this.db.prepare(`SELECT *
292
- FROM ${this.tableName}
293
- WHERE ${field} = ? LIMIT ${limit}`).all(value);
337
+ async findByIds(ids: string[], filters: IDraxFieldFilter[] = []): Promise<T[] | null> {
338
+
339
+ const inPlaceholders = ids.map(() => '?').join(',')
340
+
341
+ let where = `WHERE ID IN(${inPlaceholders})`
342
+ let params: any[] = [ids]
343
+
344
+ if (filters.length > 0) {
345
+ const result = SqlQueryFilter.applyFilters(where, filters)
346
+ where = result.where
347
+ params.push(...result.params)
348
+ }
349
+
350
+ const items = this.db
351
+ .prepare(`SELECT * FROM ${this.tableName} ${where}`)
352
+ .all(params) as T[]
353
+
294
354
  for (const item of items) {
295
355
  await this.decorate(item)
296
356
  }
357
+
297
358
  return items
359
+
298
360
  }
299
361
 
300
- async findOneBy(field: string, value: any): Promise<T | null> {
301
- const item = this.db.prepare(`SELECT *
302
- FROM ${this.tableName}
303
- WHERE ${field} = ?`).get(value);
362
+ async findBy(field: string, value: any, limit: number = 0, filters: IDraxFieldFilter[] = []): Promise<T[] | null> {
363
+
364
+ let where = `WHERE ${field} = ?`
365
+ let params: any[] = [value]
366
+
367
+ if (filters.length > 0) {
368
+ const result = SqlQueryFilter.applyFilters(where, filters)
369
+ where = result.where
370
+ params.push(...result.params)
371
+ }
372
+
373
+ const items = this.db
374
+ .prepare(`SELECT * FROM ${this.tableName} ${where} LIMIT ?`)
375
+ .all([...params, limit]) as T[]
376
+
377
+ for (const item of items) {
378
+ await this.decorate(item)
379
+ }
380
+
381
+ return items
382
+
383
+ }
384
+
385
+ async findOneBy(field: string, value: any, filters: IDraxFieldFilter[] = []): Promise<T | null> {
386
+
387
+ let where = `WHERE ${field} = ?`
388
+ let params: any[] = [value]
389
+
390
+ if (filters.length > 0) {
391
+ const result = SqlQueryFilter.applyFilters(where, filters)
392
+ where = result.where
393
+ params.push(...result.params)
394
+ }
395
+
396
+ const item = this.db
397
+ .prepare(`SELECT * FROM ${this.tableName} ${where} LIMIT 1`)
398
+ .get(params) as T
399
+
304
400
  await this.decorate(item)
401
+
305
402
  return item
403
+
306
404
  }
307
405
 
308
406
  async findOne({
@@ -311,17 +409,32 @@ class AbstractSqliteRepository<T, C, U> implements IDraxCrud<T, C, U> {
311
409
  }: IDraxFindOptions): Promise<T> {
312
410
 
313
411
  let where = ""
412
+ let params: any[] = []
314
413
 
414
+ // SEARCH
315
415
  if (search && this.searchFields.length > 0) {
316
- where = ` WHERE ${this.searchFields.map(field => `${field} LIKE '%${search}%'`).join(" OR ")}`
416
+
417
+ const searchConditions = this.searchFields
418
+ .map(field => `${field} LIKE ?`)
419
+ .join(" OR ")
420
+
421
+ where = ` WHERE (${searchConditions})`
422
+
423
+ params.push(...this.searchFields.map(() => `%${search}%`))
317
424
  }
318
425
 
426
+ // FILTERS
319
427
  if (filters.length > 0) {
320
- where = SqlQueryFilter.applyFilters(where, filters)
428
+
429
+ const result = SqlQueryFilter.applyFilters(where, filters)
430
+
431
+ where = result.where
432
+ params.push(...result.params)
321
433
  }
322
434
 
323
- const item = this.db.prepare(`SELECT *
324
- FROM ${this.tableName} ${where} LIMIT 1`).get();
435
+ const item = this.db
436
+ .prepare(`SELECT * FROM ${this.tableName} ${where} LIMIT 1`)
437
+ .get(params)
325
438
 
326
439
  if (item) {
327
440
  await this.decorate(item)
@@ -331,17 +444,20 @@ class AbstractSqliteRepository<T, C, U> implements IDraxCrud<T, C, U> {
331
444
  }
332
445
 
333
446
  async groupBy({fields = [], filters = [], dateFormat = 'day'}: IDraxGroupByOptions): Promise<Array<any>> {
447
+
334
448
  if (fields.length === 0) {
335
449
  throw new Error("At least one field is required for groupBy")
336
450
  }
337
451
 
338
- // Construir la cláusula WHERE con los filtros
339
452
  let where = ""
453
+ let params: any[] = []
454
+
340
455
  if (filters.length > 0) {
341
- where = SqlQueryFilter.applyFilters(where, filters)
456
+ const result = SqlQueryFilter.applyFilters(where, filters)
457
+ where = result.where
458
+ params.push(...result.params)
342
459
  }
343
460
 
344
- // Función para obtener el formato de fecha según SQLite
345
461
  const getDateFormatSQL = (field: string, format: string): string => {
346
462
  const formats: { [key: string]: string } = {
347
463
  'year': `strftime('%Y', ${field})`,
@@ -354,46 +470,68 @@ class AbstractSqliteRepository<T, C, U> implements IDraxCrud<T, C, U> {
354
470
  return formats[format] || formats['day']
355
471
  }
356
472
 
357
- // Determinar si cada campo es de fecha
358
473
  const isDateField = (field: string): boolean => {
359
474
  const tableField = this.tableFields.find(tf => tf.name === field)
360
- return tableField ? tableField.type === 'TEXT' && (field.includes('Date') || field.includes('date')) : false
475
+ return tableField
476
+ ? tableField.type === 'TEXT' && (field.includes('Date') || field.includes('date'))
477
+ : false
361
478
  }
362
479
 
363
- // Construir los campos SELECT con formato de fecha si aplica
364
- const selectFields = fields.map(field => {
365
- if (isDateField(field)) {
366
- return `${getDateFormatSQL(field, dateFormat)} as ${field}`
480
+ const isNumericField = (field: string): boolean => {
481
+ const tableField = this.tableFields.find(tf => tf.name === field)
482
+ if (!tableField) return false
483
+ const type = tableField.type.toUpperCase()
484
+ return ['INTEGER', 'REAL', 'NUMERIC'].includes(type)
485
+ }
486
+
487
+ const selectParts: string[] = []
488
+ const groupParts: string[] = []
489
+
490
+ for (const field of fields) {
491
+
492
+ const tableField = this.tableFields.find(tf => tf.name === field)
493
+
494
+ if (!tableField) {
495
+ throw new Error(`Invalid field ${field}`)
496
+ }
497
+
498
+ if (isNumericField(field)) {
499
+ selectParts.push(`SUM(${field}) as ${field}`)
500
+ continue
367
501
  }
368
- return field
369
- }).join(', ')
370
502
 
371
- // Construir la cláusula GROUP BY
372
- const groupByFields = fields.map(field => {
373
503
  if (isDateField(field)) {
374
- return getDateFormatSQL(field, dateFormat)
504
+ const formatted = getDateFormatSQL(field, dateFormat)
505
+ selectParts.push(`${formatted} as ${field}`)
506
+ groupParts.push(formatted)
507
+ continue
375
508
  }
376
- return field
377
- }).join(', ')
378
509
 
379
- // Construir y ejecutar la query
510
+ selectParts.push(field)
511
+ groupParts.push(field)
512
+ }
513
+
514
+ const selectFields = selectParts.join(", ")
515
+ const groupByFields = groupParts.join(", ")
516
+
380
517
  const query = `
381
- SELECT ${selectFields}, COUNT(*) as count
382
- FROM ${this.tableName}
383
- ${where}
384
- GROUP BY ${groupByFields}
385
- ORDER BY count DESC
386
- `
518
+ SELECT ${selectFields}, COUNT(*) as count
519
+ FROM ${this.tableName}
520
+ ${where}
521
+ ${groupByFields ? `GROUP BY ${groupByFields}` : ''}
522
+ ORDER BY count DESC
523
+ `
387
524
 
388
525
  try {
389
- const result = this.db.prepare(query).all() as Array<any>
390
526
 
391
- // Decorar los items si tienen campos de población
392
- for (const item of result) {
393
- await this.decorate(item)
394
- }
527
+ const result = this.db.prepare(query).all(params) as Array<any>
528
+
529
+ // for (const item of result) {
530
+ // await this.decorate(item)
531
+ // }
395
532
 
396
533
  return result
534
+
397
535
  } catch (e) {
398
536
  console.error("GroupBy query error:", e)
399
537
  throw e