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 net.javahippie.fitpub.model.entity.Achievement;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
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.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
@ -40,6 +41,12 @@ public interface AchievementRepository extends JpaRepository<Achievement, UUID>
|
||||||
*/
|
*/
|
||||||
long countByUserId(UUID userId);
|
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.
|
* Get count of achievements earned by a user in a date range.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,31 @@ public class AchievementService {
|
||||||
return newAchievements;
|
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.
|
* Check first activity achievements.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -344,11 +344,9 @@ public class BatchImportService {
|
||||||
personalRecordService.checkAndUpdatePersonalRecords(activity);
|
personalRecordService.checkAndUpdatePersonalRecords(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recalculate achievements for each activity
|
// Recalculate achievements from the full chronological activity history
|
||||||
log.debug("Recalculating achievements...");
|
log.debug("Recalculating achievements...");
|
||||||
for (Activity activity : activities) {
|
achievementService.rebuildAchievementsForUser(job.getUserId());
|
||||||
achievementService.checkAndAwardAchievements(activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recalculate training load for each activity
|
// Recalculate training load for each activity
|
||||||
log.debug("Recalculating training load...");
|
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.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InOrder;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
@ -391,6 +392,28 @@ class AchievementServiceTest {
|
||||||
assertEquals(activity.getId(), distanceAchievement.getActivityId());
|
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
|
@Test
|
||||||
@DisplayName("Should get user achievements")
|
@DisplayName("Should get user achievements")
|
||||||
void testGetUserAchievements() {
|
void testGetUserAchievements() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue