@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.
@@ -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
- sortOrderCondition = {
130
- [`${(model as any).name}_locale`]: { [sortField]: sortOrder },
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
- const query: any = {
179
- select: selectCondition,
180
- where: (customQuery as any)?.where || {},
181
- orderBy: sortOrderCondition,
182
- take: pageSize,
183
- skip,
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
- if ((customQuery as any)?.include) {
187
- query.include = (customQuery as any)?.include;
188
- delete query.select;
189
- }
217
+ if ((customQuery as any)?.include) {
218
+ query.include = (customQuery as any)?.include;
219
+ delete query.select;
220
+ }
190
221
 
191
- let [total, data] = await Promise.all([
192
- (model as any).count({ where: (customQuery as any)?.where || {} }),
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
- console.log(typeof translationKey, translationKey)
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[] {