@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
|
@@ -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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
163
|
-
|
|
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
|
|
186
|
+
setNestedValue(payload, this.tenantField, rbac.tenantId)
|
|
175
187
|
}
|
|
176
188
|
|
|
177
189
|
if (this.userSetter && rbac.userId) {
|
|
178
|
-
payload
|
|
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 (!
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
|
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 (!
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
558
|
-
|
|
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
|
-
|
|
578
|
-
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
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,
|
|
12
|
-
|
|
11
|
+
SqlSort,
|
|
12
|
+
SqlQueryFilter,
|
|
13
|
+
SqliteTableBuilder,
|
|
14
|
+
SqliteTableField,
|
|
15
|
+
SqliteErrorToValidationError
|
|
13
16
|
} from "@drax/common-back";
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
|
220
|
-
limit
|
|
241
|
+
page,
|
|
242
|
+
limit,
|
|
221
243
|
total: rCount.count,
|
|
222
|
-
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<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
247
|
-
|
|
248
|
-
|
|
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<
|
|
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
|
-
|
|
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
|
-
|
|
273
|
-
|
|
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
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
324
|
-
|
|
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
|
-
|
|
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
|
|
475
|
+
return tableField
|
|
476
|
+
? tableField.type === 'TEXT' && (field.includes('Date') || field.includes('date'))
|
|
477
|
+
: false
|
|
361
478
|
}
|
|
362
479
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
if (
|
|
366
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|