admin statistics part3
All checks were successful
Deploy to VPS / deploy (push) Successful in 1m17s

This commit is contained in:
Tihon
2026-03-20 14:06:39 +02:00
parent 31768fcc07
commit 26515ab621
3 changed files with 55 additions and 15 deletions

View File

@@ -45,7 +45,8 @@ public class AdminAnalyticsController {
public ResponseEntity<Map<String, Object>> 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<String, Object> 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);
}

View File

@@ -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}).
* <p>
* 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:
* <ul>
* <li>{@code db_users_d.date_reg} — {@code idx_users_d_date_reg} (Flyway V80)</li>
* <li>{@code payments(status, completed_at)} — {@code idx_payments_status_completed_at} (V78)</li>
* <li>{@code payments(status, ftd, completed_at)} — {@code idx_payments_status_ftd_completed_at} (V77)</li>
* <li>{@code db_users_d(master_id, referer_id_1)} — {@code idx_users_d_master_referer1} (V79) for “ours” counts</li>
* </ul>
*/
@Repository
public class AdminStatisticsTableRepository {

View File

@@ -40,14 +40,17 @@ public class AdminStatisticsTableService {
MONTH
}
public Page<AdminStatisticsTableRowDto> getStatisticsTable(TableMode mode, int page, int size) {
/**
* @param sortAscending when {@code false} (default), newest periods first; when {@code true}, oldest first.
*/
public Page<AdminStatisticsTableRowDto> 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<AdminStatisticsTableRowDto> buildDayPage(LocalDate todayUtc, LocalDate firstDay, int page, int size) {
private Page<AdminStatisticsTableRowDto> 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<AdminStatisticsTableRowDto> 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<AdminStatisticsTableRowDto> buildMonthPage(LocalDate todayUtc, LocalDate firstDay, int page, int size) {
private Page<AdminStatisticsTableRowDto> 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<AdminStatisticsTableRowDto> 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())));
}