fix(analytics): rebuild achievements from activity history
Signed-off-by: Marcus Fihlon <marcus@fihlon.swiss>
This commit is contained in:
parent
4d16e8c685
commit
6af484bcf7
4 changed files with 57 additions and 4 deletions
|
|
@ -2,6 +2,7 @@ package net.javahippie.fitpub.repository;
|
|||
|
||||
import net.javahippie.fitpub.model.entity.Achievement;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
|
@ -40,6 +41,12 @@ public interface AchievementRepository extends JpaRepository<Achievement, UUID>
|
|||
*/
|
||||
long countByUserId(UUID userId);
|
||||
|
||||
/**
|
||||
* Delete all achievements for a user.
|
||||
*/
|
||||
@Modifying
|
||||
void deleteByUserId(UUID userId);
|
||||
|
||||
/**
|
||||
* Get count of achievements earned by a user in a date range.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -86,6 +86,31 @@ public class AchievementService {
|
|||
return newAchievements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild all achievements for a user from chronological activity history.
|
||||
*/
|
||||
@Transactional
|
||||
public List<Achievement> rebuildAchievementsForUser(UUID userId) {
|
||||
if (userId == null) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
List<Activity> activityHistory = activityRepository.findByUserIdOrderByStartedAtAsc(userId);
|
||||
if (activityHistory.isEmpty()) {
|
||||
achievementRepository.deleteByUserId(userId);
|
||||
return List.of();
|
||||
}
|
||||
|
||||
achievementRepository.deleteByUserId(userId);
|
||||
|
||||
List<Achievement> rebuiltAchievements = new ArrayList<>();
|
||||
for (Activity activity : activityHistory) {
|
||||
rebuiltAchievements.addAll(checkAndAwardAchievements(activity));
|
||||
}
|
||||
|
||||
return rebuiltAchievements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check first activity achievements.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -344,11 +344,9 @@ public class BatchImportService {
|
|||
personalRecordService.checkAndUpdatePersonalRecords(activity);
|
||||
}
|
||||
|
||||
// Recalculate achievements for each activity
|
||||
// Recalculate achievements from the full chronological activity history
|
||||
log.debug("Recalculating achievements...");
|
||||
for (Activity activity : activities) {
|
||||
achievementService.checkAndAwardAchievements(activity);
|
||||
}
|
||||
achievementService.rebuildAchievementsForUser(job.getUserId());
|
||||
|
||||
// Recalculate training load for each activity
|
||||
log.debug("Recalculating training load...");
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
|
@ -391,6 +392,28 @@ class AchievementServiceTest {
|
|||
assertEquals(activity.getId(), distanceAchievement.getActivityId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should rebuild achievements by deleting existing rows and replaying history")
|
||||
void testRebuildAchievementsForUser() {
|
||||
Activity previous = createActivity(Activity.ActivityType.RUN, 9000L, BigDecimal.ZERO);
|
||||
previous.setStartedAt(LocalDateTime.of(2025, 11, 30, 10, 0));
|
||||
previous.setEndedAt(LocalDateTime.of(2025, 11, 30, 11, 0));
|
||||
Activity activity = createActivity(Activity.ActivityType.RUN, 2000L, BigDecimal.ZERO);
|
||||
activity.setStartedAt(LocalDateTime.of(2025, 12, 1, 7, 15));
|
||||
activity.setEndedAt(LocalDateTime.of(2025, 12, 1, 8, 5));
|
||||
|
||||
stubHistory(previous, activity);
|
||||
when(achievementRepository.save(any(Achievement.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
List<Achievement> rebuilt = achievementService.rebuildAchievementsForUser(userId);
|
||||
|
||||
assertTrue(rebuilt.stream().anyMatch(a -> a.getAchievementType() == Achievement.AchievementType.DISTANCE_10K));
|
||||
|
||||
InOrder inOrder = inOrder(achievementRepository);
|
||||
inOrder.verify(achievementRepository).deleteByUserId(userId);
|
||||
inOrder.verify(achievementRepository, atLeastOnce()).save(any(Achievement.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should get user achievements")
|
||||
void testGetUserAchievements() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue