Skip to content

Commit 644048a

Browse files
authored
[Feature] Add search by title for the my tasks endpoint (#553)
* feat: add support for optional search in getAllTasks method * feat: implement optional search in getAllTasks method * feat: update the db query to support optional search * feat: update the getMyTasks endpoint to support optional search * test: update tests for getMyTasks endpoint to include search parameter * fix: swagger description for getMyTasks endpoint
1 parent 3534bab commit 644048a

6 files changed

Lines changed: 57 additions & 26 deletions

File tree

src/main/java/com/itasocialacademy/oitassist/task/controller/TaskController.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ public ResponseEntity<PageResponse<TaskResponseDTO>> getAllTasks(
9090

9191
@Operation(
9292
summary = "Get current user's tasks",
93-
description = "Retrieves all tasks owned by the currently authenticated user with pagination support."
94-
+ "Requires ADMIN or ORG role.")
93+
description = "Retrieves all tasks owned by the currently authenticated user "
94+
+ "with pagination support and optional search by title. Requires ADMIN or ORG role.")
9595
@ApiResponses(value = {
9696
@ApiResponse(responseCode = "200", description = "User's tasks retrieved successfully",
9797
content = @Content(mediaType = "application/json",
@@ -104,8 +104,11 @@ public ResponseEntity<PageResponse<TaskResponseDTO>> getAllTasks(
104104
@GetMapping("/my")
105105
@PreAuthorize("hasAnyRole('ADMIN', 'ORG')")
106106
public ResponseEntity<PageResponse<TaskResponseDTO>> getMyTasks(
107-
@ParameterObject @PageableDefault(size = 15, sort = "createdAt") Pageable pageable) {
108-
return ResponseEntity.ok(PageResponse.from(taskService.getAllMyTasks(pageable)));
107+
@ParameterObject @PageableDefault(size = 15, sort = "createdAt") Pageable pageable,
108+
@Parameter(
109+
description = "Optional search query for filtering tasks by title",
110+
example = "PowerPoint") @RequestParam(required = false) String search) {
111+
return ResponseEntity.ok(PageResponse.from(taskService.getAllMyTasks(pageable, search)));
109112
}
110113

111114
@Operation(

src/main/java/com/itasocialacademy/oitassist/task/dao/repository/TaskBodyRepository.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ public interface TaskBodyRepository extends JpaRepository<TaskBody, Long> {
1717
SELECT t
1818
FROM TaskBody t
1919
JOIN t.owners o
20-
WHERE o.id.ownerId = :ownerId
20+
WHERE o.id.ownerId = :ownerId AND LOWER(t.title) LIKE LOWER(CONCAT('%', :search, '%')) ESCAPE '\\'
2121
""")
22-
Page<TaskBody> findAllByOwnerId(@Param("ownerId") Long ownerId, Pageable pageable);
22+
Page<TaskBody> findAllByOwnerId(@Param("ownerId") Long ownerId, @Param("search") String search, Pageable pageable);
2323

2424
@Query(value = "SELECT t.id AS id, t.title AS title FROM TaskBody t WHERE t.id IN :ids")
2525
List<TaskTitleView> findTitlesByIds(@Param("ids") Collection<Long> taskIds);

src/main/java/com/itasocialacademy/oitassist/task/service/TaskServiceImpl.java

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,7 @@ public Page<TaskResponseDTO> getAllTasks(Pageable pageable, String search) {
9595
log.debug("getAllTasks: page={}, size={}, sort={} search={}",
9696
pageable.getPageNumber(), pageable.getPageSize(), pageable.getSort(), search);
9797

98-
String normalizedSearch = search == null || search.isBlank()
99-
? ""
100-
: search.trim()
101-
.replaceAll("\\s+", " ")
102-
.replace("\\", "\\\\")
103-
.replace("%", "\\%")
104-
.replace("_", "\\_");
98+
String normalizedSearch = getNormalizedSearch(search);
10599

106100
Page<TaskBody> tasksPage = taskBodyRepository.findAllByTitleLike(normalizedSearch, pageable);
107101

@@ -110,14 +104,16 @@ public Page<TaskResponseDTO> getAllTasks(Pageable pageable, String search) {
110104

111105
@Override
112106
@Transactional(readOnly = true)
113-
public Page<TaskResponseDTO> getAllMyTasks(Pageable pageable) {
107+
public Page<TaskResponseDTO> getAllMyTasks(Pageable pageable, String search) {
114108
Long currentUserId = securityFacade.getCurrentUserId()
115109
.orElseThrow(() -> new AuthorizationException("User must be logged in to view created tasks",
116110
ErrorCode.ACCESS_DENIED));
117-
log.debug("getAllMyTasks: userId={}, page={}, size={}, sort={}",
118-
currentUserId, pageable.getPageNumber(), pageable.getPageSize(), pageable.getSort());
111+
log.debug("getAllMyTasks: userId={}, page={}, size={}, sort={}, search={}",
112+
currentUserId, pageable.getPageNumber(), pageable.getPageSize(), pageable.getSort(), search);
119113

120-
Page<TaskBody> myTasksPage = taskBodyRepository.findAllByOwnerId(currentUserId, pageable);
114+
String normalizedSearch = getNormalizedSearch(search);
115+
116+
Page<TaskBody> myTasksPage = taskBodyRepository.findAllByOwnerId(currentUserId, normalizedSearch, pageable);
121117

122118
return getResponseBulk(myTasksPage);
123119
}
@@ -322,4 +318,14 @@ private Map<Long, String> getUserEmailsByIdsInBulk(List<Long> userIds) {
322318
return userFacade.findByIds(userIds).stream()
323319
.collect(Collectors.toMap(UserAuthDetails::id, UserAuthDetails::email));
324320
}
321+
322+
private String getNormalizedSearch(String search) {
323+
return search == null || search.isBlank()
324+
? ""
325+
: search.trim()
326+
.replaceAll("\\s+", " ")
327+
.replace("\\", "\\\\")
328+
.replace("%", "\\%")
329+
.replace("_", "\\_");
330+
}
325331
}

src/main/java/com/itasocialacademy/oitassist/task/service/interfaces/TaskService.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,15 @@ public interface TaskService {
4747

4848
/**
4949
* Retrieves all tasks belonging to the currently authenticated user with
50-
* pagination support.
50+
* optional search by title and pagination support.
5151
*
5252
* @param pageable pagination and sorting criteria
53+
* @param search optional search query
5354
* @return a page of tasks for the current user according to the specified
5455
* pagination parameters
5556
* @throws AuthorizationException if the user is not authenticated or logged in
5657
*/
57-
Page<TaskResponseDTO> getAllMyTasks(Pageable pageable);
58+
Page<TaskResponseDTO> getAllMyTasks(Pageable pageable, String search);
5859

5960
/**
6061
* Updates a task's title, description and manages file attachments. Only the

src/test/java/com/itasocialacademy/oitassist/task/controller/TaskControllerTest.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ void getAllTasks_nonAdmin_shouldReturn403() throws Exception {
176176
@Test
177177
void getMyTasks_shouldReturnPageResponseAnd200() throws Exception {
178178
Page<TaskResponseDTO> page = new PageImpl<>(List.of(mockTaskResponse));
179-
when(taskService.getAllMyTasks(any(Pageable.class))).thenReturn(page);
179+
when(taskService.getAllMyTasks(any(Pageable.class), eq(null))).thenReturn(page);
180180

181181
mockMvc.perform(get("/api/v1/tasks/my")
182182
.param("page", "0")
@@ -188,7 +188,26 @@ void getMyTasks_shouldReturnPageResponseAnd200() throws Exception {
188188
.andExpect(jsonPath("$.pageNumber").value(0))
189189
.andExpect(jsonPath("$.totalElements").value(1));
190190

191-
verify(taskService).getAllMyTasks(any(Pageable.class));
191+
verify(taskService).getAllMyTasks(any(Pageable.class), eq(null));
192+
}
193+
194+
@Test
195+
void getMyTasks_withSearch_shouldReturnFilteredPageResponseAnd200() throws Exception {
196+
Page<TaskResponseDTO> page = new PageImpl<>(List.of(mockTaskResponse));
197+
when(taskService.getAllMyTasks(any(Pageable.class), eq("PowerPoint"))).thenReturn(page);
198+
199+
mockMvc.perform(get("/api/v1/tasks/my")
200+
.param("page", "0")
201+
.param("size", "15")
202+
.param("search", "PowerPoint"))
203+
.andExpect(status().isOk())
204+
.andExpect(jsonPath("$.content").isArray())
205+
.andExpect(jsonPath("$.content[0].id").value(1L))
206+
.andExpect(jsonPath("$.content[0].title").value("PowerPoint Різдвяна зірка"))
207+
.andExpect(jsonPath("$.pageNumber").value(0))
208+
.andExpect(jsonPath("$.totalElements").value(1));
209+
210+
verify(taskService).getAllMyTasks(any(Pageable.class), eq("PowerPoint"));
192211
}
193212

194213
// ---- updateTask ----

src/test/java/com/itasocialacademy/oitassist/task/service/TaskServiceTest.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -308,18 +308,20 @@ void getAllTasks_whenEmpty_shouldReturnEmptyPage() {
308308
void getAllMyTasks_shouldReturnCurrentUserTasks() {
309309
Pageable pageable = PageRequest.of(0, 15);
310310
Page<TaskBody> expectedRepositoryPage = new PageImpl<>(List.of(taskBody), pageable, 1);
311+
String search = " scratch ";
312+
String normalizedSearch = "scratch";
311313

312314
when(securityFacade.getCurrentUserId()).thenReturn(Optional.of(100L));
313-
when(taskBodyRepository.findAllByOwnerId(100L, pageable)).thenReturn(expectedRepositoryPage);
315+
when(taskBodyRepository.findAllByOwnerId(100L, normalizedSearch, pageable)).thenReturn(expectedRepositoryPage);
314316
when(fileManagerFacade.getFilesByEntities(any(), eq(List.of(1L)), any())).thenReturn(Map.of(1L, testFiles));
315317
when(taskBodyMapper.toResponse(taskBody, testFiles, "creator@mail.com")).thenReturn(taskResponse);
316318

317-
Page<TaskResponseDTO> result = taskService.getAllMyTasks(pageable);
319+
Page<TaskResponseDTO> result = taskService.getAllMyTasks(pageable, search);
318320

319321
assertNotNull(result);
320322
assertEquals(1, result.getTotalElements());
321323
verify(securityFacade).getCurrentUserId();
322-
verify(taskBodyRepository).findAllByOwnerId(100L, pageable);
324+
verify(taskBodyRepository).findAllByOwnerId(100L, normalizedSearch, pageable);
323325
verify(taskBodyMapper).toResponse(taskBody, testFiles, "creator@mail.com");
324326
}
325327

@@ -329,9 +331,9 @@ void getAllMyTasks_notLoggedIn_shouldThrowAuthorizationException() {
329331

330332
when(securityFacade.getCurrentUserId()).thenReturn(Optional.empty());
331333

332-
assertThrows(AuthorizationException.class, () -> taskService.getAllMyTasks(pageable));
334+
assertThrows(AuthorizationException.class, () -> taskService.getAllMyTasks(pageable, null));
333335

334-
verify(taskBodyRepository, never()).findAllByOwnerId(any(), any());
336+
verify(taskBodyRepository, never()).findAllByOwnerId(any(), any(), any());
335337
}
336338

337339
// ---- updateTask ----

0 commit comments

Comments
 (0)