Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(회원) 게시글 검색 기능 #314

Merged
merged 13 commits into from
Aug 12, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,25 @@ public ResponseEntity<Void> closePostEarly(
return ResponseEntity.ok().build();
}

@Operation(summary = "게시글 검색(회원)", description = "회원으로 키워드를 통해 게시글을 검색한다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "게시글 검색 성공"),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q
쿼리 파라미터이 네이밍이 잘못된다면 400이 나가지 않나요?
만약 그렇다면 표기 되어있으면 좋을 것 같아요 :)

@ApiResponse(responseCode = "400", description = "잘못된 쿼리 파라미터값 입력"),
})
@GetMapping("/search")
public ResponseEntity<List<PostResponse>> searchPostsWithKeyword(
@RequestParam final String keyword,
@RequestParam final int page,
@RequestParam final PostClosingType postClosingType,
@RequestParam final PostSortType postSortType,
@RequestParam(required = false, name = "category") final Long categoryId,
@Auth final Member member
) {
final List<PostResponse> responses =
postService.searchPostsWithKeyword(keyword, page, postClosingType, postSortType, categoryId, member);
return ResponseEntity.ok(responses);
}

@Operation(summary = "작성한 게시글 조회", description = "회원본인이 작성한 게시글 목록을 조회한다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "회원본인이 작성한 게시글 조회 성공")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ List<Post> findAllByClosingTypeAndSortTypeAndCategoryId(
final Pageable pageable
);

List<Post> findAllWithKeyword(
final String keyword,
final PostClosingType postClosingType,
final PostSortType postSortType,
final Long categoryId,
final Pageable pageable
);

List<Post> findAllByWriterWithClosingTypeAndSortTypeAndCategoryId(
final Member writer,
final PostClosingType postClosingType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,32 @@ private OrderSpecifier orderBy(final PostSortType postSortType) {
}
}

@Override
public List<Post> findAllWithKeyword(
final String keyword,
final PostClosingType postClosingType,
final PostSortType postSortType,
final Long categoryId,
final Pageable pageable
) {
return jpaQueryFactory
.selectFrom(post)
.join(post.writer).fetchJoin()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3
fetchJoin을 다음 줄로 넘기는 것은 어떤가요??

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3
한 줄에 점 하나씩 있는 것이 저문의 말처럼 가독성은 더 좋다고 생각합니다 👍

위의 상황에서는 member와 조인하는 것을 fetch join으로 만든다는 점에서 하나의 맥락인 것 같아 같은 라인에 붙어있는 것이 코드를 이해하기는 더 쉽다고 생각이 들어서 붙여도 괜찮다고 생각하는데 이 부분에 대해서 저문은 어떻게 생각하시나요?!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3로 지정했던 만큼 크게 중요한 사안은 아니었습니다!
저도 개인적인 생각일 뿐이고, 메서드 체이닝에 대한 컨벤션이 지금까지 그래왔기에 말씀드렸었습니다.
fetchJoin을 함께 사용하는 것이 말씀을 들어보니 더 맥락을 이해하기 쉬울 것 같기도 하네요!
루쿠가 편하신대로 반영해주시면 좋을 것 같아요~

.leftJoin(post.postCategories.postCategories, postCategory)
.where(
containsKeywordInTitleOrContent(keyword),
categoryIdEq(categoryId),
deadlineEq(postClosingType)
)
.orderBy(orderBy(postSortType))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
}

