Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.wa55death405.quizhub.dto.StandardApiResponse;
import org.wa55death405.quizhub.enums.StandardApiStatus;
import org.wa55death405.quizhub.exceptions.InputValidationException;
Expand All @@ -18,6 +19,12 @@ this class is the controller advice
it is used to handle the exceptions thrown by the controllers
*/

/*
TODO:
separate the exception handlers into different classes
* one for predefined spring or library exceptions
* one for custom exceptions
*/
@ControllerAdvice
public class ControllerExceptionHandler {

Expand All @@ -40,4 +47,9 @@ public ResponseEntity<StandardApiResponse<Void>> handleRateLimitReachedException
public ResponseEntity<StandardApiResponse<Void>> handleFileNotFoundException(FileNotFoundException e) {
return new ResponseEntity<>(new StandardApiResponse<>(StandardApiStatus.FAILURE, "File not found"), HttpStatus.NOT_FOUND);
}

@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<StandardApiResponse<Void>> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
return new ResponseEntity<>(new StandardApiResponse<>(StandardApiStatus.FAILURE, "Invalid value for parameter "+ e.getPropertyName() +". Expected type: "+ e.getRequiredType()), HttpStatus.BAD_REQUEST);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.wa55death405.quizhub.dto.StandardPageList;
import org.wa55death405.quizhub.dto.questionAttempt.QuestionAttemptSubmissionDTO;
import org.wa55death405.quizhub.dto.quiz.QuizCreationDTO;
import org.wa55death405.quizhub.dto.StandardApiResponse;
Expand Down Expand Up @@ -32,11 +33,16 @@ public class QuizController {
/*
this api is used to get search for quizzes
it takes the title of the quiz as a query parameter
along with the page number and the size of the page
and returns a list of quizzes that match the title
*/
@GetMapping("/search")
public ResponseEntity<StandardApiResponse<List<QuizGeneralInfoDTO>>> searchQuizzes(@RequestParam(required = false,defaultValue = "") String title) {
return new ResponseEntity<>(new StandardApiResponse<>(StandardApiStatus.SUCCESS,"Quizzes fetched successfully",quizService.searchQuizzes(title)), HttpStatus.OK);
public ResponseEntity<StandardApiResponse<StandardPageList<QuizGeneralInfoDTO>>> searchQuizzes(
@RequestParam(defaultValue = "") String title,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
return new ResponseEntity<>(new StandardApiResponse<>(StandardApiStatus.SUCCESS,"Quizzes fetched successfully",quizService.searchQuizzes(title, page, size)), HttpStatus.OK);
}

/*
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/org/wa55death405/quizhub/dto/StandardPageList.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.wa55death405.quizhub.dto;

import lombok.Data;

import java.util.List;

/*
* this class is used as standard response for the paginated list
* @param <T> the type of the items in the list
*/
@Data
public class StandardPageList<T>{
private Integer currentPage;
private Integer currentItemsSize;
private Long totalItems;
private List<T> items;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.wa55death405.quizhub.interfaces.services;

import org.wa55death405.quizhub.dto.StandardPageList;
import org.wa55death405.quizhub.dto.questionAttempt.QuestionAttemptSubmissionDTO;
import org.wa55death405.quizhub.dto.quiz.QuizGeneralInfoDTO;
import org.wa55death405.quizhub.dto.quizAttempt.QuizAttemptResultDTO;
Expand All @@ -23,7 +24,7 @@ public interface IQuizService {
* @param title the title to search for
* @return a list of quizzes that match the title
*/
List<QuizGeneralInfoDTO> searchQuizzes(String title);
StandardPageList<QuizGeneralInfoDTO> searchQuizzes(String title, int page, int size);

/*
* create a new quiz
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package org.wa55death405.quizhub.repositories;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.wa55death405.quizhub.entities.Quiz;

import java.util.List;
import java.util.UUID;

public interface QuizRepository extends JpaRepository<Quiz, UUID> {
List<Quiz> findAllByTitleContainingIgnoreCase(String title);
Page<Quiz> findAllByTitleContainingIgnoreCase(String title, Pageable pageable);
}
14 changes: 11 additions & 3 deletions src/main/java/org/wa55death405/quizhub/services/QuizService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
import jakarta.persistence.EntityNotFoundException;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.wa55death405.quizhub.dto.StandardPageList;
import org.wa55death405.quizhub.dto.questionAttempt.QuestionAttemptSubmissionDTO;
import org.wa55death405.quizhub.dto.quiz.QuizCreationDTO;
import org.wa55death405.quizhub.dto.quiz.QuizGeneralInfoDTO;
Expand Down Expand Up @@ -36,9 +39,14 @@ public class QuizService implements IQuizService{
private final EntityManager entityManager;

@Override
public List<QuizGeneralInfoDTO> searchQuizzes(String title) {
List<Quiz> quizzes = quizRepository.findAllByTitleContainingIgnoreCase(title);
return quizzes.stream().map(QuizGeneralInfoDTO::new).toList();
public StandardPageList<QuizGeneralInfoDTO> searchQuizzes(String title, int page, int size) {
Page<Quiz> quizzes = quizRepository.findAllByTitleContainingIgnoreCase(title,PageRequest.of(page, size));
StandardPageList<QuizGeneralInfoDTO> standardPageList = new StandardPageList<>();
standardPageList.setItems(quizzes.map(QuizGeneralInfoDTO::new).toList());
standardPageList.setCurrentPage(quizzes.getNumber());
standardPageList.setCurrentItemsSize(quizzes.getNumberOfElements());
standardPageList.setTotalItems(quizzes.getTotalElements());
return standardPageList;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ void testQuizLifecycle() throws JsonProcessingException {
// Search for the quiz
HashMap<String, String> queryParams = new HashMap<>();
queryParams.put("title", quizCreationDTO.getTitle());
queryParams.put("page", "0");
queryParams.put("size", "1");
given()
.queryParams(queryParams)
.when()
Expand All @@ -92,9 +94,12 @@ void testQuizLifecycle() throws JsonProcessingException {
.body("status", equalTo(StandardApiStatus.SUCCESS.toString()))
.body("message", notNullValue())
.body("data",notNullValue())
.body("data",hasSize(equalTo(1)))
.body("data[0].id", equalTo(quizId.toString()))
.body("data[0].title", equalTo(quizCreationDTO.getTitle()));
.body("data.currentPage",equalTo(0))
.body("data.currentItemsSize",equalTo(1))
.body("data.totalItems",equalTo(1))
.body("data.items",hasSize(equalTo(1)))
.body("data.items[0].id", equalTo(quizId.toString()))
.body("data.items[0].title", equalTo(quizCreationDTO.getTitle()));

// Start an attempt
var startQuizResponse = given()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.wa55death405.quizhub.controllers.QuizController;
import org.wa55death405.quizhub.dto.StandardPageList;
import org.wa55death405.quizhub.dto.quiz.QuizCreationDTO;
import org.wa55death405.quizhub.dto.quiz.QuizGeneralInfoDTO;
import org.wa55death405.quizhub.dto.quizAttempt.QuizAttemptResultDTO;
Expand All @@ -26,6 +27,7 @@
import org.wa55death405.quizhub.utils.FakeDataRandomGeneratorImpl;


import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

Expand Down Expand Up @@ -59,19 +61,26 @@ class QuizControllerTest {
@Test
void searchQuizzes() throws Exception {
// arrange
List<QuizGeneralInfoDTO> expectedQuizzes = new ArrayList<>();
int page = 0;
int size = 10;
long totalItems = 10;
StandardPageList<QuizGeneralInfoDTO> expectedQuizzes = new StandardPageList<>();
expectedQuizzes.setCurrentPage(page);
expectedQuizzes.setTotalItems(totalItems);
expectedQuizzes.setCurrentItemsSize(size);
expectedQuizzes.setItems(new ArrayList<>());

for (int i = 0; i < 10; i++) {
UUID id = UUID.randomUUID();
var quiz = Quiz.builder()
.id(id)
.id(UUID.randomUUID())
.title("Quiz " + i)
.build();
expectedQuizzes.add(new QuizGeneralInfoDTO(quiz));
expectedQuizzes.getItems().add(new QuizGeneralInfoDTO(quiz));
}
when(quizService.searchQuizzes(anyString())).thenReturn(expectedQuizzes);
when(quizService.searchQuizzes(anyString(),anyInt(),anyInt())).thenReturn(expectedQuizzes);

// act and assert
this.mockMvc.perform(get("/api/quiz/search"))
this.mockMvc.perform(get("/api/quiz/search?page={page}&size={size}", page, size))
.andExpect(status().isOk())
.andDo(document(
"search-quizzes",
Expand Down