From ec3c626b1a2ee21751e373df50cf6bb25cf725f8 Mon Sep 17 00:00:00 2001 From: Nancy Huang <205217630+naanci@users.noreply.github.com> Date: Fri, 9 Oct 2026 20:08:29 -0400 Subject: [PATCH 1/4] TAN-1023: migrate repository nullability to use Optional --- .../codebloom/api/admin/AdminController.java | 15 ++--- .../announcement/AnnouncementController.java | 6 +- .../codebloom/api/auth/AuthController.java | 5 +- .../CustomAuthenticationSuccessHandler.java | 27 +++++--- .../codebloom/api/club/ClubController.java | 8 +-- .../api/club/service/ClubService.java | 4 +- .../api/external/ExternalController.java | 7 +- .../leaderboard/LeaderboardController.java | 10 +-- .../api/submission/SubmissionController.java | 13 ++-- .../codebloom/api/user/UserController.java | 8 +-- .../api/user/v2/UserV2Controller.java | 8 +-- .../common/components/DiscordClubManager.java | 5 +- .../common/components/LeaderboardManager.java | 33 +++++----- .../common/components/duel/DuelManager.java | 7 +- .../db/helper/annotations/NotNullColumn.java | 19 ------ .../db/helper/annotations/NullColumn.java | 19 ------ .../codebloom/common/db/models/Session.java | 10 +-- .../db/models/achievements/Achievement.java | 24 +++---- .../db/models/announcement/Announcement.java | 12 ++-- .../codebloom/common/db/models/auth/Auth.java | 10 +-- .../codebloom/common/db/models/job/Job.java | 17 ++--- .../codebloom/common/db/models/potd/POTD.java | 6 -- .../models/question/topic/QuestionTopic.java | 8 --- .../codebloom/common/db/models/user/User.java | 30 ++++----- .../common/db/models/user/UserWithScore.java | 2 - .../achievements/AchievementRepository.java | 7 +- .../AchievementSqlRepository.java | 29 ++++---- .../announcement/AnnouncementRepository.java | 5 +- .../AnnouncementSqlRepository.java | 16 ++--- .../common/db/repos/api/ApiKeyRepository.java | 5 +- .../db/repos/api/ApiKeySqlRepository.java | 14 ++-- .../api/access/ApiKeyAccessRepository.java | 3 +- .../api/access/ApiKeyAccessSqlRepository.java | 6 +- .../common/db/repos/auth/AuthRepository.java | 5 +- .../db/repos/auth/AuthSqlRepository.java | 16 ++--- .../common/db/repos/club/ClubRepository.java | 9 +-- .../db/repos/club/ClubSqlRepository.java | 19 ++---- .../common/db/repos/job/JobRepository.java | 5 +- .../common/db/repos/job/JobSqlRepository.java | 14 ++-- .../leaderboard/LeaderboardSqlRepository.java | 38 ++++++----- .../repos/session/SessionSqlRepository.java | 5 +- .../common/db/repos/user/UserRepository.java | 13 ++-- .../db/repos/user/UserSqlRepository.java | 66 +++++++++---------- .../repos/weekly/WeeklyMessageRepository.java | 5 +- .../weekly/WeeklyMessageSqlRepository.java | 9 ++- .../dto/achievement/AchievementDto.java | 6 +- .../dto/announcement/AnnouncementDto.java | 2 +- .../common/dto/user/PrivateUserDto.java | 8 +-- .../codebloom/common/dto/user/UserDto.java | 6 +- .../common/dto/user/UserWithScoreDto.java | 6 +- .../codebloom/common/security/Protector.java | 7 +- .../submissions/SubmissionsHandler.java | 6 +- .../playwright/PlaywrightClient.java | 6 +- .../scheduled/auth/LeetcodeAuthStealer.java | 14 ++-- .../scheduled/discord/WeeklyLeaderboard.java | 7 +- .../LeetcodeQuestionProcessService.java | 6 +- .../submission/SubmissionScheduler.java | 6 +- .../api/admin/AdminControllerTest.java | 6 +- .../api/auth/AuthControllerTest.java | 2 +- ...ustomAuthenticationSuccessHandlerTest.java | 30 ++++----- .../api/duel/DuelControllerTest.java | 2 +- .../LeaderboardControllerTest.java | 2 +- .../submission/SubmissionControllerTest.java | 12 ++-- .../api/user/UserControllerTest.java | 8 +-- .../components/DiscordClubManagerTest.java | 6 +- .../components/LeaderboardManagerTest.java | 6 +- .../components/duel/DuelManagerTest.java | 12 ++-- .../components/duel/PartyManagerTest.java | 2 +- .../AchievementRepositoryTest.java | 29 ++++---- .../AnnouncementRepositoryTest.java | 20 ++++-- .../db/repos/api/ApiKeyRepositoryTest.java | 17 ++--- .../access/ApiKeyAccessRepositoryTest.java | 14 ++-- .../db/repos/auth/AuthRepositoryTest.java | 7 +- .../db/repos/club/ClubRepositoryTest.java | 22 +++---- .../db/repos/job/JobRepositoryTest.java | 14 ++-- .../LeaderboardRepositoryRankTest.java | 4 +- .../LeaderboardRepositoryTest.java | 10 +-- .../repos/user/UserMetricsRepositoryTest.java | 8 +-- .../db/repos/user/UserRepositoryTest.java | 26 ++++---- .../weekly/WeeklyMessageRepositoryTest.java | 10 +-- .../common/security/ProtectorTest.java | 8 +-- .../submissions/SubmissionsHandlerTest.java | 6 +- .../codebloom/config/TestProtector.java | 4 +- .../auth/LeetcodeAuthStealerTest.java | 20 +++--- .../LeetcodeQuestionProcessServiceTest.java | 2 +- ...eetcodeQuestionProcessServiceUnitTest.java | 10 +-- 86 files changed, 473 insertions(+), 528 deletions(-) delete mode 100644 src/main/java/org/patinanetwork/codebloom/common/db/helper/annotations/NotNullColumn.java delete mode 100644 src/main/java/org/patinanetwork/codebloom/common/db/helper/annotations/NullColumn.java diff --git a/src/main/java/org/patinanetwork/codebloom/api/admin/AdminController.java b/src/main/java/org/patinanetwork/codebloom/api/admin/AdminController.java index 5d7b7ce03..d2df1aff5 100644 --- a/src/main/java/org/patinanetwork/codebloom/api/admin/AdminController.java +++ b/src/main/java/org/patinanetwork/codebloom/api/admin/AdminController.java @@ -127,11 +127,11 @@ public ResponseEntity> updateAdmin( final String userId = newAdminBody.getId(); final boolean toggleTo = newAdminBody.getToggleTo(); - User user = userRepository.getUserById(userId); - - if (user == null) { + var userResult = userRepository.getUserById(userId); + if (userResult.isEmpty()) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponder.failure("User has not been found.")); } + User user = userResult.get(); user.setAdmin(toggleTo); boolean isSuccessful = userRepository.updateUser(user); @@ -177,7 +177,7 @@ public ResponseEntity> createNewAnnouncement( Announcement announcement = Announcement.builder() .expiresAt(expiresAtWithOffset) - .showTimer(createAnnouncementBody.isShowTimer()) + .showTimer(Optional.of(createAnnouncementBody.isShowTimer())) .message(createAnnouncementBody.getMessage()) .createdAt(nowWithOffset) .build(); @@ -209,10 +209,9 @@ public ResponseEntity> createNewAnnouncement( public ResponseEntity> deleteAnnouncement( @Valid @RequestBody final DeleteAnnouncementBody deleteAnnouncementBody, @Protected(admin = true) final AuthenticationObject authenticationObject) { - Announcement announcement = announcementRepository.getAnnouncementById(deleteAnnouncementBody.getId()); - if (announcement == null) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Announcement does not exist"); - } + Announcement announcement = announcementRepository + .getAnnouncementById(deleteAnnouncementBody.getId()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Announcement does not exist")); OffsetDateTime nowWithOffset = StandardizedOffsetDateTime.now(); announcement.setExpiresAt(nowWithOffset); boolean updatedAnnouncement = announcementRepository.updateAnnouncement(announcement); diff --git a/src/main/java/org/patinanetwork/codebloom/api/announcement/AnnouncementController.java b/src/main/java/org/patinanetwork/codebloom/api/announcement/AnnouncementController.java index 3bcd71ab2..57feefff2 100644 --- a/src/main/java/org/patinanetwork/codebloom/api/announcement/AnnouncementController.java +++ b/src/main/java/org/patinanetwork/codebloom/api/announcement/AnnouncementController.java @@ -44,11 +44,11 @@ is to use the success key of the payload and check whether it is true (announcem }) @GetMapping("") public ResponseEntity> getLatestAnnouncement() { - Announcement announcement = announcementRepository.getRecentAnnouncement(); - - if (announcement == null) { + var announcementResult = announcementRepository.getRecentAnnouncement(); + if (announcementResult.isEmpty()) { return ResponseEntity.ok().body(ApiResponder.failure("No announcement available: check back later.")); } + Announcement announcement = announcementResult.get(); OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC); boolean isExpired = announcement.getExpiresAt().isBefore(now); diff --git a/src/main/java/org/patinanetwork/codebloom/api/auth/AuthController.java b/src/main/java/org/patinanetwork/codebloom/api/auth/AuthController.java index dc7fbdc3f..da0b842f6 100644 --- a/src/main/java/org/patinanetwork/codebloom/api/auth/AuthController.java +++ b/src/main/java/org/patinanetwork/codebloom/api/auth/AuthController.java @@ -10,6 +10,7 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import java.time.Duration; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -304,7 +305,7 @@ public RedirectView verifySchoolEmail(final HttpServletRequest request) { return new RedirectView("/settings?success=false&message=This email is not supported"); } - user.setSchoolEmail(magicLink.getEmail()); + user.setSchoolEmail(Optional.ofNullable(magicLink.getEmail())); boolean isSuccessful = userRepository.updateUser(user); if (!isSuccessful) { @@ -324,7 +325,7 @@ public RedirectView verifySchoolEmail(final HttpServletRequest request) { Report.builder() .data(String.format( "User %s\nAlready has tag %s", - user.getNickname() != null ? user.getNickname() : user.getDiscordName(), + user.getNickname().orElse(user.getDiscordName()), schoolEnum.getInternalTag().name())) .build()); } else { diff --git a/src/main/java/org/patinanetwork/codebloom/api/auth/security/CustomAuthenticationSuccessHandler.java b/src/main/java/org/patinanetwork/codebloom/api/auth/security/CustomAuthenticationSuccessHandler.java index 2715db9ae..3cd07ecb3 100644 --- a/src/main/java/org/patinanetwork/codebloom/api/auth/security/CustomAuthenticationSuccessHandler.java +++ b/src/main/java/org/patinanetwork/codebloom/api/auth/security/CustomAuthenticationSuccessHandler.java @@ -9,6 +9,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; @@ -89,18 +90,24 @@ public void onAuthenticationSuccess( discordId = attributes.get("id").toString(); discordName = attributes.get("username").toString(); - User existingUser = userRepository.getUserByDiscordId(discordId); + var existingUserResult = userRepository.getUserByDiscordId(discordId); + User existingUser; - if (existingUser != null) { + if (existingUserResult.isPresent()) { + existingUser = existingUserResult.get(); existingUser.setDiscordName(discordName); - if (existingUser.getLeetcodeUsername() != null) { + if (existingUser.getLeetcodeUsername().isPresent()) { try { - UserProfile profile = leetcodeClient.getUserProfile(existingUser.getLeetcodeUsername()); + UserProfile profile = leetcodeClient.getUserProfile( + existingUser.getLeetcodeUsername().orElseThrow()); if (profile != null && profile.getUserAvatar() != null) { - existingUser.setProfileUrl(profile.getUserAvatar()); + existingUser.setProfileUrl(Optional.ofNullable(profile.getUserAvatar())); } } catch (RuntimeException ex) { - LOGGER.warn("LeetCode lookup failed for {}", existingUser.getLeetcodeUsername(), ex); + LOGGER.warn( + "LeetCode lookup failed for {}", + existingUser.getLeetcodeUsername().orElseThrow(), + ex); } } userRepository.updateUser(existingUser); @@ -160,11 +167,13 @@ public void onAuthenticationSuccess( // TODO: Abstract this logic into `DiscordClub` if ("Patina Network".equals(club.getName())) { if (member.get().getNickname() != null) { - existingUser.setNickname(member.get().getNickname()); + existingUser.setNickname( + Optional.ofNullable(member.get().getNickname())); } else if (member.get().getUser().getGlobalName() != null) { - existingUser.setNickname(member.get().getUser().getGlobalName()); + existingUser.setNickname( + Optional.ofNullable(member.get().getUser().getGlobalName())); } else { - existingUser.setNickname(existingUser.getDiscordName()); + existingUser.setNickname(Optional.ofNullable(existingUser.getDiscordName())); } userRepository.updateUser(existingUser); } diff --git a/src/main/java/org/patinanetwork/codebloom/api/club/ClubController.java b/src/main/java/org/patinanetwork/codebloom/api/club/ClubController.java index d9daafae6..d45fb6519 100644 --- a/src/main/java/org/patinanetwork/codebloom/api/club/ClubController.java +++ b/src/main/java/org/patinanetwork/codebloom/api/club/ClubController.java @@ -60,11 +60,9 @@ public ResponseEntity> validatePassword( final String clubSlug = registerClubBody.getClubSlug(); final String password = registerClubBody.getPassword(); - Club club = clubRepository.getClubBySlug(clubSlug); - - if (club == null) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Club does not exist."); - } + Club club = clubRepository + .getClubBySlug(clubSlug) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Club does not exist.")); boolean valid = clubService.isPasswordValid(club, password); diff --git a/src/main/java/org/patinanetwork/codebloom/api/club/service/ClubService.java b/src/main/java/org/patinanetwork/codebloom/api/club/service/ClubService.java index 05ab2bd98..bad710829 100644 --- a/src/main/java/org/patinanetwork/codebloom/api/club/service/ClubService.java +++ b/src/main/java/org/patinanetwork/codebloom/api/club/service/ClubService.java @@ -15,7 +15,7 @@ public ClubService(final ClubRepository clubRepository) { } public ClubDto getClubDtoBySlug(final String slug) { - Club club = clubRepository.getClubBySlug(slug); + Club club = clubRepository.getClubBySlug(slug).orElse(null); if (club == null) { return null; } @@ -34,6 +34,6 @@ public boolean isPasswordValid(final Club club, final String rawPassword) { } public Club getClubBySlug(final String slug) { - return clubRepository.getClubBySlug(slug); + return clubRepository.getClubBySlug(slug).orElse(null); } } diff --git a/src/main/java/org/patinanetwork/codebloom/api/external/ExternalController.java b/src/main/java/org/patinanetwork/codebloom/api/external/ExternalController.java index 194ea3b18..e9ce348a0 100644 --- a/src/main/java/org/patinanetwork/codebloom/api/external/ExternalController.java +++ b/src/main/java/org/patinanetwork/codebloom/api/external/ExternalController.java @@ -55,10 +55,9 @@ public ExternalController( public ResponseEntity>> getGwcUsers( @RequestHeader("X-API-Key") final String apiKey, @RequestParam("leaderboardId") final String leaderboardId) { - ApiKey validApiKey = apiKeyRepository.getApiKeyByHash(apiKey); - if (validApiKey == null) { - throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid API key"); - } + ApiKey validApiKey = apiKeyRepository + .getApiKeyByHash(apiKey) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid API key")); List accesses = apiKeyAccessRepository.getApiKeyAccessesByApiKeyId(validApiKey.getId()); boolean hasGwcAccess = diff --git a/src/main/java/org/patinanetwork/codebloom/api/leaderboard/LeaderboardController.java b/src/main/java/org/patinanetwork/codebloom/api/leaderboard/LeaderboardController.java index 21d835bce..48e80f15a 100644 --- a/src/main/java/org/patinanetwork/codebloom/api/leaderboard/LeaderboardController.java +++ b/src/main/java/org/patinanetwork/codebloom/api/leaderboard/LeaderboardController.java @@ -287,10 +287,12 @@ public ResponseEntity> getUserCurrentLeaderboardF } // we do not support point of time in this endpoint currently - UserWithScore user = userRepository.getUserWithScoreByIdAndLeaderboardId( - userId, - leaderboardData.get().getId(), - UserFilterOptions.builder().build()); + UserWithScore user = userRepository + .getUserWithScoreByIdAndLeaderboardId( + userId, + leaderboardData.get().getId(), + UserFilterOptions.builder().build()) + .orElse(null); // if (user == null) { // return diff --git a/src/main/java/org/patinanetwork/codebloom/api/submission/SubmissionController.java b/src/main/java/org/patinanetwork/codebloom/api/submission/SubmissionController.java index 29cda18f3..c9296086e 100644 --- a/src/main/java/org/patinanetwork/codebloom/api/submission/SubmissionController.java +++ b/src/main/java/org/patinanetwork/codebloom/api/submission/SubmissionController.java @@ -12,6 +12,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import org.patinanetwork.codebloom.api.submission.body.LeetcodeUsernameObject; import org.patinanetwork.codebloom.common.db.models.potd.POTD; import org.patinanetwork.codebloom.common.db.models.user.User; @@ -145,7 +146,7 @@ public ResponseEntity> setLeetcodeUsername( User user = authenticationObject.getUser(); - if (user.getLeetcodeUsername() != null) { + if (user.getLeetcodeUsername().isPresent()) { throw new ResponseStatusException( HttpStatus.CONFLICT, "User has already set a username previously. You cannot change your name anymore. Please contact support if there are any issues."); @@ -167,8 +168,8 @@ public ResponseEntity> setLeetcodeUsername( "This username has already been taken. If this is a mistake, please get in touch with us so we can attempt to rectify it."); } - user.setLeetcodeUsername(leetcodeUsernameObject.getLeetcodeUsername()); - user.setProfileUrl(leetcodeUserProfile.getUserAvatar()); + user.setLeetcodeUsername(Optional.ofNullable(leetcodeUsernameObject.getLeetcodeUsername())); + user.setProfileUrl(Optional.ofNullable(leetcodeUserProfile.getUserAvatar())); userRepository.updateUser(user); return ResponseEntity.ok().body(ApiResponder.success("Leetcode username has been set!", Empty.of())); @@ -206,7 +207,7 @@ public ResponseEntity>> checkLatestSu @Protected final AuthenticationObject authenticationObject) { User user = authenticationObject.getUser(); - if (user.getLeetcodeUsername() == null) { + if (user.getLeetcodeUsername().isEmpty()) { throw new ResponseStatusException( HttpStatus.PRECONDITION_FAILED, "You cannot access this resource without setting a Leetcode username first."); @@ -225,8 +226,8 @@ public ResponseEntity>> checkLatestSu simpleRedis.put(user.getId(), System.currentTimeMillis()); - List leetcodeSubmissions = - leetcodeClient.findSubmissionsByUsernameFast(user.getLeetcodeUsername(), 20); + List leetcodeSubmissions = leetcodeClient.findSubmissionsByUsernameFast( + user.getLeetcodeUsername().orElse(null), 20); return ResponseEntity.ok() .body(ApiResponder.success( diff --git a/src/main/java/org/patinanetwork/codebloom/api/user/UserController.java b/src/main/java/org/patinanetwork/codebloom/api/user/UserController.java index 9a2557b10..a9e8ab6b6 100644 --- a/src/main/java/org/patinanetwork/codebloom/api/user/UserController.java +++ b/src/main/java/org/patinanetwork/codebloom/api/user/UserController.java @@ -85,11 +85,9 @@ public UserController( public ResponseEntity> getUserProfileByUserId( final HttpServletRequest request, @PathVariable final String userId) { - User user = userRepository.getUserById(userId); - - if (user == null) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Failed to find user profile."); - } + User user = userRepository + .getUserById(userId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Failed to find user profile.")); return ResponseEntity.ok().body(ApiResponder.success("User profile found!", UserDto.fromUser(user))); } diff --git a/src/main/java/org/patinanetwork/codebloom/api/user/v2/UserV2Controller.java b/src/main/java/org/patinanetwork/codebloom/api/user/v2/UserV2Controller.java index 02619b810..c84493223 100644 --- a/src/main/java/org/patinanetwork/codebloom/api/user/v2/UserV2Controller.java +++ b/src/main/java/org/patinanetwork/codebloom/api/user/v2/UserV2Controller.java @@ -54,11 +54,9 @@ public UserV2Controller(final QuestionRepository questionRepository, final UserR public ResponseEntity> getUserProfileByLeetcodeUsername( final HttpServletRequest request, @PathVariable final String leetcodeUsername) { - User user = userRepository.getUserByLeetcodeUsername(leetcodeUsername); - - if (user == null) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Failed to find user profile."); - } + User user = userRepository + .getUserByLeetcodeUsername(leetcodeUsername) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Failed to find user profile.")); return ResponseEntity.ok().body(ApiResponder.success("User profile found!", UserDto.fromUser(user))); } diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java index 40cafe304..5a4b62441 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java @@ -311,8 +311,9 @@ public RefreshResultDto refreshSubmissions(String guildId, String discordId) thr String userId = user.getId(); - UserWithScore scoredUser = userRepository.getUserWithScoreByIdAndLeaderboardId( - userId, currentLeaderboard.getId(), UserFilterOptions.DEFAULT); + UserWithScore scoredUser = userRepository + .getUserWithScoreByIdAndLeaderboardId(userId, currentLeaderboard.getId(), UserFilterOptions.DEFAULT) + .orElse(null); int score = scoredUser.getTotalScore(); Indexed globalIndex = leaderboardRepository diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardManager.java b/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardManager.java index 73905c4a4..95905f396 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardManager.java @@ -91,7 +91,7 @@ public void generateAchievementsForAllWinners() { .userId(user.getId()) .leaderboardId(currentLeaderboard.get().getId()) .place(AchievementPlaceEnum.fromInteger(place)) - .leaderboard(pair.getRight()) + .leaderboard(Optional.of(pair.getRight())) .title(String.format( "%s - %s - %s Place", currentLeaderboard.get().getName(), @@ -121,7 +121,6 @@ public void generateAchievementsForAllWinners() { .userId(user.getId()) .leaderboardId(currentLeaderboard.get().getId()) .place(AchievementPlaceEnum.fromInteger(place)) - .leaderboard(null) .title(String.format( "%s - %s Place", currentLeaderboard.get().getName(), placeString)) .build(); @@ -177,26 +176,30 @@ public Page> getLeaderboardUsers( } public User refreshUserSubmissions(final String discordId) throws LeaderboardException { - User user = userRepository.getUserByDiscordId(discordId); - - if (user == null) { - throw new LeaderboardException( - "Cannot refresh submissions", - "Please link your account by [logging in to Codebloom](https://codebloom.patinanetwork.org/login) and completing onboarding."); - } - - if (user.getLeetcodeUsername() == null) { + User user = userRepository + .getUserByDiscordId(discordId) + .orElseThrow( + () -> new LeaderboardException( + "Cannot refresh submissions", + "Please link your account by [logging in to Codebloom](https://codebloom.patinanetwork.org/login) and completing onboarding.")); + + if (user.getLeetcodeUsername().isEmpty()) { throw new LeaderboardException( "Cannot refresh submissions", "Your Discord Account is not linked to a LeetCode username."); } try { - log.info("Fetching recent LeetCode submissions for user: {}", user.getLeetcodeUsername()); - List leetcodeSubmissions = - leetcodeClient.findSubmissionsByUsername(user.getLeetcodeUsername(), 20); + log.info( + "Fetching recent LeetCode submissions for user: {}", + user.getLeetcodeUsername().orElse(null)); + List leetcodeSubmissions = leetcodeClient.findSubmissionsByUsername( + user.getLeetcodeUsername().orElse(null), 20); submissionsHandler.handleSubmissions(leetcodeSubmissions, user, true); } catch (Exception e) { - log.error("Failed to fetch or process submissions for user {}", user.getLeetcodeUsername(), e); + log.error( + "Failed to fetch or process submissions for user {}", + user.getLeetcodeUsername().orElse(null), + e); throw new LeaderboardException( "Cannot refresh submissions", "Failed to fetch or process submissions from LeetCode. Please try again later."); diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/duel/DuelManager.java b/src/main/java/org/patinanetwork/codebloom/common/components/duel/DuelManager.java index c2f806d38..8166b6796 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/duel/DuelManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/duel/DuelManager.java @@ -3,7 +3,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @@ -111,7 +110,7 @@ private List buildPlayersInLobby(final String lobbyId) { return lobbyPlayers.stream() .map(lobbyPlayer -> userRepository.getUserById(lobbyPlayer.getPlayerId())) - .filter(Objects::nonNull) + .flatMap(Optional::stream) .map(UserDto::fromUser) .collect(Collectors.toList()); } @@ -290,8 +289,8 @@ public int processSubmissions(User user, Lobby activeLobby) throws DuelException .map(QuestionBank::getQuestionTitle) .collect(Collectors.toSet()); - List leetcodeSubmissions = - leetcodeClient.findSubmissionsByUsername(user.getLeetcodeUsername(), MAX_LEETCODE_SUBMISSIONS); + List leetcodeSubmissions = leetcodeClient.findSubmissionsByUsername( + user.getLeetcodeUsername().orElse(null), MAX_LEETCODE_SUBMISSIONS); var solvedLeetcodeSubmissions = leetcodeSubmissions.stream() .filter(s -> solvableQuestionTitlesSet.contains(s.getTitle())) diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/helper/annotations/NotNullColumn.java b/src/main/java/org/patinanetwork/codebloom/common/db/helper/annotations/NotNullColumn.java deleted file mode 100644 index 80ee8d776..000000000 --- a/src/main/java/org/patinanetwork/codebloom/common/db/helper/annotations/NotNullColumn.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.patinanetwork.codebloom.common.db.helper.annotations; - -import static java.lang.annotation.ElementType.*; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation to mark database fields as NOT NULL columns. - * - *

Used at compile-time to indicate that the annotated field corresponds to a database column that cannot contain - * null values. - * - *

This interface will get compiled out; there is no run-time effects from this annotation. - */ -@Target({FIELD}) -@Retention(RetentionPolicy.SOURCE) -public @interface NotNullColumn {} diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/helper/annotations/NullColumn.java b/src/main/java/org/patinanetwork/codebloom/common/db/helper/annotations/NullColumn.java deleted file mode 100644 index 1540f7b90..000000000 --- a/src/main/java/org/patinanetwork/codebloom/common/db/helper/annotations/NullColumn.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.patinanetwork.codebloom.common.db.helper.annotations; - -import static java.lang.annotation.ElementType.*; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation to mark database fields as NULL columns. - * - *

Used at compile-time to indicate that the annotated field corresponds to a database column that can contain null - * values (i.e., is nullable). - * - *

This interface will get compiled out; there is no run-time effects from this annotation. - */ -@Target({FIELD}) -@Retention(RetentionPolicy.SOURCE) -public @interface NullColumn {} diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/models/Session.java b/src/main/java/org/patinanetwork/codebloom/common/db/models/Session.java index cbc2969a7..c10f71300 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/models/Session.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/models/Session.java @@ -8,8 +8,6 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; -import org.patinanetwork.codebloom.common.db.helper.annotations.NotNullColumn; -import org.patinanetwork.codebloom.common.db.helper.annotations.NullColumn; @Getter @Setter @@ -21,15 +19,13 @@ public class Session { @Builder.Default private Optional id = Optional.empty(); - @NotNullColumn private String userId; - @NotNullColumn private LocalDateTime expiresAt; - /** can be null if it's an old entry */ - @NullColumn - private OffsetDateTime createdAt; + /** can be empty if it's an old entry */ + @Builder.Default + private Optional createdAt = Optional.empty(); // public Session(final String userId, final LocalDateTime expiresAt) { // this.userId = userId; diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/models/achievements/Achievement.java b/src/main/java/org/patinanetwork/codebloom/common/db/models/achievements/Achievement.java index 70a09d814..56a05424e 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/models/achievements/Achievement.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/models/achievements/Achievement.java @@ -1,13 +1,12 @@ package org.patinanetwork.codebloom.common.db.models.achievements; import java.time.OffsetDateTime; +import java.util.Optional; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import lombok.ToString; -import org.patinanetwork.codebloom.common.db.helper.annotations.NotNullColumn; -import org.patinanetwork.codebloom.common.db.helper.annotations.NullColumn; import org.patinanetwork.codebloom.common.db.models.usertag.Tag; @Builder @@ -17,35 +16,28 @@ @EqualsAndHashCode public class Achievement { - @NotNullColumn private String id; - @NotNullColumn private String userId; - @NotNullColumn private String leaderboardId; - @NotNullColumn private AchievementPlaceEnum place; - /** `null` indicates global leaderboard. */ - @NullColumn - private Tag leaderboard; + /** An empty value indicates the global leaderboard. */ + @Builder.Default + private Optional leaderboard = Optional.empty(); - @NotNullColumn private String title; - @NullColumn - private String description; + @Builder.Default + private Optional description = Optional.empty(); @Builder.Default - @NotNullColumn private boolean isActive = true; - @NotNullColumn private OffsetDateTime createdAt; - @NullColumn - private OffsetDateTime deletedAt; + @Builder.Default + private Optional deletedAt = Optional.empty(); } diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/models/announcement/Announcement.java b/src/main/java/org/patinanetwork/codebloom/common/db/models/announcement/Announcement.java index 2b23dbf9c..f76cb02f4 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/models/announcement/Announcement.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/models/announcement/Announcement.java @@ -1,14 +1,14 @@ package org.patinanetwork.codebloom.common.db.models.announcement; import java.time.OffsetDateTime; +import java.util.Optional; +import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import lombok.experimental.SuperBuilder; -import org.patinanetwork.codebloom.common.db.helper.annotations.NotNullColumn; -import org.patinanetwork.codebloom.common.db.helper.annotations.NullColumn; @Getter @Setter @@ -18,18 +18,14 @@ @EqualsAndHashCode public class Announcement { - @NotNullColumn private String id; - @NotNullColumn private OffsetDateTime createdAt; - @NotNullColumn private OffsetDateTime expiresAt; - @NullColumn - private boolean showTimer; + @Builder.Default + private Optional showTimer = Optional.of(false); - @NotNullColumn private String message; } diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/models/auth/Auth.java b/src/main/java/org/patinanetwork/codebloom/common/db/models/auth/Auth.java index fbd9af847..dde8ed0db 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/models/auth/Auth.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/models/auth/Auth.java @@ -1,14 +1,13 @@ package org.patinanetwork.codebloom.common.db.models.auth; import java.time.OffsetDateTime; +import java.util.Optional; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import lombok.ToString; import lombok.extern.jackson.Jacksonized; -import org.patinanetwork.codebloom.common.db.helper.annotations.NotNullColumn; -import org.patinanetwork.codebloom.common.db.helper.annotations.NullColumn; @Getter @Setter @@ -18,15 +17,12 @@ @ToString public class Auth { - @NotNullColumn private String id; - @NotNullColumn private String token; - @NullColumn - private String csrf; + @Builder.Default + private Optional csrf = Optional.empty(); - @NotNullColumn private OffsetDateTime createdAt; } diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/models/job/Job.java b/src/main/java/org/patinanetwork/codebloom/common/db/models/job/Job.java index 5d9f230ed..3aa1e95ca 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/models/job/Job.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/models/job/Job.java @@ -1,13 +1,12 @@ package org.patinanetwork.codebloom.common.db.models.job; import java.time.OffsetDateTime; +import java.util.Optional; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import lombok.ToString; -import org.patinanetwork.codebloom.common.db.helper.annotations.NotNullColumn; -import org.patinanetwork.codebloom.common.db.helper.annotations.NullColumn; @Setter @Getter @@ -16,27 +15,21 @@ @EqualsAndHashCode public class Job { - @NotNullColumn private String id; - @NotNullColumn private OffsetDateTime createdAt; - @NullColumn - private OffsetDateTime processedAt; + @Builder.Default + private Optional processedAt = Optional.empty(); - @NullColumn - private OffsetDateTime completedAt; + @Builder.Default + private Optional completedAt = Optional.empty(); - @NotNullColumn private OffsetDateTime nextAttemptAt; - @NotNullColumn private JobStatus status; - @NotNullColumn private String questionId; - @NotNullColumn private int attempts; } diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/models/potd/POTD.java b/src/main/java/org/patinanetwork/codebloom/common/db/models/potd/POTD.java index 24824839d..5fc36f5d5 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/models/potd/POTD.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/models/potd/POTD.java @@ -6,7 +6,6 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; -import org.patinanetwork.codebloom.common.db.helper.annotations.NotNullColumn; @Getter @Setter @@ -15,18 +14,13 @@ @ToString public class POTD { - @NotNullColumn private String id; - @NotNullColumn private String title; - @NotNullColumn private String slug; - @NotNullColumn private float multiplier; - @NotNullColumn private LocalDateTime createdAt; } diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/models/question/topic/QuestionTopic.java b/src/main/java/org/patinanetwork/codebloom/common/db/models/question/topic/QuestionTopic.java index 1ca48d13b..ad781c66f 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/models/question/topic/QuestionTopic.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/models/question/topic/QuestionTopic.java @@ -8,8 +8,6 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; -import org.patinanetwork.codebloom.common.db.helper.annotations.NotNullColumn; -import org.patinanetwork.codebloom.common.db.helper.annotations.NullColumn; @Getter @Setter @@ -19,22 +17,16 @@ @ToString public class QuestionTopic { - @NotNullColumn private String id; - @NullColumn private Optional questionId; - @NullColumn private Optional questionBankId; - @NotNullColumn private String topicSlug; - @NotNullColumn private LeetcodeTopicEnum topic; - @NotNullColumn private LocalDateTime createdAt; public static class QuestionTopicBuilder { diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/models/user/User.java b/src/main/java/org/patinanetwork/codebloom/common/db/models/user/User.java index e17f64335..abdd72ff1 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/models/user/User.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/models/user/User.java @@ -3,6 +3,7 @@ import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -10,8 +11,6 @@ import lombok.ToString; import lombok.experimental.SuperBuilder; import org.patinanetwork.codebloom.common.db.helper.annotations.JoinColumn; -import org.patinanetwork.codebloom.common.db.helper.annotations.NotNullColumn; -import org.patinanetwork.codebloom.common.db.helper.annotations.NullColumn; import org.patinanetwork.codebloom.common.db.models.achievements.Achievement; import org.patinanetwork.codebloom.common.db.models.usertag.UserTag; @@ -22,38 +21,33 @@ @EqualsAndHashCode public class User { - @NotNullColumn private String id; // Even though discord IDs are integers, they are very large so we just use // String instead. - @NotNullColumn private String discordId; - @NotNullColumn private String discordName; - @NullColumn - private String leetcodeUsername; + @Builder.Default + private Optional leetcodeUsername = Optional.empty(); - @NullColumn - private String nickname; + @Builder.Default + private Optional nickname = Optional.empty(); - @NotNullColumn private boolean admin; - @NullColumn - private String profileUrl; + @Builder.Default + private Optional profileUrl = Optional.empty(); - @NotNullColumn private String verifyKey; - @NullColumn - private String schoolEmail; + @Builder.Default + private Optional schoolEmail = Optional.empty(); - /** can be null if it's an old entry */ - @NullColumn - private OffsetDateTime createdAt; + /** can be empty if it's an old entry */ + @Builder.Default + private Optional createdAt = Optional.empty(); /** * If you want to update tags in the database, you have to use the diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/models/user/UserWithScore.java b/src/main/java/org/patinanetwork/codebloom/common/db/models/user/UserWithScore.java index 1f5023da7..c95497a5b 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/models/user/UserWithScore.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/models/user/UserWithScore.java @@ -5,7 +5,6 @@ import lombok.Setter; import lombok.ToString; import lombok.experimental.SuperBuilder; -import org.patinanetwork.codebloom.common.db.helper.annotations.NotNullColumn; @Getter @Setter @@ -14,6 +13,5 @@ @EqualsAndHashCode(callSuper = true) public class UserWithScore extends User { - @NotNullColumn private int totalScore; } diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementRepository.java index 9075c47cb..97fb7a376 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementRepository.java @@ -1,6 +1,7 @@ package org.patinanetwork.codebloom.common.db.repos.achievements; import java.util.List; +import java.util.Optional; import org.patinanetwork.codebloom.common.db.models.achievements.Achievement; public interface AchievementRepository { @@ -33,13 +34,13 @@ public interface AchievementRepository { *

  • leaderboardId * * - * @return updated achievement if successful + * @return the updated achievement if found, or an empty Optional if no matching row exists */ - Achievement updateAchievement(Achievement achievement); + Optional updateAchievement(Achievement achievement); boolean deleteAchievementById(String id); - Achievement getAchievementById(String id); + Optional getAchievementById(String id); List getAchievementsByUserId(String userId); } diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementSqlRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementSqlRepository.java index e9f9868e2..103221830 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementSqlRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementSqlRepository.java @@ -36,13 +36,13 @@ public class AchievementSqlRepository implements AchievementRepository { .id(id) .userId(userId) .place(place) - .leaderboard(leaderboard) + .leaderboard(Optional.ofNullable(leaderboard)) .leaderboardId(leaderboardId) .title(title) - .description(description) + .description(Optional.ofNullable(description)) .isActive(isActive) .createdAt(createdAt) - .deletedAt(deletedAt) + .deletedAt(Optional.ofNullable(deletedAt)) .build(); }; @@ -71,15 +71,13 @@ public void createAchievement(final Achievement achievement) { .param("place", achievement.getPlace().name(), Types.OTHER) .param( "leaderboard", - Optional.ofNullable(achievement.getLeaderboard()) - .map(Enum::name) - .orElse(null), + achievement.getLeaderboard().map(Enum::name).orElse(null), Types.OTHER) .param("leaderboardId", UUID.fromString(achievement.getLeaderboardId())) .param("title", achievement.getTitle()) - .param("description", achievement.getDescription()) + .param("description", achievement.getDescription().orElse(null)) .param("isActive", achievement.isActive()) - .param("deletedAt", achievement.getDeletedAt()) + .param("deletedAt", achievement.getDeletedAt().orElse(null)) .query((rs, rowNum) -> StandardizedOffsetDateTime.normalize(rs.getObject("createdAt", OffsetDateTime.class))) .optional() @@ -89,7 +87,7 @@ public void createAchievement(final Achievement achievement) { } @Override - public Achievement updateAchievement(final Achievement achievement) { + public Optional updateAchievement(final Achievement achievement) { String sql = """ UPDATE "Achievement" @@ -110,15 +108,13 @@ public Achievement updateAchievement(final Achievement achievement) { .param("place", achievement.getPlace().name(), Types.OTHER) .param( "leaderboard", - Optional.ofNullable(achievement.getLeaderboard()) - .map(Enum::name) - .orElse(null), + achievement.getLeaderboard().map(Enum::name).orElse(null), Types.OTHER) .param("leaderboardId", achievement.getLeaderboardId()) .param("title", achievement.getTitle()) - .param("description", achievement.getDescription()) + .param("description", achievement.getDescription().orElse(null)) .param("isActive", achievement.isActive()) - .param("deletedAt", achievement.getDeletedAt()) + .param("deletedAt", achievement.getDeletedAt().orElse(null)) .param("leaderboardId", UUID.fromString(achievement.getLeaderboardId())) .param("id", UUID.fromString(achievement.getId())) .update(); @@ -147,7 +143,7 @@ public boolean deleteAchievementById(final String id) { } @Override - public Achievement getAchievementById(final String id) { + public Optional getAchievementById(final String id) { String sql = """ SELECT id, @@ -171,8 +167,7 @@ public Achievement getAchievementById(final String id) { .sql(sql) .param("id", UUID.fromString(id)) .query(ACHIEVEMENT_ROW_MAPPER) - .optional() - .orElse(null); + .optional(); } @Override diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/announcement/AnnouncementRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/announcement/AnnouncementRepository.java index 331d38730..45dc58d94 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/announcement/AnnouncementRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/announcement/AnnouncementRepository.java @@ -1,14 +1,15 @@ package org.patinanetwork.codebloom.common.db.repos.announcement; import java.util.List; +import java.util.Optional; import org.patinanetwork.codebloom.common.db.models.announcement.Announcement; public interface AnnouncementRepository { List getAllAnnouncements(); - Announcement getAnnouncementById(String id); + Optional getAnnouncementById(String id); - Announcement getRecentAnnouncement(); + Optional getRecentAnnouncement(); /** * @note The id property of the object will be overriden. diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/announcement/AnnouncementSqlRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/announcement/AnnouncementSqlRepository.java index 5410de948..85e18d534 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/announcement/AnnouncementSqlRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/announcement/AnnouncementSqlRepository.java @@ -3,6 +3,7 @@ import io.micrometer.core.annotation.Timed; import java.time.OffsetDateTime; import java.util.List; +import java.util.Optional; import java.util.UUID; import org.patinanetwork.codebloom.common.db.models.announcement.Announcement; import org.springframework.jdbc.core.RowMapper; @@ -17,7 +18,7 @@ public class AnnouncementSqlRepository implements AnnouncementRepository { .id(rs.getString("id")) .createdAt(rs.getObject("createdAt", OffsetDateTime.class)) .expiresAt(rs.getObject("expiresAt", OffsetDateTime.class)) - .showTimer(rs.getBoolean("showTimer")) + .showTimer(Optional.ofNullable(rs.getObject("showTimer", Boolean.class))) .message(rs.getString("message")) .build(); @@ -46,7 +47,7 @@ public List getAllAnnouncements() { } @Override - public Announcement getAnnouncementById(final String id) { + public Optional getAnnouncementById(final String id) { String sql = """ SELECT id, @@ -64,12 +65,11 @@ public Announcement getAnnouncementById(final String id) { .sql(sql) .param(UUID.fromString(id)) .query(ANNOUNCEMENT_ROW_MAPPER) - .optional() - .orElse(null); + .optional(); } @Override - public Announcement getRecentAnnouncement() { + public Optional getRecentAnnouncement() { String sql = """ SELECT id, @@ -84,7 +84,7 @@ public Announcement getRecentAnnouncement() { LIMIT 1 """; - return jdbcClient.sql(sql).query(ANNOUNCEMENT_ROW_MAPPER).optional().orElse(null); + return jdbcClient.sql(sql).query(ANNOUNCEMENT_ROW_MAPPER).optional(); } @Override @@ -102,7 +102,7 @@ public boolean createAnnouncement(final Announcement announcement) { .sql(sql) .param(UUID.randomUUID()) .param(announcement.getExpiresAt()) - .param(announcement.isShowTimer()) + .param(announcement.getShowTimer().orElse(null)) .param(announcement.getMessage()) .query((rs, rowNum) -> { announcement.setId(rs.getString("id")); @@ -143,7 +143,7 @@ public boolean updateAnnouncement(final Announcement announcement) { int rowsAffected = jdbcClient .sql(sql) .param("expiresAt", announcement.getExpiresAt()) - .param("showTimer", announcement.isShowTimer()) + .param("showTimer", announcement.getShowTimer().orElse(null)) .param("message", announcement.getMessage()) .param("id", UUID.fromString(announcement.getId())) .update(); diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/api/ApiKeyRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/api/ApiKeyRepository.java index 0a05c4095..8751d5408 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/api/ApiKeyRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/api/ApiKeyRepository.java @@ -1,12 +1,13 @@ package org.patinanetwork.codebloom.common.db.repos.api; import java.util.List; +import java.util.Optional; import org.patinanetwork.codebloom.common.db.models.api.ApiKey; public interface ApiKeyRepository { - ApiKey getApiKeyById(String id); + Optional getApiKeyById(String id); - ApiKey getApiKeyByHash(String hash); + Optional getApiKeyByHash(String hash); List getAllApiKeys(); diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/api/ApiKeySqlRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/api/ApiKeySqlRepository.java index 46e8b8e6b..0288555d8 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/api/ApiKeySqlRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/api/ApiKeySqlRepository.java @@ -33,7 +33,7 @@ public ApiKeySqlRepository(final JdbcClient jdbcClient) { } @Override - public ApiKey getApiKeyById(final String id) { + public Optional getApiKeyById(final String id) { String sql = """ SELECT id, @@ -52,12 +52,11 @@ public ApiKey getApiKeyById(final String id) { .sql(sql) .param("id", UUID.fromString(id)) .query(API_KEY_ROW_MAPPER) - .optional() - .orElse(null); + .optional(); } @Override - public ApiKey getApiKeyByHash(final String hash) { + public Optional getApiKeyByHash(final String hash) { String sql = """ SELECT id, @@ -72,12 +71,7 @@ public ApiKey getApiKeyByHash(final String hash) { "apiKeyHash" = :hash """; - return jdbcClient - .sql(sql) - .param("hash", hash) - .query(API_KEY_ROW_MAPPER) - .optional() - .orElse(null); + return jdbcClient.sql(sql).param("hash", hash).query(API_KEY_ROW_MAPPER).optional(); } @Override diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/api/access/ApiKeyAccessRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/api/access/ApiKeyAccessRepository.java index 61d856c0e..fae16ca9b 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/api/access/ApiKeyAccessRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/api/access/ApiKeyAccessRepository.java @@ -1,10 +1,11 @@ package org.patinanetwork.codebloom.common.db.repos.api.access; import java.util.List; +import java.util.Optional; import org.patinanetwork.codebloom.common.db.models.api.access.ApiKeyAccess; public interface ApiKeyAccessRepository { - ApiKeyAccess getApiKeyAccessById(String id); + Optional getApiKeyAccessById(String id); List getApiKeyAccessesByApiKeyId(String apiKeyId); diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/api/access/ApiKeyAccessSqlRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/api/access/ApiKeyAccessSqlRepository.java index 6570be77d..391740a5a 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/api/access/ApiKeyAccessSqlRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/api/access/ApiKeyAccessSqlRepository.java @@ -3,6 +3,7 @@ import io.micrometer.core.annotation.Timed; import java.sql.Types; import java.util.ArrayList; +import java.util.Optional; import java.util.UUID; import org.patinanetwork.codebloom.common.db.models.api.ApiKeyAccessEnum; import org.patinanetwork.codebloom.common.db.models.api.access.ApiKeyAccess; @@ -27,7 +28,7 @@ public ApiKeyAccessSqlRepository(final JdbcClient jdbcClient) { } @Override - public ApiKeyAccess getApiKeyAccessById(final String id) { + public Optional getApiKeyAccessById(final String id) { String sql = """ SELECT id, @@ -43,8 +44,7 @@ public ApiKeyAccess getApiKeyAccessById(final String id) { .sql(sql) .param("id", UUID.fromString(id)) .query(API_KEY_ACCESS_ROW_MAPPER) - .optional() - .orElse(null); + .optional(); } @Override diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/auth/AuthRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/auth/AuthRepository.java index ad5d2c302..11aeaecf6 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/auth/AuthRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/auth/AuthRepository.java @@ -1,5 +1,6 @@ package org.patinanetwork.codebloom.common.db.repos.auth; +import java.util.Optional; import org.patinanetwork.codebloom.common.db.models.auth.Auth; public interface AuthRepository { @@ -25,9 +26,9 @@ public interface AuthRepository { */ boolean updateAuthById(Auth auth); - Auth getAuthById(String id); + Optional getAuthById(String id); - Auth getMostRecentAuth(); + Optional getMostRecentAuth(); boolean deleteAuthById(String id); } diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/auth/AuthSqlRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/auth/AuthSqlRepository.java index 319fd1e05..afb31b740 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/auth/AuthSqlRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/auth/AuthSqlRepository.java @@ -2,6 +2,7 @@ import io.micrometer.core.annotation.Timed; import java.time.OffsetDateTime; +import java.util.Optional; import java.util.UUID; import org.patinanetwork.codebloom.common.db.models.auth.Auth; import org.patinanetwork.codebloom.common.time.StandardizedOffsetDateTime; @@ -16,7 +17,7 @@ public class AuthSqlRepository implements AuthRepository { private static final RowMapper AUTH_ROW_MAPPER = (rs, rowNum) -> Auth.builder() .id(rs.getString("id")) .token(rs.getString("token")) - .csrf(rs.getString("csrf")) + .csrf(Optional.ofNullable(rs.getString("csrf"))) .createdAt(StandardizedOffsetDateTime.normalize(rs.getObject("createdAt", OffsetDateTime.class))) .build(); @@ -42,7 +43,7 @@ public void createAuth(final Auth auth) { .sql(sql) .param("id", UUID.fromString(auth.getId())) .param("token", auth.getToken()) - .param("csrf", auth.getCsrf()) + .param("csrf", auth.getCsrf().orElse(null)) .query((rs, rowNum) -> StandardizedOffsetDateTime.normalize(rs.getObject("createdAt", OffsetDateTime.class))) .optional() @@ -66,14 +67,14 @@ public boolean updateAuthById(final Auth auth) { .sql(sql) .param("id", UUID.fromString(auth.getId())) .param("token", auth.getToken()) - .param("csrf", auth.getCsrf()) + .param("csrf", auth.getCsrf().orElse(null)) .update(); return rowsAffected > 0; } @Override - public Auth getAuthById(final String inputtedId) { + public Optional getAuthById(final String inputtedId) { String sql = """ SELECT id, token, csrf, "createdAt" @@ -86,12 +87,11 @@ public Auth getAuthById(final String inputtedId) { .sql(sql) .param("id", UUID.fromString(inputtedId)) .query(AUTH_ROW_MAPPER) - .optional() - .orElse(null); + .optional(); } @Override - public Auth getMostRecentAuth() { + public Optional getMostRecentAuth() { String sql = """ SELECT id, token, csrf, "createdAt" @@ -100,7 +100,7 @@ public Auth getMostRecentAuth() { LIMIT 1 """; - return jdbcClient.sql(sql).query(AUTH_ROW_MAPPER).optional().orElse(null); + return jdbcClient.sql(sql).query(AUTH_ROW_MAPPER).optional(); } @Override diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/club/ClubRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/club/ClubRepository.java index 30f6bf631..9b727e0c4 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/club/ClubRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/club/ClubRepository.java @@ -1,5 +1,6 @@ package org.patinanetwork.codebloom.common.db.repos.club; +import java.util.Optional; import org.patinanetwork.codebloom.common.db.models.club.Club; public interface ClubRepository { @@ -30,13 +31,13 @@ public interface ClubRepository { *
  • tag * * - * @return updated club if successful + * @return the updated club if found, or an empty Optional if no matching row exists */ - Club updateClub(Club club); + Optional updateClub(Club club); - Club getClubById(String id); + Optional getClubById(String id); - Club getClubBySlug(String slug); + Optional getClubBySlug(String slug); boolean deleteClubBySlug(String slug); diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/club/ClubSqlRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/club/ClubSqlRepository.java index 8441d7ea7..79596b00c 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/club/ClubSqlRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/club/ClubSqlRepository.java @@ -2,6 +2,7 @@ import io.micrometer.core.annotation.Timed; import java.sql.Types; +import java.util.Optional; import java.util.UUID; import org.patinanetwork.codebloom.common.db.models.club.Club; import org.patinanetwork.codebloom.common.db.models.usertag.Tag; @@ -63,7 +64,7 @@ public void createClub(final Club club) { } @Override - public Club updateClub(final Club club) { + public Optional updateClub(final Club club) { String sql = """ UPDATE "Club" @@ -90,11 +91,11 @@ public Club updateClub(final Club club) { if (rowsAffected > 0) { return getClubById(club.getId()); } - return null; + return Optional.empty(); } @Override - public Club getClubById(final String id) { + public Optional getClubById(final String id) { String sql = """ SELECT id, @@ -114,12 +115,11 @@ public Club getClubById(final String id) { .sql(sql) .param("id", UUID.fromString(id)) .query(CLUB_ROW_MAPPER) - .optional() - .orElse(null); + .optional(); } @Override - public Club getClubBySlug(final String slug) { + public Optional getClubBySlug(final String slug) { String sql = """ SELECT id, @@ -135,12 +135,7 @@ public Club getClubBySlug(final String slug) { "slug" = :slug """; - return jdbcClient - .sql(sql) - .param("slug", slug) - .query(CLUB_ROW_MAPPER) - .optional() - .orElse(null); + return jdbcClient.sql(sql).param("slug", slug).query(CLUB_ROW_MAPPER).optional(); } @Override diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/job/JobRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/job/JobRepository.java index 41c889fa1..78b4336c5 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/job/JobRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/job/JobRepository.java @@ -1,6 +1,7 @@ package org.patinanetwork.codebloom.common.db.repos.job; import java.util.List; +import java.util.Optional; import org.patinanetwork.codebloom.common.db.models.job.Job; public interface JobRepository { @@ -24,9 +25,9 @@ public interface JobRepository { * Finds a job by its ID. * * @param id the job ID - * @return the job if found, null otherwise + * @return the job if found, Optional.empty() otherwise */ - Job findJobById(String id); + Optional findJobById(String id); /** * Returns all the jobs with the specified question id. diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/job/JobSqlRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/job/JobSqlRepository.java index 4256b8759..a2c21ec2e 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/job/JobSqlRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/job/JobSqlRepository.java @@ -4,6 +4,7 @@ import java.sql.Types; import java.time.OffsetDateTime; import java.util.List; +import java.util.Optional; import java.util.UUID; import org.patinanetwork.codebloom.common.db.models.job.Job; import org.patinanetwork.codebloom.common.db.models.job.JobStatus; @@ -18,8 +19,8 @@ public class JobSqlRepository implements JobRepository { private static final RowMapper JOB_ROW_MAPPER = (rs, rowNum) -> Job.builder() .id(rs.getString("id")) .createdAt(rs.getObject("createdAt", OffsetDateTime.class)) - .processedAt(rs.getObject("processedAt", OffsetDateTime.class)) - .completedAt(rs.getObject("completedAt", OffsetDateTime.class)) + .processedAt(Optional.ofNullable(rs.getObject("processedAt", OffsetDateTime.class))) + .completedAt(Optional.ofNullable(rs.getObject("completedAt", OffsetDateTime.class))) .nextAttemptAt(rs.getObject("nextAttemptAt", OffsetDateTime.class)) .status(JobStatus.valueOf(rs.getString("status"))) .questionId(rs.getString("questionId")) @@ -63,7 +64,7 @@ public void createJob(final Job job) { } @Override - public Job findJobById(final String id) { + public Optional findJobById(final String id) { String sql = """ SELECT id, @@ -84,8 +85,7 @@ public Job findJobById(final String id) { .sql(sql) .param(1, UUID.fromString(id)) .query(JOB_ROW_MAPPER) - .optional() - .orElse(null); + .optional(); } @Override @@ -157,8 +157,8 @@ public boolean updateJob(final Job job) { int rowsAffected = jdbcClient .sql(sql) - .param(1, job.getProcessedAt()) - .param(2, job.getCompletedAt()) + .param(1, job.getProcessedAt().orElse(null)) + .param(2, job.getCompletedAt().orElse(null)) .param(3, job.getNextAttemptAt()) .param(4, job.getStatus().name(), Types.OTHER) .param(5, job.getAttempts()) diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/leaderboard/LeaderboardSqlRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/leaderboard/LeaderboardSqlRepository.java index cddb0d674..2d97da4ef 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/leaderboard/LeaderboardSqlRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/leaderboard/LeaderboardSqlRepository.java @@ -319,11 +319,12 @@ WITH ranks AS ( @Override public Optional> getGlobalRankedUserById(final String leaderboardId, final String userId) { - UserWithScore user = userRepository.getUserWithScoreByIdAndLeaderboardId( + var userResult = userRepository.getUserWithScoreByIdAndLeaderboardId( userId, leaderboardId, UserFilterOptions.builder().build()); - if (user == null) { + if (userResult.isEmpty()) { return Optional.empty(); } + UserWithScore user = userResult.get(); String sql = """ WITH ranks AS ( @@ -375,11 +376,12 @@ WITH ranks AS ( @Override public Optional> getFilteredRankedUserById( final String leaderboardId, final String userId, final LeaderboardFilterOptions options) { - UserWithScore user = userRepository.getUserWithScoreByIdAndLeaderboardId( + var userResult = userRepository.getUserWithScoreByIdAndLeaderboardId( userId, leaderboardId, UserFilterOptions.builder().build()); - if (user == null) { + if (userResult.isEmpty()) { return Optional.empty(); } + UserWithScore user = userResult.get(); String sql = """ WITH ranks AS ( @@ -577,12 +579,14 @@ WITH latest_leaderboard AS ( var leaderboardId = rs.getString("leaderboardId"); var leaderboardDeletedAt = rs.getObject("leaderboardDeletedAt", OffsetDateTime.class); - return userRepository.getUserWithScoreByIdAndLeaderboardId( - userId, - leaderboardId, - UserFilterOptions.builder() - .pointOfTime(leaderboardDeletedAt) - .build()); + return userRepository + .getUserWithScoreByIdAndLeaderboardId( + userId, + leaderboardId, + UserFilterOptions.builder() + .pointOfTime(leaderboardDeletedAt) + .build()) + .orElse(null); }) .list(); @@ -677,12 +681,14 @@ public List getLeaderboardUsersById(final String id, final Leader var leaderboardId = rs.getString("leaderboardId"); var leaderboardDeletedAt = rs.getObject("leaderboardDeletedAt", OffsetDateTime.class); - return userRepository.getUserWithScoreByIdAndLeaderboardId( - userId, - leaderboardId, - UserFilterOptions.builder() - .pointOfTime(leaderboardDeletedAt) - .build()); + return userRepository + .getUserWithScoreByIdAndLeaderboardId( + userId, + leaderboardId, + UserFilterOptions.builder() + .pointOfTime(leaderboardDeletedAt) + .build()) + .orElse(null); }) .list(); diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/session/SessionSqlRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/session/SessionSqlRepository.java index 0cd22fc21..df6f63b7a 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/session/SessionSqlRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/session/SessionSqlRepository.java @@ -20,7 +20,8 @@ public class SessionSqlRepository implements SessionRepository { .id(Optional.of(rs.getString("id"))) .userId(rs.getString("userId")) .expiresAt(rs.getTimestamp("expiresAt").toLocalDateTime()) - .createdAt(StandardizedOffsetDateTime.normalize(rs.getObject("createdAt", OffsetDateTime.class))) + .createdAt(Optional.ofNullable( + StandardizedOffsetDateTime.normalize(rs.getObject("createdAt", OffsetDateTime.class)))) .build(); private JdbcClient jdbcClient; @@ -50,7 +51,7 @@ public void createSession(final Session session) { .optional() .ifPresent(entry -> { session.setId(Optional.of(entry.getKey())); - session.setCreatedAt(StandardizedOffsetDateTime.normalize(entry.getValue())); + session.setCreatedAt(Optional.ofNullable(StandardizedOffsetDateTime.normalize(entry.getValue()))); }); } diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/user/UserRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/user/UserRepository.java index aa9c23211..702452a37 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/user/UserRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/user/UserRepository.java @@ -1,6 +1,7 @@ package org.patinanetwork.codebloom.common.db.repos.user; import java.util.ArrayList; +import java.util.Optional; import org.patinanetwork.codebloom.common.db.models.user.User; import org.patinanetwork.codebloom.common.db.models.user.UserWithScore; import org.patinanetwork.codebloom.common.db.repos.user.options.UserFilterOptions; @@ -31,15 +32,17 @@ public interface UserRepository { */ boolean updateUser(User user); - User getUserById(String id); + Optional getUserById(String id); - User getUserByLeetcodeUsername(String leetcodeUsername); + Optional getUserByLeetcodeUsername(String leetcodeUsername); - UserWithScore getUserWithScoreByIdAndLeaderboardId(String userId, String leaderboardId, UserFilterOptions options); + Optional getUserWithScoreByIdAndLeaderboardId( + String userId, String leaderboardId, UserFilterOptions options); - UserWithScore getUserWithScoreByLeetcodeUsernameAndLeaderboardId(String userLeetcodeUsername, String leaderboardId); + Optional getUserWithScoreByLeetcodeUsernameAndLeaderboardId( + String userLeetcodeUsername, String leaderboardId); - User getUserByDiscordId(String discordId); + Optional getUserByDiscordId(String discordId); int getUserCount(); diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/user/UserSqlRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/user/UserSqlRepository.java index 9ab38cb93..2515501af 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/user/UserSqlRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/user/UserSqlRepository.java @@ -4,6 +4,7 @@ import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Map; +import java.util.Optional; import java.util.UUID; import org.patinanetwork.codebloom.common.db.models.user.User; import org.patinanetwork.codebloom.common.db.models.user.UserWithScore; @@ -40,13 +41,14 @@ public UserSqlRepository( .id(id) .discordId(rs.getString("discordId")) .discordName(rs.getString("discordName")) - .leetcodeUsername(rs.getString("leetcodeUsername")) - .nickname(rs.getString("nickname")) + .leetcodeUsername(Optional.ofNullable(rs.getString("leetcodeUsername"))) + .nickname(Optional.ofNullable(rs.getString("nickname"))) .verifyKey(rs.getString("verifyKey")) .admin(rs.getBoolean("admin")) - .schoolEmail(rs.getString("schoolEmail")) - .profileUrl(rs.getString("profileUrl")) - .createdAt(StandardizedOffsetDateTime.normalize(rs.getObject("createdAt", OffsetDateTime.class))) + .schoolEmail(Optional.ofNullable(rs.getString("schoolEmail"))) + .profileUrl(Optional.ofNullable(rs.getString("profileUrl"))) + .createdAt(Optional.ofNullable( + StandardizedOffsetDateTime.normalize(rs.getObject("createdAt", OffsetDateTime.class)))) .tags(this.userTagRepository.findTagsByUserId(id)) .achievements(this.achievementRepository.getAchievementsByUserId(id)) .build(); @@ -58,13 +60,14 @@ public UserSqlRepository( .id(id) .discordId(rs.getString("discordId")) .discordName(rs.getString("discordName")) - .leetcodeUsername(rs.getString("leetcodeUsername")) - .nickname(rs.getString("nickname")) + .leetcodeUsername(Optional.ofNullable(rs.getString("leetcodeUsername"))) + .nickname(Optional.ofNullable(rs.getString("nickname"))) .verifyKey(rs.getString("verifyKey")) .admin(rs.getBoolean("admin")) - .schoolEmail(rs.getString("schoolEmail")) - .profileUrl(rs.getString("profileUrl")) - .createdAt(StandardizedOffsetDateTime.normalize(rs.getObject("createdAt", OffsetDateTime.class))) + .schoolEmail(Optional.ofNullable(rs.getString("schoolEmail"))) + .profileUrl(Optional.ofNullable(rs.getString("profileUrl"))) + .createdAt(Optional.ofNullable( + StandardizedOffsetDateTime.normalize(rs.getObject("createdAt", OffsetDateTime.class)))) .tags(this.userTagRepository.findTagsByUserId(id)) .achievements(this.achievementRepository.getAchievementsByUserId(id)) .totalScore(rs.getInt("totalScore")) @@ -93,22 +96,22 @@ public void createUser(final User user) { .param("id", UUID.fromString(user.getId())) .param("discordName", user.getDiscordName()) .param("discordId", user.getDiscordId()) - .param("leetcodeUsername", user.getLeetcodeUsername()) - .param("nickname", user.getNickname()) - .param("schoolEmail", user.getSchoolEmail()) + .param("leetcodeUsername", user.getLeetcodeUsername().orElse(null)) + .param("nickname", user.getNickname().orElse(null)) + .param("schoolEmail", user.getSchoolEmail().orElse(null)) .param("admin", user.isAdmin()) - .param("profileUrl", user.getProfileUrl()) + .param("profileUrl", user.getProfileUrl().orElse(null)) .query((rs, rowNum) -> Map.entry(rs.getString("verifyKey"), rs.getObject("createdAt", OffsetDateTime.class))) .optional() .ifPresent(entry -> { user.setVerifyKey(entry.getKey()); - user.setCreatedAt(StandardizedOffsetDateTime.normalize(entry.getValue())); + user.setCreatedAt(Optional.ofNullable(StandardizedOffsetDateTime.normalize(entry.getValue()))); }); } @Override - public User getUserById(final String inputId) { + public Optional getUserById(final String inputId) { String sql = """ SELECT id, @@ -130,12 +133,11 @@ public User getUserById(final String inputId) { .sql(sql) .param("id", UUID.fromString(inputId)) .query(userRowMapper) - .optional() - .orElse(null); + .optional(); } @Override - public User getUserByLeetcodeUsername(final String inputLeetcodeUsername) { + public Optional getUserByLeetcodeUsername(final String inputLeetcodeUsername) { String sql = """ SELECT id, @@ -156,12 +158,11 @@ public User getUserByLeetcodeUsername(final String inputLeetcodeUsername) { .sql(sql) .param("leetcodeUsername", inputLeetcodeUsername) .query(userRowMapper) - .optional() - .orElse(null); + .optional(); } @Override - public User getUserByDiscordId(final String inputDiscordId) { + public Optional getUserByDiscordId(final String inputDiscordId) { String sql = """ SELECT id, @@ -183,8 +184,7 @@ public User getUserByDiscordId(final String inputDiscordId) { .sql(sql) .param("discordId", inputDiscordId) .query(userRowMapper) - .optional() - .orElse(null); + .optional(); } @Override @@ -218,11 +218,11 @@ public boolean updateUser(final User inputUser) { .param("id", UUID.fromString(inputUser.getId())) .param("discordName", inputUser.getDiscordName()) .param("discordId", inputUser.getDiscordId()) - .param("leetcodeUsername", inputUser.getLeetcodeUsername()) - .param("nickname", inputUser.getNickname()) + .param("leetcodeUsername", inputUser.getLeetcodeUsername().orElse(null)) + .param("nickname", inputUser.getNickname().orElse(null)) .param("admin", inputUser.isAdmin()) - .param("profileUrl", inputUser.getProfileUrl()) - .param("schoolEmail", inputUser.getSchoolEmail()) + .param("profileUrl", inputUser.getProfileUrl().orElse(null)) + .param("schoolEmail", inputUser.getSchoolEmail().orElse(null)) .update(); return rowsAffected > 0; @@ -299,7 +299,7 @@ public boolean userExistsByLeetcodeUsername(final String leetcodeUsername) { } @Override - public UserWithScore getUserWithScoreByIdAndLeaderboardId( + public Optional getUserWithScoreByIdAndLeaderboardId( final String userId, final String leaderboardId, final UserFilterOptions options) { String sql = """ SELECT @@ -328,12 +328,11 @@ public UserWithScore getUserWithScoreByIdAndLeaderboardId( .param("id", UUID.fromString(userId)) .param("leaderboardId", UUID.fromString(leaderboardId)) .query(userWithScoreRowMapper) - .optional() - .orElse(null); + .optional(); } @Override - public UserWithScore getUserWithScoreByLeetcodeUsernameAndLeaderboardId( + public Optional getUserWithScoreByLeetcodeUsernameAndLeaderboardId( final String userLeetcodeUsername, final String leaderboardId) { String sql = """ SELECT @@ -361,8 +360,7 @@ public UserWithScore getUserWithScoreByLeetcodeUsernameAndLeaderboardId( .param("leetcodeUsername", userLeetcodeUsername) .param("leaderboardId", UUID.fromString(leaderboardId)) .query(userWithScoreRowMapper) - .optional() - .orElse(null); + .optional(); } @Override diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/weekly/WeeklyMessageRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/weekly/WeeklyMessageRepository.java index 8ad37bb34..1a51a2350 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/weekly/WeeklyMessageRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/weekly/WeeklyMessageRepository.java @@ -1,11 +1,12 @@ package org.patinanetwork.codebloom.common.db.repos.weekly; +import java.util.Optional; import org.patinanetwork.codebloom.common.db.models.weekly.WeeklyMessage; public interface WeeklyMessageRepository { - WeeklyMessage getLatestWeeklyMessage(); + Optional getLatestWeeklyMessage(); - WeeklyMessage getWeeklyMessageById(String id); + Optional getWeeklyMessageById(String id); /** * @note - The provided object's methods will be overriden with any returned data from the database. diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/weekly/WeeklyMessageSqlRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/weekly/WeeklyMessageSqlRepository.java index b3cd5b6d5..d60159087 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/weekly/WeeklyMessageSqlRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/weekly/WeeklyMessageSqlRepository.java @@ -24,7 +24,7 @@ public WeeklyMessageSqlRepository(final JdbcClient jdbcClient) { } @Override - public WeeklyMessage getLatestWeeklyMessage() { + public Optional getLatestWeeklyMessage() { String sql = """ SELECT id, @@ -36,11 +36,11 @@ public WeeklyMessage getLatestWeeklyMessage() { LIMIT 1 """; - return jdbcClient.sql(sql).query(WEEKLY_MESSAGE_ROW_MAPPER).optional().orElse(null); + return jdbcClient.sql(sql).query(WEEKLY_MESSAGE_ROW_MAPPER).optional(); } @Override - public WeeklyMessage getWeeklyMessageById(final String id) { + public Optional getWeeklyMessageById(final String id) { String sql = """ SELECT id, @@ -56,8 +56,7 @@ public WeeklyMessage getWeeklyMessageById(final String id) { .sql(sql) .param(1, UUID.fromString(id)) .query(WEEKLY_MESSAGE_ROW_MAPPER) - .optional() - .orElse(null); + .optional(); } @Override diff --git a/src/main/java/org/patinanetwork/codebloom/common/dto/achievement/AchievementDto.java b/src/main/java/org/patinanetwork/codebloom/common/dto/achievement/AchievementDto.java index 73f15a959..c830c0338 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/dto/achievement/AchievementDto.java +++ b/src/main/java/org/patinanetwork/codebloom/common/dto/achievement/AchievementDto.java @@ -52,14 +52,14 @@ public static AchievementDto fromAchievement(final Achievement achievement) { return AchievementDto.builder() .id(achievement.getId()) .userId(achievement.getUserId()) - .leaderboard(achievement.getLeaderboard()) + .leaderboard(achievement.getLeaderboard().orElse(null)) .place(achievement.getPlace()) .title(achievement.getTitle()) .leaderboardId(achievement.getLeaderboardId()) - .description(achievement.getDescription()) + .description(achievement.getDescription().orElse(null)) .isActive(achievement.isActive()) .createdAt(achievement.getCreatedAt()) - .deletedAt(achievement.getDeletedAt()) + .deletedAt(achievement.getDeletedAt().orElse(null)) .build(); } } diff --git a/src/main/java/org/patinanetwork/codebloom/common/dto/announcement/AnnouncementDto.java b/src/main/java/org/patinanetwork/codebloom/common/dto/announcement/AnnouncementDto.java index b5568c636..fe89bddfb 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/dto/announcement/AnnouncementDto.java +++ b/src/main/java/org/patinanetwork/codebloom/common/dto/announcement/AnnouncementDto.java @@ -36,7 +36,7 @@ public static AnnouncementDto fromAnnouncement(final Announcement announcement) .id(announcement.getId()) .createdAt(announcement.getCreatedAt()) .expiresAt(announcement.getExpiresAt()) - .showTimer(announcement.isShowTimer()) + .showTimer(announcement.getShowTimer().orElse(false)) .message(announcement.getMessage()) .build(); } diff --git a/src/main/java/org/patinanetwork/codebloom/common/dto/user/PrivateUserDto.java b/src/main/java/org/patinanetwork/codebloom/common/dto/user/PrivateUserDto.java index f9c9b28da..3f83e41e7 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/dto/user/PrivateUserDto.java +++ b/src/main/java/org/patinanetwork/codebloom/common/dto/user/PrivateUserDto.java @@ -57,13 +57,13 @@ public static PrivateUserDto fromUser(final User user) { .id(user.getId()) .discordId(user.getDiscordId()) .discordName(user.getDiscordName()) - .leetcodeUsername(user.getLeetcodeUsername()) - .nickname(user.getNickname()) + .leetcodeUsername(user.getLeetcodeUsername().orElse(null)) + .nickname(user.getNickname().orElse(null)) .admin(user.isAdmin()) - .profileUrl(user.getProfileUrl()) + .profileUrl(user.getProfileUrl().orElse(null)) .tags(user.getTags()) .verifyKey(user.getVerifyKey()) - .schoolEmail(user.getSchoolEmail()) + .schoolEmail(user.getSchoolEmail().orElse(null)) .achievements(user.getAchievements().stream() .map(AchievementDto::fromAchievement) .toList()) diff --git a/src/main/java/org/patinanetwork/codebloom/common/dto/user/UserDto.java b/src/main/java/org/patinanetwork/codebloom/common/dto/user/UserDto.java index fe2ed3d54..226bae7ee 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/dto/user/UserDto.java +++ b/src/main/java/org/patinanetwork/codebloom/common/dto/user/UserDto.java @@ -50,10 +50,10 @@ public static UserDto fromUser(final User user) { .id(user.getId()) .discordId(user.getDiscordId()) .discordName(user.getDiscordName()) - .leetcodeUsername(user.getLeetcodeUsername()) - .nickname(user.getNickname()) + .leetcodeUsername(user.getLeetcodeUsername().orElse(null)) + .nickname(user.getNickname().orElse(null)) .admin(user.isAdmin()) - .profileUrl(user.getProfileUrl()) + .profileUrl(user.getProfileUrl().orElse(null)) .tags(user.getTags()) .achievements(user.getAchievements().stream() .map(AchievementDto::fromAchievement) diff --git a/src/main/java/org/patinanetwork/codebloom/common/dto/user/UserWithScoreDto.java b/src/main/java/org/patinanetwork/codebloom/common/dto/user/UserWithScoreDto.java index 25ca28e65..823bc4c99 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/dto/user/UserWithScoreDto.java +++ b/src/main/java/org/patinanetwork/codebloom/common/dto/user/UserWithScoreDto.java @@ -53,10 +53,10 @@ public static UserWithScoreDto fromUserWithScore(final UserWithScore user) { .id(user.getId()) .discordId(user.getDiscordId()) .discordName(user.getDiscordName()) - .leetcodeUsername(user.getLeetcodeUsername()) - .nickname(user.getNickname()) + .leetcodeUsername(user.getLeetcodeUsername().orElse(null)) + .nickname(user.getNickname().orElse(null)) .admin(user.isAdmin()) - .profileUrl(user.getProfileUrl()) + .profileUrl(user.getProfileUrl().orElse(null)) .tags(user.getTags()) .achievements(user.getAchievements().stream() .map(AchievementDto::fromAchievement) diff --git a/src/main/java/org/patinanetwork/codebloom/common/security/Protector.java b/src/main/java/org/patinanetwork/codebloom/common/security/Protector.java index f5bf019ee..3e0d4d21e 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/security/Protector.java +++ b/src/main/java/org/patinanetwork/codebloom/common/security/Protector.java @@ -53,10 +53,9 @@ public AuthenticationObject validateSession(final HttpServletRequest request) { throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Unauthorized"); } - User user = userRepository.getUserById(session.getUserId()); - if (user == null) { - throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Unauthorized"); - } + User user = userRepository + .getUserById(session.getUserId()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Unauthorized")); return new AuthenticationObject(user, session); } diff --git a/src/main/java/org/patinanetwork/codebloom/common/submissions/SubmissionsHandler.java b/src/main/java/org/patinanetwork/codebloom/common/submissions/SubmissionsHandler.java index 4830cbca6..a9cd25ddf 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/submissions/SubmissionsHandler.java +++ b/src/main/java/org/patinanetwork/codebloom/common/submissions/SubmissionsHandler.java @@ -267,8 +267,10 @@ public ArrayList handleSubmissions( acceptedSubmissions.add( new AcceptedSubmission(bankQuestion.getQuestionTitle(), createdQuestion.getId(), points)); - UserWithScore recentUserMetadata = userRepository.getUserWithScoreByIdAndLeaderboardId( - user.getId(), recentLeaderboard.get().getId(), UserFilterOptions.DEFAULT); + UserWithScore recentUserMetadata = userRepository + .getUserWithScoreByIdAndLeaderboardId( + user.getId(), recentLeaderboard.get().getId(), UserFilterOptions.DEFAULT) + .orElse(null); leaderboardRepository.updateUserPointsFromLeaderboard( recentLeaderboard.get().getId(), user.getId(), recentUserMetadata.getTotalScore() + points); diff --git a/src/main/java/org/patinanetwork/codebloom/playwright/PlaywrightClient.java b/src/main/java/org/patinanetwork/codebloom/playwright/PlaywrightClient.java index 466a512ad..188789b4e 100644 --- a/src/main/java/org/patinanetwork/codebloom/playwright/PlaywrightClient.java +++ b/src/main/java/org/patinanetwork/codebloom/playwright/PlaywrightClient.java @@ -131,8 +131,10 @@ public Optional getLeetcodeCookie(String githubUsername, String githubPass String csrf = cookieMap.get("csrftoken"); if (sessionToken != null) { log.info("Cookie found!"); - return Optional.of( - Auth.builder().token(sessionToken).csrf(csrf).build()); + return Optional.of(Auth.builder() + .token(sessionToken) + .csrf(Optional.ofNullable(csrf)) + .build()); } } else { log.info("Should be authenticated but not authenticated."); diff --git a/src/main/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealer.java b/src/main/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealer.java index cbefd4fb0..cf484d207 100644 --- a/src/main/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealer.java +++ b/src/main/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealer.java @@ -77,16 +77,14 @@ public void stealAuthCookie() { } try { - Auth mostRecentAuth = authRepository.getMostRecentAuth(); + var mostRecentAuth = authRepository.getMostRecentAuth().filter(auth -> auth.getCreatedAt() + .isAfter(StandardizedOffsetDateTime.now().minus(4, ChronoUnit.HOURS))); // The auth token should be refreshed every 4 hours. - if (mostRecentAuth != null - && mostRecentAuth - .getCreatedAt() - .isAfter(StandardizedOffsetDateTime.now().minus(4, ChronoUnit.HOURS))) { + if (mostRecentAuth.isPresent()) { log.info("Auth token already exists, using token from database."); - cookie = mostRecentAuth.getToken(); - csrf = mostRecentAuth.getCsrf(); + cookie = mostRecentAuth.get().getToken(); + csrf = mostRecentAuth.get().getCsrf().orElse(null); return; } @@ -166,7 +164,7 @@ String stealCookieImpl() { Optional auth = playwrightClient.getLeetcodeCookie(githubUsername, githubPassword); if (auth.isPresent()) { var a = auth.get(); - this.csrf = a.getCsrf(); + this.csrf = a.getCsrf().orElse(null); this.cookie = a.getToken(); redisClient.setAuth(a.getToken(), 4, ChronoUnit.HOURS); log.info("auth token stored in redis"); diff --git a/src/main/java/org/patinanetwork/codebloom/scheduled/discord/WeeklyLeaderboard.java b/src/main/java/org/patinanetwork/codebloom/scheduled/discord/WeeklyLeaderboard.java index b5d2975e7..bee6f2954 100644 --- a/src/main/java/org/patinanetwork/codebloom/scheduled/discord/WeeklyLeaderboard.java +++ b/src/main/java/org/patinanetwork/codebloom/scheduled/discord/WeeklyLeaderboard.java @@ -3,7 +3,6 @@ import java.time.LocalDateTime; import lombok.extern.slf4j.Slf4j; import org.patinanetwork.codebloom.common.components.DiscordClubManager; -import org.patinanetwork.codebloom.common.db.models.weekly.WeeklyMessage; import org.patinanetwork.codebloom.common.db.repos.weekly.WeeklyMessageRepository; import org.springframework.context.annotation.Profile; import org.springframework.scheduling.annotation.Scheduled; @@ -25,10 +24,10 @@ public class WeeklyLeaderboard { @Scheduled(initialDelay = 0, fixedDelay = 1000 * 60 * 60) public void sendWeeklyLeaderboard() { - WeeklyMessage weeklyMessage = weeklyMessageRepository.getLatestWeeklyMessage(); + var weeklyMessage = weeklyMessageRepository.getLatestWeeklyMessage().filter(message -> !message.getCreatedAt() + .isBefore(LocalDateTime.now().minusDays(7L))); - if (weeklyMessage != null - && !weeklyMessage.getCreatedAt().isBefore(LocalDateTime.now().minusDays(7L))) { + if (weeklyMessage.isPresent()) { log.info("WeeklyLeaderboard skipped."); return; } diff --git a/src/main/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessService.java b/src/main/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessService.java index 0aba00e7d..0af097388 100644 --- a/src/main/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessService.java +++ b/src/main/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessService.java @@ -146,7 +146,7 @@ private void fetchAndUpdate(final Job job) { log.info("Processing job {} for questionId: {}", job.getId(), job.getQuestionId()); job.setAttempts(job.getAttempts() + 1); job.setStatus(JobStatus.PROCESSING); - job.setProcessedAt(StandardizedOffsetDateTime.now()); + job.setProcessedAt(Optional.of(StandardizedOffsetDateTime.now())); boolean success = jobRepository.updateJob(job); if (!success) { throw new RuntimeException("Failed to update job status to PROCESSING"); @@ -251,7 +251,7 @@ private void fetchAndUpdate(final Job job) { .filter(d -> !d.isBlank()) .isPresent())) { job.setStatus(JobStatus.COMPLETE); - job.setCompletedAt(StandardizedOffsetDateTime.now()); + job.setCompletedAt(Optional.of(StandardizedOffsetDateTime.now())); log.info( "Successfully completed job {} for question: {} (ID: {})", @@ -275,7 +275,7 @@ private void fetchAndUpdate(final Job job) { } } catch (Exception e) { job.setStatus(JobStatus.INCOMPLETE); - job.setProcessedAt(null); + job.setProcessedAt(Optional.empty()); jobRepository.updateJob(job); log.error("Failed to process job {} for questionId: {}", job.getId(), job.getQuestionId(), e); diff --git a/src/main/java/org/patinanetwork/codebloom/scheduled/submission/SubmissionScheduler.java b/src/main/java/org/patinanetwork/codebloom/scheduled/submission/SubmissionScheduler.java index 85b8027b3..fc157291b 100644 --- a/src/main/java/org/patinanetwork/codebloom/scheduled/submission/SubmissionScheduler.java +++ b/src/main/java/org/patinanetwork/codebloom/scheduled/submission/SubmissionScheduler.java @@ -60,12 +60,12 @@ public void handleAllUserSubmissions() { return; } - if (user.getLeetcodeUsername() == null) { + if (user.getLeetcodeUsername().isEmpty()) { continue; } - List leetcodeSubmissions = - leetcodeClient.findSubmissionsByUsername(user.getLeetcodeUsername()); + List leetcodeSubmissions = leetcodeClient.findSubmissionsByUsername( + user.getLeetcodeUsername().orElse(null)); submissionsHandler.handleSubmissions(leetcodeSubmissions, user, false); } diff --git a/src/test/java/org/patinanetwork/codebloom/api/admin/AdminControllerTest.java b/src/test/java/org/patinanetwork/codebloom/api/admin/AdminControllerTest.java index 6b90724e8..6d7942408 100644 --- a/src/test/java/org/patinanetwork/codebloom/api/admin/AdminControllerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/api/admin/AdminControllerTest.java @@ -340,7 +340,7 @@ void testDeleteAnnouncementNull() { DeleteAnnouncementBody body = DeleteAnnouncementBody.builder() .id("4f6bbb9a-0baa-11f1-9607-77d42f1cf060") .build(); - when(announcementRepository.getAnnouncementById(anyString())).thenReturn(null); + when(announcementRepository.getAnnouncementById(anyString())).thenReturn(Optional.empty()); ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> adminController.deleteAnnouncement(body, null)); @@ -354,7 +354,7 @@ void testDeleteAnnouncementFailure() { .id("4f6bbb9a-0baa-11f1-9607-77d42f1cf060") .build(); Announcement mockAnnouncement = mock(Announcement.class); - when(announcementRepository.getAnnouncementById(anyString())).thenReturn(mockAnnouncement); + when(announcementRepository.getAnnouncementById(anyString())).thenReturn(Optional.of(mockAnnouncement)); when(announcementRepository.updateAnnouncement(mockAnnouncement)).thenReturn(false); ResponseEntity> response = adminController.deleteAnnouncement(body, null); @@ -371,7 +371,7 @@ void testDeleteAnnouncementSuccess() { .id("4f6bbb9a-0baa-11f1-9607-77d42f1cf060") .build(); Announcement mockAnnouncement = mock(Announcement.class); - when(announcementRepository.getAnnouncementById(anyString())).thenReturn(mockAnnouncement); + when(announcementRepository.getAnnouncementById(anyString())).thenReturn(Optional.of(mockAnnouncement)); when(announcementRepository.updateAnnouncement(mockAnnouncement)).thenReturn(true); ResponseEntity> response = adminController.deleteAnnouncement(body, null); diff --git a/src/test/java/org/patinanetwork/codebloom/api/auth/AuthControllerTest.java b/src/test/java/org/patinanetwork/codebloom/api/auth/AuthControllerTest.java index 3aed9b115..0eb19932e 100644 --- a/src/test/java/org/patinanetwork/codebloom/api/auth/AuthControllerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/api/auth/AuthControllerTest.java @@ -95,7 +95,7 @@ private User createRandomUser() { .id(randomUUID()) .discordId(String.valueOf(faker.number().randomNumber(18, true))) .discordName(faker.name().username()) - .leetcodeUsername(faker.name().username()) + .leetcodeUsername(Optional.ofNullable(faker.name().username())) .admin(false) .verifyKey(faker.crypto().md5()) .build(); diff --git a/src/test/java/org/patinanetwork/codebloom/api/auth/security/CustomAuthenticationSuccessHandlerTest.java b/src/test/java/org/patinanetwork/codebloom/api/auth/security/CustomAuthenticationSuccessHandlerTest.java index f77cea8b8..8cdfb9340 100644 --- a/src/test/java/org/patinanetwork/codebloom/api/auth/security/CustomAuthenticationSuccessHandlerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/api/auth/security/CustomAuthenticationSuccessHandlerTest.java @@ -82,7 +82,7 @@ void updatesNameAndSetsCookie() throws Exception { .discordName("old-name") .build(); when(authentication.getPrincipal()).thenReturn(oAuth2User); - when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(existingUser); + when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(Optional.of(existingUser)); doAnswer(inv -> { Session s = inv.getArgument(0); s.setId(Optional.of("session-abc")); @@ -113,10 +113,10 @@ void updatesProfileUrl() throws Exception { .id("user-1") .discordId(DISCORD_ID) .discordName("old-name") - .leetcodeUsername("leet_user") + .leetcodeUsername(Optional.of("leet_user")) .build(); when(authentication.getPrincipal()).thenReturn(oAuth2User); - when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(existingUser); + when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(Optional.of(existingUser)); UserProfile profile = new UserProfile("leet_user", "1000", "https://avatar.url", null, null); when(leetcodeClient.getUserProfile("leet_user")).thenReturn(profile); doAnswer(inv -> { @@ -129,7 +129,7 @@ void updatesProfileUrl() throws Exception { handler.onAuthenticationSuccess(request, response, authentication); - assertEquals("https://avatar.url", existingUser.getProfileUrl()); + assertEquals("https://avatar.url", existingUser.getProfileUrl().orElse(null)); } @Test @@ -139,10 +139,10 @@ void survivesLeetcodeLookupFailure() throws Exception { .id("user-1") .discordId(DISCORD_ID) .discordName("old-name") - .leetcodeUsername("bad_user") + .leetcodeUsername(Optional.of("bad_user")) .build(); when(authentication.getPrincipal()).thenReturn(oAuth2User); - when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(existingUser); + when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(Optional.of(existingUser)); when(leetcodeClient.getUserProfile("bad_user")).thenThrow(new RuntimeException("API down")); doAnswer(inv -> { Session s = inv.getArgument(0); @@ -160,7 +160,7 @@ void survivesLeetcodeLookupFailure() throws Exception { @DisplayName("creates a user, adds them to the leaderboard, and sets session cookie") void createsUserAndAddsToLeaderboard() throws Exception { when(authentication.getPrincipal()).thenReturn(oAuth2User); - when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(null); + when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(Optional.empty()); Leaderboard lb = Leaderboard.builder().id("lb-1").build(); when(leaderboardRepository.getRecentLeaderboardMetadata()).thenReturn(Optional.of(lb)); doAnswer(inv -> { @@ -191,7 +191,7 @@ void assignsTagForGuildMember() throws Exception { .discordName(DISCORD_NAME) .build(); when(authentication.getPrincipal()).thenReturn(oAuth2User); - when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(existingUser); + when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(Optional.of(existingUser)); doAnswer(inv -> { Session s = inv.getArgument(0); s.setId(Optional.of("s-club")); @@ -233,7 +233,7 @@ void skipsExistingTag() throws Exception { .discordName(DISCORD_NAME) .build(); when(authentication.getPrincipal()).thenReturn(oAuth2User); - when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(existingUser); + when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(Optional.of(existingUser)); doAnswer(inv -> { Session s = inv.getArgument(0); s.setId(Optional.of("s-club")); @@ -274,7 +274,7 @@ void setsNicknameForPatinaClub() throws Exception { .discordName(DISCORD_NAME) .build(); when(authentication.getPrincipal()).thenReturn(oAuth2User); - when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(existingUser); + when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(Optional.of(existingUser)); doAnswer(inv -> { Session s = inv.getArgument(0); s.setId(Optional.of("s-club")); @@ -303,7 +303,7 @@ void setsNicknameForPatinaClub() throws Exception { handler.onAuthenticationSuccess(request, response, authentication); - assertEquals("Cool Nickname", existingUser.getNickname()); + assertEquals("Cool Nickname", existingUser.getNickname().orElse(null)); } @Test @@ -315,7 +315,7 @@ void fallsBackToGlobalNameForPatina() throws Exception { .discordName(DISCORD_NAME) .build(); when(authentication.getPrincipal()).thenReturn(oAuth2User); - when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(existingUser); + when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(Optional.of(existingUser)); doAnswer(inv -> { Session s = inv.getArgument(0); s.setId(Optional.of("s-club")); @@ -347,7 +347,7 @@ void fallsBackToGlobalNameForPatina() throws Exception { handler.onAuthenticationSuccess(request, response, authentication); - assertEquals("Global Name", existingUser.getNickname()); + assertEquals("Global Name", existingUser.getNickname().orElse(null)); } @Test @@ -359,7 +359,7 @@ void skipsClubWithNoGuildId() throws Exception { .discordName(DISCORD_NAME) .build(); when(authentication.getPrincipal()).thenReturn(oAuth2User); - when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(existingUser); + when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(Optional.of(existingUser)); doAnswer(inv -> { Session s = inv.getArgument(0); s.setId(Optional.of("s-club")); @@ -400,7 +400,7 @@ void sessionCreationFailureRedirects() throws Exception { .discordId(DISCORD_ID) .discordName(DISCORD_NAME) .build(); - when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(existingUser); + when(userRepository.getUserByDiscordId(DISCORD_ID)).thenReturn(Optional.of(existingUser)); doNothing().when(sessionRepository).createSession(any(Session.class)); diff --git a/src/test/java/org/patinanetwork/codebloom/api/duel/DuelControllerTest.java b/src/test/java/org/patinanetwork/codebloom/api/duel/DuelControllerTest.java index ea5a4cb5d..faef2f10a 100644 --- a/src/test/java/org/patinanetwork/codebloom/api/duel/DuelControllerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/api/duel/DuelControllerTest.java @@ -66,7 +66,7 @@ private User createRandomUser() { .id(randomUUID()) .discordId(String.valueOf(faker.number().randomNumber(18, true))) .discordName(faker.name().username()) - .leetcodeUsername(faker.name().username()) + .leetcodeUsername(Optional.ofNullable(faker.name().username())) .admin(false) .verifyKey(faker.crypto().md5()) .build(); diff --git a/src/test/java/org/patinanetwork/codebloom/api/leaderboard/LeaderboardControllerTest.java b/src/test/java/org/patinanetwork/codebloom/api/leaderboard/LeaderboardControllerTest.java index f386347cc..be0663e3b 100644 --- a/src/test/java/org/patinanetwork/codebloom/api/leaderboard/LeaderboardControllerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/api/leaderboard/LeaderboardControllerTest.java @@ -283,7 +283,7 @@ void returnsUserData() { .totalScore(500) .build(); when(userRepository.getUserWithScoreByIdAndLeaderboardId(eq("u1"), eq(LEADERBOARD_ID), any())) - .thenReturn(uws); + .thenReturn(Optional.of(uws)); ResponseEntity> response = leaderboardController.getUserCurrentLeaderboardFull(null, "u1"); diff --git a/src/test/java/org/patinanetwork/codebloom/api/submission/SubmissionControllerTest.java b/src/test/java/org/patinanetwork/codebloom/api/submission/SubmissionControllerTest.java index 59d5da747..db357b617 100644 --- a/src/test/java/org/patinanetwork/codebloom/api/submission/SubmissionControllerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/api/submission/SubmissionControllerTest.java @@ -91,7 +91,7 @@ void testSetLeetcodeUsernameSuccess() { when(auth.getUser()).thenReturn(user); - when(user.getLeetcodeUsername()).thenReturn(null); + when(user.getLeetcodeUsername()).thenReturn(Optional.empty()); when(user.getVerifyKey()).thenReturn("verify-123"); when(body.getLeetcodeUsername()).thenReturn("leetcodeUser"); @@ -109,8 +109,8 @@ void testSetLeetcodeUsernameSuccess() { assertTrue(response.getBody().isSuccess()); assertEquals("Leetcode username has been set!", response.getBody().getMessage()); - verify(user).setLeetcodeUsername("leetcodeUser"); - verify(user).setProfileUrl("avatar-url"); + verify(user).setLeetcodeUsername(Optional.of("leetcodeUser")); + verify(user).setProfileUrl(Optional.of("avatar-url")); verify(userRepository).updateUser(user); } @@ -121,7 +121,7 @@ void testSetLeetcodeUsernameFailure() { LeetcodeUsernameObject body = mock(LeetcodeUsernameObject.class); when(auth.getUser()).thenReturn(user); - when(user.getLeetcodeUsername()).thenReturn("existing"); + when(user.getLeetcodeUsername()).thenReturn(Optional.of("existing")); ResponseStatusException ex = assertThrows(ResponseStatusException.class, () -> submissionController.setLeetcodeUsername(auth, body)); @@ -139,7 +139,7 @@ void testCheckLatestSubmissionsSuccess() { when(auth.getUser()).thenReturn(user); - when(user.getLeetcodeUsername()).thenReturn("leetcodeUser"); + when(user.getLeetcodeUsername()).thenReturn(Optional.of("leetcodeUser")); when(user.getId()).thenReturn("abcdefg123456"); when(simpleRedis.containsKey("abcdefg123456")).thenReturn(false); @@ -169,7 +169,7 @@ void testCheckLatestSubmissionsFailure() { User user = mock(User.class); when(auth.getUser()).thenReturn(user); - when(user.getLeetcodeUsername()).thenReturn(null); + when(user.getLeetcodeUsername()).thenReturn(Optional.empty()); ResponseStatusException ex = assertThrows(ResponseStatusException.class, () -> submissionController.checkLatestSubmissions(auth)); diff --git a/src/test/java/org/patinanetwork/codebloom/api/user/UserControllerTest.java b/src/test/java/org/patinanetwork/codebloom/api/user/UserControllerTest.java index 1500c5919..103fdeac1 100644 --- a/src/test/java/org/patinanetwork/codebloom/api/user/UserControllerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/api/user/UserControllerTest.java @@ -64,7 +64,7 @@ private User createRandomUser() { .id(randomUUID()) .discordId(String.valueOf(faker.number().randomNumber(18, true))) .discordName(faker.name().username()) - .leetcodeUsername(faker.name().username()) + .leetcodeUsername(Optional.ofNullable(faker.name().username())) .admin(false) .verifyKey(faker.crypto().md5()) .build(); @@ -87,7 +87,7 @@ private Question createRandomQuestion(final String userId) { void getUserProfileUserNotFound() { String userId = randomUUID(); - when(userRepository.getUserById(eq(userId))).thenReturn(null); + when(userRepository.getUserById(eq(userId))).thenReturn(Optional.empty()); ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> { userController.getUserProfileByUserId(request, userId); @@ -104,7 +104,7 @@ void getUserProfileUserNotFound() { void getUserProfileReturnsUserProfileSuccessfully() { User user = createRandomUser(); - when(userRepository.getUserById(eq(user.getId()))).thenReturn(user); + when(userRepository.getUserById(eq(user.getId()))).thenReturn(Optional.of(user)); var response = userController.getUserProfileByUserId(request, user.getId()); @@ -120,7 +120,7 @@ void getUserProfileReturnsUserProfileSuccessfully() { assertEquals(user.getId(), payload.getId()); assertEquals(user.getDiscordId(), payload.getDiscordId()); assertEquals(user.getDiscordName(), payload.getDiscordName()); - assertEquals(user.getLeetcodeUsername(), payload.getLeetcodeUsername()); + assertEquals(user.getLeetcodeUsername().orElse(null), payload.getLeetcodeUsername()); verify(userRepository, times(1)).getUserById(eq(user.getId())); } diff --git a/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java b/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java index 47b431923..8a03df40f 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java @@ -401,7 +401,7 @@ void testRefreshSubmissionsSuccess() throws LeaderboardException { when(scoredUser.getTotalScore()).thenReturn(42); when(userRepository.getUserWithScoreByIdAndLeaderboardId( eq("user-id-1"), eq("leaderboard-id"), eq(UserFilterOptions.DEFAULT))) - .thenReturn(scoredUser); + .thenReturn(Optional.of(scoredUser)); Indexed globalIndex = Indexed.of(scoredUser, 5); Indexed clubIndex = Indexed.of(scoredUser, 2); @@ -475,7 +475,7 @@ void testRefreshSubmissionsReturnsCorrectClubRankWithDifferentTags() throws Lead when(scoredUser.getTotalScore()).thenReturn(100); when(userRepository.getUserWithScoreByIdAndLeaderboardId( eq("user-id-2"), eq("lb-id"), eq(UserFilterOptions.DEFAULT))) - .thenReturn(scoredUser); + .thenReturn(Optional.of(scoredUser)); Indexed globalIndex = Indexed.of(scoredUser, 10); Indexed clubIndex = Indexed.of(scoredUser, 1); @@ -512,7 +512,7 @@ void testRefreshSubmissionsWithZeroScore() throws LeaderboardException { when(scoredUser.getTotalScore()).thenReturn(0); when(userRepository.getUserWithScoreByIdAndLeaderboardId( eq("user-id-1"), eq("leaderboard-id"), eq(UserFilterOptions.DEFAULT))) - .thenReturn(scoredUser); + .thenReturn(Optional.of(scoredUser)); Indexed globalIndex = Indexed.of(scoredUser, 50); Indexed clubIndex = Indexed.of(scoredUser, 20); diff --git a/src/test/java/org/patinanetwork/codebloom/common/components/LeaderboardManagerTest.java b/src/test/java/org/patinanetwork/codebloom/common/components/LeaderboardManagerTest.java index 0fcf036f9..deefd75f9 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/components/LeaderboardManagerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/components/LeaderboardManagerTest.java @@ -64,7 +64,7 @@ private String randomSnowflake() { .id(UUID.randomUUID().toString()) .discordId(randomSnowflake()) .discordName(faker.name().username()) - .leetcodeUsername(faker.name().username()) + .leetcodeUsername(Optional.ofNullable(faker.name().username())) .admin(faker.bool().bool()) .verifyKey(faker.crypto().md5()); } @@ -75,7 +75,7 @@ private void assertAchievement( final AchievementPlaceEnum placeEnum, final String userId, final String leaderboardId) { - assertEquals(leaderboard, achievement.getLeaderboard()); + assertEquals(Optional.ofNullable(leaderboard), achievement.getLeaderboard()); assertEquals(placeEnum, achievement.getPlace()); assertEquals(userId, achievement.getUserId()); assertEquals(leaderboardId, achievement.getLeaderboardId()); @@ -767,7 +767,7 @@ void testWinnersWithNullTagsAreSkippedInTagLeaderboards() { var achievements = captor.getAllValues(); for (var achievement : achievements) { - assertNull(achievement.getLeaderboard()); + assertTrue(achievement.getLeaderboard().isEmpty()); assertEquals(latestLeaderboard.getId(), achievement.getLeaderboardId()); } } diff --git a/src/test/java/org/patinanetwork/codebloom/common/components/duel/DuelManagerTest.java b/src/test/java/org/patinanetwork/codebloom/common/components/duel/DuelManagerTest.java index 2b2a9bf5f..b4c135738 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/components/duel/DuelManagerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/components/duel/DuelManagerTest.java @@ -74,7 +74,7 @@ private User createRandomUser() { .id(UUID.randomUUID().toString()) .discordId(String.valueOf(faker.number().randomNumber(18, true))) .discordName(faker.name().username()) - .leetcodeUsername(faker.name().username()) + .leetcodeUsername(Optional.ofNullable(faker.name().username())) .admin(false) .verifyKey(faker.crypto().md5()) .build(); @@ -256,8 +256,8 @@ void testBuildPlayersInLobbyConvertsUsersToUserDtos() { .build(); when(lobbyPlayerRepository.findPlayersByLobbyId(lobbyId)).thenReturn(List.of(player1, player2)); - when(userRepository.getUserById(playerId1)).thenReturn(user1); - when(userRepository.getUserById(playerId2)).thenReturn(user2); + when(userRepository.getUserById(playerId1)).thenReturn(Optional.of(user1)); + when(userRepository.getUserById(playerId2)).thenReturn(Optional.of(user2)); when(lobbyRepository.findLobbyById(lobbyId)).thenReturn(Optional.empty()); when(lobbyQuestionRepository.findLobbyQuestionsByLobbyId(lobbyId)).thenReturn(List.of()); @@ -1400,7 +1400,8 @@ void testProcessSubmissionsSuccessful() { when(lobbyQuestionRepository.findLobbyQuestionsByLobbyId(eq(activeLobby.getId()))) .thenReturn(List.of(lobbyQuestion)); when(questionBankRepository.getQuestionById(eq(questionBank.getId()))).thenReturn(Optional.of(questionBank)); - when(throttledLeetcodeClient.findSubmissionsByUsername(eq(user.getLeetcodeUsername()), eq(5))) + when(throttledLeetcodeClient.findSubmissionsByUsername( + eq(user.getLeetcodeUsername().orElse(null)), eq(5))) .thenReturn(List.of(leetcodeSubmission)); when(submissionsHandler.handleSubmissions(any(), eq(user), eq(true))).thenReturn(new ArrayList<>() { { @@ -1420,7 +1421,8 @@ void testProcessSubmissionsSuccessful() { verify(lobbyPlayerRepository, times(1)).findValidLobbyPlayerByPlayerId(eq(user.getId())); verify(lobbyQuestionRepository, times(1)).findLobbyQuestionsByLobbyId(eq(activeLobby.getId())); verify(questionBankRepository, times(1)).getQuestionById(eq(questionBank.getId())); - verify(throttledLeetcodeClient, times(1)).findSubmissionsByUsername(eq(user.getLeetcodeUsername()), eq(5)); + verify(throttledLeetcodeClient, times(1)) + .findSubmissionsByUsername(eq(user.getLeetcodeUsername().orElse(null)), eq(5)); verify(submissionsHandler, times(1)).handleSubmissions(any(), eq(user), eq(true)); verify(lobbyPlayerQuestionRepository, times(1)).createLobbyPlayerQuestion(any()); verify(lobbyPlayerRepository, times(1)).updateLobbyPlayer(any()); diff --git a/src/test/java/org/patinanetwork/codebloom/common/components/duel/PartyManagerTest.java b/src/test/java/org/patinanetwork/codebloom/common/components/duel/PartyManagerTest.java index 5ea1271d3..e20c81eb3 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/components/duel/PartyManagerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/components/duel/PartyManagerTest.java @@ -42,7 +42,7 @@ private User createRandomUser() { .id(randomUUID()) .discordId(String.valueOf(faker.number().randomNumber(18, true))) .discordName(faker.name().username()) - .leetcodeUsername(faker.name().username()) + .leetcodeUsername(Optional.ofNullable(faker.name().username())) .admin(false) .verifyKey(faker.crypto().md5()) .build(); diff --git a/src/test/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementRepositoryTest.java b/src/test/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementRepositoryTest.java index 5c7935add..b39f2911f 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementRepositoryTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/db/repos/achievements/AchievementRepositoryTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.*; import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.*; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.patinanetwork.codebloom.common.db.models.achievements.Achievement; @@ -35,13 +36,13 @@ void createAchievement() { testAchievement = Achievement.builder() .userId(mockUserId) .place(AchievementPlaceEnum.ONE) - .leaderboard(null) + .leaderboard(Optional.empty()) .leaderboardId(mockLeaderboardId) .title("Test Achievement") - .description("Integration test achievement") + .description(Optional.of("Integration test achievement")) .isActive(true) .createdAt(StandardizedOffsetDateTime.now()) - .deletedAt(null) + .deletedAt(Optional.empty()) .build(); repo.createAchievement(testAchievement); @@ -58,7 +59,7 @@ void cleanUp() { @Test @Order(1) void testGetAchievementById() { - Achievement found = repo.getAchievementById(testAchievement.getId()); + Achievement found = repo.getAchievementById(testAchievement.getId()).orElse(null); assertNotNull(found); assertEquals(testAchievement.getId(), found.getId()); assertEquals(testAchievement, found); @@ -80,21 +81,21 @@ void testUpdateAchievement() { .id(testAchievement.getId()) .userId(testAchievement.getUserId()) .place(AchievementPlaceEnum.THREE) - .leaderboard(Tag.Patina) + .leaderboard(Optional.of(Tag.Patina)) .leaderboardId(mockLeaderboardId) .title("Updated Title") - .description("Updated Description") + .description(Optional.of("Updated Description")) .isActive(false) .createdAt(testAchievement.getCreatedAt()) .deletedAt(testAchievement.getDeletedAt()) .build(); - Achievement result = repo.updateAchievement(updatedAchievement); + Achievement result = repo.updateAchievement(updatedAchievement).orElse(null); assertNotNull(result); assertEquals("Updated Title", result.getTitle()); - assertEquals("Updated Description", result.getDescription()); + assertEquals(Optional.of("Updated Description"), result.getDescription()); assertFalse(result.isActive()); } @@ -104,25 +105,25 @@ void testDeleteAchievementById() { deletableAchievement = Achievement.builder() .userId(mockUserId) .place(AchievementPlaceEnum.ONE) - .leaderboard(null) + .leaderboard(Optional.empty()) .leaderboardId(mockLeaderboardId) .title("Deletable Achievement") - .description("Should be deleted") + .description(Optional.of("Should be deleted")) .isActive(true) .createdAt(StandardizedOffsetDateTime.now()) - .deletedAt(null) + .deletedAt(Optional.empty()) .build(); repo.createAchievement(deletableAchievement); - Achievement found = repo.getAchievementById(deletableAchievement.getId()); + Achievement found = + repo.getAchievementById(deletableAchievement.getId()).orElse(null); assertNotNull(found); assertEquals(deletableAchievement.getId(), found.getId()); boolean deleted = repo.deleteAchievementById(deletableAchievement.getId()); assertTrue(deleted); - Achievement deletedFetched = repo.getAchievementById(deletableAchievement.getId()); - assertNull(deletedFetched); + assertTrue(repo.getAchievementById(deletableAchievement.getId()).isEmpty()); } } diff --git a/src/test/java/org/patinanetwork/codebloom/common/db/repos/announcement/AnnouncementRepositoryTest.java b/src/test/java/org/patinanetwork/codebloom/common/db/repos/announcement/AnnouncementRepositoryTest.java index 3d7bbb162..3936b01e0 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/db/repos/announcement/AnnouncementRepositoryTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/db/repos/announcement/AnnouncementRepositoryTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.*; import java.util.List; +import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -38,7 +39,7 @@ void createTestAnnouncement() { .id(null) .expiresAt(StandardizedOffsetDateTime.normalize( StandardizedOffsetDateTime.now().plusMinutes(5L))) - .showTimer(true) + .showTimer(Optional.of(true)) .message("Hi this is a test announcement!") .build(); boolean isSuccessful = announcementRepository.createAnnouncement(testAnnouncement); @@ -46,7 +47,7 @@ void createTestAnnouncement() { if (!isSuccessful) { fail("Failed to create test announcement"); } - testAnnouncement = announcementRepository.getRecentAnnouncement(); + testAnnouncement = announcementRepository.getRecentAnnouncement().orElse(null); if (testAnnouncement == null) { fail("Failed to fetch the created test announcement"); } @@ -70,7 +71,8 @@ void deleteTestAnnouncement() { @Test @Order(1) void findMostRecentAnnouncement() { - Announcement possibleTestAnnouncement = announcementRepository.getRecentAnnouncement(); + Announcement possibleTestAnnouncement = + announcementRepository.getRecentAnnouncement().orElse(null); if (possibleTestAnnouncement == null) { fail("most recent announcement doesn't exist, even though we created a test announcement"); @@ -88,7 +90,9 @@ void findMostRecentAnnouncement() { @Test @Order(2) void findAnnouncementById() { - Announcement possibleTestAnnouncement = announcementRepository.getAnnouncementById(testAnnouncement.getId()); + Announcement possibleTestAnnouncement = announcementRepository + .getAnnouncementById(testAnnouncement.getId()) + .orElse(null); if (possibleTestAnnouncement == null) { fail("failed to find announcement by id"); @@ -108,18 +112,20 @@ void updateAnnouncementTest() { Announcement updatedAnnouncement = Announcement.builder() .id(testAnnouncement.getId()) .expiresAt(testAnnouncement.getExpiresAt()) - .showTimer(true) + .showTimer(Optional.of(true)) .message("Hi this is an update announcement!") .build(); boolean result = announcementRepository.updateAnnouncement(updatedAnnouncement); if (!result) { fail("failure to update announcement"); } - Announcement resultAnnouncement = announcementRepository.getAnnouncementById(updatedAnnouncement.getId()); + Announcement resultAnnouncement = announcementRepository + .getAnnouncementById(updatedAnnouncement.getId()) + .orElse(null); assertNotNull(resultAnnouncement); assertEquals(resultAnnouncement.getMessage(), "Hi this is an update announcement!"); assertEquals(resultAnnouncement.getId(), testAnnouncement.getId()); - assertTrue(resultAnnouncement.isShowTimer()); + assertTrue(resultAnnouncement.getShowTimer().orElse(false)); } @Test diff --git a/src/test/java/org/patinanetwork/codebloom/common/db/repos/api/ApiKeyRepositoryTest.java b/src/test/java/org/patinanetwork/codebloom/common/db/repos/api/ApiKeyRepositoryTest.java index f3985032a..9b9a85b83 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/db/repos/api/ApiKeyRepositoryTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/db/repos/api/ApiKeyRepositoryTest.java @@ -2,7 +2,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -51,7 +50,7 @@ void createTestApiKey() { apiKeyRepository.createApiKey(tempApiKey); - testApiKey = apiKeyRepository.getApiKeyById(tempApiKey.getId()); + testApiKey = apiKeyRepository.getApiKeyById(tempApiKey.getId()).orElse(null); if (testApiKey == null) { fail("Failed to create and retrieve test ApiKey"); @@ -76,7 +75,8 @@ void deleteTestApiKey() { @Test @Order(1) void findApiKeyById() { - ApiKey possibleTestApiKey = apiKeyRepository.getApiKeyById(testApiKey.getId()); + ApiKey possibleTestApiKey = + apiKeyRepository.getApiKeyById(testApiKey.getId()).orElse(null); if (possibleTestApiKey == null) { fail("most recent apiKey doesn't exist, even though we created a test apiKey"); @@ -91,7 +91,8 @@ void findApiKeyById() { @Test @Order(2) void findApiKeyByHash() { - ApiKey possibleTestApiKey = apiKeyRepository.getApiKeyByHash(testApiKey.getApiKey()); + ApiKey possibleTestApiKey = + apiKeyRepository.getApiKeyByHash(testApiKey.getApiKey()).orElse(null); if (possibleTestApiKey == null) { fail("most recent apiKey doesn't exist, even though we created a test apiKey"); @@ -136,7 +137,7 @@ void updateApiKeyByIdTest() { fail("failure to update apiKeyById"); } - ApiKey resultApiKey = apiKeyRepository.getApiKeyById(testApiKey.getId()); + ApiKey resultApiKey = apiKeyRepository.getApiKeyById(testApiKey.getId()).orElse(null); assertNotNull(resultApiKey); assertEquals(resultApiKey.getId(), testApiKey.getId()); @@ -156,14 +157,14 @@ void testDeleteApiKeyByHash() { apiKeyRepository.createApiKey(deletableApiKey); - ApiKey found = apiKeyRepository.getApiKeyByHash(deletableApiKey.getApiKey()); + ApiKey found = + apiKeyRepository.getApiKeyByHash(deletableApiKey.getApiKey()).orElse(null); assertNotNull(found); assertEquals(deletableApiKey.getId(), found.getId()); boolean deleted = apiKeyRepository.deleteApiKeyByHash(deletableApiKey.getApiKey()); assertTrue(deleted); - ApiKey deletedFetched = apiKeyRepository.getApiKeyByHash(deletableApiKey.getApiKey()); - assertNull(deletedFetched); + assertTrue(apiKeyRepository.getApiKeyByHash(deletableApiKey.getApiKey()).isEmpty()); } } diff --git a/src/test/java/org/patinanetwork/codebloom/common/db/repos/api/access/ApiKeyAccessRepositoryTest.java b/src/test/java/org/patinanetwork/codebloom/common/db/repos/api/access/ApiKeyAccessRepositoryTest.java index 7caea4b74..1644a22d8 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/db/repos/api/access/ApiKeyAccessRepositoryTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/db/repos/api/access/ApiKeyAccessRepositoryTest.java @@ -2,7 +2,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -70,8 +69,9 @@ void testDeleteApiKeyAccessesByApiKeyId() { boolean deleted = apiKeyAccessRepository.deleteApiKeyAccessesByApiKeyId(deletableApiKeyAccess.getApiKeyId()); assertTrue(deleted); - ApiKeyAccess deletedFetched = apiKeyAccessRepository.getApiKeyAccessById(deletableApiKeyAccess.getId()); - assertNull(deletedFetched); + assertTrue(apiKeyAccessRepository + .getApiKeyAccessById(deletableApiKeyAccess.getId()) + .isEmpty()); } /** @@ -83,7 +83,9 @@ void testDeleteApiKeyAccessesByApiKeyId() { @Test @Order(1) void findApiKeyAccessById() { - ApiKeyAccess possibleTestApiKeyAccess = apiKeyAccessRepository.getApiKeyAccessById(testApiKeyAccess.getId()); + ApiKeyAccess possibleTestApiKeyAccess = apiKeyAccessRepository + .getApiKeyAccessById(testApiKeyAccess.getId()) + .orElse(null); if (possibleTestApiKeyAccess == null) { fail("most recent apiKeyAccess doesn't exist, even though we created a test apiKeyAccess"); @@ -129,7 +131,9 @@ void updateApiKeyAccessByIdTest() { fail("failure to update updateApiKeyAccessById"); } - ApiKeyAccess resultApiKeyAccess = apiKeyAccessRepository.getApiKeyAccessById(testApiKeyAccess.getId()); + ApiKeyAccess resultApiKeyAccess = apiKeyAccessRepository + .getApiKeyAccessById(testApiKeyAccess.getId()) + .orElse(null); assertNotNull(resultApiKeyAccess); assertEquals(resultApiKeyAccess.getId(), testApiKeyAccess.getId()); diff --git a/src/test/java/org/patinanetwork/codebloom/common/db/repos/auth/AuthRepositoryTest.java b/src/test/java/org/patinanetwork/codebloom/common/db/repos/auth/AuthRepositoryTest.java index 2f944de7c..e78095474 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/db/repos/auth/AuthRepositoryTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/db/repos/auth/AuthRepositoryTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.fail; +import java.util.Optional; import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.AfterAll; @@ -34,7 +35,7 @@ public AuthRepositoryTest(final AuthRepository authRepository) { void createTestAuth() { testAuth = Auth.builder() .token(UUID.randomUUID().toString()) - .csrf(UUID.randomUUID().toString()) + .csrf(Optional.ofNullable(UUID.randomUUID().toString())) .build(); authRepository.createAuth(testAuth); @@ -43,7 +44,7 @@ void createTestAuth() { @Test @Order(1) void findMostRecentAuth() { - Auth possibleAuth = authRepository.getMostRecentAuth(); + Auth possibleAuth = authRepository.getMostRecentAuth().orElse(null); if (possibleAuth == null) { fail("most recent auth not found"); @@ -58,7 +59,7 @@ void findMostRecentAuth() { @Test void findAuthById() { - Auth possibleAuth = authRepository.getAuthById(testAuth.getId()); + Auth possibleAuth = authRepository.getAuthById(testAuth.getId()).orElse(null); if (possibleAuth == null) { fail("most recent auth not found"); diff --git a/src/test/java/org/patinanetwork/codebloom/common/db/repos/club/ClubRepositoryTest.java b/src/test/java/org/patinanetwork/codebloom/common/db/repos/club/ClubRepositoryTest.java index 33808d352..23e7c281a 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/db/repos/club/ClubRepositoryTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/db/repos/club/ClubRepositoryTest.java @@ -37,7 +37,7 @@ void createClub() { .build(); repo.createClub(testClub); - testClub = repo.getClubBySlug(testClub.getSlug()); + testClub = repo.getClubBySlug(testClub.getSlug()).orElse(null); assertNotNull(testClub, "Test club should be created and retrievable"); } @@ -54,7 +54,7 @@ void cleanUp() { @Test @Order(1) void testGetClubById() { - Club found = repo.getClubById(testClub.getId()); + Club found = repo.getClubById(testClub.getId()).orElse(null); assertNotNull(found); assertEquals(testClub.getId(), found.getId()); assertEquals(testClub, found); @@ -63,7 +63,7 @@ void testGetClubById() { @Test @Order(2) void testGetClubBySlug() { - Club found = repo.getClubBySlug(testClub.getSlug()); + Club found = repo.getClubBySlug(testClub.getSlug()).orElse(null); assertNotNull(found); assertEquals(testClub.getSlug(), found.getSlug()); assertEquals(testClub, found); @@ -82,7 +82,7 @@ void testUpdateClub() { .tag(Tag.Gwc) .build(); - Club result = repo.updateClub(updatedClub); + Club result = repo.updateClub(updatedClub).orElse(null); assertNotNull(result); assertEquals("Updated Club Name", result.getName()); @@ -106,9 +106,9 @@ void testDeleteClubById() { .build(); repo.createClub(deletableClub); - deletableClub = repo.getClubBySlug(deletableClub.getSlug()); + deletableClub = repo.getClubBySlug(deletableClub.getSlug()).orElse(null); - Club found = repo.getClubById(deletableClub.getId()); + Club found = repo.getClubById(deletableClub.getId()).orElse(null); assertNotNull(found); assertEquals(deletableClub.getId(), found.getId()); assertEquals(deletableClub.getName(), found.getName()); @@ -116,8 +116,7 @@ void testDeleteClubById() { boolean deleted = repo.deleteClubById(deletableClub.getId()); assertTrue(deleted); - Club deletedFetched = repo.getClubById(deletableClub.getId()); - assertNull(deletedFetched); + assertTrue(repo.getClubById(deletableClub.getId()).isEmpty()); } @Test @@ -134,9 +133,9 @@ void testDeleteClubBySlug() { .build(); repo.createClub(deletableBySlugClub); - deletableBySlugClub = repo.getClubBySlug(deletableBySlugClub.getSlug()); + deletableBySlugClub = repo.getClubBySlug(deletableBySlugClub.getSlug()).orElse(null); - Club found = repo.getClubBySlug(deletableBySlugClub.getSlug()); + Club found = repo.getClubBySlug(deletableBySlugClub.getSlug()).orElse(null); assertNotNull(found); assertEquals(deletableBySlugClub.getSlug(), found.getSlug()); assertEquals(deletableBySlugClub.getName(), found.getName()); @@ -144,7 +143,6 @@ void testDeleteClubBySlug() { boolean deleted = repo.deleteClubBySlug(deletableBySlugClub.getSlug()); assertTrue(deleted); - Club deletedFetched = repo.getClubBySlug(deletableBySlugClub.getSlug()); - assertNull(deletedFetched); + assertTrue(repo.getClubBySlug(deletableBySlugClub.getSlug()).isEmpty()); } } diff --git a/src/test/java/org/patinanetwork/codebloom/common/db/repos/job/JobRepositoryTest.java b/src/test/java/org/patinanetwork/codebloom/common/db/repos/job/JobRepositoryTest.java index 55d21bf2a..09d4c56cd 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/db/repos/job/JobRepositoryTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/db/repos/job/JobRepositoryTest.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.fail; import java.util.List; +import java.util.Optional; import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.AfterAll; @@ -65,7 +66,7 @@ void cleanup() { @Test @Order(1) void testFindJobById() { - Job foundJob = jobRepository.findJobById(testJob.getId()); + Job foundJob = jobRepository.findJobById(testJob.getId()).orElse(null); assertNotNull(foundJob); assertEquals(testJob, foundJob); } @@ -81,19 +82,20 @@ void testFindIncompleteJobs() { @Test @Order(3) void testUpdateJob() { - testJob.setProcessedAt(StandardizedOffsetDateTime.now()); - testJob.setCompletedAt(StandardizedOffsetDateTime.now().plusMinutes(5)); + testJob.setProcessedAt(Optional.ofNullable(StandardizedOffsetDateTime.now())); + testJob.setCompletedAt( + Optional.ofNullable(StandardizedOffsetDateTime.now().plusMinutes(5))); testJob.setNextAttemptAt(StandardizedOffsetDateTime.now().plusMinutes(30)); testJob.setStatus(JobStatus.COMPLETE); boolean updateResult = jobRepository.updateJob(testJob); assertTrue(updateResult); - Job updatedJob = jobRepository.findJobById(testJob.getId()); + Job updatedJob = jobRepository.findJobById(testJob.getId()).orElse(null); assertNotNull(updatedJob); assertEquals(JobStatus.COMPLETE, updatedJob.getStatus()); - assertNotNull(updatedJob.getProcessedAt()); - assertNotNull(updatedJob.getCompletedAt()); + assertTrue(updatedJob.getProcessedAt().isPresent()); + assertTrue(updatedJob.getCompletedAt().isPresent()); assertNotNull(updatedJob.getNextAttemptAt()); } diff --git a/src/test/java/org/patinanetwork/codebloom/common/db/repos/leaderboard/LeaderboardRepositoryRankTest.java b/src/test/java/org/patinanetwork/codebloom/common/db/repos/leaderboard/LeaderboardRepositoryRankTest.java index c90d906af..1b6377e81 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/db/repos/leaderboard/LeaderboardRepositoryRankTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/db/repos/leaderboard/LeaderboardRepositoryRankTest.java @@ -119,8 +119,8 @@ void assertTiedScoresRankingCorrectly() { } else { // Rule 2: For tied scores, check zero-score leetcode username logic if (current.getTotalScore() == 0) { - boolean currentHasLeetcode = current.getLeetcodeUsername() != null; - boolean nextHasLeetcode = next.getLeetcodeUsername() != null; + boolean currentHasLeetcode = current.getLeetcodeUsername().isPresent(); + boolean nextHasLeetcode = next.getLeetcodeUsername().isPresent(); if (currentHasLeetcode != nextHasLeetcode) { assertTrue( diff --git a/src/test/java/org/patinanetwork/codebloom/common/db/repos/leaderboard/LeaderboardRepositoryTest.java b/src/test/java/org/patinanetwork/codebloom/common/db/repos/leaderboard/LeaderboardRepositoryTest.java index 3545d6aae..8717ea3c4 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/db/repos/leaderboard/LeaderboardRepositoryTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/db/repos/leaderboard/LeaderboardRepositoryTest.java @@ -238,10 +238,12 @@ void testGivingPointsToSuperUserOnMockLeaderboard() { assertTrue(isUserUpdatePointsSuccessful, "failed to update points of super user on mock leaderboard"); // TODO - Write tests for pointOfTime - UserWithScore superUser = userRepository.getUserWithScoreByIdAndLeaderboardId( - superUserId, - mockLeaderboard.getId(), - UserFilterOptions.builder().build()); + UserWithScore superUser = userRepository + .getUserWithScoreByIdAndLeaderboardId( + superUserId, + mockLeaderboard.getId(), + UserFilterOptions.builder().build()) + .orElse(null); assertTrue(superUser != null); assertEquals(newPoints, superUser.getTotalScore()); diff --git a/src/test/java/org/patinanetwork/codebloom/common/db/repos/user/UserMetricsRepositoryTest.java b/src/test/java/org/patinanetwork/codebloom/common/db/repos/user/UserMetricsRepositoryTest.java index 8f971aad9..1331d897a 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/db/repos/user/UserMetricsRepositoryTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/db/repos/user/UserMetricsRepositoryTest.java @@ -54,11 +54,11 @@ void setUp() { testUser = User.builder() .discordId("test-metrics-" + System.currentTimeMillis()) .discordName("TestMetricsUser") - .leetcodeUsername("testmetricsuser") - .nickname("TestMetricsNickname") + .leetcodeUsername(Optional.of("testmetricsuser")) + .nickname(Optional.of("TestMetricsNickname")) .admin(false) - .schoolEmail("testmetrics@example.com") - .profileUrl("") + .schoolEmail(Optional.of("testmetrics@example.com")) + .profileUrl(Optional.of("")) .tags(new ArrayList<>()) .build(); userRepository.createUser(testUser); diff --git a/src/test/java/org/patinanetwork/codebloom/common/db/repos/user/UserRepositoryTest.java b/src/test/java/org/patinanetwork/codebloom/common/db/repos/user/UserRepositoryTest.java index e59e3194e..6b751d41f 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/db/repos/user/UserRepositoryTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/db/repos/user/UserRepositoryTest.java @@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.fail; import java.util.ArrayList; +import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -41,11 +42,11 @@ void setUp() { testUser = User.builder() .discordId(uniqueDiscordId) .discordName("TestUser") - .leetcodeUsername("testuser") - .nickname("TestNickname") + .leetcodeUsername(Optional.of("testuser")) + .nickname(Optional.of("TestNickname")) .admin(false) - .schoolEmail("test@example.com") - .profileUrl("") + .schoolEmail(Optional.of("test@example.com")) + .profileUrl(Optional.of("")) .tags(new ArrayList<>()) .build(); @@ -63,7 +64,7 @@ void cleanUp() { @Test @Order(1) void testGetId() { - User found = userRepository.getUserById(testUser.getId()); + User found = userRepository.getUserById(testUser.getId()).orElse(null); assertNotNull(found); assertEquals(testUser, found); } @@ -71,7 +72,7 @@ void testGetId() { @Test @Order(2) void testGetUserByDiscordId() { - User found = userRepository.getUserByDiscordId(testUser.getDiscordId()); + User found = userRepository.getUserByDiscordId(testUser.getDiscordId()).orElse(null); assertNotNull(found); assertEquals(testUser, found); } @@ -79,7 +80,9 @@ void testGetUserByDiscordId() { @Test @Order(3) void testGetUserByLeetcodeUsername() { - User found = userRepository.getUserByLeetcodeUsername(testUser.getLeetcodeUsername()); + User found = userRepository + .getUserByLeetcodeUsername(testUser.getLeetcodeUsername().orElse(null)) + .orElse(null); assertNotNull(found); assertEquals(testUser, found); } @@ -88,14 +91,14 @@ void testGetUserByLeetcodeUsername() { @Order(4) void testUpdateUser() { String newNickname = "Updated Nickname"; - testUser.setNickname(newNickname); + testUser.setNickname(Optional.ofNullable(newNickname)); boolean updateResult = userRepository.updateUser(testUser); assertTrue(updateResult); - User updatedUser = userRepository.getUserById(testUser.getId()); + User updatedUser = userRepository.getUserById(testUser.getId()).orElse(null); assertNotNull(updatedUser); - assertEquals(newNickname, updatedUser.getNickname()); + assertEquals(newNickname, updatedUser.getNickname().orElse(null)); } @Test @@ -134,7 +137,8 @@ void testGetAllUsersWithPagination() { @Test @Order(9) void testUserExistsByLeetcodeUsername() { - boolean exists = userRepository.userExistsByLeetcodeUsername(testUser.getLeetcodeUsername()); + boolean exists = userRepository.userExistsByLeetcodeUsername( + testUser.getLeetcodeUsername().orElse(null)); assertTrue(exists); } } diff --git a/src/test/java/org/patinanetwork/codebloom/common/db/repos/weekly/WeeklyMessageRepositoryTest.java b/src/test/java/org/patinanetwork/codebloom/common/db/repos/weekly/WeeklyMessageRepositoryTest.java index 38e7bd630..a50c856d0 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/db/repos/weekly/WeeklyMessageRepositoryTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/db/repos/weekly/WeeklyMessageRepositoryTest.java @@ -47,7 +47,7 @@ void createTestWeeklyMessage() { @AfterAll void cleanUp() { - WeeklyMessage latest = weeklyMessageRepository.getLatestWeeklyMessage(); + WeeklyMessage latest = weeklyMessageRepository.getLatestWeeklyMessage().orElse(null); boolean isSuccessful = latest != null && weeklyMessageRepository.deleteWeeklyMessageById(latest.getId()); if (!isSuccessful) { @@ -58,7 +58,8 @@ void cleanUp() { @Test @Order(1) void testGetLatestWeeklyMessage() { - WeeklyMessage possibleTestWeeklyMessage = weeklyMessageRepository.getLatestWeeklyMessage(); + WeeklyMessage possibleTestWeeklyMessage = + weeklyMessageRepository.getLatestWeeklyMessage().orElse(null); assertNotNull(possibleTestWeeklyMessage, "Latest weekly message should not be null"); log.info("testWeeklyMessage: {}", testWeeklyMessage); @@ -73,8 +74,9 @@ void testGetLatestWeeklyMessage() { @Test @Order(2) void findWeeklyMessageById() { - WeeklyMessage latest = weeklyMessageRepository.getLatestWeeklyMessage(); - WeeklyMessage possibleTestWeeklyMessage = weeklyMessageRepository.getWeeklyMessageById(latest.getId()); + WeeklyMessage latest = weeklyMessageRepository.getLatestWeeklyMessage().orElse(null); + WeeklyMessage possibleTestWeeklyMessage = + weeklyMessageRepository.getWeeklyMessageById(latest.getId()).orElse(null); log.info("testWeeklyMessage: {}", testWeeklyMessage.toString()); log.info( diff --git a/src/test/java/org/patinanetwork/codebloom/common/security/ProtectorTest.java b/src/test/java/org/patinanetwork/codebloom/common/security/ProtectorTest.java index 1866625ff..d16c56923 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/security/ProtectorTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/security/ProtectorTest.java @@ -111,7 +111,7 @@ void rejectsMissingUser() { when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("session_token", "session-1")}); when(sessionRepository.getSessionById("session-1")).thenReturn(Optional.of(validSession)); - when(userRepository.getUserById("missing-user")).thenReturn(null); + when(userRepository.getUserById("missing-user")).thenReturn(Optional.empty()); ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> protector.validateSession(request)); @@ -138,7 +138,7 @@ void returnsAuthForValidSession() { when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("session_token", "session-2")}); when(sessionRepository.getSessionById("session-2")).thenReturn(Optional.of(validSession)); - when(userRepository.getUserById("user-2")).thenReturn(user); + when(userRepository.getUserById("user-2")).thenReturn(Optional.of(user)); AuthenticationObject auth = protector.validateSession(request); @@ -165,7 +165,7 @@ void rejectsNonAdmin() { when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("session_token", "session-3")}); when(sessionRepository.getSessionById("session-3")).thenReturn(Optional.of(validSession)); - when(userRepository.getUserById("user-3")).thenReturn(nonAdminUser); + when(userRepository.getUserById("user-3")).thenReturn(Optional.of(nonAdminUser)); ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> protector.validateAdminSession(request)); @@ -192,7 +192,7 @@ void returnsAuthForAdmin() { when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("session_token", "session-4")}); when(sessionRepository.getSessionById("session-4")).thenReturn(Optional.of(validSession)); - when(userRepository.getUserById("user-4")).thenReturn(adminUser); + when(userRepository.getUserById("user-4")).thenReturn(Optional.of(adminUser)); AuthenticationObject auth = protector.validateAdminSession(request); diff --git a/src/test/java/org/patinanetwork/codebloom/common/submissions/SubmissionsHandlerTest.java b/src/test/java/org/patinanetwork/codebloom/common/submissions/SubmissionsHandlerTest.java index 24445154e..0fe1d4947 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/submissions/SubmissionsHandlerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/submissions/SubmissionsHandlerTest.java @@ -65,7 +65,7 @@ class SubmissionsHandlerTest { .id(USER_ID) .discordId("d123") .discordName("tester") - .leetcodeUsername("leet_tester") + .leetcodeUsername(Optional.of("leet_tester")) .build(); private final Leaderboard leaderboard = Leaderboard.builder() @@ -102,12 +102,12 @@ void commonStubs() { }); when(userRepository.getUserWithScoreByIdAndLeaderboardId(eq(USER_ID), eq(LEADERBOARD_ID), any())) - .thenReturn(UserWithScore.builder() + .thenReturn(Optional.of(UserWithScore.builder() .id(USER_ID) .discordId("d123") .discordName("tester") .totalScore(0) - .build()); + .build())); } @Test diff --git a/src/test/java/org/patinanetwork/codebloom/config/TestProtector.java b/src/test/java/org/patinanetwork/codebloom/config/TestProtector.java index ec9d2b28c..8d1d71f0e 100644 --- a/src/test/java/org/patinanetwork/codebloom/config/TestProtector.java +++ b/src/test/java/org/patinanetwork/codebloom/config/TestProtector.java @@ -37,7 +37,9 @@ public Protector protector() { return new Protector(sesssionRepository, userRepository) { @Override public AuthenticationObject validateSession(final HttpServletRequest request) { - User mockAdminUser = userRepository.getUserById("ed3bfe18-e42a-467f-b4fa-07e8da4d2555"); + User mockAdminUser = userRepository + .getUserById("ed3bfe18-e42a-467f-b4fa-07e8da4d2555") + .orElse(null); Optional mockAdminSessionOp = sesssionRepository.getSessionById("d99e10a2-6285-46f0-8150-ba4727b520f4"); diff --git a/src/test/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealerTest.java b/src/test/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealerTest.java index 20ae890b3..2c8456f13 100644 --- a/src/test/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealerTest.java @@ -81,10 +81,10 @@ private void playwrightClientResolvesSlowly(Auth authToReturn) { void testReadLockConcurrentAccessSameThreadPoolAuthRepo() throws InterruptedException { Auth mockAuth = Auth.builder() .token("test-token") - .csrf("test-csrf") + .csrf(Optional.of("test-csrf")) .createdAt(StandardizedOffsetDateTime.now()) .build(); - when(authRepository.getMostRecentAuth()).thenReturn(mockAuth); + when(authRepository.getMostRecentAuth()).thenReturn(Optional.of(mockAuth)); leetcodeAuthStealer.stealAuthCookie(); @@ -129,7 +129,7 @@ void testReadLockConcurrentAccessSameThreadPoolAuthRepo() throws InterruptedExce @DisplayName( "Verifies multiple threads can acquire read locks concurrently across the same thread pool - New cookie fetched") void testReadLockConcurrentAccessSameThreadPoolNewCookieFetched() throws InterruptedException { - when(authRepository.getMostRecentAuth()).thenReturn(null); + when(authRepository.getMostRecentAuth()).thenReturn(Optional.empty()); doReturn("cookie").when(leetcodeAuthStealer).stealCookieImpl(); doReturn("cookie").when(leetcodeAuthStealer).getCookie(); @@ -176,10 +176,10 @@ void testReadLockConcurrentAccessSameThreadPoolNewCookieFetched() throws Interru void testReadLockConcurrentAccessDifferentThreadPoolsAuthRepository() throws InterruptedException { Auth mockAuth = Auth.builder() .token("test-token") - .csrf("test-csrf") + .csrf(Optional.of("test-csrf")) .createdAt(StandardizedOffsetDateTime.now()) .build(); - when(authRepository.getMostRecentAuth()).thenReturn(mockAuth); + when(authRepository.getMostRecentAuth()).thenReturn(Optional.of(mockAuth)); leetcodeAuthStealer.stealAuthCookie(); @@ -246,7 +246,7 @@ void testReadLockConcurrentAccessDifferentThreadPoolsAuthRepository() throws Int @DisplayName( "Verifies multiple threads can acquire read locks concurrently across different thread pools - New cookie fetched") void testReadLockConcurrentAccessDifferentThreadPoolsFetchedNewCookie() throws InterruptedException { - when(authRepository.getMostRecentAuth()).thenReturn(null); + when(authRepository.getMostRecentAuth()).thenReturn(Optional.empty()); doReturn("string").when(leetcodeAuthStealer).stealCookieImpl(); doReturn("string").when(leetcodeAuthStealer).getCookie(); @@ -314,7 +314,7 @@ void testReadLockConcurrentAccessDifferentThreadPoolsFetchedNewCookie() throws I @Timeout(15) @DisplayName("Verifies that only one thread can try to steal cookie at a time across the same thread pool") void testWriteLockExclusiveAccessSameThreadPool() throws InterruptedException { - when(authRepository.getMostRecentAuth()).thenReturn(null); + when(authRepository.getMostRecentAuth()).thenReturn(Optional.empty()); ExecutorService pool = Executors.newFixedThreadPool(3); CountDownLatch startLatch = new CountDownLatch(1); @@ -363,7 +363,7 @@ void testWriteLockExclusiveAccessSameThreadPool() throws InterruptedException { @Timeout(15) @DisplayName("Verifies that only one thread can try to steal cookie at a time across different thread pools") void testWriteLockExclusiveAccessDifferentThreadPools() throws InterruptedException { - when(authRepository.getMostRecentAuth()).thenReturn(null); + when(authRepository.getMostRecentAuth()).thenReturn(Optional.empty()); ExecutorService pool1 = Executors.newFixedThreadPool(2); ExecutorService pool2 = Executors.newFixedThreadPool(2); @@ -415,7 +415,7 @@ void testWriteLockExclusiveAccessDifferentThreadPools() throws InterruptedExcept @DisplayName( "Verifies that no read operations can happen when stealing authentication cookie in the same thread pool") void testReadWriteLockInteractionSameThreadPool() throws InterruptedException { - when(authRepository.getMostRecentAuth()).thenReturn(null); + when(authRepository.getMostRecentAuth()).thenReturn(Optional.empty()); ExecutorService pool = Executors.newFixedThreadPool(4); CountDownLatch startLatch = new CountDownLatch(1); @@ -463,7 +463,7 @@ void testReadWriteLockInteractionSameThreadPool() throws InterruptedException { @DisplayName( "Verifies that no read operations can happen when stealing authentication cookie in different thread pools") void testReadWriteLockInteractionDifferentThreadPools() throws InterruptedException { - when(authRepository.getMostRecentAuth()).thenReturn(null); + when(authRepository.getMostRecentAuth()).thenReturn(Optional.empty()); ExecutorService writePool = Executors.newFixedThreadPool(1); ExecutorService readPool = Executors.newFixedThreadPool(3); diff --git a/src/test/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessServiceTest.java b/src/test/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessServiceTest.java index 3416b8f90..9d2527471 100644 --- a/src/test/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessServiceTest.java +++ b/src/test/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessServiceTest.java @@ -172,7 +172,7 @@ void jobStatusTransitionValid() { boolean updateSuccess = jobRepository.updateJob(processingJob); assertTrue(updateSuccess); - Job updatedJob = jobRepository.findJobById(processingJob.getId()); + Job updatedJob = jobRepository.findJobById(processingJob.getId()).orElse(null); assertTrue(updatedJob != null); assertEquals(JobStatus.PROCESSING, updatedJob.getStatus()); diff --git a/src/test/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessServiceUnitTest.java b/src/test/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessServiceUnitTest.java index 8e7c34071..d5acb72c0 100644 --- a/src/test/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessServiceUnitTest.java +++ b/src/test/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessServiceUnitTest.java @@ -120,7 +120,7 @@ void repairsExistingSubmissionWithoutLosingOtherFields(String missingDescription assertEquals(Optional.of("existing code"), question.getCode()); assertEquals(Optional.of("3 ms"), question.getRuntime()); assertEquals(JobStatus.COMPLETE, job.getStatus()); - assertNotNull(job.getCompletedAt()); + assertTrue(job.getCompletedAt().isPresent()); verify(questions).updateQuestion(question); } @@ -133,7 +133,7 @@ void retriesWhenLeetcodeHasNoDescription(String content) { runQueue(); assertEquals(JobStatus.INCOMPLETE, job.getStatus()); assertTrue(job.getNextAttemptAt().isAfter(before.plusMinutes(29))); - assertNull(job.getCompletedAt()); + assertTrue(job.getCompletedAt().isEmpty()); assertEquals(Optional.empty(), question.getDescription()); verify(questions, never()).updateQuestion(any()); } @@ -152,8 +152,8 @@ void failedFetchLeavesJobRetryable() { when(client.findQuestionBySlug("two-sum")).thenThrow(new RuntimeException("LeetCode unavailable")); runQueue(); assertEquals(JobStatus.INCOMPLETE, job.getStatus()); - assertNull(job.getProcessedAt()); - assertNull(job.getCompletedAt()); + assertTrue(job.getProcessedAt().isEmpty()); + assertTrue(job.getCompletedAt().isEmpty()); verify(questions, never()).updateQuestion(any()); } @@ -208,7 +208,7 @@ void failedSaveDoesNotMarkJobComplete() { when(questions.updateQuestion(question)).thenThrow(new RuntimeException("Database unavailable")); runQueue(); assertEquals(JobStatus.INCOMPLETE, job.getStatus()); - assertNull(job.getCompletedAt()); + assertTrue(job.getCompletedAt().isEmpty()); } @Test From a2a40436923ecd3a39cba1de2a7bc8a43ff99eed Mon Sep 17 00:00:00 2001 From: Nancy Huang <205217630+naanci@users.noreply.github.com> Date: Sat, 10 Oct 2026 22:19:27 -0400 Subject: [PATCH 2/4] TAN-1023: fix race condition in test --- .../leetcode/LeetcodeQuestionProcessServiceTest.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessServiceTest.java b/src/test/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessServiceTest.java index 9d2527471..f5951ea15 100644 --- a/src/test/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessServiceTest.java +++ b/src/test/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessServiceTest.java @@ -9,8 +9,11 @@ import java.util.Optional; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; import org.patinanetwork.codebloom.common.db.models.job.Job; import org.patinanetwork.codebloom.common.db.models.job.JobStatus; import org.patinanetwork.codebloom.common.db.models.question.Question; @@ -29,6 +32,7 @@ @SpringBootTest(properties = {"codebloom.scheduling.enabled=false", "codebloom.notify.enabled=false"}) @ActiveProfiles({"ci", "thread"}) @TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestMethodOrder(OrderAnnotation.class) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class LeetcodeQuestionProcessServiceTest extends NoJdaRequired { @@ -99,11 +103,13 @@ void cleanup() { } @Test + @Order(1) void serviceValid() { assertNotNull(service); } @Test + @Order(2) void findIncompleteJobsValid() { Question tempQuestion = Question.builder() .userId(mockUserId) @@ -139,6 +145,7 @@ void findIncompleteJobsValid() { } @Test + @Order(3) void jobStatusTransitionValid() { Question tempQuestion = Question.builder() .userId(mockUserId) @@ -181,10 +188,12 @@ void jobStatusTransitionValid() { } @Test + @Order(4) void drainQueueValid() { service.drainQueue().join(); assertEquals( - JobStatus.COMPLETE, jobRepository.findJobById(testJob.getId()).getStatus()); + JobStatus.COMPLETE, + jobRepository.findJobById(testJob.getId()).orElseThrow().getStatus()); } // TODO: (TAN-32) re-enable From 6759de9b2640a973c6351b33977b0381a309d444 Mon Sep 17 00:00:00 2001 From: Nancy Huang <205217630+naanci@users.noreply.github.com> Date: Sat, 10 Oct 2026 23:07:43 -0400 Subject: [PATCH 3/4] TAN-1023: handle executor shutdown load and isolate background processing in tests --- .../VirtualThreadExecutorServiceConfig.java | 2 +- .../common/utils/lock/QueueLockTest.java | 16 ++++++++++++++++ .../LeetcodeQuestionProcessServiceTest.java | 3 ++- .../scheduled/pg/NotifyListenerTest.java | 2 +- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/patinanetwork/codebloom/utilities/VirtualThreadExecutorServiceConfig.java b/src/main/java/org/patinanetwork/codebloom/utilities/VirtualThreadExecutorServiceConfig.java index 812694835..8929efa4e 100644 --- a/src/main/java/org/patinanetwork/codebloom/utilities/VirtualThreadExecutorServiceConfig.java +++ b/src/main/java/org/patinanetwork/codebloom/utilities/VirtualThreadExecutorServiceConfig.java @@ -8,7 +8,7 @@ @Configuration public class VirtualThreadExecutorServiceConfig { - @Bean + @Bean(destroyMethod = "shutdownNow") public ExecutorService virtualThreadExecutorService() { return Executors.newVirtualThreadPerTaskExecutor(); } diff --git a/src/test/java/org/patinanetwork/codebloom/common/utils/lock/QueueLockTest.java b/src/test/java/org/patinanetwork/codebloom/common/utils/lock/QueueLockTest.java index 0ae118ae0..94c90389f 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/utils/lock/QueueLockTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/utils/lock/QueueLockTest.java @@ -14,6 +14,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import org.patinanetwork.codebloom.utilities.VirtualThreadExecutorServiceConfig; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class QueueLockTest { private final BlockingBucket bucket = mock(BlockingBucket.class); @@ -31,6 +33,20 @@ void tearDown() { executor.shutdownNow(); } + @Test + @Timeout(value = 5, unit = TimeUnit.SECONDS) + void contextShutdownShouldStopWaitingTicker() throws InterruptedException { + ExecutorService managedExecutor; + + try (var context = new AnnotationConfigApplicationContext(VirtualThreadExecutorServiceConfig.class)) { + managedExecutor = context.getBean(ExecutorService.class); + new QueueLock(bucket, managedExecutor); + } + + assertTrue(managedExecutor.isShutdown()); + assertTrue(managedExecutor.awaitTermination(2, TimeUnit.SECONDS)); + } + @Test @Timeout(value = 5, unit = TimeUnit.SECONDS) void acquireShouldAppendToEndOfQueue() throws InterruptedException { diff --git a/src/test/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessServiceTest.java b/src/test/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessServiceTest.java index f5951ea15..105efc0b1 100644 --- a/src/test/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessServiceTest.java +++ b/src/test/java/org/patinanetwork/codebloom/scheduled/leetcode/LeetcodeQuestionProcessServiceTest.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Optional; +import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; @@ -190,7 +191,7 @@ void jobStatusTransitionValid() { @Test @Order(4) void drainQueueValid() { - service.drainQueue().join(); + service.drainQueue().orTimeout(30, TimeUnit.SECONDS).join(); assertEquals( JobStatus.COMPLETE, jobRepository.findJobById(testJob.getId()).orElseThrow().getStatus()); diff --git a/src/test/java/org/patinanetwork/codebloom/scheduled/pg/NotifyListenerTest.java b/src/test/java/org/patinanetwork/codebloom/scheduled/pg/NotifyListenerTest.java index 31d42f3e4..0d4090aef 100644 --- a/src/test/java/org/patinanetwork/codebloom/scheduled/pg/NotifyListenerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/scheduled/pg/NotifyListenerTest.java @@ -24,7 +24,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; -@SpringBootTest +@SpringBootTest(properties = "codebloom.scheduling.enabled=false") @ActiveProfiles({"ci", "thread"}) @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Slf4j From 5171996ec5f9f3285f1e528d20806bc8439d7a67 Mon Sep 17 00:00:00 2001 From: Nancy Huang <205217630+naanci@users.noreply.github.com> Date: Sat, 10 Oct 2026 23:38:14 -0400 Subject: [PATCH 4/4] TAN-1023: sonar errors --- .../leaderboard/LeaderboardController.java | 9 +-- .../db/repos/user/UserSqlRepository.java | 48 +++++++++------- .../scheduled/discord/WeeklyLeaderboard.java | 3 +- .../LeaderboardControllerTest.java | 14 +++++ .../components/LeaderboardManagerTest.java | 56 +++++++++++++++++++ 5 files changed, 101 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/patinanetwork/codebloom/api/leaderboard/LeaderboardController.java b/src/main/java/org/patinanetwork/codebloom/api/leaderboard/LeaderboardController.java index 48e80f15a..3223a4393 100644 --- a/src/main/java/org/patinanetwork/codebloom/api/leaderboard/LeaderboardController.java +++ b/src/main/java/org/patinanetwork/codebloom/api/leaderboard/LeaderboardController.java @@ -292,13 +292,8 @@ public ResponseEntity> getUserCurrentLeaderboardF userId, leaderboardData.get().getId(), UserFilterOptions.builder().build()) - .orElse(null); - - // if (user == null) { - // return - // ResponseEntity.status(HttpStatus.NOT_FOUND).body(ApiResponder.failure("This - // user does not exist on this leaderboard.")); - // } + .orElseThrow(() -> + new ResponseStatusException(HttpStatus.NOT_FOUND, "User does not exist on this leaderboard.")); return ResponseEntity.ok().body(ApiResponder.success("User found!", UserWithScoreDto.fromUserWithScore(user))); } diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/user/UserSqlRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/user/UserSqlRepository.java index 2515501af..441edb6e3 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/user/UserSqlRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/user/UserSqlRepository.java @@ -20,6 +20,12 @@ @Timed(value = "repository.execution") public class UserSqlRepository implements UserRepository { + private static final String LEETCODE_USERNAME = "leetcodeUsername"; + private static final String NICKNAME = "nickname"; + private static final String SCHOOL_EMAIL = "schoolEmail"; + private static final String PROFILE_URL = "profileUrl"; + private static final String CREATED_AT = "createdAt"; + private final JdbcClient jdbcClient; private final UserTagRepository userTagRepository; private final AchievementRepository achievementRepository; @@ -41,14 +47,14 @@ public UserSqlRepository( .id(id) .discordId(rs.getString("discordId")) .discordName(rs.getString("discordName")) - .leetcodeUsername(Optional.ofNullable(rs.getString("leetcodeUsername"))) - .nickname(Optional.ofNullable(rs.getString("nickname"))) + .leetcodeUsername(Optional.ofNullable(rs.getString(LEETCODE_USERNAME))) + .nickname(Optional.ofNullable(rs.getString(NICKNAME))) .verifyKey(rs.getString("verifyKey")) .admin(rs.getBoolean("admin")) - .schoolEmail(Optional.ofNullable(rs.getString("schoolEmail"))) - .profileUrl(Optional.ofNullable(rs.getString("profileUrl"))) + .schoolEmail(Optional.ofNullable(rs.getString(SCHOOL_EMAIL))) + .profileUrl(Optional.ofNullable(rs.getString(PROFILE_URL))) .createdAt(Optional.ofNullable( - StandardizedOffsetDateTime.normalize(rs.getObject("createdAt", OffsetDateTime.class)))) + StandardizedOffsetDateTime.normalize(rs.getObject(CREATED_AT, OffsetDateTime.class)))) .tags(this.userTagRepository.findTagsByUserId(id)) .achievements(this.achievementRepository.getAchievementsByUserId(id)) .build(); @@ -60,14 +66,14 @@ public UserSqlRepository( .id(id) .discordId(rs.getString("discordId")) .discordName(rs.getString("discordName")) - .leetcodeUsername(Optional.ofNullable(rs.getString("leetcodeUsername"))) - .nickname(Optional.ofNullable(rs.getString("nickname"))) + .leetcodeUsername(Optional.ofNullable(rs.getString(LEETCODE_USERNAME))) + .nickname(Optional.ofNullable(rs.getString(NICKNAME))) .verifyKey(rs.getString("verifyKey")) .admin(rs.getBoolean("admin")) - .schoolEmail(Optional.ofNullable(rs.getString("schoolEmail"))) - .profileUrl(Optional.ofNullable(rs.getString("profileUrl"))) + .schoolEmail(Optional.ofNullable(rs.getString(SCHOOL_EMAIL))) + .profileUrl(Optional.ofNullable(rs.getString(PROFILE_URL))) .createdAt(Optional.ofNullable( - StandardizedOffsetDateTime.normalize(rs.getObject("createdAt", OffsetDateTime.class)))) + StandardizedOffsetDateTime.normalize(rs.getObject(CREATED_AT, OffsetDateTime.class)))) .tags(this.userTagRepository.findTagsByUserId(id)) .achievements(this.achievementRepository.getAchievementsByUserId(id)) .totalScore(rs.getInt("totalScore")) @@ -96,13 +102,13 @@ public void createUser(final User user) { .param("id", UUID.fromString(user.getId())) .param("discordName", user.getDiscordName()) .param("discordId", user.getDiscordId()) - .param("leetcodeUsername", user.getLeetcodeUsername().orElse(null)) - .param("nickname", user.getNickname().orElse(null)) - .param("schoolEmail", user.getSchoolEmail().orElse(null)) + .param(LEETCODE_USERNAME, user.getLeetcodeUsername().orElse(null)) + .param(NICKNAME, user.getNickname().orElse(null)) + .param(SCHOOL_EMAIL, user.getSchoolEmail().orElse(null)) .param("admin", user.isAdmin()) - .param("profileUrl", user.getProfileUrl().orElse(null)) + .param(PROFILE_URL, user.getProfileUrl().orElse(null)) .query((rs, rowNum) -> - Map.entry(rs.getString("verifyKey"), rs.getObject("createdAt", OffsetDateTime.class))) + Map.entry(rs.getString("verifyKey"), rs.getObject(CREATED_AT, OffsetDateTime.class))) .optional() .ifPresent(entry -> { user.setVerifyKey(entry.getKey()); @@ -156,7 +162,7 @@ public Optional getUserByLeetcodeUsername(final String inputLeetcodeUserna return jdbcClient .sql(sql) - .param("leetcodeUsername", inputLeetcodeUsername) + .param(LEETCODE_USERNAME, inputLeetcodeUsername) .query(userRowMapper) .optional(); } @@ -218,11 +224,11 @@ public boolean updateUser(final User inputUser) { .param("id", UUID.fromString(inputUser.getId())) .param("discordName", inputUser.getDiscordName()) .param("discordId", inputUser.getDiscordId()) - .param("leetcodeUsername", inputUser.getLeetcodeUsername().orElse(null)) - .param("nickname", inputUser.getNickname().orElse(null)) + .param(LEETCODE_USERNAME, inputUser.getLeetcodeUsername().orElse(null)) + .param(NICKNAME, inputUser.getNickname().orElse(null)) .param("admin", inputUser.isAdmin()) - .param("profileUrl", inputUser.getProfileUrl().orElse(null)) - .param("schoolEmail", inputUser.getSchoolEmail().orElse(null)) + .param(PROFILE_URL, inputUser.getProfileUrl().orElse(null)) + .param(SCHOOL_EMAIL, inputUser.getSchoolEmail().orElse(null)) .update(); return rowsAffected > 0; @@ -357,7 +363,7 @@ public Optional getUserWithScoreByLeetcodeUsernameAndLeaderboardI return jdbcClient .sql(sql) - .param("leetcodeUsername", userLeetcodeUsername) + .param(LEETCODE_USERNAME, userLeetcodeUsername) .param("leaderboardId", UUID.fromString(leaderboardId)) .query(userWithScoreRowMapper) .optional(); diff --git a/src/main/java/org/patinanetwork/codebloom/scheduled/discord/WeeklyLeaderboard.java b/src/main/java/org/patinanetwork/codebloom/scheduled/discord/WeeklyLeaderboard.java index bee6f2954..bd9ffb591 100644 --- a/src/main/java/org/patinanetwork/codebloom/scheduled/discord/WeeklyLeaderboard.java +++ b/src/main/java/org/patinanetwork/codebloom/scheduled/discord/WeeklyLeaderboard.java @@ -1,6 +1,7 @@ package org.patinanetwork.codebloom.scheduled.discord; import java.time.LocalDateTime; +import java.time.ZoneId; import lombok.extern.slf4j.Slf4j; import org.patinanetwork.codebloom.common.components.DiscordClubManager; import org.patinanetwork.codebloom.common.db.repos.weekly.WeeklyMessageRepository; @@ -25,7 +26,7 @@ public class WeeklyLeaderboard { @Scheduled(initialDelay = 0, fixedDelay = 1000 * 60 * 60) public void sendWeeklyLeaderboard() { var weeklyMessage = weeklyMessageRepository.getLatestWeeklyMessage().filter(message -> !message.getCreatedAt() - .isBefore(LocalDateTime.now().minusDays(7L))); + .isBefore(LocalDateTime.now(ZoneId.systemDefault()).minusDays(7L))); if (weeklyMessage.isPresent()) { log.info("WeeklyLeaderboard skipped."); diff --git a/src/test/java/org/patinanetwork/codebloom/api/leaderboard/LeaderboardControllerTest.java b/src/test/java/org/patinanetwork/codebloom/api/leaderboard/LeaderboardControllerTest.java index be0663e3b..678f4a0ed 100644 --- a/src/test/java/org/patinanetwork/codebloom/api/leaderboard/LeaderboardControllerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/api/leaderboard/LeaderboardControllerTest.java @@ -472,4 +472,18 @@ void capsAllMetadataPageSize() { verify(leaderboardRepository).getAllLeaderboardsShallow(captor.capture()); assertEquals(20, captor.getValue().getPageSize()); } + + @Test + void throws404WhenUserIsMissingFromLeaderboard() { + Leaderboard leaderboard = Leaderboard.builder().id(LEADERBOARD_ID).build(); + when(leaderboardRepository.getRecentLeaderboardMetadata()).thenReturn(Optional.of(leaderboard)); + when(userRepository.getUserWithScoreByIdAndLeaderboardId(eq("missing-user"), eq(LEADERBOARD_ID), any())) + .thenReturn(Optional.empty()); + + ResponseStatusException exception = assertThrows( + ResponseStatusException.class, + () -> leaderboardController.getUserCurrentLeaderboardFull(null, "missing-user")); + + assertEquals(HttpStatus.NOT_FOUND, exception.getStatusCode()); + } } diff --git a/src/test/java/org/patinanetwork/codebloom/common/components/LeaderboardManagerTest.java b/src/test/java/org/patinanetwork/codebloom/common/components/LeaderboardManagerTest.java index deefd75f9..7163acd0c 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/components/LeaderboardManagerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/components/LeaderboardManagerTest.java @@ -921,4 +921,60 @@ void testGetLeaderboardUsersOneTagIsEnabled(LeaderboardFilterOptions opts) { verify(leaderboardRepository, never()).getGlobalRankedIndexedLeaderboardUsersById(eq(leaderboardId), eq(opts)); verify(leaderboardRepository, times(1)).getRankedIndexedLeaderboardUsersById(eq(leaderboardId), eq(opts)); } + + @Test + void refreshSubmissionsRejectsMissingUser() { + when(userRepository.getUserByDiscordId("discord-id")).thenReturn(Optional.empty()); + + LeaderboardException exception = + assertThrows(LeaderboardException.class, () -> leaderboardManager.refreshUserSubmissions("discord-id")); + + assertEquals("Cannot refresh submissions", exception.getTitle()); + verifyNoInteractions(leetcodeClient, submissionsHandler); + } + + @Test + void refreshSubmissionsRejectsMissingLeetcodeUsername() { + var user = UserWithScore.builder().id("user-id").build(); + when(userRepository.getUserByDiscordId("discord-id")).thenReturn(Optional.of(user)); + + LeaderboardException exception = + assertThrows(LeaderboardException.class, () -> leaderboardManager.refreshUserSubmissions("discord-id")); + + assertEquals("Your Discord Account is not linked to a LeetCode username.", exception.getDescription()); + verifyNoInteractions(leetcodeClient, submissionsHandler); + } + + @Test + void refreshSubmissionsProcessesLinkedUsers() throws LeaderboardException { + var user = UserWithScore.builder() + .id("user-id") + .leetcodeUsername(Optional.of("leetcode-user")) + .build(); + when(userRepository.getUserByDiscordId("discord-id")).thenReturn(Optional.of(user)); + when(leetcodeClient.findSubmissionsByUsername("leetcode-user", 20)).thenReturn(List.of()); + + assertSame(user, leaderboardManager.refreshUserSubmissions("discord-id")); + + verify(submissionsHandler).handleSubmissions(List.of(), user, true); + } + + @Test + void refreshSubmissionsReportsLeetcodeFailures() { + var user = UserWithScore.builder() + .id("user-id") + .leetcodeUsername(Optional.of("leetcode-user")) + .build(); + when(userRepository.getUserByDiscordId("discord-id")).thenReturn(Optional.of(user)); + when(leetcodeClient.findSubmissionsByUsername("leetcode-user", 20)) + .thenThrow(new RuntimeException("API unavailable")); + + LeaderboardException exception = + assertThrows(LeaderboardException.class, () -> leaderboardManager.refreshUserSubmissions("discord-id")); + + assertEquals( + "Failed to fetch or process submissions from LeetCode. Please try again later.", + exception.getDescription()); + verifyNoInteractions(submissionsHandler); + } }