Add on-demand heatmap rebuild and remove nightly scheduler
- Added rebuild button to heatmap page with loading states - Added POST /api/heatmap/me/rebuild endpoint for on-demand recalculation - Removed HeatmapRecalculationScheduler (nightly 3 AM cron job) - Removed @EnableScheduling annotation since no schedulers remain - Users can now rebuild their heatmap manually instead of relying on automatic nightly recalculation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f391028061
commit
66b14ebf7f
6 changed files with 90 additions and 60 deletions
|
|
@ -10,7 +10,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
|
|
@ -20,7 +19,6 @@ import org.springframework.web.client.RestTemplate;
|
|||
*/
|
||||
@SpringBootApplication
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
@Slf4j
|
||||
public class FitPubApplication {
|
||||
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ public class SecurityConfig {
|
|||
|
||||
// Protected endpoints - Heatmap API
|
||||
.requestMatchers("/api/heatmap/me").authenticated()
|
||||
.requestMatchers(HttpMethod.POST, "/api/heatmap/me/rebuild").authenticated()
|
||||
.requestMatchers(HttpMethod.GET, "/api/heatmap/user/*").permitAll()
|
||||
|
||||
// Protected endpoints - Activities API (upload, edit, delete)
|
||||
|
|
|
|||
|
|
@ -98,4 +98,34 @@ public class HeatmapController {
|
|||
|
||||
return ResponseEntity.ok(heatmapData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild heatmap for the authenticated user.
|
||||
* This triggers a full recalculation of the user's heatmap grid from all their activities.
|
||||
*
|
||||
* @param userDetails authenticated user
|
||||
* @return success message
|
||||
*/
|
||||
@PostMapping("/me/rebuild")
|
||||
public ResponseEntity<?> rebuildMyHeatmap(@AuthenticationPrincipal UserDetails userDetails) {
|
||||
log.info("User {} requested heatmap rebuild", userDetails.getUsername());
|
||||
|
||||
User user = userRepository.findByUsername(userDetails.getUsername())
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
|
||||
try {
|
||||
heatmapGridService.recalculateUserHeatmap(user);
|
||||
log.info("Heatmap rebuild completed successfully for user {}", userDetails.getUsername());
|
||||
return ResponseEntity.ok().body(new RebuildResponse("Heatmap rebuilt successfully"));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to rebuild heatmap for user {}", userDetails.getUsername(), e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(new RebuildResponse("Failed to rebuild heatmap: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple response DTO for rebuild endpoint
|
||||
*/
|
||||
private record RebuildResponse(String message) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
package org.operaton.fitpub.scheduler;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.operaton.fitpub.model.entity.User;
|
||||
import org.operaton.fitpub.repository.UserRepository;
|
||||
import org.operaton.fitpub.service.HeatmapGridService;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Scheduled task to recalculate user heatmaps nightly.
|
||||
* Ensures heatmap data stays in sync with activities even if incremental updates fail.
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class HeatmapRecalculationScheduler {
|
||||
|
||||
private final HeatmapGridService heatmapGridService;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
/**
|
||||
* Recalculate heatmaps for all users.
|
||||
* Runs daily at 3 AM server time.
|
||||
*/
|
||||
@Scheduled(cron = "0 0 3 * * *")
|
||||
public void recalculateAllUserHeatmaps() {
|
||||
log.info("Starting nightly heatmap recalculation for all users");
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
List<User> users = userRepository.findAll();
|
||||
log.info("Found {} users to process", users.size());
|
||||
|
||||
int successCount = 0;
|
||||
int errorCount = 0;
|
||||
|
||||
for (User user : users) {
|
||||
try {
|
||||
heatmapGridService.recalculateUserHeatmap(user);
|
||||
successCount++;
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to recalculate heatmap for user {}", user.getUsername(), e);
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
log.info("Heatmap recalculation completed in {}ms. Success: {}, Errors: {}",
|
||||
duration, successCount, errorCount);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,12 @@ document.addEventListener('DOMContentLoaded', async function() {
|
|||
}
|
||||
|
||||
await loadHeatmap();
|
||||
|
||||
// Attach rebuild button handler
|
||||
const rebuildBtn = document.getElementById('rebuildBtn');
|
||||
if (rebuildBtn) {
|
||||
rebuildBtn.addEventListener('click', rebuildHeatmap);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -175,3 +181,46 @@ function fitMapToBounds(features) {
|
|||
|
||||
heatmapMap.fitBounds(bounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the heatmap by triggering a full recalculation
|
||||
*/
|
||||
async function rebuildHeatmap() {
|
||||
const rebuildBtn = document.getElementById('rebuildBtn');
|
||||
const originalContent = rebuildBtn.innerHTML;
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
|
||||
// Disable button and show loading state
|
||||
rebuildBtn.disabled = true;
|
||||
rebuildBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Rebuilding...';
|
||||
errorAlert.classList.add('d-none');
|
||||
|
||||
try {
|
||||
// Call rebuild endpoint
|
||||
const response = await FitPubAuth.authenticatedFetch('/api/heatmap/me/rebuild', {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to rebuild heatmap');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// Show success message
|
||||
FitPub.showAlert('success', result.message || 'Heatmap rebuilt successfully!');
|
||||
|
||||
// Reload the heatmap
|
||||
await loadHeatmap();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error rebuilding heatmap:', error);
|
||||
errorAlert.classList.remove('d-none');
|
||||
errorMessage.textContent = 'Failed to rebuild heatmap. Please try again later.';
|
||||
} finally {
|
||||
// Restore button state
|
||||
rebuildBtn.disabled = false;
|
||||
rebuildBtn.innerHTML = originalContent;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,8 +52,8 @@
|
|||
|
||||
<!-- Stats Card -->
|
||||
<div class="heatmap-stats mb-4" id="statsCard" style="display: none;">
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3 mb-md-0">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-3 mb-3 mb-md-0">
|
||||
<div class="stat-item">
|
||||
<i class="bi bi-grid-3x3"></i>
|
||||
<div>
|
||||
|
|
@ -62,7 +62,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3 mb-md-0">
|
||||
<div class="col-md-3 mb-3 mb-md-0">
|
||||
<div class="stat-item">
|
||||
<i class="bi bi-fire"></i>
|
||||
<div>
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3 mb-3 mb-md-0">
|
||||
<div class="stat-item">
|
||||
<i class="bi bi-clock-history"></i>
|
||||
<div>
|
||||
|
|
@ -80,6 +80,12 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 text-md-end">
|
||||
<button id="rebuildBtn" class="btn btn-outline-primary">
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
Rebuild Heatmap
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue