From 2ac3d82fda82c1629ec1fb3bd3b3370518064e8c Mon Sep 17 00:00:00 2001 From: Marcus Fihlon Date: Wed, 29 Apr 2026 10:07:01 +0200 Subject: [PATCH] feat(analytics): add manual achievement rebuild action Signed-off-by: Marcus Fihlon --- .../controller/AnalyticsController.java | 21 +++++++ .../templates/analytics/achievements.html | 55 ++++++++++++++++--- 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/javahippie/fitpub/controller/AnalyticsController.java b/src/main/java/net/javahippie/fitpub/controller/AnalyticsController.java index 43b5793..e5c40eb 100644 --- a/src/main/java/net/javahippie/fitpub/controller/AnalyticsController.java +++ b/src/main/java/net/javahippie/fitpub/controller/AnalyticsController.java @@ -133,6 +133,25 @@ public class AnalyticsController { return ResponseEntity.ok(achievements); } + /** + * Rebuild achievements for the authenticated user. + */ + @PostMapping("/achievements/rebuild") + public ResponseEntity rebuildAchievements( + @AuthenticationPrincipal UserDetails userDetails) { + + UUID userId = getUserId(userDetails); + + try { + achievementService.rebuildAchievementsForUser(userId); + return ResponseEntity.ok(new RebuildResponse("Achievements recalculated successfully")); + } catch (Exception e) { + log.error("Failed to rebuild achievements for user {}", userDetails.getUsername(), e); + return ResponseEntity.internalServerError() + .body(new RebuildResponse("Failed to recalculate achievements: " + e.getMessage())); + } + } + /** * Get training load for a date range. */ @@ -276,4 +295,6 @@ public class AnalyticsController { case UNKNOWN -> "Not enough data to calculate form status."; }; } + + private record RebuildResponse(String message) {} } diff --git a/src/main/resources/templates/analytics/achievements.html b/src/main/resources/templates/analytics/achievements.html index 1187c57..cd4e122 100644 --- a/src/main/resources/templates/analytics/achievements.html +++ b/src/main/resources/templates/analytics/achievements.html @@ -8,15 +8,20 @@
-
-
-

- Achievements -

- - Back to Dashboard - -
+
+
+

+ Achievements +

+
+ + + Back to Dashboard + +
+
@@ -100,6 +105,35 @@ } } + async function rebuildAchievements() { + const rebuildBtn = document.getElementById('rebuild-achievements-btn'); + const originalContent = rebuildBtn.innerHTML; + + rebuildBtn.disabled = true; + rebuildBtn.innerHTML = 'Recalculating...'; + + try { + const response = await FitPubAuth.authenticatedFetch('/api/analytics/achievements/rebuild', { + method: 'POST' + }); + + const result = await response.json(); + + if (!response.ok) { + throw new Error(result.message || 'Failed to recalculate achievements'); + } + + FitPub.showAlert('success', result.message || 'Achievements recalculated successfully'); + await loadAchievements(); + } catch (error) { + console.error('Error rebuilding achievements:', error); + FitPub.showAlert('danger', error.message || 'Failed to recalculate achievements'); + } finally { + rebuildBtn.disabled = false; + rebuildBtn.innerHTML = originalContent; + } + } + function updateStats(achievements) { // Earned count document.getElementById('earned-count').textContent = achievements.length; @@ -108,6 +142,8 @@ if (achievements.length > 0) { const latest = new Date(achievements[0].earnedAt); document.getElementById('latest-date').textContent = latest.toLocaleDateString(); + } else { + document.getElementById('latest-date').textContent = '-'; } // Completion percentage @@ -217,6 +253,7 @@ window.location.href = '/auth/login'; return; } + document.getElementById('rebuild-achievements-btn').addEventListener('click', rebuildAchievements); loadAchievements(); });