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 dc93f59fa..c2f806d38 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 @@ -147,6 +147,8 @@ public DuelData generateDuelData(final String lobbyId) throws DuelException { List lobbyQuestions = lobbyQuestionRepository.findLobbyQuestionsByLobbyId(lobbyId).stream() .map(lq -> questionBankRepository.getQuestionById(lq.getQuestionBankId())) + .filter(Optional::isPresent) + .map(Optional::get) .map(QuestionBankDto::fromQuestionBank) .collect(Collectors.toList()); @@ -186,7 +188,9 @@ public void startDuel(final String playerId, final boolean isAdminOverride) thro lobby.setExpiresAt(Optional.of(StandardizedOffsetDateTime.now().plusMinutes(30))); lobbyRepository.updateLobby(lobby); - QuestionBank randomQuestion = questionBankRepository.getRandomQuestion(); + QuestionBank randomQuestion = questionBankRepository + .getRandomQuestion() + .orElseThrow(() -> new DuelException(HttpStatus.NOT_FOUND, "No questions available.")); LobbyQuestion lobbyQuestion = LobbyQuestion.builder() .lobbyId(lobby.getId()) @@ -281,6 +285,8 @@ public int processSubmissions(User user, Lobby activeLobby) throws DuelException var solvableQuestionTitlesSet = lobbyQuestions.stream() .map(LobbyQuestion::getQuestionBankId) .map(questionBankRepository::getQuestionById) + .filter(Optional::isPresent) + .map(Optional::get) .map(QuestionBank::getQuestionTitle) .collect(Collectors.toSet()); diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/models/question/bank/QuestionBank.java b/src/main/java/org/patinanetwork/codebloom/common/db/models/question/bank/QuestionBank.java index e4f98d8b2..755bea217 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/models/question/bank/QuestionBank.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/models/question/bank/QuestionBank.java @@ -2,14 +2,13 @@ import java.time.OffsetDateTime; import java.util.List; +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.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.question.QuestionDifficulty; import org.patinanetwork.codebloom.common.db.models.question.topic.QuestionTopic; @@ -20,31 +19,23 @@ @ToString public class QuestionBank { - @NotNullColumn private String id; - @NotNullColumn private String questionSlug; - @NotNullColumn private QuestionDifficulty questionDifficulty; - @NotNullColumn private String questionTitle; - @NotNullColumn private int questionNumber; - @NotNullColumn private String questionLink; - @NullColumn - private String description; + @Builder.Default + private Optional description = Optional.empty(); - @NotNullColumn private float acceptanceRate; - @NotNullColumn private OffsetDateTime createdAt; /** Join field, update/create with {@link QuestionTopicRepository} */ diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/question/questionbank/QuestionBankRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/question/questionbank/QuestionBankRepository.java index 429b6b051..a9601f92e 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/question/questionbank/QuestionBankRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/question/questionbank/QuestionBankRepository.java @@ -1,6 +1,7 @@ package org.patinanetwork.codebloom.common.db.repos.question.questionbank; import java.util.List; +import java.util.Optional; import org.patinanetwork.codebloom.common.db.models.question.QuestionDifficulty; import org.patinanetwork.codebloom.common.db.models.question.bank.QuestionBank; import org.patinanetwork.codebloom.common.db.models.question.topic.LeetcodeTopicEnum; @@ -8,9 +9,9 @@ public interface QuestionBankRepository { void createQuestion(QuestionBank question); - QuestionBank getQuestionById(String id); + Optional getQuestionById(String id); - QuestionBank getQuestionBySlug(String slug); + Optional getQuestionBySlug(String slug); /** * @note - The provided object's methods will be overridden with any returned data from the database. @@ -29,7 +30,7 @@ public interface QuestionBankRepository { boolean deleteQuestionById(String id); - QuestionBank getRandomQuestion(); + Optional getRandomQuestion(); List getQuestionsByTopic(LeetcodeTopicEnum topic); diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/question/questionbank/QuestionBankSqlRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/question/questionbank/QuestionBankSqlRepository.java index 65c8078ef..20c003fce 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/question/questionbank/QuestionBankSqlRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/question/questionbank/QuestionBankSqlRepository.java @@ -6,6 +6,7 @@ import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.UUID; import javax.sql.DataSource; import org.patinanetwork.codebloom.common.db.helper.NamedPreparedStatement; @@ -45,7 +46,7 @@ private QuestionBank mapResultSetToQuestion(final ResultSet rs) throws SQLExcept .questionNumber(questionNumber) .questionLink(questionLink) .questionTitle(questionTitle) - .description(description) + .description(Optional.ofNullable(description)) .acceptanceRate(acceptanceRate) .createdAt(createdAt) .topics(questionTopicRepository.findQuestionTopicsByQuestionBankId(questionBankId)) @@ -79,7 +80,7 @@ public void createQuestion(final QuestionBank question) { stmt.setInt("number", question.getQuestionNumber()); stmt.setString("link", question.getQuestionLink()); stmt.setString("title", question.getQuestionTitle()); - stmt.setString("desc", question.getDescription()); + stmt.setString("desc", question.getDescription().orElse(null)); stmt.setObject("ac", question.getAcceptanceRate()); stmt.executeUpdate(); @@ -89,8 +90,7 @@ public void createQuestion(final QuestionBank question) { } @Override - public QuestionBank getQuestionById(final String id) { - QuestionBank question = null; + public Optional getQuestionById(final String id) { String sql = """ SELECT id, @@ -113,20 +113,18 @@ public QuestionBank getQuestionById(final String id) { stmt.setObject("id", UUID.fromString(id)); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { - question = mapResultSetToQuestion(rs); - return question; + return Optional.of(mapResultSetToQuestion(rs)); } } } catch (SQLException e) { throw new RuntimeException("Failed to retrieve question", e); } - return question; + return Optional.empty(); } @Override - public QuestionBank getQuestionBySlug(final String slug) { - QuestionBank question = null; + public Optional getQuestionBySlug(final String slug) { String sql = """ SELECT id, @@ -150,15 +148,14 @@ public QuestionBank getQuestionBySlug(final String slug) { stmt.setObject("questionSlug", slug); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { - question = mapResultSetToQuestion(rs); - return question; + return Optional.of(mapResultSetToQuestion(rs)); } } } catch (SQLException e) { throw new RuntimeException("Failed to retrieve question", e); } - return question; + return Optional.empty(); } @Override @@ -184,7 +181,7 @@ public boolean updateQuestion(final QuestionBank inputQuestion) { stmt.setInt("number", inputQuestion.getQuestionNumber()); stmt.setString("link", inputQuestion.getQuestionLink()); stmt.setString("title", inputQuestion.getQuestionTitle()); - stmt.setString("desc", inputQuestion.getDescription()); + stmt.setString("desc", inputQuestion.getDescription().orElse(null)); stmt.setObject("ac", inputQuestion.getAcceptanceRate()); stmt.setObject("id", UUID.fromString(inputQuestion.getId())); @@ -211,8 +208,7 @@ public boolean deleteQuestionById(final String id) { } @Override - public QuestionBank getRandomQuestion() { - QuestionBank question = null; + public Optional getRandomQuestion() { String sql = """ SELECT id, @@ -234,15 +230,14 @@ ORDER BY RANDOM() NamedPreparedStatement stmt = new NamedPreparedStatement(conn, sql)) { try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { - question = mapResultSetToQuestion(rs); - return question; + return Optional.of(mapResultSetToQuestion(rs)); } } } catch (SQLException e) { throw new RuntimeException("Failed to retrieve random question", e); } - return question; + return Optional.empty(); } @Override diff --git a/src/main/java/org/patinanetwork/codebloom/common/dto/question/QuestionBankDto.java b/src/main/java/org/patinanetwork/codebloom/common/dto/question/QuestionBankDto.java index 20f01ffad..9ef1c56ff 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/dto/question/QuestionBankDto.java +++ b/src/main/java/org/patinanetwork/codebloom/common/dto/question/QuestionBankDto.java @@ -57,7 +57,7 @@ public static QuestionBankDto fromQuestionBank(final QuestionBank questionBank) .questionTitle(questionBank.getQuestionTitle()) .questionNumber(questionBank.getQuestionNumber()) .questionLink(questionBank.getQuestionLink()) - .description(questionBank.getDescription()) + .description(questionBank.getDescription().orElse(null)) .acceptanceRate(questionBank.getAcceptanceRate()) .createdAt(questionBank.getCreatedAt()) .topics( 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 6975b172d..27c7d60e7 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/submissions/SubmissionsHandler.java +++ b/src/main/java/org/patinanetwork/codebloom/common/submissions/SubmissionsHandler.java @@ -116,26 +116,26 @@ public ArrayList handleSubmissions( .map(s -> { String slug = s.getTitleSlug(); - QuestionBank bankQuestion = questionBankRepository.getQuestionBySlug(slug); - - if (bankQuestion == null) { - LeetcodeQuestion question = fast - ? leetcodeClient.findQuestionBySlugFast(slug) - : leetcodeClient.findQuestionBySlug(slug); - - bankQuestion = QuestionBank.builder() - .questionSlug(question.getTitleSlug()) - .questionDifficulty(QuestionDifficulty.valueOf(question.getDifficulty())) - .questionTitle(question.getQuestionTitle()) - .questionNumber(question.getQuestionId()) - .questionLink("https://leetcode.com/problems/" + question.getTitleSlug()) - .description(question.getQuestion()) - .acceptanceRate(question.getAcceptanceRate()) - .topics(question.getTopics().stream() - .map(SubmissionsHandler::topicTagToQuestionTopic) - .toList()) - .build(); - } + QuestionBank bankQuestion = questionBankRepository + .getQuestionBySlug(slug) + .orElseGet(() -> { + LeetcodeQuestion question = fast + ? leetcodeClient.findQuestionBySlugFast(slug) + : leetcodeClient.findQuestionBySlug(slug); + + return QuestionBank.builder() + .questionSlug(question.getTitleSlug()) + .questionDifficulty(QuestionDifficulty.valueOf(question.getDifficulty())) + .questionTitle(question.getQuestionTitle()) + .questionNumber(question.getQuestionId()) + .questionLink("https://leetcode.com/problems/" + question.getTitleSlug()) + .description(Optional.ofNullable(question.getQuestion())) + .acceptanceRate(question.getAcceptanceRate()) + .topics(question.getTopics().stream() + .map(SubmissionsHandler::topicTagToQuestionTopic) + .toList()) + .build(); + }); return Pair.of(slug, bankQuestion); }) @@ -213,7 +213,7 @@ public ArrayList handleSubmissions( .questionNumber(bankQuestion.getQuestionNumber()) .questionLink("https://leetcode.com/problems/" + bankQuestion.getQuestionSlug()) .questionTitle(bankQuestion.getQuestionTitle()) - .description(Optional.ofNullable(bankQuestion.getDescription())) + .description(bankQuestion.getDescription()) .pointsAwarded(Optional.of(points)) .acceptanceRate(bankQuestion.getAcceptanceRate()) .submittedAt(leetcodeSubmission.getTimestamp()) 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 be79ed08d..2b2a9bf5f 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 @@ -931,7 +931,7 @@ void testStartDuelFailsLobbyDoesNotHaveEnoughPlayersButIsAdminUser() { when(lobbyPlayerRepository.findValidLobbyPlayerByPlayerId(eq(userId))).thenReturn(Optional.of(lobbyPlayer)); when(lobbyRepository.findLobbyById(eq(lobbyPlayer.getLobbyId()))).thenReturn(Optional.of(lobby)); when(lobbyRepository.updateLobby(any())).thenReturn(true); - when(questionBankRepository.getRandomQuestion()).thenReturn(questionBank); + when(questionBankRepository.getRandomQuestion()).thenReturn(Optional.of(questionBank)); doNothing().when(lobbyQuestionRepository).createLobbyQuestion(any()); try { @@ -975,7 +975,7 @@ void testStartDuelSuccess() { when(lobbyPlayerRepository.findValidLobbyPlayerByPlayerId(eq(userId))).thenReturn(Optional.of(lobbyPlayer)); when(lobbyRepository.findLobbyById(eq(lobbyPlayer.getLobbyId()))).thenReturn(Optional.of(lobby)); when(lobbyRepository.updateLobby(any())).thenReturn(true); - when(questionBankRepository.getRandomQuestion()).thenReturn(questionBank); + when(questionBankRepository.getRandomQuestion()).thenReturn(Optional.of(questionBank)); doNothing().when(lobbyQuestionRepository).createLobbyQuestion(any()); try { @@ -1142,7 +1142,7 @@ void testStartDuelThrowsNonDuelExceptionFromLobbyQuestionRepository() { when(lobbyPlayerRepository.findValidLobbyPlayerByPlayerId(eq(userId))).thenReturn(Optional.of(lobbyPlayer)); when(lobbyRepository.findLobbyById(eq(lobbyPlayer.getLobbyId()))).thenReturn(Optional.of(lobby)); when(lobbyRepository.updateLobby(any())).thenReturn(true); - when(questionBankRepository.getRandomQuestion()).thenReturn(questionBank); + when(questionBankRepository.getRandomQuestion()).thenReturn(Optional.of(questionBank)); doThrow(new RuntimeException("Simulated db exception")) .when(lobbyQuestionRepository) .createLobbyQuestion(any()); @@ -1399,7 +1399,7 @@ void testProcessSubmissionsSuccessful() { .thenReturn(Optional.of(lobbyPlayer)); when(lobbyQuestionRepository.findLobbyQuestionsByLobbyId(eq(activeLobby.getId()))) .thenReturn(List.of(lobbyQuestion)); - when(questionBankRepository.getQuestionById(eq(questionBank.getId()))).thenReturn(questionBank); + when(questionBankRepository.getQuestionById(eq(questionBank.getId()))).thenReturn(Optional.of(questionBank)); when(throttledLeetcodeClient.findSubmissionsByUsername(eq(user.getLeetcodeUsername()), eq(5))) .thenReturn(List.of(leetcodeSubmission)); when(submissionsHandler.handleSubmissions(any(), eq(user), eq(true))).thenReturn(new ArrayList<>() { diff --git a/src/test/java/org/patinanetwork/codebloom/common/db/repos/question/bank/QuestionBankRepositoryTest.java b/src/test/java/org/patinanetwork/codebloom/common/db/repos/question/bank/QuestionBankRepositoryTest.java index cef43b900..920e0e14c 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/db/repos/question/bank/QuestionBankRepositoryTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/db/repos/question/bank/QuestionBankRepositoryTest.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; @@ -42,7 +43,8 @@ void createQuestion() { .questionNumber(1) .questionLink("https://leetcode.com/problems/two-sum/") .description( - "Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.") + Optional.of( + "Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.")) .acceptanceRate(0.8f) .build(); @@ -62,7 +64,8 @@ void cleanUp() { @Test @Order(1) void testGetQuestionById() { - QuestionBank possibleTestQuestion = questionBankRepository.getQuestionById(testQuestionBank.getId()); + QuestionBank possibleTestQuestion = + questionBankRepository.getQuestionById(testQuestionBank.getId()).orElse(null); assertNotNull(possibleTestQuestion, "Retrieved question should not be null"); assertEquals(testQuestionBank.getId(), possibleTestQuestion.getId(), "Question IDs should match"); @@ -81,8 +84,9 @@ void testGetQuestionById() { @Test @Order(2) void testGetQuestionBySlug() { - QuestionBank possibleTestQuestion = - questionBankRepository.getQuestionBySlug(testQuestionBank.getQuestionSlug()); + QuestionBank possibleTestQuestion = questionBankRepository + .getQuestionBySlug(testQuestionBank.getQuestionSlug()) + .orElse(null); assertNotNull(possibleTestQuestion, "Retrieved question should not be null"); assertEquals(testQuestionBank.getId(), possibleTestQuestion.getId(), "Question IDs should match"); @@ -118,7 +122,8 @@ void testUpdateQuestion() { fail("Failed to update question"); } - testQuestionBank = questionBankRepository.getQuestionById(testQuestionBank.getId()); + testQuestionBank = + questionBankRepository.getQuestionById(testQuestionBank.getId()).orElse(null); assertNotNull(testQuestionBank, "Updated question should not be null"); assertEquals("Updated Two Sum", testQuestionBank.getQuestionTitle(), "Question title should be updated"); } @@ -126,7 +131,7 @@ void testUpdateQuestion() { @Test @Order(4) void testGetRandomQuestion() { - QuestionBank randomQuestion = questionBankRepository.getRandomQuestion(); + QuestionBank randomQuestion = questionBankRepository.getRandomQuestion().orElse(null); assertNotNull(randomQuestion, "Random question should not be null"); assertNotNull(randomQuestion.getId(), "Random question ID should not be null"); 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 98cc90d42..910545a38 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/submissions/SubmissionsHandlerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/submissions/SubmissionsHandlerTest.java @@ -299,7 +299,7 @@ void testNoBank() { .questionTitle(lcQuestion.getQuestionTitle()) .questionNumber(lcQuestion.getQuestionId()) .questionLink("https://leetcode.com/problems/" + lcQuestion.getTitleSlug()) - .description(lcQuestion.getQuestion()) + .description(Optional.ofNullable(lcQuestion.getQuestion())) .acceptanceRate(lcQuestion.getAcceptanceRate()) .topics(lcQuestion.getTopics().stream() .map(t -> QuestionTopic.builder() @@ -309,7 +309,7 @@ void testNoBank() { .toList()) .build(); - when(questionBankRepository.getQuestionBySlug(anyString())).thenReturn(bankQuestion); + when(questionBankRepository.getQuestionBySlug(anyString())).thenReturn(Optional.of(bankQuestion)); ArrayList result = handler.handleSubmissions(List.of(sub), user, true);