From 26515ab621225255cda24c797f2b75eaac44340e Mon Sep 17 00:00:00 2001 From: Tihon Date: Fri, 20 Mar 2026 14:06:39 +0200 Subject: [PATCH] admin statistics part3 --- .../controller/AdminAnalyticsController.java | 12 ++++- .../AdminStatisticsTableRepository.java | 9 ++++ .../service/AdminStatisticsTableService.java | 49 ++++++++++++++----- 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/honey/honey/controller/AdminAnalyticsController.java b/src/main/java/com/honey/honey/controller/AdminAnalyticsController.java index a57f1cc..c650d9e 100644 --- a/src/main/java/com/honey/honey/controller/AdminAnalyticsController.java +++ b/src/main/java/com/honey/honey/controller/AdminAnalyticsController.java @@ -45,7 +45,8 @@ public class AdminAnalyticsController { public ResponseEntity> getStatisticsTable( @RequestParam String mode, @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "50") int size) { + @RequestParam(defaultValue = "50") int size, + @RequestParam(defaultValue = "desc") String sortDir) { AdminStatisticsTableService.TableMode tableMode; try { @@ -54,10 +55,16 @@ public class AdminAnalyticsController { return ResponseEntity.badRequest().build(); } + String dir = sortDir != null ? sortDir.trim().toLowerCase() : "desc"; + if (!dir.equals("asc") && !dir.equals("desc")) { + return ResponseEntity.badRequest().build(); + } + boolean sortAscending = dir.equals("asc"); + int cappedSize = Math.min(200, Math.max(1, size)); int safePage = Math.max(0, page); - var dtoPage = adminStatisticsTableService.getStatisticsTable(tableMode, safePage, cappedSize); + var dtoPage = adminStatisticsTableService.getStatisticsTable(tableMode, safePage, cappedSize, sortAscending); Map response = new HashMap<>(); response.put("content", dtoPage.getContent()); @@ -68,6 +75,7 @@ public class AdminAnalyticsController { response.put("hasNext", dtoPage.hasNext()); response.put("hasPrevious", dtoPage.hasPrevious()); response.put("mode", tableMode.name()); + response.put("sortDir", dir); return ResponseEntity.ok(response); } diff --git a/src/main/java/com/honey/honey/repository/AdminStatisticsTableRepository.java b/src/main/java/com/honey/honey/repository/AdminStatisticsTableRepository.java index a379884..758e678 100644 --- a/src/main/java/com/honey/honey/repository/AdminStatisticsTableRepository.java +++ b/src/main/java/com/honey/honey/repository/AdminStatisticsTableRepository.java @@ -17,6 +17,15 @@ import java.util.Map; /** * Native aggregate queries for the admin statistics-by-day/month table. * Registration metrics use {@code db_users_d.date_reg} only (no join to {@code db_users_a}). + *

+ * Row order (newest vs oldest) is applied in the service when building pages, not via SQL {@code ORDER BY}. + * Range filters use columns that should stay indexed for performance: + *

    + *
  • {@code db_users_d.date_reg} — {@code idx_users_d_date_reg} (Flyway V80)
  • + *
  • {@code payments(status, completed_at)} — {@code idx_payments_status_completed_at} (V78)
  • + *
  • {@code payments(status, ftd, completed_at)} — {@code idx_payments_status_ftd_completed_at} (V77)
  • + *
  • {@code db_users_d(master_id, referer_id_1)} — {@code idx_users_d_master_referer1} (V79) for “ours” counts
  • + *
*/ @Repository public class AdminStatisticsTableRepository { diff --git a/src/main/java/com/honey/honey/service/AdminStatisticsTableService.java b/src/main/java/com/honey/honey/service/AdminStatisticsTableService.java index dffcb6f..b6251c2 100644 --- a/src/main/java/com/honey/honey/service/AdminStatisticsTableService.java +++ b/src/main/java/com/honey/honey/service/AdminStatisticsTableService.java @@ -40,14 +40,17 @@ public class AdminStatisticsTableService { MONTH } - public Page getStatisticsTable(TableMode mode, int page, int size) { + /** + * @param sortAscending when {@code false} (default), newest periods first; when {@code true}, oldest first. + */ + public Page getStatisticsTable(TableMode mode, int page, int size, boolean sortAscending) { LocalDate todayUtc = LocalDate.now(ZoneOffset.UTC); LocalDate firstDay = resolveFirstProjectDay(todayUtc); if (mode == TableMode.DAY) { - return buildDayPage(todayUtc, firstDay, page, size); + return buildDayPage(todayUtc, firstDay, page, size, sortAscending); } - return buildMonthPage(todayUtc, firstDay, page, size); + return buildMonthPage(todayUtc, firstDay, page, size, sortAscending); } private LocalDate resolveFirstProjectDay(LocalDate todayUtc) { @@ -62,7 +65,8 @@ public class AdminStatisticsTableService { return d; } - private Page buildDayPage(LocalDate todayUtc, LocalDate firstDay, int page, int size) { + private Page buildDayPage( + LocalDate todayUtc, LocalDate firstDay, int page, int size, boolean sortAscending) { long totalPeriods = ChronoUnit.DAYS.between(firstDay, todayUtc) + 1; if (totalPeriods < 1) { totalPeriods = 1; @@ -74,8 +78,16 @@ public class AdminStatisticsTableService { } int pageLen = (int) Math.min(size, totalPeriods - offset); - LocalDate newestOnPage = todayUtc.minusDays(offset); - LocalDate oldestOnPage = todayUtc.minusDays(offset + pageLen - 1); + + LocalDate oldestOnPage; + LocalDate newestOnPage; + if (sortAscending) { + oldestOnPage = firstDay.plusDays(offset); + newestOnPage = firstDay.plusDays(offset + pageLen - 1L); + } else { + newestOnPage = todayUtc.minusDays(offset); + oldestOnPage = todayUtc.minusDays(offset + pageLen - 1L); + } long startSec = oldestOnPage.atStartOfDay(ZoneOffset.UTC).toEpochSecond(); long endSec = newestOnPage.plusDays(1).atStartOfDay(ZoneOffset.UTC).toEpochSecond(); @@ -91,15 +103,17 @@ public class AdminStatisticsTableService { List rows = new ArrayList<>(pageLen); for (int i = 0; i < pageLen; i++) { - int k = offset + i; - LocalDate day = todayUtc.minusDays(k); + LocalDate day = sortAscending + ? firstDay.plusDays(offset + (long) i) + : todayUtc.minusDays(offset + (long) i); rows.add(toRowDay(day, buckets.getOrDefault(day, new StatisticsTableBucket()))); } return new PageImpl<>(rows, PageRequest.of(page, size), totalPeriods); } - private Page buildMonthPage(LocalDate todayUtc, LocalDate firstDay, int page, int size) { + private Page buildMonthPage( + LocalDate todayUtc, LocalDate firstDay, int page, int size, boolean sortAscending) { YearMonth nowYm = YearMonth.from(todayUtc); YearMonth firstYm = YearMonth.from(firstDay); @@ -115,8 +129,16 @@ public class AdminStatisticsTableService { } int pageLen = (int) Math.min(size, totalMonths - offset); - YearMonth newestOnPage = nowYm.minusMonths(offset); - YearMonth oldestOnPage = nowYm.minusMonths(offset + pageLen - 1); + + YearMonth oldestOnPage; + YearMonth newestOnPage; + if (sortAscending) { + oldestOnPage = firstYm.plusMonths(offset); + newestOnPage = firstYm.plusMonths(offset + pageLen - 1L); + } else { + newestOnPage = nowYm.minusMonths(offset); + oldestOnPage = nowYm.minusMonths(offset + pageLen - 1L); + } long startSec = oldestOnPage.atDay(1).atStartOfDay(ZoneOffset.UTC).toEpochSecond(); long endSec = newestOnPage.plusMonths(1).atDay(1).atStartOfDay(ZoneOffset.UTC).toEpochSecond(); @@ -132,8 +154,9 @@ public class AdminStatisticsTableService { List rows = new ArrayList<>(pageLen); for (int i = 0; i < pageLen; i++) { - int k = offset + i; - YearMonth ym = nowYm.minusMonths(k); + YearMonth ym = sortAscending + ? firstYm.plusMonths(offset + (long) i) + : nowYm.minusMonths(offset + (long) i); rows.add(toRowMonth(ym, buckets.getOrDefault(ym.toString(), new StatisticsTableBucket()))); }