@hed-hog/api-pagination 0.0.4 → 0.0.5
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/databases/abstract.database.d.ts +3 -1
- package/dist/databases/abstract.database.d.ts.map +1 -1
- package/dist/databases/abstract.database.js +42 -1
- package/dist/databases/abstract.database.js.map +1 -1
- package/dist/pagination.service.d.ts +3 -1
- package/dist/pagination.service.d.ts.map +1 -1
- package/dist/pagination.service.js +134 -19
- package/dist/pagination.service.js.map +1 -1
- package/package.json +3 -3
- package/src/databases/abstract.database.ts +59 -1
- package/src/pagination.service.ts +213 -23
- package/tsconfig.production.tsbuildinfo +1 -1
- package/.turbo/turbo-build.log +0 -15
|
@@ -111,13 +111,22 @@ export class PaginationService {
|
|
|
111
111
|
let sortOrderCondition: any = {
|
|
112
112
|
id: paginationParams.sortOrder || PageOrderDirection.Asc,
|
|
113
113
|
};
|
|
114
|
+
let needsRawQueryOrdering = false;
|
|
115
|
+
let localeTableName = '';
|
|
116
|
+
let localeSortField = '';
|
|
117
|
+
|
|
118
|
+
this.logger.debug(`Sort field: ${sortField}`);
|
|
114
119
|
|
|
115
120
|
if (sortField) {
|
|
116
121
|
const invalid = this.isInvalidField(sortField, model);
|
|
117
122
|
let localeInvalid = false;
|
|
123
|
+
|
|
124
|
+
this.logger.debug(`Field ${sortField} is invalid in main model: ${invalid}`);
|
|
125
|
+
|
|
118
126
|
if (invalid) {
|
|
119
127
|
localeInvalid = this.isInvalidLocaleField(sortField, model);
|
|
120
|
-
|
|
128
|
+
this.logger.debug(`Field ${sortField} is invalid in locale model: ${localeInvalid}`);
|
|
129
|
+
|
|
121
130
|
if (localeInvalid) {
|
|
122
131
|
this.logger.error(`Invalid field: ${sortField}`);
|
|
123
132
|
throw new BadRequestException(
|
|
@@ -126,9 +135,11 @@ export class PaginationService {
|
|
|
126
135
|
).join(', ')}`,
|
|
127
136
|
);
|
|
128
137
|
} else {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
138
|
+
// Campo existe na tabela locale - precisa usar raw query
|
|
139
|
+
localeTableName = `${(model as any).name}_locale`;
|
|
140
|
+
localeSortField = sortField;
|
|
141
|
+
needsRawQueryOrdering = true;
|
|
142
|
+
this.logger.debug(`Will use raw query ordering for ${localeTableName}.${localeSortField}`);
|
|
132
143
|
}
|
|
133
144
|
} else {
|
|
134
145
|
sortOrderCondition = { [sortField]: sortOrder };
|
|
@@ -175,29 +186,46 @@ export class PaginationService {
|
|
|
175
186
|
delete (customQuery as any).where.OR;
|
|
176
187
|
}
|
|
177
188
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
where: (customQuery as any)?.where || {}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
189
|
+
// Contar total sempre com Prisma normal
|
|
190
|
+
const total = await (model as any).count({
|
|
191
|
+
where: (customQuery as any)?.where || {}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
let data: any[];
|
|
195
|
+
|
|
196
|
+
if (needsRawQueryOrdering) {
|
|
197
|
+
// Usar raw query para ordenação por campo de tabela relacionada
|
|
198
|
+
data = await this.paginateWithLocaleOrdering(
|
|
199
|
+
model,
|
|
200
|
+
localeTableName,
|
|
201
|
+
localeSortField,
|
|
202
|
+
sortOrder,
|
|
203
|
+
skip,
|
|
204
|
+
pageSize,
|
|
205
|
+
customQuery,
|
|
206
|
+
);
|
|
207
|
+
} else {
|
|
208
|
+
// Usar Prisma normal
|
|
209
|
+
const query: any = {
|
|
210
|
+
select: selectCondition,
|
|
211
|
+
where: (customQuery as any)?.where || {},
|
|
212
|
+
orderBy: sortOrderCondition,
|
|
213
|
+
take: pageSize,
|
|
214
|
+
skip,
|
|
215
|
+
};
|
|
185
216
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
217
|
+
if ((customQuery as any)?.include) {
|
|
218
|
+
query.include = (customQuery as any)?.include;
|
|
219
|
+
delete query.select;
|
|
220
|
+
}
|
|
190
221
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
(model as any).findMany(query),
|
|
194
|
-
//this.query(model, query),
|
|
195
|
-
]);
|
|
222
|
+
data = await (model as any).findMany(query);
|
|
223
|
+
}
|
|
196
224
|
|
|
197
225
|
const lastPage = Math.ceil(total / pageSize);
|
|
198
226
|
|
|
199
227
|
if (translationKey !== undefined && translationKey !== null) {
|
|
200
|
-
|
|
228
|
+
this.logger.debug(`Applying translations with key: ${translationKey}`);
|
|
201
229
|
data = data.map((item: any) => {
|
|
202
230
|
return itemTranslations(translationKey, item);
|
|
203
231
|
});
|
|
@@ -212,15 +240,177 @@ export class PaginationService {
|
|
|
212
240
|
next: page < lastPage ? page + 1 : null,
|
|
213
241
|
data,
|
|
214
242
|
};
|
|
215
|
-
} catch (error) {
|
|
243
|
+
} catch (error: any) {
|
|
216
244
|
this.logger.error('Pagination Error:', error);
|
|
217
245
|
|
|
218
246
|
if (error instanceof BadRequestException) {
|
|
219
247
|
throw error;
|
|
220
248
|
}
|
|
221
249
|
|
|
222
|
-
throw new BadRequestException(`Failed to paginate: ${error}`);
|
|
250
|
+
throw new BadRequestException(`Failed to paginate: ${error.message || error}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Método auxiliar para paginação com ordenação por campos de tabela locale
|
|
256
|
+
* Usa raw query para ordenação, mas mantém estrutura aninhada do Prisma
|
|
257
|
+
*/
|
|
258
|
+
private async paginateWithLocaleOrdering(
|
|
259
|
+
model: any,
|
|
260
|
+
localeTableName: string,
|
|
261
|
+
sortField: string,
|
|
262
|
+
sortOrder: string,
|
|
263
|
+
skip: number,
|
|
264
|
+
take: number,
|
|
265
|
+
customQuery?: any,
|
|
266
|
+
): Promise<any[]> {
|
|
267
|
+
try {
|
|
268
|
+
const tableName = (model as any).name;
|
|
269
|
+
const db = await this.getDb(model);
|
|
270
|
+
|
|
271
|
+
// Detectar locale code do include (se disponível)
|
|
272
|
+
let localeCode = 'en'; // fallback
|
|
273
|
+
if (customQuery?.include?.[localeTableName]?.where?.locale?.code) {
|
|
274
|
+
localeCode = customQuery.include[localeTableName].where.locale.code;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
this.logger.debug(`Building raw query for ${tableName} ordered by ${localeTableName}.${sortField}`);
|
|
278
|
+
|
|
279
|
+
// Construir WHERE clause da raw query
|
|
280
|
+
const whereConditions: string[] = [];
|
|
281
|
+
let whereParams: any[] = [];
|
|
282
|
+
let paramIndex = 1;
|
|
283
|
+
|
|
284
|
+
if (customQuery?.where) {
|
|
285
|
+
const { conditions, params, nextIndex } = this.buildWhereClause(
|
|
286
|
+
customQuery.where,
|
|
287
|
+
tableName,
|
|
288
|
+
db,
|
|
289
|
+
paramIndex,
|
|
290
|
+
);
|
|
291
|
+
whereConditions.push(...conditions);
|
|
292
|
+
whereParams.push(...params);
|
|
293
|
+
paramIndex = nextIndex;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Adicionar filtro de locale
|
|
297
|
+
whereConditions.push(`l.code = $${paramIndex}`);
|
|
298
|
+
whereParams.push(localeCode);
|
|
299
|
+
paramIndex++;
|
|
300
|
+
|
|
301
|
+
const whereClause = whereConditions.length > 0
|
|
302
|
+
? `WHERE ${whereConditions.join(' AND ')}`
|
|
303
|
+
: '';
|
|
304
|
+
|
|
305
|
+
// Query para buscar IDs ordenados
|
|
306
|
+
const orderDirection = sortOrder.toUpperCase() === 'DESC' ? 'DESC' : 'ASC';
|
|
307
|
+
const orderByClause = `ORDER BY tl.${db.escapeIdentifier(sortField)} ${orderDirection}`;
|
|
308
|
+
|
|
309
|
+
const rawQuery = `
|
|
310
|
+
SELECT DISTINCT t.id
|
|
311
|
+
FROM ${db.escapeIdentifier(tableName)} t
|
|
312
|
+
LEFT JOIN ${db.escapeIdentifier(localeTableName)} tl ON t.id = tl.${tableName}_id
|
|
313
|
+
LEFT JOIN locale l ON tl.locale_id = l.id
|
|
314
|
+
${whereClause}
|
|
315
|
+
${orderByClause}
|
|
316
|
+
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
|
|
317
|
+
`;
|
|
318
|
+
|
|
319
|
+
whereParams.push(take, skip);
|
|
320
|
+
|
|
321
|
+
this.logger.debug(`Raw query: ${rawQuery}`);
|
|
322
|
+
this.logger.debug(`Params: ${JSON.stringify(whereParams)}`);
|
|
323
|
+
|
|
324
|
+
// Executar raw query para obter IDs ordenados
|
|
325
|
+
const orderedIds = await db.queryRaw(rawQuery, whereParams);
|
|
326
|
+
|
|
327
|
+
if (!orderedIds || orderedIds.length === 0) {
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const ids = orderedIds.map((row: any) => row.id);
|
|
332
|
+
|
|
333
|
+
this.logger.debug(`Ordered IDs: ${ids.join(', ')}`);
|
|
334
|
+
|
|
335
|
+
// Buscar dados completos com Prisma mantendo estrutura aninhada
|
|
336
|
+
const query: any = {
|
|
337
|
+
where: {
|
|
338
|
+
id: { in: ids },
|
|
339
|
+
...(customQuery?.where || {}),
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
if (customQuery?.include) {
|
|
344
|
+
query.include = customQuery.include;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (customQuery?.select) {
|
|
348
|
+
query.select = customQuery.select;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const data = await model.findMany(query);
|
|
352
|
+
|
|
353
|
+
// Reordenar dados para manter a ordem da raw query
|
|
354
|
+
const orderedData = ids
|
|
355
|
+
.map(id => data.find((item: any) => item.id === id))
|
|
356
|
+
.filter(Boolean);
|
|
357
|
+
|
|
358
|
+
this.logger.debug(`Returning ${orderedData.length} ordered records`);
|
|
359
|
+
|
|
360
|
+
return orderedData;
|
|
361
|
+
|
|
362
|
+
} catch (error: any) {
|
|
363
|
+
this.logger.error(`Error in paginateWithLocaleOrdering: ${error.message}`, error.stack);
|
|
364
|
+
throw new BadRequestException(
|
|
365
|
+
`Failed to paginate with locale ordering: ${error.message}`
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Constrói cláusula WHERE para raw queries
|
|
372
|
+
*/
|
|
373
|
+
private buildWhereClause(
|
|
374
|
+
where: any,
|
|
375
|
+
tableName: string,
|
|
376
|
+
db: any,
|
|
377
|
+
startIndex: number = 1,
|
|
378
|
+
): { conditions: string[]; params: any[]; nextIndex: number } {
|
|
379
|
+
const conditions: string[] = [];
|
|
380
|
+
const params: any[] = [];
|
|
381
|
+
let paramIndex = startIndex;
|
|
382
|
+
|
|
383
|
+
for (const key in where) {
|
|
384
|
+
if (key === 'OR' || key === 'AND') {
|
|
385
|
+
continue; // Simplificação: ignorar OR/AND por enquanto
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const value = where[key];
|
|
389
|
+
|
|
390
|
+
if (typeof value === 'object' && value !== null) {
|
|
391
|
+
// Operadores especiais do Prisma
|
|
392
|
+
if ('in' in value) {
|
|
393
|
+
const placeholders = value.in.map(() => `$${paramIndex++}`).join(', ');
|
|
394
|
+
conditions.push(`t.${db.escapeIdentifier(key)} IN (${placeholders})`);
|
|
395
|
+
params.push(...value.in);
|
|
396
|
+
} else if ('contains' in value) {
|
|
397
|
+
conditions.push(`t.${db.escapeIdentifier(key)} ILIKE $${paramIndex}`);
|
|
398
|
+
params.push(`%${value.contains}%`);
|
|
399
|
+
paramIndex++;
|
|
400
|
+
} else if ('equals' in value) {
|
|
401
|
+
conditions.push(`t.${db.escapeIdentifier(key)} = $${paramIndex}`);
|
|
402
|
+
params.push(value.equals);
|
|
403
|
+
paramIndex++;
|
|
404
|
+
}
|
|
405
|
+
} else {
|
|
406
|
+
// Valor simples
|
|
407
|
+
conditions.push(`t.${db.escapeIdentifier(key)} = $${paramIndex}`);
|
|
408
|
+
params.push(value);
|
|
409
|
+
paramIndex++;
|
|
410
|
+
}
|
|
223
411
|
}
|
|
412
|
+
|
|
413
|
+
return { conditions, params, nextIndex: paramIndex };
|
|
224
414
|
}
|
|
225
415
|
|
|
226
416
|
extractFieldNames(model: Record<string, any>): string[] {
|