@drax/crud-back 2.9.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.
@@ -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): 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)
@@ -311,17 +358,32 @@ class AbstractSqliteRepository<T, C, U> implements IDraxCrud<T, C, U> {
311
358
  }: IDraxFindOptions): Promise<T> {
312
359
 
313
360
  let where = ""
361
+ let params: any[] = []
314
362
 
363
+ // SEARCH
315
364
  if (search && this.searchFields.length > 0) {
316
- where = ` WHERE ${this.searchFields.map(field => `${field} LIKE '%${search}%'`).join(" OR ")}`
365
+
366
+ const searchConditions = this.searchFields
367
+ .map(field => `${field} LIKE ?`)
368
+ .join(" OR ")
369
+
370
+ where = ` WHERE (${searchConditions})`
371
+
372
+ params.push(...this.searchFields.map(() => `%${search}%`))
317
373
  }
318
374
 
375
+ // FILTERS
319
376
  if (filters.length > 0) {
320
- where = SqlQueryFilter.applyFilters(where, filters)
377
+
378
+ const result = SqlQueryFilter.applyFilters(where, filters)
379
+
380
+ where = result.where
381
+ params.push(...result.params)
321
382
  }
322
383
 
323
- const item = this.db.prepare(`SELECT *
324
- FROM ${this.tableName} ${where} LIMIT 1`).get();
384
+ const item = this.db
385
+ .prepare(`SELECT * FROM ${this.tableName} ${where} LIMIT 1`)
386
+ .get(params)
325
387
 
326
388
  if (item) {
327
389
  await this.decorate(item)
@@ -331,17 +393,20 @@ class AbstractSqliteRepository<T, C, U> implements IDraxCrud<T, C, U> {
331
393
  }
332
394
 
333
395
  async groupBy({fields = [], filters = [], dateFormat = 'day'}: IDraxGroupByOptions): Promise<Array<any>> {
396
+
334
397
  if (fields.length === 0) {
335
398
  throw new Error("At least one field is required for groupBy")
336
399
  }
337
400
 
338
- // Construir la cláusula WHERE con los filtros
339
401
  let where = ""
402
+ let params: any[] = []
403
+
340
404
  if (filters.length > 0) {
341
- where = SqlQueryFilter.applyFilters(where, filters)
405
+ const result = SqlQueryFilter.applyFilters(where, filters)
406
+ where = result.where
407
+ params.push(...result.params)
342
408
  }
343
409
 
344
- // Función para obtener el formato de fecha según SQLite
345
410
  const getDateFormatSQL = (field: string, format: string): string => {
346
411
  const formats: { [key: string]: string } = {
347
412
  'year': `strftime('%Y', ${field})`,
@@ -354,46 +419,68 @@ class AbstractSqliteRepository<T, C, U> implements IDraxCrud<T, C, U> {
354
419
  return formats[format] || formats['day']
355
420
  }
356
421
 
357
- // Determinar si cada campo es de fecha
358
422
  const isDateField = (field: string): boolean => {
359
423
  const tableField = this.tableFields.find(tf => tf.name === field)
360
- return tableField ? tableField.type === 'TEXT' && (field.includes('Date') || field.includes('date')) : false
424
+ return tableField
425
+ ? tableField.type === 'TEXT' && (field.includes('Date') || field.includes('date'))
426
+ : false
361
427
  }
362
428
 
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}`
429
+ const isNumericField = (field: string): boolean => {
430
+ const tableField = this.tableFields.find(tf => tf.name === field)
431
+ if (!tableField) return false
432
+ const type = tableField.type.toUpperCase()
433
+ return ['INTEGER', 'REAL', 'NUMERIC'].includes(type)
434
+ }
435
+
436
+ const selectParts: string[] = []
437
+ const groupParts: string[] = []
438
+
439
+ for (const field of fields) {
440
+
441
+ const tableField = this.tableFields.find(tf => tf.name === field)
442
+
443
+ if (!tableField) {
444
+ throw new Error(`Invalid field ${field}`)
445
+ }
446
+
447
+ if (isNumericField(field)) {
448
+ selectParts.push(`SUM(${field}) as ${field}`)
449
+ continue
367
450
  }
368
- return field
369
- }).join(', ')
370
451
 
371
- // Construir la cláusula GROUP BY
372
- const groupByFields = fields.map(field => {
373
452
  if (isDateField(field)) {
374
- return getDateFormatSQL(field, dateFormat)
453
+ const formatted = getDateFormatSQL(field, dateFormat)
454
+ selectParts.push(`${formatted} as ${field}`)
455
+ groupParts.push(formatted)
456
+ continue
375
457
  }
376
- return field
377
- }).join(', ')
378
458
 
379
- // Construir y ejecutar la query
459
+ selectParts.push(field)
460
+ groupParts.push(field)
461
+ }
462
+
463
+ const selectFields = selectParts.join(", ")
464
+ const groupByFields = groupParts.join(", ")
465
+
380
466
  const query = `
381
- SELECT ${selectFields}, COUNT(*) as count
382
- FROM ${this.tableName}
383
- ${where}
384
- GROUP BY ${groupByFields}
385
- ORDER BY count DESC
386
- `
467
+ SELECT ${selectFields}, COUNT(*) as count
468
+ FROM ${this.tableName}
469
+ ${where}
470
+ ${groupByFields ? `GROUP BY ${groupByFields}` : ''}
471
+ ORDER BY count DESC
472
+ `
387
473
 
388
474
  try {
389
- const result = this.db.prepare(query).all() as Array<any>
390
475
 
391
- // Decorar los items si tienen campos de población
392
- for (const item of result) {
393
- await this.decorate(item)
394
- }
476
+ const result = this.db.prepare(query).all(params) as Array<any>
477
+
478
+ // for (const item of result) {
479
+ // await this.decorate(item)
480
+ // }
395
481
 
396
482
  return result
483
+
397
484
  } catch (e) {
398
485
  console.error("GroupBy query error:", e)
399
486
  throw e