From 86e276e7359db4f3431f486c32f25320f46b90d8 Mon Sep 17 00:00:00 2001 From: Marcus Fihlon Date: Wed, 29 Apr 2026 13:02:11 +0200 Subject: [PATCH] fix(analytics): rebuild personal records after activity deletion Signed-off-by: Marcus Fihlon --- .../repository/PersonalRecordRepository.java | 5 +++ .../ActivityDeleteRecalculationService.java | 4 ++- .../fitpub/service/PersonalRecordService.java | 17 +++++++++ ...ctivityDeleteRecalculationServiceTest.java | 5 +++ .../service/PersonalRecordServiceTest.java | 35 +++++++++++++++++++ 5 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/javahippie/fitpub/repository/PersonalRecordRepository.java b/src/main/java/net/javahippie/fitpub/repository/PersonalRecordRepository.java index a4faf4b..f5a8d69 100644 --- a/src/main/java/net/javahippie/fitpub/repository/PersonalRecordRepository.java +++ b/src/main/java/net/javahippie/fitpub/repository/PersonalRecordRepository.java @@ -35,6 +35,11 @@ public interface PersonalRecordRepository extends JpaRepository pendingRecalculations = new ConcurrentHashMap<>(); @Async @@ -42,6 +43,7 @@ public class ActivityDeleteRecalculationService { do { Set datesToRecalculate = pending.drainDates(); achievementService.rebuildAchievementsForUser(event.userId()); + personalRecordService.rebuildPersonalRecordsForUser(event.userId()); for (LocalDate date : datesToRecalculate) { activitySummaryService.updateWeeklySummary(event.userId(), date); activitySummaryService.updateMonthlySummary(event.userId(), date); @@ -49,7 +51,7 @@ public class ActivityDeleteRecalculationService { } } while (pending.keepProcessing()); - log.info("Recalculated achievements and summaries after deleting activities for user {}", event.userId()); + log.info("Recalculated achievements, personal records, and summaries after deleting activities for user {}", event.userId()); } private static final class PendingUserRecalculation { diff --git a/src/main/java/net/javahippie/fitpub/service/PersonalRecordService.java b/src/main/java/net/javahippie/fitpub/service/PersonalRecordService.java index 9485944..7e802e0 100644 --- a/src/main/java/net/javahippie/fitpub/service/PersonalRecordService.java +++ b/src/main/java/net/javahippie/fitpub/service/PersonalRecordService.java @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import net.javahippie.fitpub.model.entity.Activity; import net.javahippie.fitpub.model.entity.PersonalRecord; +import net.javahippie.fitpub.repository.ActivityRepository; import net.javahippie.fitpub.repository.PersonalRecordRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -23,6 +24,7 @@ import java.util.UUID; @Slf4j public class PersonalRecordService { + private final ActivityRepository activityRepository; private final PersonalRecordRepository personalRecordRepository; /** @@ -320,4 +322,19 @@ public class PersonalRecordService { public long getPersonalRecordCount(UUID userId) { return personalRecordRepository.countByUserId(userId); } + + /** + * Rebuild all personal records for a user from remaining activities. + */ + @Transactional + public void rebuildPersonalRecordsForUser(UUID userId) { + personalRecordRepository.deleteByUserId(userId); + + List activities = activityRepository.findByUserIdOrderByStartedAtAsc(userId); + for (Activity activity : activities) { + checkAndUpdatePersonalRecords(activity); + } + + log.info("Rebuilt personal records for user {} from {} activities", userId, activities.size()); + } } diff --git a/src/test/java/net/javahippie/fitpub/service/ActivityDeleteRecalculationServiceTest.java b/src/test/java/net/javahippie/fitpub/service/ActivityDeleteRecalculationServiceTest.java index dd2f522..88734a5 100644 --- a/src/test/java/net/javahippie/fitpub/service/ActivityDeleteRecalculationServiceTest.java +++ b/src/test/java/net/javahippie/fitpub/service/ActivityDeleteRecalculationServiceTest.java @@ -24,6 +24,9 @@ class ActivityDeleteRecalculationServiceTest { @Mock private ActivitySummaryService activitySummaryService; + @Mock + private PersonalRecordService personalRecordService; + @InjectMocks private ActivityDeleteRecalculationService activityDeleteRecalculationService; @@ -36,6 +39,7 @@ class ActivityDeleteRecalculationServiceTest { activityDeleteRecalculationService.handleActivityDeleted(new ActivityDeletedEvent(userId, activityDate)); verify(achievementService).rebuildAchievementsForUser(userId); + verify(personalRecordService).rebuildPersonalRecordsForUser(userId); verify(activitySummaryService).updateWeeklySummary(userId, activityDate); verify(activitySummaryService).updateMonthlySummary(userId, activityDate); verify(activitySummaryService).updateYearlySummary(userId, activityDate); @@ -59,6 +63,7 @@ class ActivityDeleteRecalculationServiceTest { activityDeleteRecalculationService.handleActivityDeleted(new ActivityDeletedEvent(userId, firstDate)); verify(achievementService, times(2)).rebuildAchievementsForUser(userId); + verify(personalRecordService, times(2)).rebuildPersonalRecordsForUser(userId); verify(activitySummaryService).updateWeeklySummary(userId, firstDate); verify(activitySummaryService).updateMonthlySummary(userId, firstDate); verify(activitySummaryService).updateYearlySummary(userId, firstDate); diff --git a/src/test/java/net/javahippie/fitpub/service/PersonalRecordServiceTest.java b/src/test/java/net/javahippie/fitpub/service/PersonalRecordServiceTest.java index c789bee..04ca121 100644 --- a/src/test/java/net/javahippie/fitpub/service/PersonalRecordServiceTest.java +++ b/src/test/java/net/javahippie/fitpub/service/PersonalRecordServiceTest.java @@ -10,6 +10,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import net.javahippie.fitpub.model.entity.Activity; import net.javahippie.fitpub.model.entity.ActivityMetrics; import net.javahippie.fitpub.model.entity.PersonalRecord; +import net.javahippie.fitpub.repository.ActivityRepository; import net.javahippie.fitpub.repository.PersonalRecordRepository; import java.math.BigDecimal; @@ -33,6 +34,9 @@ class PersonalRecordServiceTest { @Mock private PersonalRecordRepository personalRecordRepository; + @Mock + private ActivityRepository activityRepository; + @InjectMocks private PersonalRecordService personalRecordService; @@ -415,6 +419,37 @@ class PersonalRecordServiceTest { )); } + @Test + @DisplayName("Should rebuild personal records from remaining activities") + void testRebuildPersonalRecordsForUser() { + Activity firstActivity = createActivity( + 10000L, + 3600L, + BigDecimal.valueOf(100) + ); + firstActivity.setStartedAt(testTime.minusDays(2)); + + Activity secondActivity = createActivity( + 15000L, + 4500L, + BigDecimal.valueOf(200) + ); + secondActivity.setStartedAt(testTime.minusDays(1)); + + when(activityRepository.findByUserIdOrderByStartedAtAsc(userId)) + .thenReturn(List.of(firstActivity, secondActivity)); + when(personalRecordRepository.findByUserIdAndActivityTypeAndRecordType(any(), any(), any())) + .thenReturn(Optional.empty()); + when(personalRecordRepository.save(any(PersonalRecord.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + personalRecordService.rebuildPersonalRecordsForUser(userId); + + verify(personalRecordRepository).deleteByUserId(userId); + verify(activityRepository).findByUserIdOrderByStartedAtAsc(userId); + verify(personalRecordRepository, atLeastOnce()).save(any(PersonalRecord.class)); + } + // Helper methods private Activity createActivity(long distanceMeters, long durationSeconds, BigDecimal elevationGain) {