diff --git a/src/main/java/net/javahippie/fitpub/repository/ActivityRepository.java b/src/main/java/net/javahippie/fitpub/repository/ActivityRepository.java index 910c66e..f0cc31d 100644 --- a/src/main/java/net/javahippie/fitpub/repository/ActivityRepository.java +++ b/src/main/java/net/javahippie/fitpub/repository/ActivityRepository.java @@ -48,9 +48,15 @@ public interface ActivityRepository extends JpaRepository { * @return list of activities */ List findByUserIdAndStartedAtBetweenOrderByStartedAtDesc( - UUID userId, - LocalDateTime startDate, - LocalDateTime endDate + UUID userId, + LocalDateTime startDate, + LocalDateTime endDate + ); + + boolean existsByUserIdAndStartedAtBetween( + UUID userId, + LocalDateTime startDate, + LocalDateTime endDate ); /** diff --git a/src/main/java/net/javahippie/fitpub/service/ActivitySummaryService.java b/src/main/java/net/javahippie/fitpub/service/ActivitySummaryService.java index 6579e45..fd054ac 100644 --- a/src/main/java/net/javahippie/fitpub/service/ActivitySummaryService.java +++ b/src/main/java/net/javahippie/fitpub/service/ActivitySummaryService.java @@ -229,26 +229,67 @@ public class ActivitySummaryService { /** * Get current week summary. */ - @Transactional(readOnly = true) + @Transactional public ActivitySummary getCurrentWeekSummary(UUID userId) { LocalDate weekStart = LocalDate.now().with(TemporalAdjusters.previousOrSame(java.time.DayOfWeek.MONDAY)); - return activitySummaryRepository.findByUserIdAndPeriodTypeAndPeriodStart( + LocalDate weekEndExclusive = weekStart.plusDays(7); + return getOrBuildCurrentSummary( userId, ActivitySummary.PeriodType.WEEK, - weekStart - ).orElse(null); + weekStart, + weekEndExclusive, + () -> updateWeeklySummary(userId, weekStart) + ); } /** * Get current month summary. */ - @Transactional(readOnly = true) + @Transactional public ActivitySummary getCurrentMonthSummary(UUID userId) { LocalDate monthStart = LocalDate.now().with(TemporalAdjusters.firstDayOfMonth()); - return activitySummaryRepository.findByUserIdAndPeriodTypeAndPeriodStart( + LocalDate monthEndExclusive = monthStart.plusMonths(1); + return getOrBuildCurrentSummary( userId, ActivitySummary.PeriodType.MONTH, - monthStart + monthStart, + monthEndExclusive, + () -> updateMonthlySummary(userId, monthStart) + ); + } + + private ActivitySummary getOrBuildCurrentSummary( + UUID userId, + ActivitySummary.PeriodType periodType, + LocalDate periodStart, + LocalDate periodEndExclusive, + Runnable rebuildAction + ) { + ActivitySummary existingSummary = activitySummaryRepository.findByUserIdAndPeriodTypeAndPeriodStart( + userId, + periodType, + periodStart + ).orElse(null); + + if (existingSummary != null) { + return existingSummary; + } + + boolean hasActivitiesInPeriod = activityRepository.existsByUserIdAndStartedAtBetween( + userId, + periodStart.atStartOfDay(), + periodEndExclusive.atStartOfDay() + ); + + if (!hasActivitiesInPeriod) { + return null; + } + + rebuildAction.run(); + return activitySummaryRepository.findByUserIdAndPeriodTypeAndPeriodStart( + userId, + periodType, + periodStart ).orElse(null); } } diff --git a/src/test/java/net/javahippie/fitpub/service/ActivitySummaryServiceTest.java b/src/test/java/net/javahippie/fitpub/service/ActivitySummaryServiceTest.java index fdccc74..bab62b8 100644 --- a/src/test/java/net/javahippie/fitpub/service/ActivitySummaryServiceTest.java +++ b/src/test/java/net/javahippie/fitpub/service/ActivitySummaryServiceTest.java @@ -21,6 +21,8 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -92,4 +94,72 @@ class ActivitySummaryServiceTest { summary.getActivityCount() == 1 )); } + + @Test + @DisplayName("Should rebuild current month summary on demand when activities exist but summary is missing") + void getCurrentMonthSummary_RebuildsMissingSummary() { + LocalDate monthStart = LocalDate.now().withDayOfMonth(1); + LocalDateTime startDateTime = monthStart.atStartOfDay(); + LocalDateTime endDateTime = monthStart.plusMonths(1).atStartOfDay(); + + Activity activity = Activity.builder() + .id(UUID.randomUUID()) + .userId(userId) + .activityType(Activity.ActivityType.RUN) + .startedAt(startDateTime.plusDays(2).plusHours(7)) + .endedAt(startDateTime.plusDays(2).plusHours(8)) + .totalDistance(BigDecimal.valueOf(10000)) + .totalDurationSeconds(3600L) + .elevationGain(BigDecimal.valueOf(150)) + .build(); + + ActivitySummary rebuiltSummary = ActivitySummary.builder() + .userId(userId) + .periodType(ActivitySummary.PeriodType.MONTH) + .periodStart(monthStart) + .periodEnd(monthStart.plusMonths(1).minusDays(1)) + .activityCount(1) + .build(); + + when(activitySummaryRepository.findByUserIdAndPeriodTypeAndPeriodStart( + userId, + ActivitySummary.PeriodType.MONTH, + monthStart + )).thenReturn(Optional.empty(), Optional.of(rebuiltSummary)); + when(activityRepository.existsByUserIdAndStartedAtBetween(userId, startDateTime, endDateTime)) + .thenReturn(true); + when(activityRepository.findByUserIdAndStartedAtBetweenOrderByStartedAtDesc(userId, startDateTime, endDateTime)) + .thenReturn(List.of(activity)); + when(personalRecordRepository.countByUserIdAndDateRange(userId, startDateTime, endDateTime)).thenReturn(0L); + when(achievementRepository.countByUserIdAndActivityStartedDateRange(userId, startDateTime, endDateTime)).thenReturn(0L); + when(activitySummaryRepository.save(org.mockito.ArgumentMatchers.any(ActivitySummary.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + ActivitySummary result = activitySummaryService.getCurrentMonthSummary(userId); + + assertNotNull(result); + assertEquals(1, result.getActivityCount()); + verify(activityRepository).existsByUserIdAndStartedAtBetween(userId, startDateTime, endDateTime); + } + + @Test + @DisplayName("Should return null for current month summary when no activities exist in the period") + void getCurrentMonthSummary_ReturnsNullWhenNoActivitiesExist() { + LocalDate monthStart = LocalDate.now().withDayOfMonth(1); + LocalDateTime startDateTime = monthStart.atStartOfDay(); + LocalDateTime endDateTime = monthStart.plusMonths(1).atStartOfDay(); + + when(activitySummaryRepository.findByUserIdAndPeriodTypeAndPeriodStart( + userId, + ActivitySummary.PeriodType.MONTH, + monthStart + )).thenReturn(Optional.empty()); + when(activityRepository.existsByUserIdAndStartedAtBetween(userId, startDateTime, endDateTime)) + .thenReturn(false); + + ActivitySummary result = activitySummaryService.getCurrentMonthSummary(userId); + + assertNull(result); + verify(activityRepository).existsByUserIdAndStartedAtBetween(userId, startDateTime, endDateTime); + } }