diff --git a/src/main/java/com/be08/smart_notes/common/DefaultConstants.java b/src/main/java/com/be08/smart_notes/common/DefaultConstants.java new file mode 100644 index 0000000..098b1fe --- /dev/null +++ b/src/main/java/com/be08/smart_notes/common/DefaultConstants.java @@ -0,0 +1,11 @@ +package com.be08.smart_notes.common; + +public class DefaultConstants { + // Pagination + public static final String PAGE_NUMBER = "1"; + public static final String PAGE_SIZE = "6"; + + // Sorting + public static final String SORT_BY = "updatedAt"; + public static final String SORT_ORDER = "desc"; +} diff --git a/src/main/java/com/be08/smart_notes/controller/AttemptController.java b/src/main/java/com/be08/smart_notes/controller/AttemptController.java index 5dc05f4..3b578cc 100644 --- a/src/main/java/com/be08/smart_notes/controller/AttemptController.java +++ b/src/main/java/com/be08/smart_notes/controller/AttemptController.java @@ -1,8 +1,10 @@ package com.be08.smart_notes.controller; +import com.be08.smart_notes.common.DefaultConstants; import com.be08.smart_notes.dto.request.AttemptDetailUpdateRequest; import com.be08.smart_notes.dto.response.ApiResponse; import com.be08.smart_notes.dto.response.AttemptResponse; +import com.be08.smart_notes.dto.response.PageResponse; import com.be08.smart_notes.dto.view.AttemptView; import com.be08.smart_notes.service.AttemptService; import com.fasterxml.jackson.annotation.JsonView; @@ -15,8 +17,6 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; -import java.util.List; - @Controller @RequestMapping("/api/quizzes") @RequiredArgsConstructor @@ -37,8 +37,10 @@ public ResponseEntity createAttempt(@PathVariable int quizId) { @GetMapping("/{quizId}/attempts") @JsonView(AttemptView.Basic.class) - public ResponseEntity getAllAttemptsForQuiz(@PathVariable int quizId) { - List attemptResponseList = attemptService.getAllAttemptsByQuizId(quizId); + public ResponseEntity getAllAttemptsForQuiz(@PathVariable int quizId, + @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_NUMBER) int page, + @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_SIZE) int size) { + PageResponse attemptResponseList = attemptService.getAllAttemptsByQuizId(quizId, page, size); ApiResponse apiResponse = ApiResponse.builder() .message("All attempts for quiz fetched successfully") .data(attemptResponseList) diff --git a/src/main/java/com/be08/smart_notes/controller/DocumentController.java b/src/main/java/com/be08/smart_notes/controller/DocumentController.java index 6e269d9..1dc6096 100644 --- a/src/main/java/com/be08/smart_notes/controller/DocumentController.java +++ b/src/main/java/com/be08/smart_notes/controller/DocumentController.java @@ -1,17 +1,13 @@ package com.be08.smart_notes.controller; -import java.util.List; - +import com.be08.smart_notes.common.DefaultConstants; +import com.be08.smart_notes.dto.response.PageResponse; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import com.be08.smart_notes.dto.response.ApiResponse; import com.be08.smart_notes.model.Document; @@ -25,8 +21,10 @@ public class DocumentController { DocumentService documentService; @GetMapping - public ResponseEntity getAllDocuments() { - List documentList = documentService.getAllDocuments(); + public ResponseEntity getAllDocuments( + @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_NUMBER) int page, + @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_SIZE) int size) { + PageResponse documentList = documentService.getAllDocuments(page, size); ApiResponse apiResponse = ApiResponse.builder() .message("All document fetched successfully") .data(documentList) diff --git a/src/main/java/com/be08/smart_notes/controller/NoteController.java b/src/main/java/com/be08/smart_notes/controller/NoteController.java index de5f08d..7771240 100644 --- a/src/main/java/com/be08/smart_notes/controller/NoteController.java +++ b/src/main/java/com/be08/smart_notes/controller/NoteController.java @@ -1,6 +1,9 @@ package com.be08.smart_notes.controller; +import com.be08.smart_notes.common.DefaultConstants; +import com.be08.smart_notes.dto.filter.BasicFilterDTO; import com.be08.smart_notes.dto.response.NoteResponse; +import com.be08.smart_notes.dto.response.PageResponse; import jakarta.validation.Valid; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; @@ -13,8 +16,6 @@ import com.be08.smart_notes.dto.response.ApiResponse; import com.be08.smart_notes.service.NoteService; -import java.util.List; - @RestController @RequestMapping("/api/documents/notes") @RequiredArgsConstructor @@ -43,11 +44,16 @@ public ResponseEntity getNote(@PathVariable int id) { } @GetMapping - public ResponseEntity getAllNotes() { - List noteList = noteService.getAllNotes(); + public ResponseEntity getAllNotes( + @ModelAttribute BasicFilterDTO filterDTO, + @RequestParam(required = false, defaultValue = DefaultConstants.SORT_BY) String sortBy, + @RequestParam(required = false, defaultValue = DefaultConstants.SORT_ORDER) String sortOrder, + @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_NUMBER) int page, + @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_SIZE) int size) { + PageResponse pageResponse = noteService.getAllNotes(filterDTO, sortBy, sortOrder, page, size); ApiResponse apiResponse = ApiResponse.builder() - .message("Notes fetched successfully") - .data(noteList) + .message("Note fetched successfully") + .data(pageResponse) .build(); return ResponseEntity.status(HttpStatus.OK).body(apiResponse); } diff --git a/src/main/java/com/be08/smart_notes/controller/QuizController.java b/src/main/java/com/be08/smart_notes/controller/QuizController.java index f240c60..483c6a1 100644 --- a/src/main/java/com/be08/smart_notes/controller/QuizController.java +++ b/src/main/java/com/be08/smart_notes/controller/QuizController.java @@ -1,10 +1,12 @@ package com.be08.smart_notes.controller; +import com.be08.smart_notes.common.DefaultConstants; import com.be08.smart_notes.dto.QuizUpsertDTO; +import com.be08.smart_notes.dto.filter.QuizFilterDTO; import com.be08.smart_notes.dto.response.ApiResponse; +import com.be08.smart_notes.dto.response.PageResponse; import com.be08.smart_notes.dto.response.QuizResponse; import com.be08.smart_notes.dto.view.QuizView; -import com.be08.smart_notes.dto.view.View; import com.be08.smart_notes.service.QuizService; import com.be08.smart_notes.validation.group.OnCreate; import com.be08.smart_notes.validation.group.OnUpdate; @@ -39,8 +41,14 @@ public ResponseEntity createQuiz(@RequestBody @Validated(OnCreate.class) @GetMapping @JsonView(QuizView.Basic.class) - public ResponseEntity getAllQuizzes() { - List quizResponseList = quizService.getAllQuizzes(); + public ResponseEntity getAllQuizzes( + @ModelAttribute QuizFilterDTO filterDTO, + @RequestParam(required = false, defaultValue = DefaultConstants.SORT_BY) String sortBy, + @RequestParam(required = false, defaultValue = DefaultConstants.SORT_ORDER) String sortOrder, + @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_NUMBER) int page, + @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_SIZE) int size + ) { + PageResponse quizResponseList = quizService.getAllQuizzes(filterDTO, sortBy, sortOrder, page, size); ApiResponse apiResponse = ApiResponse.builder() .message("Quizzes fetched successfully") .data(quizResponseList) diff --git a/src/main/java/com/be08/smart_notes/controller/QuizSetController.java b/src/main/java/com/be08/smart_notes/controller/QuizSetController.java index dcc9abf..0b13ff0 100644 --- a/src/main/java/com/be08/smart_notes/controller/QuizSetController.java +++ b/src/main/java/com/be08/smart_notes/controller/QuizSetController.java @@ -1,7 +1,10 @@ package com.be08.smart_notes.controller; +import com.be08.smart_notes.common.DefaultConstants; +import com.be08.smart_notes.dto.filter.BasicFilterDTO; import com.be08.smart_notes.dto.request.QuizSetUpsertRequest; import com.be08.smart_notes.dto.response.ApiResponse; +import com.be08.smart_notes.dto.response.PageResponse; import com.be08.smart_notes.dto.response.QuizSetResponse; import com.be08.smart_notes.dto.view.QuizView; import com.be08.smart_notes.enums.OriginType; @@ -15,8 +18,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.util.List; - @RestController @RequestMapping("/api/quiz-sets") @RequiredArgsConstructor @@ -37,8 +38,13 @@ public ResponseEntity createQuizSet(@RequestBody @Valid QuizSetUpsertReq @GetMapping @JsonView(QuizView.Basic.class) - public ResponseEntity getAllQuizSets() { - List quizSetResponseList = quizSetService.getAllQuizSets(); + public ResponseEntity getAllQuizSets( + @ModelAttribute BasicFilterDTO filterDTO, + @RequestParam(required = false, defaultValue = DefaultConstants.SORT_BY) String sortBy, + @RequestParam(required = false, defaultValue = DefaultConstants.SORT_ORDER) String sortOrder, + @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_NUMBER) int page, + @RequestParam(required = false, defaultValue = DefaultConstants.PAGE_SIZE) int size) { + PageResponse quizSetResponseList = quizSetService.getAllQuizSets(filterDTO, sortBy, sortOrder, page, size); ApiResponse apiResponse = ApiResponse.builder() .message("Quiz set fetched successfully") .data(quizSetResponseList) @@ -60,7 +66,18 @@ public ResponseEntity getDefaultQuizSet() { @GetMapping("/{id}") @JsonView(QuizView.Detail.class) public ResponseEntity getQuizSet(@PathVariable int id) { - QuizSetResponse quizSetResponse = quizSetService.getQuizSetById(id); + QuizSetResponse quizSetResponse = quizSetService.getQuizSetById(id, false); + ApiResponse apiResponse = ApiResponse.builder() + .message("Quiz set fetched successfully") + .data(quizSetResponse) + .build(); + return ResponseEntity.status(HttpStatus.OK).body(apiResponse); + } + + @GetMapping("/{id}/quizzes") + @JsonView(QuizView.Detail.class) + public ResponseEntity getQuizSetWithQuizzes(@PathVariable int id) { + QuizSetResponse quizSetResponse = quizSetService.getQuizSetById(id, true); ApiResponse apiResponse = ApiResponse.builder() .message("Quiz set fetched successfully") .data(quizSetResponse) diff --git a/src/main/java/com/be08/smart_notes/dto/filter/BasicFilterDTO.java b/src/main/java/com/be08/smart_notes/dto/filter/BasicFilterDTO.java new file mode 100644 index 0000000..06dc460 --- /dev/null +++ b/src/main/java/com/be08/smart_notes/dto/filter/BasicFilterDTO.java @@ -0,0 +1,21 @@ +package com.be08.smart_notes.dto.filter; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldDefaults; + +import java.time.LocalDate; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) +public class BasicFilterDTO { + String keyword; + LocalDate createdFrom; + LocalDate createdTo; + LocalDate updatedFrom; + LocalDate updatedTo; +} diff --git a/src/main/java/com/be08/smart_notes/dto/filter/QuizFilterDTO.java b/src/main/java/com/be08/smart_notes/dto/filter/QuizFilterDTO.java new file mode 100644 index 0000000..f98dc72 --- /dev/null +++ b/src/main/java/com/be08/smart_notes/dto/filter/QuizFilterDTO.java @@ -0,0 +1,15 @@ +package com.be08.smart_notes.dto.filter; + +import lombok.*; +import lombok.experimental.FieldDefaults; + +import java.time.LocalDate; + +@EqualsAndHashCode(callSuper = true) +@Data +@NoArgsConstructor +@AllArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) +public class QuizFilterDTO extends BasicFilterDTO{ + Integer quizSetId; +} diff --git a/src/main/java/com/be08/smart_notes/dto/response/NoteResponse.java b/src/main/java/com/be08/smart_notes/dto/response/NoteResponse.java index 68b8a44..d77ec41 100644 --- a/src/main/java/com/be08/smart_notes/dto/response/NoteResponse.java +++ b/src/main/java/com/be08/smart_notes/dto/response/NoteResponse.java @@ -1,10 +1,8 @@ package com.be08.smart_notes.dto.response; import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; +import lombok.*; +import lombok.experimental.FieldDefaults; import java.time.LocalDateTime; @@ -12,14 +10,11 @@ @NoArgsConstructor @AllArgsConstructor @Builder +@FieldDefaults(level = AccessLevel.PRIVATE) public class NoteResponse { - private Integer id; - private String title; - private String content; - - @JsonFormat(pattern = "dd-MM-yyyy HH:mm:ss") - private LocalDateTime createdAt; - - @JsonFormat(pattern = "dd-MM-yyyy HH:mm:ss") - private LocalDateTime updatedAt; + Integer id; + String title; + String content; + LocalDateTime createdAt; + LocalDateTime updatedAt; } diff --git a/src/main/java/com/be08/smart_notes/dto/response/PageResponse.java b/src/main/java/com/be08/smart_notes/dto/response/PageResponse.java new file mode 100644 index 0000000..ceaeef0 --- /dev/null +++ b/src/main/java/com/be08/smart_notes/dto/response/PageResponse.java @@ -0,0 +1,32 @@ +package com.be08.smart_notes.dto.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.*; +import lombok.experimental.FieldDefaults; + +import java.util.Collections; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@FieldDefaults(level = AccessLevel.PRIVATE) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class PageResponse { + @Builder.Default + List pageData = Collections.emptyList(); + + PageInfo pageInfo; + + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class PageInfo { + int pageSize; + int currentPage; + int totalPages; + long totalElements; + } +} diff --git a/src/main/java/com/be08/smart_notes/mapper/QuizSetMapper.java b/src/main/java/com/be08/smart_notes/mapper/QuizSetMapper.java index 7b484ce..89813d5 100644 --- a/src/main/java/com/be08/smart_notes/mapper/QuizSetMapper.java +++ b/src/main/java/com/be08/smart_notes/mapper/QuizSetMapper.java @@ -3,10 +3,7 @@ import com.be08.smart_notes.dto.request.QuizSetUpsertRequest; import com.be08.smart_notes.dto.response.QuizSetResponse; import com.be08.smart_notes.model.QuizSet; -import org.mapstruct.BeanMapping; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.MappingTarget; +import org.mapstruct.*; import java.util.List; @@ -18,6 +15,14 @@ public interface QuizSetMapper { void updateQuizSet(@MappingTarget QuizSet quizSet, QuizSetUpsertRequest request); // QuizSet entity <--> QuizSetResponse dto + @Named("basic") + @Mapping(target = "quizzes", ignore = true) QuizSetResponse toQuizSetResponse(QuizSet quizSet); + + @IterableMapping(qualifiedByName = "basic") List toQuizSetResponseList(List quizSet); + + // QuizSet with Quizzes + @Named("detail") + QuizSetResponse toQuizSetResponseWithQuizzes(QuizSet quizSet); } diff --git a/src/main/java/com/be08/smart_notes/repository/AttemptRepository.java b/src/main/java/com/be08/smart_notes/repository/AttemptRepository.java index 4f91a1f..72c4214 100644 --- a/src/main/java/com/be08/smart_notes/repository/AttemptRepository.java +++ b/src/main/java/com/be08/smart_notes/repository/AttemptRepository.java @@ -1,6 +1,8 @@ package com.be08.smart_notes.repository; import com.be08.smart_notes.model.Attempt; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -12,4 +14,5 @@ public interface AttemptRepository extends JpaRepository { Optional findByIdAndQuiz_QuizSet_UserId(int id, int userId); List findByQuizIdAndQuiz_QuizSet_UserId(int quizId, int userId); + Page findByQuizIdAndQuiz_QuizSet_UserId(int quizId, int userId, Pageable pageable); } diff --git a/src/main/java/com/be08/smart_notes/repository/DocumentRepository.java b/src/main/java/com/be08/smart_notes/repository/DocumentRepository.java index d4045c2..99dd73d 100644 --- a/src/main/java/com/be08/smart_notes/repository/DocumentRepository.java +++ b/src/main/java/com/be08/smart_notes/repository/DocumentRepository.java @@ -3,18 +3,23 @@ import java.util.List; import java.util.Optional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.JpaRepository; import com.be08.smart_notes.model.Document; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Repository; @Repository -public interface DocumentRepository extends JpaRepository { - List findAllByUserId(Integer userId); +public interface DocumentRepository extends JpaRepository, JpaSpecificationExecutor { + List findAllByUserId(int userId); + Page findAllByUserId(int userId, Pageable pageable); Optional findByIdAndUserId(int documentId, int userId); Optional findFirstByTitleAndUserId(String title, int userId); List findAllByIdIn(List ids); - List findAllByUserIdAndIdIn(Integer userId, List ids); + List findAllByUserIdAndIdIn(int userId, List ids); } diff --git a/src/main/java/com/be08/smart_notes/repository/QuizRepository.java b/src/main/java/com/be08/smart_notes/repository/QuizRepository.java index 2482bdf..d7b5411 100644 --- a/src/main/java/com/be08/smart_notes/repository/QuizRepository.java +++ b/src/main/java/com/be08/smart_notes/repository/QuizRepository.java @@ -1,13 +1,17 @@ package com.be08.smart_notes.repository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import com.be08.smart_notes.model.Quiz; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import java.util.List; import java.util.Optional; -public interface QuizRepository extends JpaRepository { +public interface QuizRepository extends JpaRepository, JpaSpecificationExecutor { Optional findByIdAndQuizSetUserId(int id, int userId); List findAllByQuizSetUserId(int userId); + Page findAllByQuizSetUserId(int userId, Pageable pageable); } diff --git a/src/main/java/com/be08/smart_notes/repository/QuizSetRepository.java b/src/main/java/com/be08/smart_notes/repository/QuizSetRepository.java index 069fc13..f35c16c 100644 --- a/src/main/java/com/be08/smart_notes/repository/QuizSetRepository.java +++ b/src/main/java/com/be08/smart_notes/repository/QuizSetRepository.java @@ -2,15 +2,19 @@ import com.be08.smart_notes.enums.OriginType; import com.be08.smart_notes.model.QuizSet; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import java.util.List; import java.util.Optional; -public interface QuizSetRepository extends JpaRepository { +public interface QuizSetRepository extends JpaRepository, JpaSpecificationExecutor { Optional findByUserIdAndOriginType(int userID, OriginType originType); Optional findByIdAndUserId(int quizSetId, int userId); List findAllByUserId(int userId); + Page findAllByUserId(int userId, Pageable pageable); void deleteAllByUserId(int userId); } diff --git a/src/main/java/com/be08/smart_notes/service/AttemptService.java b/src/main/java/com/be08/smart_notes/service/AttemptService.java index 2b255a0..9d484ad 100644 --- a/src/main/java/com/be08/smart_notes/service/AttemptService.java +++ b/src/main/java/com/be08/smart_notes/service/AttemptService.java @@ -1,6 +1,7 @@ package com.be08.smart_notes.service; import com.be08.smart_notes.dto.request.AttemptDetailUpdateRequest; +import com.be08.smart_notes.dto.response.PageResponse; import com.be08.smart_notes.dto.response.AttemptResponse; import com.be08.smart_notes.exception.AppException; import com.be08.smart_notes.exception.ErrorCode; @@ -16,6 +17,9 @@ import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -78,16 +82,25 @@ public AttemptResponse getAttemptByIdAndQuizId(int quizId, int attemptId) { } /** - * Get list of attempts by quiz id + * Get list of attempts (by pages) based on given quiz id * @param quizId id of target quiz * @return list of attempt response dto */ - public List getAllAttemptsByQuizId(int quizId) { + public PageResponse getAllAttemptsByQuizId(int quizId, int pageNumber, int pageSize) { int currentUserId = authorizationService.getCurrentUserId(); - List attempts = attemptRepository.findByQuizIdAndQuiz_QuizSet_UserId(quizId, currentUserId); + Pageable pageable = PageRequest.of(pageNumber - 1, pageSize); + Page page = attemptRepository.findByQuizIdAndQuiz_QuizSet_UserId(quizId, currentUserId, pageable); - return attemptMapper.toAttemptResponseList(attempts); + List attempts = page.getContent().stream().map(attemptMapper::toAttemptResponse).toList(); + + return PageResponse.builder() + .pageInfo(PageResponse.PageInfo.builder() + .currentPage(pageNumber) + .pageSize(pageSize) + .totalPages(page.getTotalPages()) + .totalElements(page.getTotalElements()).build()) + .pageData(attempts).build(); } /** diff --git a/src/main/java/com/be08/smart_notes/service/DocumentService.java b/src/main/java/com/be08/smart_notes/service/DocumentService.java index 0c30307..118fac9 100644 --- a/src/main/java/com/be08/smart_notes/service/DocumentService.java +++ b/src/main/java/com/be08/smart_notes/service/DocumentService.java @@ -2,12 +2,16 @@ import java.util.List; +import com.be08.smart_notes.dto.response.PageResponse; import com.be08.smart_notes.exception.AppException; import com.be08.smart_notes.exception.ErrorCode; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import com.be08.smart_notes.model.Document; @@ -23,11 +27,22 @@ public class DocumentService { private static final String SYSTEM_SOURCE_TITLE = "__SYSTEM_UNFILED_SOURCE__"; - public List getAllDocuments() { + public PageResponse getAllDocuments(int pageNumber, int pageSize) { // Get current user int currentUserId = authorizationService.getCurrentUserId(); - return documentRepository.findAllByUserId(currentUserId); + Pageable pageable = PageRequest.of(pageNumber - 1, pageSize); + Page page = documentRepository.findAllByUserId(currentUserId, pageable); + + List documents = page.stream().toList(); + + return PageResponse.builder() + .pageInfo(PageResponse.PageInfo.builder() + .currentPage(pageNumber) + .pageSize(pageSize) + .totalPages(page.getTotalPages()) + .totalElements(page.getTotalElements()).build()) + .pageData(documents).build(); } public void deleteDocument(int id) { diff --git a/src/main/java/com/be08/smart_notes/service/NoteService.java b/src/main/java/com/be08/smart_notes/service/NoteService.java index 2f1b243..b97b5de 100644 --- a/src/main/java/com/be08/smart_notes/service/NoteService.java +++ b/src/main/java/com/be08/smart_notes/service/NoteService.java @@ -3,14 +3,22 @@ import java.time.LocalDateTime; import java.util.List; +import com.be08.smart_notes.dto.filter.BasicFilterDTO; import com.be08.smart_notes.dto.response.NoteResponse; +import com.be08.smart_notes.dto.response.PageResponse; import com.be08.smart_notes.exception.AppException; import com.be08.smart_notes.exception.ErrorCode; import com.be08.smart_notes.mapper.DocumentMapper; +import com.be08.smart_notes.specification.NoteSpecificationBuilder; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import com.be08.smart_notes.dto.request.NoteUpsertRequest; @@ -56,6 +64,48 @@ public NoteResponse createNote(NoteUpsertRequest newData) { return documentMapper.toNoteResponse(savedNote); } + public PageResponse getAllNotes(BasicFilterDTO filterDTO, String sortBy, String sortOrder, int pageNumber, int pageSize) { + // Get current user id + int currentUserId = authorizationService.getCurrentUserId(); + + // Filtering and sorting + Specification spec = NoteSpecificationBuilder.getSpecification(currentUserId, filterDTO); + Sort sortOption = sortOrder.equalsIgnoreCase("asc") ? Sort.by(sortBy).ascending() : Sort.by(sortBy).descending(); + Pageable pageable = PageRequest.of(pageNumber - 1, pageSize, sortOption); + + // Get note + Page page = documentRepository.findAll(spec, pageable); + List noteResponses = page.getContent().stream().map(documentMapper::toNoteResponse).toList(); + + return PageResponse.builder() + .pageInfo(PageResponse.PageInfo.builder() + .currentPage(pageNumber) + .pageSize(pageSize) + .totalPages(page.getTotalPages()) + .totalElements(page.getTotalElements()).build()) + .pageData(noteResponses).build(); + } + + public PageResponse getAllNotes(int pageNumber, int pageSize) { + // Get current user id + int currentUserId = authorizationService.getCurrentUserId(); + + // Get page data + Pageable pageable = PageRequest.of(pageNumber - 1, pageSize); + Page page = documentRepository.findAllByUserId(currentUserId, pageable); + + // Get note + List noteResponses = page.getContent().stream().map(documentMapper::toNoteResponse).toList(); + + return PageResponse.builder() + .pageInfo(PageResponse.PageInfo.builder() + .currentPage(pageNumber) + .pageSize(pageSize) + .totalPages(page.getTotalPages()) + .totalElements(page.getTotalElements()).build()) + .pageData(noteResponses).build(); + } + public List getAllNotes() { // Get current user id int currentUserId = authorizationService.getCurrentUserId(); diff --git a/src/main/java/com/be08/smart_notes/service/QuizService.java b/src/main/java/com/be08/smart_notes/service/QuizService.java index 37a1c4f..607a9dd 100644 --- a/src/main/java/com/be08/smart_notes/service/QuizService.java +++ b/src/main/java/com/be08/smart_notes/service/QuizService.java @@ -1,6 +1,8 @@ package com.be08.smart_notes.service; import com.be08.smart_notes.dto.QuizUpsertDTO; +import com.be08.smart_notes.dto.filter.QuizFilterDTO; +import com.be08.smart_notes.dto.response.PageResponse; import com.be08.smart_notes.dto.response.QuizResponse; import com.be08.smart_notes.exception.AppException; import com.be08.smart_notes.exception.ErrorCode; @@ -8,10 +10,16 @@ import com.be08.smart_notes.model.Quiz; import com.be08.smart_notes.model.QuizSet; import com.be08.smart_notes.repository.QuizRepository; +import com.be08.smart_notes.specification.QuizSpecificationBuilder; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import java.util.List; @@ -68,12 +76,23 @@ public QuizResponse getQuizById(int quizId) { * Get all quiz and its questions based on given id * @return quiz response list dto */ - public List getAllQuizzes() { + public PageResponse getAllQuizzes(QuizFilterDTO filterDTO, String sortBy, String sortOrder, int pageNumber, int pageSize) { int currentUserId = authorizationService.getCurrentUserId(); - List quizzes = quizRepository.findAllByQuizSetUserId(currentUserId); + Specification spec = QuizSpecificationBuilder.getSpecification(currentUserId, filterDTO); + Sort sortOption = sortOrder.equalsIgnoreCase("asc") ? Sort.by(sortBy).ascending() : Sort.by(sortBy).descending(); + Pageable pageable = PageRequest.of(pageNumber - 1, pageSize, sortOption); + Page page = quizRepository.findAll(spec, pageable); - return quizMapper.toQuizResponseList(quizzes); + List quizzesResponse = page.getContent().stream().map(quizMapper::toQuizResponse).toList(); + + return PageResponse.builder() + .pageInfo(PageResponse.PageInfo.builder() + .currentPage(pageNumber) + .pageSize(pageSize) + .totalPages(page.getTotalPages()) + .totalElements(page.getTotalElements()).build()) + .pageData(quizzesResponse).build(); } /** diff --git a/src/main/java/com/be08/smart_notes/service/QuizSetService.java b/src/main/java/com/be08/smart_notes/service/QuizSetService.java index 71128d4..0903238 100644 --- a/src/main/java/com/be08/smart_notes/service/QuizSetService.java +++ b/src/main/java/com/be08/smart_notes/service/QuizSetService.java @@ -2,7 +2,9 @@ import com.be08.smart_notes.common.AppConstants; import com.be08.smart_notes.dto.QuizUpsertDTO; +import com.be08.smart_notes.dto.filter.BasicFilterDTO; import com.be08.smart_notes.dto.request.QuizSetUpsertRequest; +import com.be08.smart_notes.dto.response.PageResponse; import com.be08.smart_notes.dto.response.QuizSetResponse; import com.be08.smart_notes.enums.OriginType; import com.be08.smart_notes.exception.AppException; @@ -12,10 +14,16 @@ import com.be08.smart_notes.model.Quiz; import com.be08.smart_notes.model.QuizSet; import com.be08.smart_notes.repository.QuizSetRepository; +import com.be08.smart_notes.specification.QuizSetSpecificationBuilder; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -71,11 +79,12 @@ public QuizSetResponse createQuizSet(String quizSetTitle, List qu } /** - * Get a QuizSet with associated quizzes using given id + * Get a QuizSet using given id * @param quizSetId id of target quiz set + * @param includeQuizzes choices to include or not include quizzes * @return response dto for quiz set */ - public QuizSetResponse getQuizSetById(int quizSetId) { + public QuizSetResponse getQuizSetById(int quizSetId, boolean includeQuizzes) { int currentUserId = authorizationService.getCurrentUserId(); QuizSet quiz = quizSetRepository.findByIdAndUserId(quizSetId, currentUserId).orElseThrow(() -> { @@ -83,18 +92,32 @@ public QuizSetResponse getQuizSetById(int quizSetId) { return new AppException(ErrorCode.QUIZ_SET_NOT_FOUND); }); - return quizSetMapper.toQuizSetResponse(quiz); + return includeQuizzes ? quizSetMapper.toQuizSetResponseWithQuizzes(quiz) : quizSetMapper.toQuizSetResponse(quiz); } /** * Get a QuizSet with associated quizzes using given id * @return response dto for all quiz set */ - public List getAllQuizSets() { + public PageResponse getAllQuizSets(BasicFilterDTO filterDTO, String sortBy, String sortOrder, int pageNumber, int pageSize) { int currentUserId = authorizationService.getCurrentUserId(); - List quizSets = quizSetRepository.findAllByUserId(currentUserId); - return quizSetMapper.toQuizSetResponseList(quizSets); + // Filtering and Sorting + Specification spec = QuizSetSpecificationBuilder.getSpecification(currentUserId, filterDTO); + Sort sortOption = sortOrder.equalsIgnoreCase("asc") ? Sort.by(sortBy).ascending() : Sort.by(sortBy).descending(); + Pageable pageable = PageRequest.of(pageNumber - 1, pageSize, sortOption); + + // Get quiz set + Page page = quizSetRepository.findAll(spec, pageable); + List quizSetResponses = page.stream().map(quizSetMapper::toQuizSetResponse).toList(); + + return PageResponse.builder() + .pageInfo(PageResponse.PageInfo.builder() + .currentPage(pageNumber) + .pageSize(pageSize) + .totalPages(page.getTotalPages()) + .totalElements(page.getTotalElements()).build()) + .pageData(quizSetResponses).build(); } /** diff --git a/src/main/java/com/be08/smart_notes/specification/CommonPredicateBuilder.java b/src/main/java/com/be08/smart_notes/specification/CommonPredicateBuilder.java new file mode 100644 index 0000000..25b9fce --- /dev/null +++ b/src/main/java/com/be08/smart_notes/specification/CommonPredicateBuilder.java @@ -0,0 +1,44 @@ +package com.be08.smart_notes.specification; + +import com.be08.smart_notes.dto.filter.BasicFilterDTO; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; + +import java.util.List; + +public class CommonPredicateBuilder { + public static void addDateRangePredicates(Root root, CriteriaBuilder cb, List predicates, BasicFilterDTO filterDTO) { + if (filterDTO.getCreatedFrom() != null) { + predicates.add(cb.greaterThanOrEqualTo(root.get("createdAt"), filterDTO.getCreatedFrom())); + } + if (filterDTO.getCreatedTo() != null) { + predicates.add(cb.lessThanOrEqualTo(root.get("createdAt"), filterDTO.getCreatedTo())); + } + + if (filterDTO.getUpdatedFrom() != null) { + predicates.add(cb.greaterThanOrEqualTo(root.get("updatedAt"), filterDTO.getUpdatedFrom())); + } + if (filterDTO.getUpdatedTo() != null) { + predicates.add(cb.lessThanOrEqualTo(root.get("updatedAt"), filterDTO.getUpdatedTo())); + } + } + + public static void addKeywordPredicate(Root root, CriteriaBuilder cb, List predicates, BasicFilterDTO filterDTO, String targetProp) { + if (filterDTO.getKeyword() != null && !filterDTO.getKeyword().isEmpty()) { + predicates.add(cb.like(cb.lower(root.get(targetProp)), "%" + filterDTO.getKeyword().toLowerCase() + "%")); + } + } + + public static void addKeywordOrPredicate(Root root, CriteriaBuilder cb, List predicates, BasicFilterDTO filterDTO, String targetProp1, String targetProp2) { + if (filterDTO.getKeyword() != null && !filterDTO.getKeyword().isEmpty()) { + String targetKeyword = "%" + filterDTO.getKeyword().toLowerCase() + "%"; + + Predicate firstMatch = cb.like(cb.lower(root.get(targetProp1)), targetKeyword); + Predicate secondMatch = cb.like(cb.lower(root.get(targetProp2)), targetKeyword); + + Predicate keywordPredicate = cb.or(firstMatch, secondMatch); + predicates.add(keywordPredicate); + } + } +} diff --git a/src/main/java/com/be08/smart_notes/specification/NoteSpecificationBuilder.java b/src/main/java/com/be08/smart_notes/specification/NoteSpecificationBuilder.java new file mode 100644 index 0000000..ce90b3f --- /dev/null +++ b/src/main/java/com/be08/smart_notes/specification/NoteSpecificationBuilder.java @@ -0,0 +1,24 @@ +package com.be08.smart_notes.specification; + +import com.be08.smart_notes.dto.filter.BasicFilterDTO; +import com.be08.smart_notes.model.Document; +import jakarta.persistence.criteria.Predicate; +import org.springframework.data.jpa.domain.Specification; + +import java.util.ArrayList; +import java.util.List; + +public class NoteSpecificationBuilder { + public static Specification getSpecification(int userId, BasicFilterDTO filterDTO) { + // Build Specification using root (Document), criteria query, and criteria builder + return (root, query, cb) -> { + List predicates = new ArrayList<>(); + + CommonPredicateBuilder.addKeywordOrPredicate(root, cb, predicates, filterDTO, "title", "content"); + CommonPredicateBuilder.addDateRangePredicates(root, cb, predicates, filterDTO); + + predicates.add(cb.equal(root.get("userId"), userId)); + return cb.and(predicates.toArray(new Predicate[0])); + }; + } +} diff --git a/src/main/java/com/be08/smart_notes/specification/QuizSetSpecificationBuilder.java b/src/main/java/com/be08/smart_notes/specification/QuizSetSpecificationBuilder.java new file mode 100644 index 0000000..1006a0b --- /dev/null +++ b/src/main/java/com/be08/smart_notes/specification/QuizSetSpecificationBuilder.java @@ -0,0 +1,24 @@ +package com.be08.smart_notes.specification; + +import com.be08.smart_notes.dto.filter.BasicFilterDTO; +import com.be08.smart_notes.model.QuizSet; +import jakarta.persistence.criteria.Predicate; +import org.springframework.data.jpa.domain.Specification; + +import java.util.ArrayList; +import java.util.List; + +public class QuizSetSpecificationBuilder { + public static Specification getSpecification(int userId, BasicFilterDTO filterDTO) { + // Build Specification using root (QuizSet), criteria query, and criteria builder + return (root, query, cb) -> { + List predicates = new ArrayList<>(); + + CommonPredicateBuilder.addKeywordPredicate(root, cb, predicates, filterDTO, "title"); + CommonPredicateBuilder.addDateRangePredicates(root, cb, predicates, filterDTO); + + predicates.add(cb.equal(root.get("userId"), userId)); + return cb.and(predicates.toArray(new Predicate[0])); + }; + } +} diff --git a/src/main/java/com/be08/smart_notes/specification/QuizSpecificationBuilder.java b/src/main/java/com/be08/smart_notes/specification/QuizSpecificationBuilder.java new file mode 100644 index 0000000..5adb529 --- /dev/null +++ b/src/main/java/com/be08/smart_notes/specification/QuizSpecificationBuilder.java @@ -0,0 +1,28 @@ +package com.be08.smart_notes.specification; + +import com.be08.smart_notes.dto.filter.QuizFilterDTO; +import com.be08.smart_notes.model.Quiz; +import jakarta.persistence.criteria.Predicate; +import org.springframework.data.jpa.domain.Specification; + +import java.util.ArrayList; +import java.util.List; + +public class QuizSpecificationBuilder { + public static Specification getSpecification(int userId, QuizFilterDTO filterDTO) { + // Build Specification using root (Quiz), criteria query, and criteria builder + return (root, query, cb) -> { + List predicates = new ArrayList<>(); + + CommonPredicateBuilder.addKeywordPredicate(root, cb, predicates, filterDTO, "title"); + CommonPredicateBuilder.addDateRangePredicates(root, cb, predicates, filterDTO); + + if (filterDTO.getQuizSetId() != null) { + predicates.add(cb.equal(root.get("quizSet").get("id"), filterDTO.getQuizSetId())); + } + + predicates.add(cb.equal(root.get("quizSet").get("userId"), userId)); + return cb.and(predicates.toArray(new Predicate[0])); + }; + } +} diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 0f327b6..2826672 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -2,7 +2,7 @@ spring.datasource.url=jdbc:mysql://localhost:${DB_PORT}/${DB_NAME} spring.datasource.username=${DB_USERNAME} spring.datasource.password=${DB_PASSWORD} -spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # JPA Settings for Development spring.jpa.show-sql=true diff --git a/src/test/java/com/be08/smart_notes/integration/controller/NoteControllerIntegrationTest.java b/src/test/java/com/be08/smart_notes/integration/controller/NoteControllerIntegrationTest.java index 6810a23..4ef1b93 100644 --- a/src/test/java/com/be08/smart_notes/integration/controller/NoteControllerIntegrationTest.java +++ b/src/test/java/com/be08/smart_notes/integration/controller/NoteControllerIntegrationTest.java @@ -173,7 +173,7 @@ void shouldRetrieveAllNotesForCurrentUser() throws Exception { .with(jwtWithUserId(TEST_USER_ID)) ) .andExpect(status().isOk()) - .andExpect(jsonPath("$.data", hasSize(1))); + .andExpect(jsonPath("$.data.pageData", hasSize(1))); } @Test @@ -187,7 +187,7 @@ void shouldReturnEmptyListWhenUserHasNoNotes() throws Exception { .with(jwtWithUserId(TEST_USER_ID)) ) .andExpect(status().isOk()) - .andExpect(jsonPath("$.data", hasSize(0))); + .andExpect(jsonPath("$.data.pageData", hasSize(0))); } @Test diff --git a/src/test/java/com/be08/smart_notes/integration/repository/DocumentRepositoryTest.java b/src/test/java/com/be08/smart_notes/integration/repository/DocumentRepositoryTest.java index 04014cf..3246a38 100644 --- a/src/test/java/com/be08/smart_notes/integration/repository/DocumentRepositoryTest.java +++ b/src/test/java/com/be08/smart_notes/integration/repository/DocumentRepositoryTest.java @@ -6,6 +6,9 @@ import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import java.util.Arrays; import java.util.Collections; @@ -80,6 +83,91 @@ void shouldNotReturnOtherUsersDocuments() { } } + @Nested + @DisplayName("findAllByUserId(): Page") + class FindAllByUserIdPaginatedTest { + @Test + void shouldReturnFirstPageWithCorrectSize() { + // Arrange + Pageable pageable = PageRequest.of(0, 2); + + // Act + Page result = documentRepository.findAllByUserId(FIRST_USER_ID, pageable); + + // Assert + assertNotNull(result); + assertEquals(2, result.getContent().size()); + assertEquals(3, result.getTotalElements()); + assertEquals(2, result.getTotalPages()); + assertEquals(0, result.getNumber()); + assertTrue(result.getContent().stream().allMatch(doc -> doc.getUserId() == FIRST_USER_ID)); + } + + @Test + void shouldReturnSecondPageWithRemainingDocuments() { + // Arrange + Pageable pageable = PageRequest.of(1, 2); + + // Act + Page result = documentRepository.findAllByUserId(FIRST_USER_ID, pageable); + + // Assert + assertNotNull(result); + assertEquals(1, result.getContent().size()); + assertEquals(3, result.getTotalElements()); + assertEquals(2, result.getTotalPages()); + assertEquals(1, result.getNumber()); + assertTrue(result.getContent().stream().allMatch(doc -> doc.getUserId() == FIRST_USER_ID)); + } + + @Test + void shouldReturnEmptyPageWhenUserHasNoDocuments() { + // Arrange + int nonExistentUserId = 999; + Pageable pageable = PageRequest.of(0, 10); + + // Act + Page result = documentRepository.findAllByUserId(nonExistentUserId, pageable); + + // Assert + assertNotNull(result); + assertTrue(result.getContent().isEmpty()); + assertEquals(0, result.getTotalElements()); + assertEquals(0, result.getTotalPages()); + assertEquals(0, result.getNumber()); + } + + @Test + void shouldReturnEmptyPageWhenPageNumberExceedsTotalPages() { + // Arrange + Pageable pageable = PageRequest.of(10, 10); + + // Act + Page result = documentRepository.findAllByUserId(FIRST_USER_ID, pageable); + + // Assert + assertNotNull(result); + assertTrue(result.getContent().isEmpty()); + assertEquals(3, result.getTotalElements()); + assertEquals(1, result.getTotalPages()); + assertEquals(10, result.getNumber()); + } + + @Test + void shouldNotReturnOtherUsersDocuments() { + // Arrange + Pageable pageable = PageRequest.of(0, 10); + + // Act + Page result = documentRepository.findAllByUserId(FIRST_USER_ID, pageable); + + // Assert + assertNotNull(result); + assertFalse(result.getContent().stream().anyMatch(doc -> doc.getUserId() == SECOND_USER_ID)); + assertTrue(result.getContent().stream().allMatch(doc -> doc.getUserId() == FIRST_USER_ID)); + } + } + // --- findByIdAndUserId --- // @Nested @DisplayName("findByIdAndUserId(): Optional") diff --git a/src/test/java/com/be08/smart_notes/unit/service/NoteServiceTest.java b/src/test/java/com/be08/smart_notes/unit/service/NoteServiceTest.java index 532ce38..7f6a4f4 100644 --- a/src/test/java/com/be08/smart_notes/unit/service/NoteServiceTest.java +++ b/src/test/java/com/be08/smart_notes/unit/service/NoteServiceTest.java @@ -1,5 +1,6 @@ package com.be08.smart_notes.unit.service; +import com.be08.smart_notes.dto.response.PageResponse; import com.be08.smart_notes.helper.DocumentDataBuilder; import com.be08.smart_notes.dto.request.NoteUpsertRequest; import com.be08.smart_notes.dto.response.NoteResponse; @@ -20,6 +21,10 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import java.util.Collections; import java.util.List; @@ -176,6 +181,100 @@ void shouldReturnSingleNoteResponseListWhenGivenSingleNote() { } } + @Nested + @DisplayName("getAllNotes(): PageResponse") + class GetAllNotesPaginatedTest { + @Test + void shouldReturnEmptyPageResponseWhenNoNotesExist() { + // Arrange + int userId = existingUser.getId(); + int pageNumber = 1; + int pageSize = 10; + Pageable pageable = PageRequest.of(0, pageSize); + Page emptyPage = new PageImpl<>(Collections.emptyList(), pageable, 0); + + when(documentRepository.findAllByUserId(userId, pageable)).thenReturn(emptyPage); + + // Act + PageResponse actualResponse = noteService.getAllNotes(pageNumber, pageSize); + + // Assert + assertNotNull(actualResponse); + assertEquals(pageNumber, actualResponse.getPageInfo().getCurrentPage()); + assertEquals(pageSize, actualResponse.getPageInfo().getPageSize()); + assertEquals(0, actualResponse.getPageInfo().getTotalPages()); + assertEquals(0, actualResponse.getPageInfo().getTotalElements()); + assertTrue(actualResponse.getPageData().isEmpty()); + + verify(authorizationService).getCurrentUserId(); + verify(documentRepository).findAllByUserId(userId, pageable); + verify(documentMapper, never()).toNoteResponse(any()); + } + + @Test + void shouldReturnFirstPageCorrectly() { + // Arrange + int userId = existingUser.getId(); + int pageNumber = 1; + int pageSize = 10; + Pageable pageable = PageRequest.of(0, pageSize); + List noteList = List.of(existingNote, anotherExistingNote); + Page page = new PageImpl<>(noteList, pageable, 2); + + when(documentRepository.findAllByUserId(userId, pageable)).thenReturn(page); + when(documentMapper.toNoteResponse(existingNote)).thenReturn(existingNoteResponse); + when(documentMapper.toNoteResponse(anotherExistingNote)).thenReturn(anotherExistingNoteResponse); + + // Act + PageResponse actualResponse = noteService.getAllNotes(pageNumber, pageSize); + + // Assert + assertNotNull(actualResponse); + assertEquals(pageNumber, actualResponse.getPageInfo().getCurrentPage()); + assertEquals(pageSize, actualResponse.getPageInfo().getPageSize()); + assertEquals(1, actualResponse.getPageInfo().getTotalPages()); + assertEquals(2, actualResponse.getPageInfo().getTotalElements()); + assertEquals(2, actualResponse.getPageData().size()); + assertEquals(existingNoteResponse, actualResponse.getPageData().get(0)); + assertEquals(anotherExistingNoteResponse, actualResponse.getPageData().get(1)); + + verify(authorizationService).getCurrentUserId(); + verify(documentRepository).findAllByUserId(userId, pageable); + verify(documentMapper).toNoteResponse(existingNote); + verify(documentMapper).toNoteResponse(anotherExistingNote); + } + + @Test + void shouldReturnSecondPageCorrectly() { + // Arrange + int userId = existingUser.getId(); + int pageNumber = 2; + int pageSize = 5; + Pageable pageable = PageRequest.of(1, pageSize); + List noteList = List.of(existingNote); + Page page = new PageImpl<>(noteList, pageable, 6); + + when(documentRepository.findAllByUserId(userId, pageable)).thenReturn(page); + when(documentMapper.toNoteResponse(existingNote)).thenReturn(existingNoteResponse); + + // Act + PageResponse actualResponse = noteService.getAllNotes(pageNumber, pageSize); + + // Assert + assertNotNull(actualResponse); + assertEquals(pageNumber, actualResponse.getPageInfo().getCurrentPage()); + assertEquals(pageSize, actualResponse.getPageInfo().getPageSize()); + assertEquals(2, actualResponse.getPageInfo().getTotalPages()); + assertEquals(6, actualResponse.getPageInfo().getTotalElements()); + assertEquals(1, actualResponse.getPageData().size()); + assertEquals(existingNoteResponse, actualResponse.getPageData().get(0)); + + verify(authorizationService).getCurrentUserId(); + verify(documentRepository).findAllByUserId(userId, pageable); + verify(documentMapper).toNoteResponse(existingNote); + } + } + // --- Create note --- // @Nested @DisplayName("createNote(): NoteResponse")