-
Notifications
You must be signed in to change notification settings - Fork 3
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
[feat] 게시물 목록 기능 구현 #30
Merged
Merged
Changes from 11 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
0696489
feat : 해쉬태그로 리스트 불러오기
K-0joo bd87f87
feat : 해쉬태그 및 type 입력받아 리스트 불러오기
K-0joo e4d6cc1
refactor : 해쉬태그 및 type 입력받아 리스트 불러오기
K-0joo 963459e
feat :오름차순, 내림차순으로 정렬하기
K-0joo 2c777e3
feat :게시물 목록 search_by 쿼리 내용 추가
K-0joo 62222d9
feat :게시물 목록 페이지네이션 추가
K-0joo 084283a
Merge remote-tracking branch 'origin/dev' into feat/content_list_search
K-0joo d2f3c47
feat :게시물 목록 기능 구현
K-0joo 1888f86
feat :게시물 목록 기능 에러핸들링 추가
K-0joo a671189
feat :게시물 목록 기능 테스트 추가
K-0joo 6f06467
refactor :게시물 목록 중복 파일 제거 및 어노테이션 수정, EOL 반영
K-0joo 9454ccf
refactor :게시물 목록 final 추가
K-0joo b857c5a
Merge branch 'dev' into feat/content_list_search
K-0joo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,18 @@ | ||
package wanted.media.exception; | ||
|
||
import org.springframework.http.HttpStatus; | ||
|
||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
|
||
@Getter | ||
@RequiredArgsConstructor | ||
public enum ErrorCode { | ||
ENTITY_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 엔티티입니다."); | ||
ENTITY_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 엔티티입니다."), | ||
INVALID_INPUT_VALUE(HttpStatus.BAD_REQUEST, "클라이언트의 입력 값을 확인해주세요."), | ||
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류가 발생했습니다."), | ||
UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "인증이 필요합니다."), | ||
FORBIDDEN(HttpStatus.FORBIDDEN, "권한이 없습니다."); | ||
|
||
private final HttpStatus status; | ||
private final String message; | ||
private final HttpStatus status; | ||
private final String message; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
src/main/java/wanted/media/exception/PostListCustomException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package wanted.media.exception; | ||
|
||
import lombok.Getter; | ||
|
||
@Getter | ||
public class PostListCustomException extends RuntimeException { | ||
private final ErrorCode errorCode; | ||
|
||
public PostListCustomException(ErrorCode errorCode) { | ||
super(errorCode.getMessage()); | ||
this.errorCode = errorCode; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 38 additions & 1 deletion
39
src/main/java/wanted/media/post/controller/PostController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,46 @@ | ||
package wanted.media.post.controller; | ||
|
||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.data.domain.Page; | ||
import org.springframework.http.HttpStatus; | ||
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; | ||
import wanted.media.post.domain.Post; | ||
import wanted.media.post.domain.Type; | ||
import wanted.media.post.dto.PostDto; | ||
import wanted.media.post.service.PostService; | ||
|
||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
@RestController | ||
@RequestMapping("/posts") | ||
@RequestMapping("/api/posts") | ||
public class PostController { | ||
@Autowired | ||
private PostService postService; | ||
|
||
@GetMapping | ||
public ResponseEntity<List<PostDto>> list(@RequestParam(value = "hashtag", required = true) String account, | ||
@RequestParam(value = "type", required = false) Type type, | ||
@RequestParam(value = "orderBy", defaultValue = "createdAt") String orderBy, | ||
@RequestParam(value = "sortDirection", defaultValue = "ASC") String sortDirection, | ||
@RequestParam(value = "search_by", defaultValue = "title, content") String searchBy, | ||
@RequestParam(value = "search", required = false) String search, | ||
@RequestParam(value = "page", defaultValue = "0") int page, | ||
@RequestParam(value = "page_count", defaultValue = "10") int pageCount) { | ||
Page<Post> postPage = postService.findPosts(account, type, orderBy, sortDirection, searchBy, search, page, pageCount); | ||
List<PostDto> postDtos = postPage.getContent().stream() | ||
.map(PostDto::allPosts) | ||
.collect(Collectors.toList()); | ||
|
||
if (postDtos.isEmpty()) { | ||
return new ResponseEntity<>(HttpStatus.NO_CONTENT); | ||
} | ||
|
||
return new ResponseEntity<>(postDtos, HttpStatus.OK); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package wanted.media.post.dto; | ||
|
||
import wanted.media.post.domain.Post; | ||
import wanted.media.post.domain.Type; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
public record PostDto( | ||
String id, | ||
Long likeCount, | ||
Type type, | ||
String title, | ||
String content, | ||
String hashtags, | ||
Long viewCount, | ||
Long shareCount, | ||
LocalDateTime updatedAt, | ||
LocalDateTime createdAt, | ||
String account | ||
) { | ||
public static PostDto allPosts(Post post) { | ||
return new PostDto( | ||
post.getId(), | ||
post.getLikeCount(), | ||
post.getType(), | ||
post.getTitle(), | ||
post.getContent(), | ||
post.getHashtags(), | ||
post.getViewCount(), | ||
post.getShareCount(), | ||
post.getUpdatedAt(), | ||
post.getCreatedAt(), | ||
post.getUser().getAccount() | ||
); | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
src/main/java/wanted/media/post/repository/PostRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package wanted.media.post.repository; | ||
|
||
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.Query; | ||
import org.springframework.data.repository.query.Param; | ||
import wanted.media.post.domain.Post; | ||
import wanted.media.post.domain.Type; | ||
|
||
public interface PostRepository extends JpaRepository<Post, Long> { | ||
@Query("SELECT p FROM Post p " + | ||
"WHERE p.user.account = :account " + | ||
"AND (:type IS NULL OR p.type = :type) " + | ||
"AND ((:searchBy = 'title' AND LOWER(p.title) LIKE LOWER(CONCAT('%', :search, '%'))) " + | ||
"OR (:searchBy = 'content' AND LOWER(p.content) LIKE LOWER(CONCAT('%', :search, '%'))) " + | ||
"OR (:searchBy = 'title,content' AND (LOWER(p.title) LIKE LOWER(CONCAT('%', :search, '%')) " + | ||
"OR LOWER(p.content) LIKE LOWER(CONCAT('%', :search, '%')))))") | ||
Page<Post> findBySearchContaining(@Param("account") String account, @Param("type") Type type, | ||
@Param("searchBy") String searchBy, @Param("search") String search, | ||
Pageable pageable); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,28 @@ | ||
package wanted.media.post.service; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
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.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
import wanted.media.post.domain.Post; | ||
import wanted.media.post.domain.Type; | ||
import wanted.media.post.repository.PostRepository; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class PostService { | ||
private PostRepository postRepository; | ||
|
||
@Transactional(readOnly = true) | ||
public Page<Post> findPosts(String account, Type type, String orderBy, String sortDirection, String searchBy, String search, int page, int pageCount) { | ||
Sort sort = Sort.by(Sort.Direction.fromString(sortDirection), orderBy); | ||
Pageable pageable = PageRequest.of(page, pageCount, sort); | ||
|
||
Page<Post> posts = postRepository.findBySearchContaining(account, type, searchBy, search, pageable); | ||
|
||
return posts; | ||
} | ||
} |
94 changes: 94 additions & 0 deletions
94
src/test/java/wanted/media/post/controller/PostControllerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package wanted.media.post.controller; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.boot.test.context.SpringBootTest; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.test.context.TestPropertySource; | ||
import org.springframework.test.web.servlet.MockMvc; | ||
|
||
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; | ||
|
||
@SpringBootTest | ||
@RequiredArgsConstructor | ||
@TestPropertySource(locations = "classpath:application-test.yml") | ||
class PostControllerTest { | ||
private MockMvc mockMvc; | ||
|
||
@Test | ||
void posts_list_성공() throws Exception { | ||
mockMvc.perform(get("/api/posts") | ||
.param("hashtag", "wanted") | ||
.param("type", "FACEBOOK") | ||
.param("orderBy", "createdAt") | ||
.param("sortDirection", "ASC") | ||
.param("search_by", "title") | ||
.param("search", "판교") | ||
.param("page", "0") | ||
.param("page_count", "10") | ||
.contentType(MediaType.APPLICATION_JSON)) | ||
.andExpect(status().isOk()) | ||
.andExpect(jsonPath("$").isNotEmpty()); // 응답이 비어있지 않음을 확인 | ||
} | ||
|
||
@Test | ||
void search_by_성공() throws Exception { | ||
mockMvc.perform(get("/api/posts") | ||
.param("hashtag", "wanted") | ||
.param("type", "FACEBOOK") | ||
.param("orderBy", "createdAt") | ||
.param("sortDirection", "ASC") | ||
.param("search_by", "invalidSearchBy") | ||
.param("page", "0") | ||
.param("page_count", "10") | ||
.contentType(MediaType.APPLICATION_JSON)) | ||
.andExpect(status().isBadRequest()); // 잘못된 검색 조건에 대한 에러 확인 | ||
} | ||
|
||
@Test | ||
void 유효하지_않은_page값() throws Exception { | ||
mockMvc.perform(get("/api/posts") | ||
.param("hashtag", "wanted") | ||
.param("type", "FACEBOOK") | ||
.param("orderBy", "createdAt") | ||
.param("sortDirection", "ASC") | ||
.param("search_by", "title") | ||
.param("search", "판교") | ||
.param("page", "-1") // 유효하지 않은 페이지 번호 | ||
.param("page_count", "10") | ||
.contentType(MediaType.APPLICATION_JSON)) | ||
.andExpect(status().isBadRequest()); // 페이지 값이 유효하지 않음을 확인 | ||
} | ||
|
||
@Test | ||
void 유효하지_않은_page_count_값() throws Exception { | ||
mockMvc.perform(get("/api/posts") | ||
.param("hashtag", "wanted") | ||
.param("type", "FACEBOOK") | ||
.param("orderBy", "createdAt") | ||
.param("sortDirection", "ASC") | ||
.param("search_by", "title") | ||
.param("search", "판교") | ||
.param("page", "0") | ||
.param("page_count", "-10") // 유효하지 않은 페이지 수 | ||
.contentType(MediaType.APPLICATION_JSON)) | ||
.andExpect(status().isBadRequest()); // 페이지 수가 유효하지 않음을 확인 | ||
} | ||
|
||
@Test | ||
void search_키워드가_없을_때() throws Exception { | ||
mockMvc.perform(get("/api/posts") | ||
.param("hashtag", "wanted") | ||
.param("type", "FACEBOOK") | ||
.param("orderBy", "createdAt") | ||
.param("sortDirection", "ASC") | ||
.param("search_by", "title") | ||
.param("page", "0") | ||
.param("page_count", "10") | ||
.contentType(MediaType.APPLICATION_JSON)) | ||
.andExpect(status().isOk()) | ||
.andExpect(jsonPath("$").isNotEmpty()); // 응답이 비어있지 않음을 확인 | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final을 붙여주셔야
@RequiredArgsConstructor
가 동작하는 걸로 알고 있어용