private BooleanExpression containsKeywordInTitleOrContent(final String keyword) {
return post.postBody.title.contains(keyword)
.or(post.postBody.content.contains(keyword));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,24 @@ public List<PostResponse> getPostsByWriter(
.toList();
}

public List<PostResponse> searchPostsWithKeyword(
final String keyword,
final int page,
final PostClosingType postClosingType,
final PostSortType postSortType,
final Long categoryId,
final Member member
) {
final Pageable pageable = PageRequest.of(page, BASIC_PAGING_SIZE);
final List<Post> posts =
postRepository.findAllWithKeyword(keyword, postClosingType, postSortType, categoryId, pageable);

return posts.stream()
.map(post -> PostResponse.of(post, member))
.toList();
}


@Transactional
public void delete(final Long postId) {
final Post post = postRepository.findById(postId)
Expand Down Expand Up @@ -327,3 +345,4 @@ private <T, R> List<R> transformElements(final List<T> elements, final Function<
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import com.votogether.domain.post.entity.Post;
import com.votogether.domain.post.entity.PostBody;
import com.votogether.domain.post.entity.PostClosingType;
import com.votogether.domain.post.entity.PostOption;
import com.votogether.domain.post.entity.PostSortType;
import com.votogether.domain.post.service.PostService;
import com.votogether.exception.GlobalExceptionHandler;
Expand Down Expand Up @@ -659,7 +658,6 @@ void postClosedEarly() throws Exception {
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
}

@Test
@DisplayName("회원본인이 작성한 게시글 목록을 조회한다.")
void getPostsByWriter() throws JsonProcessingException {
// given
Expand Down Expand Up @@ -714,6 +712,53 @@ void getPostsByWriter() throws JsonProcessingException {
);
}

@Test
@DisplayName("키워드를 통해 게시글 목록을 조회한다.")
void searchPostsWithKeyword() throws JsonProcessingException {
// given
long postId = 1L;
Member member = MemberFixtures.FEMALE_20.get();

TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L);
given(tokenProcessor.resolveToken(anyString())).willReturn("token");
given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload);
given(memberService.findById(anyLong())).willReturn(member);

PostBody postBody = PostBody.builder()
.title("title")
.content("content")
.build();

Post post = Post.builder()
.writer(MALE_30.get())
.postBody(postBody)
.deadline(LocalDateTime.now().plusDays(3L).truncatedTo(ChronoUnit.MINUTES))
.build();

PostResponse postResponse = PostResponse.of(post, member);

given(postService.searchPostsWithKeyword(anyString(), anyInt(), any(), any(), anyLong(), any(Member.class)))
.willReturn(List.of(postResponse));

// when
List<PostResponse> result = RestAssuredMockMvc.given().log().all()
.headers(HttpHeaders.AUTHORIZATION, "Bearer token")
.param("keyword", "하이")
.param("page", 0)
.param("postClosingType", PostClosingType.PROGRESS)
.param("postSortType", PostSortType.LATEST)
.param("category", 1L)
.when().get("/posts/search")
.then().log().all()
.status(HttpStatus.OK)
.extract()
.as(new ParameterizedTypeReference<List<PostResponse>>() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q
ParameterizedTypeReference는 어떤 역할을 하나요?
List로 반환하기 위해서 사용하는건가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이해하신바가 맞습니다!

}.getType());

// then
assertThat(result.get(0)).usingRecursiveComparison().isEqualTo(postResponse);
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2
해당 기능에도 Service 테스트가 없는 것 같아요 :) 이전과 동일하게 검색한 게시글이 마감되었을 때, 투표했을 때, 내가 쓴 게시글일때 투표 결과를 잘 담고 있는지 검증해주는 것이 중요할 것 같아요!

