diff --git a/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java b/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java index b89da14a..2655cca6 100644 --- a/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java +++ b/back/src/main/java/com/back/domain/post/comment/controller/PostCommentController.java @@ -20,7 +20,6 @@ @RestController @RequestMapping("/post/comment") -@RequiredArgsConstructor public class PostCommentController { @Autowired private Rq rq; diff --git a/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java b/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java index 63513c42..47415bf9 100644 --- a/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java +++ b/back/src/main/java/com/back/domain/post/post/controller/InformationPostController.java @@ -8,7 +8,12 @@ import com.back.global.rsData.RsData; import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; +import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -21,6 +26,19 @@ public class InformationPostController { private final Rq rq; + @Operation(summary = "게시글 조회 - 페이징 처리") + @GetMapping + public RsData getPostWithPage( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) String keyword + ) { + Page postPage = postService.getPosts(keyword, page,size); + PostPagingResponse resDto = PostPagingResponse.from(postPage); + + return new RsData<>("200", "게시글이 조회 되었습니다.", resDto); + } + @Operation(summary = "게시글 생성") @PostMapping public RsData createPost( @@ -34,7 +52,7 @@ public RsData createPost( } @Operation(summary = "게시글 다건 조회") - @GetMapping + @GetMapping("/all") public RsData> getAllPost() { List postAllResponse = postService.getAllPostResponse(); diff --git a/back/src/main/java/com/back/domain/post/post/dto/PostDto.java b/back/src/main/java/com/back/domain/post/post/dto/PostDto.java new file mode 100644 index 00000000..e2a50698 --- /dev/null +++ b/back/src/main/java/com/back/domain/post/post/dto/PostDto.java @@ -0,0 +1,18 @@ +package com.back.domain.post.post.dto; + + +import com.back.domain.post.post.entity.Post; + +public record PostDto( + Long postId, + String title, + String content +) { + public static PostDto from(Post post) { + return new PostDto( + post.getId(), + post.getTitle(), + post.getContent() + ); + } +} diff --git a/back/src/main/java/com/back/domain/post/post/dto/PostPagingResponse.java b/back/src/main/java/com/back/domain/post/post/dto/PostPagingResponse.java new file mode 100644 index 00000000..cdc08383 --- /dev/null +++ b/back/src/main/java/com/back/domain/post/post/dto/PostPagingResponse.java @@ -0,0 +1,32 @@ +package com.back.domain.post.post.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.data.domain.Page; + +import java.util.List; + +public record PostPagingResponse( + @Schema(description = "게시글 목록") + List posts, + @Schema(description = "현재 페이지 (0부터 시작)") + int currentPage, + @Schema(description = "총 페이지") + int totalPage, + @Schema(description = "총 개수") + long totalElements, + @Schema(description = "다음 페이지 존재 여부") + boolean hasNext +) { + + public static PostPagingResponse from(Page page) { + return new PostPagingResponse( + page.getContent(), + page.getNumber(), + page.getTotalPages(), + page.getTotalElements(), + page.hasNext() + ); + } + +} + diff --git a/back/src/main/java/com/back/domain/post/post/repository/PostRepository.java b/back/src/main/java/com/back/domain/post/post/repository/PostRepository.java index d48774c3..cb6f9cfe 100644 --- a/back/src/main/java/com/back/domain/post/post/repository/PostRepository.java +++ b/back/src/main/java/com/back/domain/post/post/repository/PostRepository.java @@ -1,10 +1,12 @@ package com.back.domain.post.post.repository; import com.back.domain.post.post.entity.Post; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository -public interface PostRepository extends JpaRepository { +public interface PostRepository extends JpaRepository , PostRepositoryCustom{ } diff --git a/back/src/main/java/com/back/domain/post/post/repository/PostRepositoryCustom.java b/back/src/main/java/com/back/domain/post/post/repository/PostRepositoryCustom.java new file mode 100644 index 00000000..52ed920d --- /dev/null +++ b/back/src/main/java/com/back/domain/post/post/repository/PostRepositoryCustom.java @@ -0,0 +1,9 @@ +package com.back.domain.post.post.repository; + +import com.back.domain.post.post.entity.Post; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface PostRepositoryCustom { + Page searchPosts(String keyword, Pageable pageable); +} diff --git a/back/src/main/java/com/back/domain/post/post/repository/PostRepositoryImpl.java b/back/src/main/java/com/back/domain/post/post/repository/PostRepositoryImpl.java new file mode 100644 index 00000000..0ac4506c --- /dev/null +++ b/back/src/main/java/com/back/domain/post/post/repository/PostRepositoryImpl.java @@ -0,0 +1,57 @@ +package com.back.domain.post.post.repository; + +import com.back.domain.member.member.entity.QMember; +import com.back.domain.post.post.entity.Post; +import com.back.domain.post.post.entity.QPost; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +@RequiredArgsConstructor +public class PostRepositoryImpl implements PostRepositoryCustom{ + private final JPAQueryFactory queryFactory; + + @Override + public Page searchPosts(String keyword, Pageable pageable) { + QPost post = QPost.post; + QMember member = QMember.member; + + BooleanBuilder builder = new BooleanBuilder(); + + if(keyword != null && !keyword.isBlank()) { + builder.and( + post.title.containsIgnoreCase(keyword) + .or(post.authorName.containsIgnoreCase(keyword)) + ); + } + + List content = queryFactory + .selectFrom(post) + .where(builder) + .orderBy(post.createDate.desc()) + .offset(pageable.getOffset())// 이거뭐임 + .limit(pageable.getPageSize()) + .fetch(); + + long total = getTotal(post, builder); + + return new PageImpl<>(content,pageable,total); + + } + + private long getTotal(QPost post, BooleanBuilder builder) { + Long totalCount = queryFactory + .select(post.count()) + .from(post) + .where(builder) + .fetchOne(); + + return totalCount != null ? totalCount : 0L; + } + +} diff --git a/back/src/main/java/com/back/domain/post/post/service/PostService.java b/back/src/main/java/com/back/domain/post/post/service/PostService.java index fe37fe37..b924cd53 100644 --- a/back/src/main/java/com/back/domain/post/post/service/PostService.java +++ b/back/src/main/java/com/back/domain/post/post/service/PostService.java @@ -1,13 +1,20 @@ package com.back.domain.post.post.service; import com.back.domain.member.member.entity.Member; +import com.back.domain.mentoring.mentoring.dto.MentoringDto; import com.back.domain.post.post.dto.PostAllResponse; import com.back.domain.post.post.dto.PostCreateRequest; +import com.back.domain.post.post.dto.PostDto; import com.back.domain.post.post.entity.Post; import com.back.domain.post.post.repository.PostRepository; +import com.back.domain.post.post.repository.PostRepositoryCustom; import com.back.global.exception.ServiceException; +import jakarta.transaction.Transactional; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +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.List; @@ -24,7 +31,7 @@ public List getAllPosts() { return posts; } - + @Transactional public Post createPost(PostCreateRequest postCreateRequest, Member member) { String postTypeStr = postCreateRequest.getPostType(); @@ -47,16 +54,11 @@ public Post createPost(PostCreateRequest postCreateRequest, Member member) { } private void validPostType(String postTypeStr) { - boolean eq = false; - - String[] validType = new String[3]; - validType[0] = "INFORMATIONPOST"; - validType[1] = "PRACTICEPOST"; - validType[2] = "QUESTIONPOST"; - - for(String x : validType) if(x.equals(postTypeStr)) eq = true; - - if(!eq) throw new ServiceException("400-2", "유효하지 않은 PostType입니다."); + try { + Post.PostType.valueOf(postTypeStr); + } catch (IllegalArgumentException e) { + throw new ServiceException("400-2", "유효하지 않은 PostType입니다."); + } } @@ -70,7 +72,7 @@ public List getAllPostResponse() { .map(PostAllResponse::new) .toList(); } - + @Transactional public void removePost(Long postId, Member member) { Post post = postRepository.findById(postId).orElseThrow(() -> new ServiceException("400", "해당 Id의 게시글이 없습니다.")); Long authorId = post.getMember().getId(); @@ -78,7 +80,7 @@ public void removePost(Long postId, Member member) { postRepository.delete(post); } - + @Transactional public void updatePost(long postId, Member member, @Valid PostCreateRequest postCreateRequest) { Post post = postRepository.findById(postId).orElseThrow(() -> new ServiceException("400", "해당 Id의 게시글이 없습니다.")); Long authorId = post.getMember().getId(); @@ -89,23 +91,31 @@ public void updatePost(long postId, Member member, @Valid PostCreateRequest post postRepository.save(post); } - + @Transactional public void likePost(long postId) { Post post = postRepository.findById(postId).orElseThrow(() -> new ServiceException("400", "해당 Id의 게시글이 없습니다.")); post.setLiked(post.getLiked()+1); } - + @Transactional public void disLikePost(long postId) { Post post = postRepository.findById(postId).orElseThrow(() -> new ServiceException("400", "해당 Id의 게시글이 없습니다.")); post.setLiked(post.getLiked()-1); } - + @Transactional() public int showLikeCount(long postId) { Post post = postRepository.findById(postId).orElseThrow(() -> new ServiceException("400", "해당 Id의 게시글이 없습니다.")); int count = post.getLiked(); return count; } + + + public Page getPosts(String keyword, int page, int size) { + Pageable pageable = PageRequest.of(page,size); + + + return postRepository.searchPosts(keyword, pageable).map(PostDto::from); + } } diff --git a/back/src/test/java/com/back/domain/post/post/controller/InformationPostControllerTest.java b/back/src/test/java/com/back/domain/post/post/controller/InformationPostControllerTest.java index 27e07966..e4d82433 100644 --- a/back/src/test/java/com/back/domain/post/post/controller/InformationPostControllerTest.java +++ b/back/src/test/java/com/back/domain/post/post/controller/InformationPostControllerTest.java @@ -151,22 +151,106 @@ void t2() throws Exception { } @Test - @DisplayName("게시글 다건조회") + @DisplayName("게시글 조회 - 페이징 처리") void t3() throws Exception { + // 테스트용 게시글 먼저 생성 + mvc.perform( + post("/post/infor") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "memberId": 1, + "postType": "INFORMATIONPOST", + "title": "페이징 테스트 제목 1", + "content": "페이징 테스트 내용 1" + } + """) + ); + + mvc.perform( + post("/post/infor") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "memberId": 1, + "postType": "INFORMATIONPOST", + "title": "페이징 테스트 제목 2", + "content": "페이징 테스트 내용 2" + } + """) + ); + + // 페이징 조회 테스트 - 기본값 ResultActions resultActions = mvc .perform( get("/post/infor") ) .andDo(print()); + resultActions + .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().methodName("getPostWithPage")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").exists()) + .andExpect(jsonPath("$.data.posts").isArray()) + .andExpect(jsonPath("$.data.currentPage").value(0)) + .andExpect(jsonPath("$.data.totalElements").exists()) + .andExpect(jsonPath("$.msg").value("게시글이 조회 되었습니다.")); + } + + @Test + @DisplayName("게시글 조회 - 페이징 처리 (파라미터 지정)") + void t3_1() throws Exception { + ResultActions resultActions = mvc + .perform( + get("/post/infor") + .param("page", "0") + .param("size", "5") + ) + .andDo(print()); resultActions .andExpect(handler().handlerType(InformationPostController.class)) - .andExpect(handler().methodName("getAllPost")) + .andExpect(handler().methodName("getPostWithPage")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.data").isArray()) - .andExpect(jsonPath("$.msg").value("게시글 다건 조회 성공")) - .andExpect(jsonPath("$.data").exists()); + .andExpect(jsonPath("$.data.posts").isArray()) + .andExpect(jsonPath("$.data.currentPage").value(0)) + .andExpect(jsonPath("$.data.totalPage").exists()) + .andExpect(jsonPath("$.data.hasNext").exists()); + } + + @Test + @DisplayName("게시글 조회 - 키워드 검색") + void t3_2() throws Exception { + // 검색 대상 게시글 생성 + mvc.perform( + post("/post/infor") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "memberId": 1, + "postType": "INFORMATIONPOST", + "title": "Spring Boot 검색용 제목", + "content": "Spring Boot 검색용 내용" + } + """) + ); + + ResultActions resultActions = mvc + .perform( + get("/post/infor") + .param("keyword", "Spring") + .param("page", "0") + .param("size", "10") + ) + .andDo(print()); + + resultActions + .andExpect(handler().handlerType(InformationPostController.class)) + .andExpect(handler().methodName("getPostWithPage")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.posts").isArray()) + .andExpect(jsonPath("$.msg").value("게시글이 조회 되었습니다.")); } @Test @@ -224,7 +308,7 @@ void t7() throws Exception { ResultActions resultActions = mvc .perform( - delete("/post/infor/{post_id}", 5L) + delete("/post/infor/{post_id}", 7L) ) .andDo(print()); @@ -270,7 +354,7 @@ void t9() throws Exception { ResultActions resultActions = mvc .perform( - put("/post/infor/{post_id}", 6L) + put("/post/infor/{post_id}", 8L) .contentType(MediaType.APPLICATION_JSON) .content(""" {