diff --git a/backend/src/main/java/io/f1/backend/domain/admin/api/AdminController.java b/backend/src/main/java/io/f1/backend/domain/admin/api/AdminController.java index 096681d7..472f7783 100644 --- a/backend/src/main/java/io/f1/backend/domain/admin/api/AdminController.java +++ b/backend/src/main/java/io/f1/backend/domain/admin/api/AdminController.java @@ -6,10 +6,12 @@ import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -21,8 +23,15 @@ public class AdminController { @LimitPageSize @GetMapping("/users") - public ResponseEntity getUsers(Pageable pageable) { - UserPageResponse response = adminService.getAllUsers(pageable); + public ResponseEntity getUsers( + @RequestParam(required = false) String nickname, Pageable pageable) { + UserPageResponse response; + + if (StringUtils.isBlank(nickname)) { + response = adminService.getAllUsers(pageable); + } else { + response = adminService.searchUsersByNickname(nickname, pageable); + } return ResponseEntity.ok().body(response); } } diff --git a/backend/src/main/java/io/f1/backend/domain/admin/app/AdminService.java b/backend/src/main/java/io/f1/backend/domain/admin/app/AdminService.java index 66621538..15dae5e6 100644 --- a/backend/src/main/java/io/f1/backend/domain/admin/app/AdminService.java +++ b/backend/src/main/java/io/f1/backend/domain/admin/app/AdminService.java @@ -24,4 +24,10 @@ public UserPageResponse getAllUsers(Pageable pageable) { Page users = userRepository.findAllUsersWithPaging(pageable); return toUserListPageResponse(users); } + + @Transactional(readOnly = true) + public UserPageResponse searchUsersByNickname(String nickname, Pageable pageable) { + Page users = userRepository.findUsersByNicknameContaining(nickname, pageable); + return toUserListPageResponse(users); + } } diff --git a/backend/src/main/java/io/f1/backend/domain/user/app/UserService.java b/backend/src/main/java/io/f1/backend/domain/user/app/UserService.java index bdf86e96..4db37734 100644 --- a/backend/src/main/java/io/f1/backend/domain/user/app/UserService.java +++ b/backend/src/main/java/io/f1/backend/domain/user/app/UserService.java @@ -66,7 +66,7 @@ private void validateNicknameFormat(String nickname) { @Transactional(readOnly = true) public void validateNicknameDuplicate(String nickname) { - if (userRepository.existsUserByNickname(nickname)) { + if (userRepository.existsUserByNicknameIgnoreCase(nickname)) { throw new CustomException(UserErrorCode.NICKNAME_CONFLICT); } } diff --git a/backend/src/main/java/io/f1/backend/domain/user/dao/UserRepository.java b/backend/src/main/java/io/f1/backend/domain/user/dao/UserRepository.java index 0cb50caf..fe06a352 100644 --- a/backend/src/main/java/io/f1/backend/domain/user/dao/UserRepository.java +++ b/backend/src/main/java/io/f1/backend/domain/user/dao/UserRepository.java @@ -16,10 +16,16 @@ public interface UserRepository extends JpaRepository { Optional findByProviderAndProviderId(String provider, String providerId); - Boolean existsUserByNickname(String nickname); + Boolean existsUserByNicknameIgnoreCase(String nickname); @Query( "SELECT new io.f1.backend.domain.admin.dto.UserResponse(u.id, u.nickname, u.lastLogin," + " u.createdAt)FROM User u ORDER BY u.id") Page findAllUsersWithPaging(Pageable pageable); + + @Query( + "SELECT new io.f1.backend.domain.admin.dto.UserResponse(u.id, u.nickname, u.lastLogin," + + " u.createdAt) FROM User u WHERE LOWER(u.nickname) LIKE CONCAT('%'," + + " LOWER(:nickname), '%')") + Page findUsersByNicknameContaining(String nickname, Pageable pageable); } diff --git a/backend/src/test/java/io/f1/backend/domain/admin/app/AdminServiceTests.java b/backend/src/test/java/io/f1/backend/domain/admin/app/AdminServiceTests.java index b3db7eea..357ff7b5 100644 --- a/backend/src/test/java/io/f1/backend/domain/admin/app/AdminServiceTests.java +++ b/backend/src/test/java/io/f1/backend/domain/admin/app/AdminServiceTests.java @@ -1,5 +1,6 @@ package io.f1.backend.domain.admin.app; +import static org.hamcrest.Matchers.hasSize; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -35,18 +36,34 @@ void totalUser() throws Exception { @Test @DataSet("datasets/admin/sorted-user.yml") @DisplayName("유저 목록이 id 순으로 정렬되어 반환된다") - void getUsersSortedByLastLogin() throws Exception { + void getUsersSortedByUserId() throws Exception { // when ResultActions result = mockMvc.perform(get("/admin/users")); // then result.andExpectAll( status().isOk(), jsonPath("$.totalElements").value(3), - // 가장 최근 로그인한 USER3이 첫 번째 jsonPath("$.users[0].id").value(1), - // 중간 로그인한 USER2가 두 번째 jsonPath("$.users[1].id").value(2), - // 가장 오래된 로그인한 USER1이 세 번째 + jsonPath("$.users[2].id").value(3)); + } + + @Test + @DataSet("datasets/admin/search-user.yml") + @DisplayName("특정 닉네임이 포함된 유저들의 정보를 조회한다") + void searchUsersByNickname() throws Exception { + // given + String searchNickname = "us"; + // when + ResultActions result = + mockMvc.perform(get("/admin/users").param("nickname", searchNickname)); + // then + result.andExpectAll( + status().isOk(), + jsonPath("$.totalElements").value(3), + jsonPath("$.users", hasSize(3)), + jsonPath("$.users[0].id").value(1), + jsonPath("$.users[1].id").value(2), jsonPath("$.users[2].id").value(3)); } } diff --git a/backend/src/test/resources/datasets/admin/search-user.yml b/backend/src/test/resources/datasets/admin/search-user.yml new file mode 100644 index 00000000..542deef1 --- /dev/null +++ b/backend/src/test/resources/datasets/admin/search-user.yml @@ -0,0 +1,22 @@ + +user_test: + - id: 1 + nickname: "US" + provider: "kakao" + provider_id: "kakao1" + last_login: 2025-07-16 09:00:00 + created_at: 2025-07-01 10:00:00 + + - id: 2 + nickname: "USE" + provider: "kakao" + provider_id: "kakao2" + last_login: 2025-07-17 12:00:00 + created_at: 2025-07-02 10:00:00 + + - id: 3 + nickname: "USER" + provider: "kakao" + provider_id: "kakao3" + last_login: 2025-07-18 15:00:00 + created_at: 2025-07-03 10:00:00 \ No newline at end of file