@DisplayName("게시글을 삭제한다.")
void delete() {
// given
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,10 @@ void findClosedPostsVotedByMember() {
Slice<Post> posts = postRepository.findClosedPostsVotedByMember(member, pageRequest);

// then
assertThat(posts).hasSize(2);
assertThat(posts.getContent().get(0)).usingRecursiveComparison().isEqualTo(closedPost1);
assertAll(
() -> assertThat(posts).hasSize(2),
() -> assertThat(posts.getContent().get(0)).usingRecursiveComparison().isEqualTo(closedPost1)
);
}

@Test
Expand Down Expand Up @@ -472,8 +474,10 @@ void findOpenPostsVotedByMember() {
Slice<Post> posts = postRepository.findOpenPostsVotedByMember(member, pageRequest);

// then
assertThat(posts).hasSize(2);
assertThat(posts.getContent().get(0)).usingRecursiveComparison().isEqualTo(openPost1);
assertAll(
() -> assertThat(posts).hasSize(2),
() -> assertThat(posts.getContent().get(0)).usingRecursiveComparison().isEqualTo(openPost1)
);
}

@Test
Expand Down Expand Up @@ -526,6 +530,127 @@ void findPostsVotedByMember() {

}

@Nested
@DisplayName("키워드 검색을 통해 게시글 목록을 조회한다.")
class FindingPostsByKeyword {

Category development;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2
개행이 필요해보입니다!

Category love;

Post devClosedPost;
Post devOpenPost;
Post loveClosedPost;
Post loveOpenPost;

@BeforeEach
void setUp() {
development = categoryRepository.save(Category.builder().name("개발").build());
love = categoryRepository.save(Category.builder().name("연애").build());

devClosedPost = postTestPersister.builder()
.postBody(PostBody.builder().title("자바제목").content("자바내용").build())
.deadline(LocalDateTime.of(1000, 7, 12, 0, 0))
.save();
postCategoryRepository.save(PostCategory.builder().post(devClosedPost).category(development).build());

devOpenPost = postTestPersister.builder()
.postBody(PostBody.builder().title("자바제목1").content("자바내용1").build())
.deadline(LocalDateTime.of(3000, 7, 12, 0, 0))
.save();
postCategoryRepository.save(PostCategory.builder().post(devOpenPost).category(development).build());

loveClosedPost = postTestPersister.builder()
.postBody(PostBody.builder().title("커플제목").content("커플내용").build())
.deadline(LocalDateTime.of(1000, 7, 12, 0, 0))
.save();
postCategoryRepository.save(PostCategory.builder().post(loveClosedPost).category(love).build());

loveOpenPost = postTestPersister.builder()
.postBody(PostBody.builder().title("커플제목1").content("커플내용1").build())
.deadline(LocalDateTime.of(3000, 7, 12, 0, 0))
.save();
postCategoryRepository.save(PostCategory.builder().post(loveOpenPost).category(love).build());

}

@Test
@DisplayName("특정 카테고리에 속한 게시글 목록을 키워드를 통해 검색한다.")
void searchPostsWithCategory() {
// when
List<Post> posts = postRepository.findAllWithKeyword(
"자바",
PostClosingType.ALL,
PostSortType.LATEST,
development.getId(),
PageRequest.of(0, 10)
);

//then
assertAll(
() -> assertThat(posts).hasSize(2),
() -> assertThat(posts.get(0)).isEqualTo(devOpenPost),
() -> assertThat(posts.get(1)).isEqualTo(devClosedPost)
);
}

@Test
@DisplayName("게시글 목록을 키워드를 통해 검색한다.(제목)")
void searchPostsWithKeywordInTitle() {
// when
List<Post> posts = postRepository.findAllWithKeyword(
"제목1",
PostClosingType.ALL,
PostSortType.LATEST,
null,
PageRequest.of(0, 10)
);

//then
assertAll(
() -> assertThat(posts).hasSize(2),
() -> assertThat(posts.get(0)).isEqualTo(loveOpenPost),
() -> assertThat(posts.get(1)).isEqualTo(devOpenPost)
);
}

@Test
@DisplayName("게시글 목록을 키워드를 통해 검색한다.(내용)")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2
제목과 내용에 둘다 포함되어 있는 키워드로 검색했을 때도 잘 가져오는지에 대한 테스트도 있으면 좋을 것 같아요 :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제목 or 내용으로 검색하는건데 중복되서 테스트 하는건 아닐까요,,,?? 일단 추가해보았습니다!

void searchPostsWithKeywordInContent() {
// when
List<Post> posts = postRepository.findAllWithKeyword(
"내용",
PostClosingType.ALL,
PostSortType.LATEST,
null,
PageRequest.of(0, 10)
);

//then
assertThat(posts).hasSize(4);
}

@Test
@DisplayName("게시글 목록을 키워드를 통해 검색한다.(제목 + 내용)")
void searchPostsWithKeywordInTitleAndContent() {
// when
List<Post> posts = postRepository.findAllWithKeyword(
"1",
PostClosingType.ALL,
PostSortType.LATEST,
null,
PageRequest.of(0, 10)
);

//then
assertAll(
() -> assertThat(posts).hasSize(2),
() -> assertThat(posts.get(0)).isEqualTo(loveOpenPost),
() -> assertThat(posts.get(1)).isEqualTo(devOpenPost)
);
}

}

@Nested
@DisplayName("회원이 작성한 게시글 목록을 조회한다.")
class findPostsByWriter {
Expand Down
Loading