From f1a488d70a8357b7a3560ac9cd7bedaab58309e6 Mon Sep 17 00:00:00 2001 From: sBubshait Date: Thu, 7 Aug 2025 09:38:37 +0300 Subject: [PATCH] feat: Leaderboard to use Riyadh timezone --- .../wesal/controller/PuzzleController.java | 12 +++-- .../wesal/wesal/service/PuzzleService.java | 52 +++++++++++++++++-- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/online/wesal/wesal/controller/PuzzleController.java b/backend/src/main/java/online/wesal/wesal/controller/PuzzleController.java index 14dcd6b..a35aab3 100644 --- a/backend/src/main/java/online/wesal/wesal/controller/PuzzleController.java +++ b/backend/src/main/java/online/wesal/wesal/controller/PuzzleController.java @@ -68,10 +68,11 @@ public class PuzzleController { } @GetMapping("/leaderboard") - @Operation(summary = "Get daily leaderboard", description = "Returns leaderboard for today's puzzle ordered by submission time") - public ResponseEntity>> getLeaderboard() { + @Operation(summary = "Get daily leaderboard", description = "Returns leaderboard with 9am cutoff logic (before 9am shows yesterday, from 9am shows today)") + public ResponseEntity>> getLeaderboard() { try { List attempts = puzzleService.getTodaysLeaderboard(); + String leaderboardInfo = puzzleService.getLeaderboardDateInfo(); List leaderboard = attempts.stream() .filter(attempt -> attempt.isSolved()) @@ -85,7 +86,12 @@ public class PuzzleController { )) .collect(Collectors.toList()); - return ResponseEntity.ok(ApiResponse.success(leaderboard)); + Map response = Map.of( + "leaderboard", leaderboard, + "dateInfo", leaderboardInfo + ); + + return ResponseEntity.ok(ApiResponse.success(response)); } catch (RuntimeException e) { return ResponseEntity.ok(ApiResponse.error(e.getMessage())); } diff --git a/backend/src/main/java/online/wesal/wesal/service/PuzzleService.java b/backend/src/main/java/online/wesal/wesal/service/PuzzleService.java index b2d16fc..eeb1516 100644 --- a/backend/src/main/java/online/wesal/wesal/service/PuzzleService.java +++ b/backend/src/main/java/online/wesal/wesal/service/PuzzleService.java @@ -9,6 +9,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -26,6 +30,9 @@ public class PuzzleService { @Autowired private UserService userService; + private static final ZoneId RIYADH_TIMEZONE = ZoneId.of("Asia/Riyadh"); + private static final LocalTime CUTOFF_TIME = LocalTime.of(9, 0); // 9:00 AM + private static final List WORD_LIST = Arrays.asList( "APPLE", "BEACH", "CHAIN", "DANCE", "EAGLE", "FLAME", "GRAPE", "HOUSE", "IMAGE", "JUDGE", "KNIFE", "LIGHT", "MUSIC", "NIGHT", "OCEAN", "PEACE", "QUEEN", "RIVER", "STAGE", "TIGER", @@ -88,7 +95,7 @@ public class PuzzleService { ); public Puzzle getTodaysPuzzle() { - LocalDate today = LocalDate.now(); + LocalDate today = getRiyadhToday(); Optional existingPuzzle = puzzleRepository.findByDate(today); if (existingPuzzle.isPresent()) { @@ -105,6 +112,26 @@ public class PuzzleService { return WORD_LIST.get(random.nextInt(WORD_LIST.size())); } + private LocalDate getRiyadhToday() { + return ZonedDateTime.now(RIYADH_TIMEZONE).toLocalDate(); + } + + private LocalDateTime getRiyadhNow() { + return ZonedDateTime.now(RIYADH_TIMEZONE).toLocalDateTime(); + } + + private LocalDate getLeaderboardDate() { + LocalDateTime riyadhNow = getRiyadhNow(); + LocalDate today = riyadhNow.toLocalDate(); + + // If it's before 9 AM, show yesterday's leaderboard + if (riyadhNow.toLocalTime().isBefore(CUTOFF_TIME)) { + return today.minusDays(1); + } + // From 9 AM onwards, show today's leaderboard + return today; + } + public boolean hasUserSolvedToday() { User currentUser = userService.getCurrentUser(); Puzzle todaysPuzzle = getTodaysPuzzle(); @@ -134,7 +161,26 @@ public class PuzzleService { } public List getTodaysLeaderboard() { - Puzzle todaysPuzzle = getTodaysPuzzle(); - return puzzleAttemptRepository.findByPuzzleOrderBySubmittedAtAsc(todaysPuzzle); + LocalDate leaderboardDate = getLeaderboardDate(); + Optional puzzleOpt = puzzleRepository.findByDate(leaderboardDate); + + if (puzzleOpt.isEmpty()) { + // If no puzzle exists for the leaderboard date, return empty list + return Arrays.asList(); + } + + return puzzleAttemptRepository.findByPuzzleOrderBySubmittedAtAsc(puzzleOpt.get()); + } + + public String getLeaderboardDateInfo() { + LocalDateTime riyadhNow = getRiyadhNow(); + LocalDate leaderboardDate = getLeaderboardDate(); + LocalDate today = riyadhNow.toLocalDate(); + + if (leaderboardDate.equals(today)) { + return "Today's Leaderboard (from 9:00 AM Riyadh time)"; + } else { + return "Yesterday's Leaderboard (before 9:00 AM Riyadh time)"; + } } } \ No newline at end of file