From 549a96cd1ca617281455a306fe888f7eafcb8f21 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 10 Jan 2024 13:55:24 +0900 Subject: [PATCH 01/19] =?UTF-8?q?feat:=20=EA=B3=A8=EA=B2=A9=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CommunityController.java | 23 +++++++++++++++++++ .../community/dto/ArticlesResponse.java | 4 ++++ .../community/service/CommunityService.java | 7 ++++++ 3 files changed, 34 insertions(+) create mode 100644 src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java create mode 100644 src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java create mode 100644 src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java diff --git a/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java b/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java new file mode 100644 index 000000000..a4952b6be --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java @@ -0,0 +1,23 @@ +package in.koreatech.koin.domain.community.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import in.koreatech.koin.domain.community.dto.ArticlesResponse; +import in.koreatech.koin.domain.community.service.CommunityService; +import lombok.RequiredArgsConstructor; + +@Controller +@RequiredArgsConstructor +public class CommunityController { + + private final CommunityService communityService; + + @GetMapping("/articles") + public ResponseEntity getArticles(@RequestParam Long boardId, @RequestParam(required = false) Long page, + @RequestParam(required = false) Long limit) { + return ResponseEntity.ok().body(null); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java b/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java new file mode 100644 index 000000000..31f696d88 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java @@ -0,0 +1,4 @@ +package in.koreatech.koin.domain.community.dto; + +public class ArticlesResponse { +} diff --git a/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java b/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java new file mode 100644 index 000000000..ee6264cab --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java @@ -0,0 +1,7 @@ +package in.koreatech.koin.domain.community.service; + +import org.springframework.stereotype.Service; + +@Service +public class CommunityService { +} From 25e0647cf1ccb36045ae8de3d0a289d60d6e9600 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 10 Jan 2024 14:12:32 +0900 Subject: [PATCH 02/19] =?UTF-8?q?feat:=20=EC=97=94=ED=8B=B0=ED=8B=B0=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/community/model/Article.java | 104 ++++++++++++++++++ .../koin/domain/community/model/Board.java | 74 +++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 src/main/java/in/koreatech/koin/domain/community/model/Article.java create mode 100644 src/main/java/in/koreatech/koin/domain/community/model/Board.java diff --git a/src/main/java/in/koreatech/koin/domain/community/model/Article.java b/src/main/java/in/koreatech/koin/domain/community/model/Article.java new file mode 100644 index 000000000..6de329e42 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/community/model/Article.java @@ -0,0 +1,104 @@ +package in.koreatech.koin.domain.community.model; + +import in.koreatech.koin.global.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Entity +@Table(name = "articles") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Article extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @NotNull + @Column(name = "board_id", nullable = false) + private Long boardId; + + @Size(max = 255) + @NotNull + @Column(name = "title", nullable = false) + private String title; + + @NotNull + @Lob + @Column(name = "content", nullable = false) + private String content; + + @NotNull + @Column(name = "user_id", nullable = false) + private Long userId; + + @Size(max = 50) + @NotNull + @Column(name = "nickname", nullable = false, length = 50) + private String nickname; + + @NotNull + @Column(name = "hit", nullable = false) + private Long hit; + + @Size(max = 45) + @NotNull + @Column(name = "ip", nullable = false, length = 45) + private String ip; + + @NotNull + @Column(name = "is_solved", nullable = false) + private Boolean isSolved = false; + + @NotNull + @Column(name = "is_deleted", nullable = false) + private Boolean isDeleted = false; + + @NotNull + @Column(name = "comment_count", nullable = false) + private Byte commentCount; + + @Lob + @Column(name = "meta") + private String meta; + + @NotNull + @Column(name = "is_notice", nullable = false) + private Boolean isNotice = false; + + @Column(name = "notice_article_id") + private Long noticeArticleId; + + @Builder + private Article(Long boardId, String title, String content, Long userId, String nickname, Long hit, + String ip, Boolean isSolved, Boolean isDeleted, Byte commentCount, String meta, Boolean isNotice, + Long noticeArticleId) { + this.boardId = boardId; + this.title = title; + this.content = content; + this.userId = userId; + this.nickname = nickname; + this.hit = hit; + this.ip = ip; + this.isSolved = isSolved; + this.isDeleted = isDeleted; + this.commentCount = commentCount; + this.meta = meta; + this.isNotice = isNotice; + this.noticeArticleId = noticeArticleId; + } +} diff --git a/src/main/java/in/koreatech/koin/domain/community/model/Board.java b/src/main/java/in/koreatech/koin/domain/community/model/Board.java new file mode 100644 index 000000000..412349beb --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/community/model/Board.java @@ -0,0 +1,74 @@ +package in.koreatech.koin.domain.community.model; + +import in.koreatech.koin.global.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Entity +@Table(name = "boards") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Board extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @Size(max = 10) + @NotNull + @Column(name = "tag", nullable = false, length = 10) + private String tag; + + @Size(max = 50) + @NotNull + @Column(name = "name", nullable = false, length = 50) + private String name; + + @NotNull + @Column(name = "is_anonymous", nullable = false) + private Boolean isAnonymous = false; + + @NotNull + @Column(name = "article_count", nullable = false) + private Long articleCount; + + @NotNull + @Column(name = "is_deleted", nullable = false) + private Boolean isDeleted = false; + + @NotNull + @Column(name = "is_notice", nullable = false) + private Boolean isNotice = false; + + @Column(name = "parent_id") + private Long parentId; + + @NotNull + @Column(name = "seq", nullable = false) + private Long seq; + + @Builder + private Board(String tag, String name, Boolean isAnonymous, Long articleCount, Boolean isDeleted, + Boolean isNotice, Long parentId, Long seq) { + this.tag = tag; + this.name = name; + this.isAnonymous = isAnonymous; + this.articleCount = articleCount; + this.isDeleted = isDeleted; + this.isNotice = isNotice; + this.parentId = parentId; + this.seq = seq; + } +} From 2225d5b6314638f03193cd87fd4d3a9d9d23d470 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 10 Jan 2024 14:44:34 +0900 Subject: [PATCH 03/19] =?UTF-8?q?feat:=20repository=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../community/repository/ArticleRepository.java | 12 ++++++++++++ .../domain/community/repository/BoardRepository.java | 11 +++++++++++ 2 files changed, 23 insertions(+) create mode 100644 src/main/java/in/koreatech/koin/domain/community/repository/ArticleRepository.java create mode 100644 src/main/java/in/koreatech/koin/domain/community/repository/BoardRepository.java diff --git a/src/main/java/in/koreatech/koin/domain/community/repository/ArticleRepository.java b/src/main/java/in/koreatech/koin/domain/community/repository/ArticleRepository.java new file mode 100644 index 000000000..c37c32167 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/community/repository/ArticleRepository.java @@ -0,0 +1,12 @@ +package in.koreatech.koin.domain.community.repository; + +import java.util.Optional; + +import org.springframework.data.repository.Repository; + +import in.koreatech.koin.domain.community.model.Article; + +public interface ArticleRepository extends Repository { + + Optional
findByBoardId(Long boardId); +} diff --git a/src/main/java/in/koreatech/koin/domain/community/repository/BoardRepository.java b/src/main/java/in/koreatech/koin/domain/community/repository/BoardRepository.java new file mode 100644 index 000000000..bebb41254 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/community/repository/BoardRepository.java @@ -0,0 +1,11 @@ +package in.koreatech.koin.domain.community.repository; + +import java.util.Optional; + +import org.springframework.data.repository.Repository; + +import in.koreatech.koin.domain.community.model.Board; + +public interface BoardRepository extends Repository { + Optional findById(Long id); +} From 4e028f5cbe459b2eabdcee1b917529068323be59 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 10 Jan 2024 14:45:11 +0900 Subject: [PATCH 04/19] =?UTF-8?q?feat:=20service=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CommunityController.java | 3 ++- .../community/service/CommunityService.java | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java b/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java index a4952b6be..ab686d133 100644 --- a/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java +++ b/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java @@ -18,6 +18,7 @@ public class CommunityController { @GetMapping("/articles") public ResponseEntity getArticles(@RequestParam Long boardId, @RequestParam(required = false) Long page, @RequestParam(required = false) Long limit) { - return ResponseEntity.ok().body(null); + ArticlesResponse foundArticles = communityService.getArticles(boardId, page, limit); + return ResponseEntity.ok().body(foundArticles); } } diff --git a/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java b/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java index ee6264cab..7d842591f 100644 --- a/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java +++ b/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java @@ -2,6 +2,26 @@ import org.springframework.stereotype.Service; +import in.koreatech.koin.domain.community.dto.ArticlesResponse; +import in.koreatech.koin.domain.community.model.Article; +import in.koreatech.koin.domain.community.model.Board; +import in.koreatech.koin.domain.community.repository.ArticleRepository; +import in.koreatech.koin.domain.community.repository.BoardRepository; +import lombok.RequiredArgsConstructor; + @Service +@RequiredArgsConstructor public class CommunityService { + + private final ArticleRepository articleRepository; + private final BoardRepository boardRepository; + + public ArticlesResponse getArticles(Long boardId, Long page, Long limit) { + Board board = boardRepository.findById(boardId) + .orElseThrow(); //TODO: 404 Not Found Handling + Article article = articleRepository.findByBoardId(boardId) + .orElse(null); + + return null; + } } From 7381c4df7cebf48bee6509692b7505303dd6122c Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 10 Jan 2024 15:51:37 +0900 Subject: [PATCH 05/19] =?UTF-8?q?feat:=20response=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../community/dto/ArticlesResponse.java | 95 ++++++++++++++++++- .../community/service/CommunityService.java | 2 +- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java b/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java index 31f696d88..3e5bfa855 100644 --- a/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java +++ b/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java @@ -1,4 +1,97 @@ package in.koreatech.koin.domain.community.dto; -public class ArticlesResponse { +import java.time.LocalDateTime; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +import in.koreatech.koin.domain.community.model.Article; +import in.koreatech.koin.domain.community.model.Board; + +public record ArticlesResponse( + List articles, + InnerBoardResponse board, + Long totalPage +) { + + public static ArticlesResponse of(List
articles, Board board, Long totalPage) { + return new ArticlesResponse( + articles.stream() + .map(InnerArticleResponse::from) + .toList(), + InnerBoardResponse.from(board), + totalPage + ); + } + + @JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) + private record InnerArticleResponse( + Long id, + Long boardId, + String title, + String content, + Long userId, + String nickname, + Long hit, String ip, + Boolean isSolved, + Boolean isDeleted, + Byte commentCount, + String meta, + Long noticeArticleId, + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createdAt, + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updatedAt + ) { + + public static InnerArticleResponse from(Article article) { + return new InnerArticleResponse( + article.getId(), + article.getBoardId(), + article.getTitle(), + article.getContent(), + article.getUserId(), + article.getNickname(), + article.getHit(), + article.getIp(), + article.getIsSolved(), + article.getIsDeleted(), + article.getCommentCount(), + article.getMeta(), + article.getNoticeArticleId(), + article.getCreatedAt(), + article.getUpdatedAt() + ); + } + } + + @JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) + public static record InnerBoardResponse( + Long id, + String tag, + String name, + Boolean isAnonymous, + Long articleCount, + Boolean isDeleted, + Long parentId, + Long seq, + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createdAt, + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updatedAt + ) { + + public static InnerBoardResponse from(Board board) { + return new InnerBoardResponse( + board.getId(), + board.getTag(), + board.getName(), + board.getIsAnonymous(), + board.getArticleCount(), + board.getIsDeleted(), + board.getParentId(), + board.getSeq(), + board.getCreatedAt(), + board.getUpdatedAt() + ); + } + } } diff --git a/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java b/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java index 7d842591f..5551c8db5 100644 --- a/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java +++ b/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java @@ -20,7 +20,7 @@ public ArticlesResponse getArticles(Long boardId, Long page, Long limit) { Board board = boardRepository.findById(boardId) .orElseThrow(); //TODO: 404 Not Found Handling Article article = articleRepository.findByBoardId(boardId) - .orElse(null); + .orElseThrow(); //TODO: 404 Not Found Handling - errorResponse, "There is no article" return null; } From 61b17572a60375fca902d74a84443835d3b1dae8 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 10 Jan 2024 17:14:05 +0900 Subject: [PATCH 06/19] =?UTF-8?q?feat:=20DB=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CommunityController.java | 8 ++++---- .../community/repository/ArticleRepository.java | 6 +++--- .../community/service/CommunityService.java | 16 +++++++++++++--- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java b/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java index ab686d133..8ead03a0e 100644 --- a/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java +++ b/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java @@ -1,23 +1,23 @@ package in.koreatech.koin.domain.community.controller; import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import in.koreatech.koin.domain.community.dto.ArticlesResponse; import in.koreatech.koin.domain.community.service.CommunityService; import lombok.RequiredArgsConstructor; -@Controller +@RestController @RequiredArgsConstructor public class CommunityController { private final CommunityService communityService; @GetMapping("/articles") - public ResponseEntity getArticles(@RequestParam Long boardId, @RequestParam(required = false) Long page, - @RequestParam(required = false) Long limit) { + public ResponseEntity getArticles(@RequestParam Long boardId, @RequestParam(required = false, defaultValue = "1") Long page, + @RequestParam(required = false, defaultValue = "10") Long limit) { ArticlesResponse foundArticles = communityService.getArticles(boardId, page, limit); return ResponseEntity.ok().body(foundArticles); } diff --git a/src/main/java/in/koreatech/koin/domain/community/repository/ArticleRepository.java b/src/main/java/in/koreatech/koin/domain/community/repository/ArticleRepository.java index c37c32167..ee522d807 100644 --- a/src/main/java/in/koreatech/koin/domain/community/repository/ArticleRepository.java +++ b/src/main/java/in/koreatech/koin/domain/community/repository/ArticleRepository.java @@ -1,12 +1,12 @@ package in.koreatech.koin.domain.community.repository; -import java.util.Optional; - +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.repository.Repository; import in.koreatech.koin.domain.community.model.Article; public interface ArticleRepository extends Repository { - Optional
findByBoardId(Long boardId); + Page
findByBoardId(Long boardId, Pageable pageable); } diff --git a/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java b/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java index 5551c8db5..72e66a8fb 100644 --- a/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java +++ b/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java @@ -1,6 +1,9 @@ package in.koreatech.koin.domain.community.service; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import in.koreatech.koin.domain.community.dto.ArticlesResponse; import in.koreatech.koin.domain.community.model.Article; @@ -8,20 +11,27 @@ import in.koreatech.koin.domain.community.repository.ArticleRepository; import in.koreatech.koin.domain.community.repository.BoardRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class CommunityService { private final ArticleRepository articleRepository; private final BoardRepository boardRepository; public ArticlesResponse getArticles(Long boardId, Long page, Long limit) { + page -= 1; Board board = boardRepository.findById(boardId) .orElseThrow(); //TODO: 404 Not Found Handling - Article article = articleRepository.findByBoardId(boardId) - .orElseThrow(); //TODO: 404 Not Found Handling - errorResponse, "There is no article" - return null; + PageRequest pageRequest = PageRequest.of(page.intValue(), limit.intValue()); + Page
articles = articleRepository.findByBoardId(boardId, pageRequest); + // Article article = articleRepository.findAllByBoardId(boardId) + // .orElseThrow(); //TODO: 404 Not Found Handling - errorResponse, "There is no article" + + return ArticlesResponse.of(articles.getContent(), board, (long)articles.getTotalPages()); } } From 86556c8320daeb2537bc80bd7ded113b92dcdc15 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 10 Jan 2024 17:38:52 +0900 Subject: [PATCH 07/19] =?UTF-8?q?feat:=20Jsoup=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 40711ab88..607cada3e 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'io.jsonwebtoken:jjwt-api:0.12.3' implementation 'com.mysql:mysql-connector-j' + implementation 'org.jsoup:jsoup:1.15.3' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3' runtimeOnly 'com.h2database:h2' From 48bf5e732d98fabf87cd051ee8888aebce976f4c Mon Sep 17 00:00:00 2001 From: songsunkook Date: Wed, 10 Jan 2024 20:01:23 +0900 Subject: [PATCH 08/19] =?UTF-8?q?feat:=20=EC=9D=91=EB=8B=B5=20=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. article.is_notice 2. article.contentSummary 3. board.is_notice 4. board.children (must null) --- .../domain/community/dto/ArticlesResponse.java | 16 +++++++++++++--- .../koin/domain/community/model/Article.java | 15 +++++++++++++++ .../koin/domain/community/model/Board.java | 7 +++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java b/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java index 3e5bfa855..0e25a5d7c 100644 --- a/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java +++ b/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java @@ -4,6 +4,7 @@ import java.util.List; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; @@ -39,9 +40,11 @@ private record InnerArticleResponse( Boolean isDeleted, Byte commentCount, String meta, + Boolean isNotice, Long noticeArticleId, @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createdAt, - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updatedAt + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updatedAt, + @JsonProperty("contentSummary") String contentSummary ) { public static InnerArticleResponse from(Article article) { @@ -58,23 +61,27 @@ public static InnerArticleResponse from(Article article) { article.getIsDeleted(), article.getCommentCount(), article.getMeta(), + article.getIsNotice(), article.getNoticeArticleId(), article.getCreatedAt(), - article.getUpdatedAt() + article.getUpdatedAt(), + article.getContentSummary() ); } } @JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) - public static record InnerBoardResponse( + public record InnerBoardResponse( Long id, String tag, String name, Boolean isAnonymous, Long articleCount, Boolean isDeleted, + Boolean isNotice, Long parentId, Long seq, + List children, @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createdAt, @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updatedAt ) { @@ -87,8 +94,11 @@ public static InnerBoardResponse from(Board board) { board.getIsAnonymous(), board.getArticleCount(), board.getIsDeleted(), + board.getIsNotice(), board.getParentId(), board.getSeq(), + board.getChildren().isEmpty() ? + null : board.getChildren().stream().map(InnerBoardResponse::from).toList(), board.getCreatedAt(), board.getUpdatedAt() ); diff --git a/src/main/java/in/koreatech/koin/domain/community/model/Article.java b/src/main/java/in/koreatech/koin/domain/community/model/Article.java index 6de329e42..73a44fb48 100644 --- a/src/main/java/in/koreatech/koin/domain/community/model/Article.java +++ b/src/main/java/in/koreatech/koin/domain/community/model/Article.java @@ -1,5 +1,7 @@ package in.koreatech.koin.domain.community.model; +import org.jsoup.Jsoup; + import in.koreatech.koin.global.common.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -23,6 +25,9 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Article extends BaseEntity { + private static final int SUMMARY_MIN_LENGTH = 0; + private static final int SUMMARY_MAX_LENGTH = 100; + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) @@ -83,6 +88,16 @@ public class Article extends BaseEntity { @Column(name = "notice_article_id") private Long noticeArticleId; + public String getContentSummary() { + if (content == null) + return ""; + String summary = Jsoup.parse(content).text(); + summary = summary.replace(" ", "").trim(); + summary = (summary.length() > SUMMARY_MAX_LENGTH) ? + summary.substring(SUMMARY_MIN_LENGTH, SUMMARY_MAX_LENGTH) : summary; + return summary; + } + @Builder private Article(Long boardId, String title, String content, Long userId, String nickname, Long hit, String ip, Boolean isSolved, Boolean isDeleted, Byte commentCount, String meta, Boolean isNotice, diff --git a/src/main/java/in/koreatech/koin/domain/community/model/Board.java b/src/main/java/in/koreatech/koin/domain/community/model/Board.java index 412349beb..ba215fdac 100644 --- a/src/main/java/in/koreatech/koin/domain/community/model/Board.java +++ b/src/main/java/in/koreatech/koin/domain/community/model/Board.java @@ -1,5 +1,8 @@ package in.koreatech.koin.domain.community.model; +import java.util.ArrayList; +import java.util.List; + import in.koreatech.koin.global.common.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -59,6 +62,10 @@ public class Board extends BaseEntity { @Column(name = "seq", nullable = false) private Long seq; + public List getChildren() { + return new ArrayList<>(); + } + @Builder private Board(String tag, String name, Boolean isAnonymous, Long articleCount, Boolean isDeleted, Boolean isNotice, Long parentId, Long seq) { From a0edf014fa97eef86b69bf6f7d1da7d1b99ef047 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 11 Jan 2024 11:18:08 +0900 Subject: [PATCH 09/19] =?UTF-8?q?feat:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/ArticleNotFoundException.java | 14 ++++++++++++ .../community/repository/BoardRepository.java | 4 +--- .../community/service/CommunityService.java | 14 +++++------- .../exception/GlobalExceptionHandler.java | 22 ++++++++++++++++--- 4 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/community/exception/ArticleNotFoundException.java diff --git a/src/main/java/in/koreatech/koin/domain/community/exception/ArticleNotFoundException.java b/src/main/java/in/koreatech/koin/domain/community/exception/ArticleNotFoundException.java new file mode 100644 index 000000000..ac1c49dd5 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/community/exception/ArticleNotFoundException.java @@ -0,0 +1,14 @@ +package in.koreatech.koin.domain.community.exception; + +public class ArticleNotFoundException extends RuntimeException { + private static final String DEFAULT_MESSAGE = "게시글이 존재하지 않습니다."; + + public ArticleNotFoundException(String message) { + super(message); + } + + public static ArticleNotFoundException withDetail(String detail) { + String message = String.format("%s %s", DEFAULT_MESSAGE, detail); + return new ArticleNotFoundException(message); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/community/repository/BoardRepository.java b/src/main/java/in/koreatech/koin/domain/community/repository/BoardRepository.java index bebb41254..8458de863 100644 --- a/src/main/java/in/koreatech/koin/domain/community/repository/BoardRepository.java +++ b/src/main/java/in/koreatech/koin/domain/community/repository/BoardRepository.java @@ -1,11 +1,9 @@ package in.koreatech.koin.domain.community.repository; -import java.util.Optional; - import org.springframework.data.repository.Repository; import in.koreatech.koin.domain.community.model.Board; public interface BoardRepository extends Repository { - Optional findById(Long id); + Board findById(Long id); } diff --git a/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java b/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java index 72e66a8fb..015723069 100644 --- a/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java +++ b/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java @@ -6,14 +6,13 @@ import org.springframework.transaction.annotation.Transactional; import in.koreatech.koin.domain.community.dto.ArticlesResponse; +import in.koreatech.koin.domain.community.exception.ArticleNotFoundException; import in.koreatech.koin.domain.community.model.Article; import in.koreatech.koin.domain.community.model.Board; import in.koreatech.koin.domain.community.repository.ArticleRepository; import in.koreatech.koin.domain.community.repository.BoardRepository; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -@Slf4j @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -23,14 +22,13 @@ public class CommunityService { private final BoardRepository boardRepository; public ArticlesResponse getArticles(Long boardId, Long page, Long limit) { - page -= 1; - Board board = boardRepository.findById(boardId) - .orElseThrow(); //TODO: 404 Not Found Handling + Board board = boardRepository.findById(boardId); - PageRequest pageRequest = PageRequest.of(page.intValue(), limit.intValue()); + PageRequest pageRequest = PageRequest.of(page.intValue() - 1, limit.intValue()); Page
articles = articleRepository.findByBoardId(boardId, pageRequest); - // Article article = articleRepository.findAllByBoardId(boardId) - // .orElseThrow(); //TODO: 404 Not Found Handling - errorResponse, "There is no article" + if (articles.getContent().isEmpty()) { + throw ArticleNotFoundException.withDetail("boardId: " + boardId + ", page: " + page + ", limit: " + limit); + } return ArticlesResponse.of(articles.getContent(), board, (long)articles.getTotalPages()); } diff --git a/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java b/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java index 05875f41a..a4acb92bd 100644 --- a/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java @@ -1,14 +1,16 @@ package in.koreatech.koin.global.exception; -import in.koreatech.koin.domain.auth.exception.AuthException; -import in.koreatech.koin.domain.user.exception.UserNotFoundException; -import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import in.koreatech.koin.domain.auth.exception.AuthException; +import in.koreatech.koin.domain.community.exception.ArticleNotFoundException; +import in.koreatech.koin.domain.user.exception.UserNotFoundException; +import lombok.extern.slf4j.Slf4j; + @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { @@ -38,4 +40,18 @@ public ResponseEntity handleAuthException(AuthException e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .body(ErrorResponse.from("잘못된 인증정보입니다.")); } + + @ExceptionHandler + public ResponseEntity handleArticleNotFoundException(ArticleNotFoundException e) { + log.warn(e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ErrorResponseWrapper.from(ErrorResponse.from("There is no article"))); + } + + public record ErrorResponseWrapper(ErrorResponse error) { + + public static ErrorResponseWrapper from(ErrorResponse error) { + return new ErrorResponseWrapper(error); + } + } } From e065e3d233c1e80a8d85702c2492c6a56f618139 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 11 Jan 2024 14:55:50 +0900 Subject: [PATCH 10/19] =?UTF-8?q?refactor:=20=ED=8E=98=EC=9D=B4=EC=A7=95?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=EC=A0=95=EB=B3=B4=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CommunityController.java | 4 +- .../koin/domain/community/model/Criteria.java | 46 +++++++++++++++++++ .../community/service/CommunityService.java | 7 +-- 3 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/community/model/Criteria.java diff --git a/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java b/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java index 8ead03a0e..567adee8e 100644 --- a/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java +++ b/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java @@ -16,8 +16,8 @@ public class CommunityController { private final CommunityService communityService; @GetMapping("/articles") - public ResponseEntity getArticles(@RequestParam Long boardId, @RequestParam(required = false, defaultValue = "1") Long page, - @RequestParam(required = false, defaultValue = "10") Long limit) { + public ResponseEntity getArticles(@RequestParam Long boardId, @RequestParam(required = false) Long page, + @RequestParam(required = false) Long limit) { ArticlesResponse foundArticles = communityService.getArticles(boardId, page, limit); return ResponseEntity.ok().body(foundArticles); } diff --git a/src/main/java/in/koreatech/koin/domain/community/model/Criteria.java b/src/main/java/in/koreatech/koin/domain/community/model/Criteria.java new file mode 100644 index 000000000..66ccafb06 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/community/model/Criteria.java @@ -0,0 +1,46 @@ +package in.koreatech.koin.domain.community.model; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class Criteria { + private static final Long DEFAULT_PAGE = 1L; + private static final Long MIN_PAGE = 1L; + + private static final Long DEFAULT_LIMIT = 10L; + private static final Long MIN_LIMIT = 1L; + private static final Long MAX_LIMIT = 50L; + + private final int page; + private final int limit; + + public static Criteria of(Long page, Long limit) { + return new Criteria(validatePage(page), validateLimit(limit)); + } + + private static int validatePage(Long page) { + if (page == null) { + page = DEFAULT_PAGE; + } + page -= 1; // start from 0 + if (page < MIN_PAGE) { + page = MIN_PAGE; + } + return page.intValue(); + } + + private static int validateLimit(Long limit) { + if (limit == null) { + limit = DEFAULT_LIMIT; + } + if (limit < MIN_LIMIT) { + limit = MIN_LIMIT; + } + if (limit > MAX_LIMIT) { + limit = MAX_LIMIT; + } + return limit.intValue(); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java b/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java index 015723069..ab8054a4f 100644 --- a/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java +++ b/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java @@ -9,6 +9,7 @@ import in.koreatech.koin.domain.community.exception.ArticleNotFoundException; import in.koreatech.koin.domain.community.model.Article; import in.koreatech.koin.domain.community.model.Board; +import in.koreatech.koin.domain.community.model.Criteria; import in.koreatech.koin.domain.community.repository.ArticleRepository; import in.koreatech.koin.domain.community.repository.BoardRepository; import lombok.RequiredArgsConstructor; @@ -22,12 +23,12 @@ public class CommunityService { private final BoardRepository boardRepository; public ArticlesResponse getArticles(Long boardId, Long page, Long limit) { + Criteria criteria = Criteria.of(page, limit); Board board = boardRepository.findById(boardId); - - PageRequest pageRequest = PageRequest.of(page.intValue() - 1, limit.intValue()); + PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit()); Page
articles = articleRepository.findByBoardId(boardId, pageRequest); if (articles.getContent().isEmpty()) { - throw ArticleNotFoundException.withDetail("boardId: " + boardId + ", page: " + page + ", limit: " + limit); + throw ArticleNotFoundException.withDetail("boardId: " + boardId + ", page: " + criteria.getPage() + ", limit: " + criteria.getLimit()); } return ArticlesResponse.of(articles.getContent(), board, (long)articles.getTotalPages()); From a8e7de3fe1e5761bda475cb769f43576c3c04378 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 11 Jan 2024 15:40:57 +0900 Subject: [PATCH 11/19] =?UTF-8?q?fix:=20soft=20delete=EB=90=9C=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=ED=8C=90=20or=20=EA=B2=8C=EC=8B=9C=EA=B8=80=EC=9D=80?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EC=95=88=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/koreatech/koin/domain/community/model/Article.java | 2 ++ .../in/koreatech/koin/domain/community/model/Board.java | 3 +++ .../koin/domain/community/repository/BoardRepository.java | 4 +++- .../koin/domain/community/service/CommunityService.java | 7 +++++-- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/community/model/Article.java b/src/main/java/in/koreatech/koin/domain/community/model/Article.java index 73a44fb48..aef4373e7 100644 --- a/src/main/java/in/koreatech/koin/domain/community/model/Article.java +++ b/src/main/java/in/koreatech/koin/domain/community/model/Article.java @@ -1,5 +1,6 @@ package in.koreatech.koin.domain.community.model; +import org.hibernate.annotations.Where; import org.jsoup.Jsoup; import in.koreatech.koin.global.common.BaseEntity; @@ -22,6 +23,7 @@ @Setter @Entity @Table(name = "articles") +@Where(clause = "is_deleted=0") @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Article extends BaseEntity { diff --git a/src/main/java/in/koreatech/koin/domain/community/model/Board.java b/src/main/java/in/koreatech/koin/domain/community/model/Board.java index ba215fdac..554c4ea4b 100644 --- a/src/main/java/in/koreatech/koin/domain/community/model/Board.java +++ b/src/main/java/in/koreatech/koin/domain/community/model/Board.java @@ -3,6 +3,8 @@ import java.util.ArrayList; import java.util.List; +import org.hibernate.annotations.Where; + import in.koreatech.koin.global.common.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -22,6 +24,7 @@ @Setter @Entity @Table(name = "boards") +@Where(clause = "is_deleted=0") @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Board extends BaseEntity { @Id diff --git a/src/main/java/in/koreatech/koin/domain/community/repository/BoardRepository.java b/src/main/java/in/koreatech/koin/domain/community/repository/BoardRepository.java index 8458de863..bebb41254 100644 --- a/src/main/java/in/koreatech/koin/domain/community/repository/BoardRepository.java +++ b/src/main/java/in/koreatech/koin/domain/community/repository/BoardRepository.java @@ -1,9 +1,11 @@ package in.koreatech.koin.domain.community.repository; +import java.util.Optional; + import org.springframework.data.repository.Repository; import in.koreatech.koin.domain.community.model.Board; public interface BoardRepository extends Repository { - Board findById(Long id); + Optional findById(Long id); } diff --git a/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java b/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java index ab8054a4f..06aba09d7 100644 --- a/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java +++ b/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java @@ -24,11 +24,14 @@ public class CommunityService { public ArticlesResponse getArticles(Long boardId, Long page, Long limit) { Criteria criteria = Criteria.of(page, limit); - Board board = boardRepository.findById(boardId); + Board board = boardRepository.findById(boardId) + .orElseThrow(() -> ArticleNotFoundException.withDetail( + "boardId: " + boardId + ", page: " + criteria.getPage() + ", limit: " + criteria.getLimit())); PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit()); Page
articles = articleRepository.findByBoardId(boardId, pageRequest); if (articles.getContent().isEmpty()) { - throw ArticleNotFoundException.withDetail("boardId: " + boardId + ", page: " + criteria.getPage() + ", limit: " + criteria.getLimit()); + throw ArticleNotFoundException.withDetail( + "boardId: " + boardId + ", page: " + criteria.getPage() + ", limit: " + criteria.getLimit()); } return ArticlesResponse.of(articles.getContent(), board, (long)articles.getTotalPages()); From 8fcdb3114c3f3368284a878f85d00e1e5d3e4164 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 11 Jan 2024 15:41:20 +0900 Subject: [PATCH 12/19] =?UTF-8?q?fix:=201=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=95=88=EB=90=98=EB=8A=94=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/in/koreatech/koin/domain/community/model/Criteria.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/in/koreatech/koin/domain/community/model/Criteria.java b/src/main/java/in/koreatech/koin/domain/community/model/Criteria.java index 66ccafb06..f5dde5aef 100644 --- a/src/main/java/in/koreatech/koin/domain/community/model/Criteria.java +++ b/src/main/java/in/koreatech/koin/domain/community/model/Criteria.java @@ -24,10 +24,10 @@ private static int validatePage(Long page) { if (page == null) { page = DEFAULT_PAGE; } - page -= 1; // start from 0 if (page < MIN_PAGE) { page = MIN_PAGE; } + page -= 1; // start from 0 return page.intValue(); } From 90291fc305bc4195bb0862c12f4761c0e681440e Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 11 Jan 2024 16:37:46 +0900 Subject: [PATCH 13/19] =?UTF-8?q?fix:=20=EB=88=84=EB=9D=BD=EB=90=9C=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EC=B6=94=EA=B0=80=20(summary)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koreatech/koin/domain/community/dto/ArticlesResponse.java | 2 ++ .../in/koreatech/koin/domain/community/model/Article.java | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java b/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java index 0e25a5d7c..608e6f750 100644 --- a/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java +++ b/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java @@ -42,6 +42,7 @@ private record InnerArticleResponse( String meta, Boolean isNotice, Long noticeArticleId, + String summary, @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createdAt, @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updatedAt, @JsonProperty("contentSummary") String contentSummary @@ -63,6 +64,7 @@ public static InnerArticleResponse from(Article article) { article.getMeta(), article.getIsNotice(), article.getNoticeArticleId(), + article.getSummary(), article.getCreatedAt(), article.getUpdatedAt(), article.getContentSummary() diff --git a/src/main/java/in/koreatech/koin/domain/community/model/Article.java b/src/main/java/in/koreatech/koin/domain/community/model/Article.java index aef4373e7..f35006e3a 100644 --- a/src/main/java/in/koreatech/koin/domain/community/model/Article.java +++ b/src/main/java/in/koreatech/koin/domain/community/model/Article.java @@ -11,6 +11,7 @@ import jakarta.persistence.Id; import jakarta.persistence.Lob; import jakarta.persistence.Table; +import jakarta.persistence.Transient; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.AccessLevel; @@ -90,6 +91,9 @@ public class Article extends BaseEntity { @Column(name = "notice_article_id") private Long noticeArticleId; + @Transient + private String summary; + public String getContentSummary() { if (content == null) return ""; From e78149243418f44f4b3581f0a8a825ab1577f7af Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 11 Jan 2024 16:49:58 +0900 Subject: [PATCH 14/19] =?UTF-8?q?fix:=20=EC=B5=9C=EC=8B=A0=EA=B8=80?= =?UTF-8?q?=EB=B6=80=ED=84=B0=20=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/community/service/CommunityService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java b/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java index 06aba09d7..624b0c57a 100644 --- a/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java +++ b/src/main/java/in/koreatech/koin/domain/community/service/CommunityService.java @@ -2,6 +2,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,12 +23,14 @@ public class CommunityService { private final ArticleRepository articleRepository; private final BoardRepository boardRepository; + public static final Sort SORT_ORDER_BY = Sort.by(Sort.Direction.DESC, "id"); + public ArticlesResponse getArticles(Long boardId, Long page, Long limit) { Criteria criteria = Criteria.of(page, limit); Board board = boardRepository.findById(boardId) .orElseThrow(() -> ArticleNotFoundException.withDetail( "boardId: " + boardId + ", page: " + criteria.getPage() + ", limit: " + criteria.getLimit())); - PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit()); + PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit(), SORT_ORDER_BY); Page
articles = articleRepository.findByBoardId(boardId, pageRequest); if (articles.getContent().isEmpty()) { throw ArticleNotFoundException.withDetail( From a774d36e5c84e8e9392626cc259b0569a3a30e5f Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 11 Jan 2024 17:27:17 +0900 Subject: [PATCH 15/19] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ArticleRepository.java | 2 + .../community/repository/BoardRepository.java | 2 + .../koin/acceptance/CommunityApiTest.java | 130 ++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 src/test/java/in/koreatech/koin/acceptance/CommunityApiTest.java diff --git a/src/main/java/in/koreatech/koin/domain/community/repository/ArticleRepository.java b/src/main/java/in/koreatech/koin/domain/community/repository/ArticleRepository.java index ee522d807..96f0ac03f 100644 --- a/src/main/java/in/koreatech/koin/domain/community/repository/ArticleRepository.java +++ b/src/main/java/in/koreatech/koin/domain/community/repository/ArticleRepository.java @@ -9,4 +9,6 @@ public interface ArticleRepository extends Repository { Page
findByBoardId(Long boardId, Pageable pageable); + + Article save(Article article); } diff --git a/src/main/java/in/koreatech/koin/domain/community/repository/BoardRepository.java b/src/main/java/in/koreatech/koin/domain/community/repository/BoardRepository.java index bebb41254..cf288a4ad 100644 --- a/src/main/java/in/koreatech/koin/domain/community/repository/BoardRepository.java +++ b/src/main/java/in/koreatech/koin/domain/community/repository/BoardRepository.java @@ -8,4 +8,6 @@ public interface BoardRepository extends Repository { Optional findById(Long id); + + Board save(Board board); } diff --git a/src/test/java/in/koreatech/koin/acceptance/CommunityApiTest.java b/src/test/java/in/koreatech/koin/acceptance/CommunityApiTest.java new file mode 100644 index 000000000..c20707b97 --- /dev/null +++ b/src/test/java/in/koreatech/koin/acceptance/CommunityApiTest.java @@ -0,0 +1,130 @@ +package in.koreatech.koin.acceptance; + +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; + +import in.koreatech.koin.AcceptanceTest; +import in.koreatech.koin.domain.community.model.Article; +import in.koreatech.koin.domain.community.model.Board; +import in.koreatech.koin.domain.community.repository.ArticleRepository; +import in.koreatech.koin.domain.community.repository.BoardRepository; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; + +class CommunityApiTest extends AcceptanceTest { + + @Autowired + private ArticleRepository articleRepository; + + @Autowired + private BoardRepository boardRepository; + + @Test + @DisplayName("게시글들을 페이지네이션하여 조회한다.") + void getArticlesByPagination() { + // given + final long PAGE_NUMBER = 1L; + final long PAGE_LIMIT = 1L; + final long ARTICLE_COUNT = 2L; + + Board board = Board.builder() + .tag("FA001") + .name("자유게시판") + .isAnonymous(false) + .articleCount(338L) + .isDeleted(false) + .isNotice(false) + .parentId(null) + .seq(1L) + .build(); + + Article article1 = Article.builder() + .boardId(1L) + .title("제목") + .content("

내용

") + .userId(1L) + .nickname("BCSD") + .hit(14L) + .ip("123.21.234.321") + .isSolved(false) + .isDeleted(false) + .commentCount((byte)2) + .meta(null) + .isNotice(false) + .noticeArticleId(null) + .build(); + + Article article2 = Article.builder() + .boardId(1L) + .title("TITLE") + .content("

CONTENT

") + .userId(1L) + .nickname("BCSD") + .hit(14L) + .ip("123.14.321.213") + .isSolved(false) + .isDeleted(false) + .commentCount((byte)2) + .meta(null) + .isNotice(false) + .noticeArticleId(null) + .build(); + + boardRepository.save(board); + articleRepository.save(article1); + articleRepository.save(article2); + + // when then + ExtractableResponse response = RestAssured + .given() + .log().all() + .when() + .log().all() + .param("boardId", board.getId()) + .param("page", PAGE_NUMBER) + .param("limit", PAGE_LIMIT) + .get("/articles") + .then() + .log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(response.jsonPath().getLong("board.id")).isEqualTo(board.getId()); + softly.assertThat(response.jsonPath().getString("board.tag")).isEqualTo(board.getTag()); + softly.assertThat(response.jsonPath().getString("board.name")).isEqualTo(board.getName()); + softly.assertThat(response.jsonPath().getBoolean("board.is_anonymous")).isEqualTo(board.getIsAnonymous()); + softly.assertThat(response.jsonPath().getLong("board.article_count")).isEqualTo(board.getArticleCount()); + softly.assertThat(response.jsonPath().getBoolean("board.is_deleted")).isEqualTo(board.getIsDeleted()); + softly.assertThat(response.jsonPath().getBoolean("board.is_notice")).isEqualTo(board.getIsNotice()); + softly.assertThat(response.jsonPath().getString("board.parent_id")).isEqualTo(board.getParentId()); + softly.assertThat(response.jsonPath().getLong("board.seq")).isEqualTo(board.getSeq()); + softly.assertThat(response.jsonPath().getString("board.children")).isEqualTo(board.getChildren().isEmpty() ? null : board.getChildren()); + + softly.assertThat(response.jsonPath().getLong("articles[0].id")).isEqualTo(article2.getId()); + softly.assertThat(response.jsonPath().getLong("articles[0].board_id")).isEqualTo(article2.getBoardId()); + softly.assertThat(response.jsonPath().getString("articles[0].title")).isEqualTo(article2.getTitle()); + softly.assertThat(response.jsonPath().getString("articles[0].content")).isEqualTo(article2.getContent()); + softly.assertThat(response.jsonPath().getLong("articles[0].user_id")).isEqualTo(article2.getUserId()); + softly.assertThat(response.jsonPath().getString("articles[0].nickname")).isEqualTo(article2.getNickname()); + softly.assertThat(response.jsonPath().getLong("articles[0].hit")).isEqualTo(article2.getHit()); + softly.assertThat(response.jsonPath().getString("articles[0].ip")).isEqualTo(article2.getIp()); + softly.assertThat(response.jsonPath().getBoolean("articles[0].is_solved")).isEqualTo(article2.getIsSolved()); + softly.assertThat(response.jsonPath().getBoolean("articles[0].is_deleted")).isEqualTo(article2.getIsDeleted()); + softly.assertThat(response.jsonPath().getByte("articles[0].comment_count")).isEqualTo(article2.getCommentCount()); + softly.assertThat(response.jsonPath().getString("articles[0].meta")).isEqualTo(article2.getMeta()); + softly.assertThat(response.jsonPath().getBoolean("articles[0].is_notice")).isEqualTo(article2.getIsNotice()); + softly.assertThat(response.jsonPath().getString("articles[0].notice_article_id")).isEqualTo(article2.getNoticeArticleId()); + softly.assertThat(response.jsonPath().getString("articles[0].summary")).isEqualTo(article2.getSummary()); + softly.assertThat(response.jsonPath().getString("articles[0].contentSummary")).isEqualTo(article2.getContentSummary()); + + softly.assertThat(response.jsonPath().getLong("totalPage")).isEqualTo(ARTICLE_COUNT / PAGE_LIMIT); + } + ); + } +} From 437292898cf2dbe5d4f6d33d08b866d1ae6fccc4 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 11 Jan 2024 17:38:40 +0900 Subject: [PATCH 16/19] =?UTF-8?q?move:=20ErrorResponseWrapper=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/koreatech/koin/global/exception/ErrorResponse.java | 7 +++++++ .../koin/global/exception/GlobalExceptionHandler.java | 8 +------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/in/koreatech/koin/global/exception/ErrorResponse.java b/src/main/java/in/koreatech/koin/global/exception/ErrorResponse.java index ae066867d..dedb3aa98 100644 --- a/src/main/java/in/koreatech/koin/global/exception/ErrorResponse.java +++ b/src/main/java/in/koreatech/koin/global/exception/ErrorResponse.java @@ -20,4 +20,11 @@ public static ErrorResponse of(int code, String message) { public static ErrorResponse from(String message) { return new ErrorResponse(0, message); } + + public record ErrorResponseWrapper(ErrorResponse error) { + + public static ErrorResponseWrapper from(ErrorResponse error) { + return new ErrorResponseWrapper(error); + } + } } diff --git a/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java b/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java index a4acb92bd..17b4be846 100644 --- a/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/in/koreatech/koin/global/exception/GlobalExceptionHandler.java @@ -9,6 +9,7 @@ import in.koreatech.koin.domain.auth.exception.AuthException; import in.koreatech.koin.domain.community.exception.ArticleNotFoundException; import in.koreatech.koin.domain.user.exception.UserNotFoundException; +import in.koreatech.koin.global.exception.ErrorResponse.ErrorResponseWrapper; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -47,11 +48,4 @@ public ResponseEntity handleArticleNotFoundException(Artic return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(ErrorResponseWrapper.from(ErrorResponse.from("There is no article"))); } - - public record ErrorResponseWrapper(ErrorResponse error) { - - public static ErrorResponseWrapper from(ErrorResponse error) { - return new ErrorResponseWrapper(error); - } - } } From 9997bd2888d476ac2989f78ae038b419dc8907b5 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Thu, 11 Jan 2024 17:44:17 +0900 Subject: [PATCH 17/19] =?UTF-8?q?refactor:=20=EC=BD=94=EB=94=A9=20?= =?UTF-8?q?=EC=BB=A8=EB=B2=A4=EC=85=98=20=EC=A4=80=EC=88=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../community/controller/CommunityController.java | 7 +++++-- .../koin/domain/community/dto/ArticlesResponse.java | 4 ++-- .../koin/domain/community/model/Article.java | 13 +++++++------ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java b/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java index 567adee8e..2f879b630 100644 --- a/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java +++ b/src/main/java/in/koreatech/koin/domain/community/controller/CommunityController.java @@ -16,8 +16,11 @@ public class CommunityController { private final CommunityService communityService; @GetMapping("/articles") - public ResponseEntity getArticles(@RequestParam Long boardId, @RequestParam(required = false) Long page, - @RequestParam(required = false) Long limit) { + public ResponseEntity getArticles( + @RequestParam Long boardId, + @RequestParam(required = false) Long page, + @RequestParam(required = false) Long limit + ) { ArticlesResponse foundArticles = communityService.getArticles(boardId, page, limit); return ResponseEntity.ok().body(foundArticles); } diff --git a/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java b/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java index 608e6f750..2d38f4099 100644 --- a/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java +++ b/src/main/java/in/koreatech/koin/domain/community/dto/ArticlesResponse.java @@ -99,8 +99,8 @@ public static InnerBoardResponse from(Board board) { board.getIsNotice(), board.getParentId(), board.getSeq(), - board.getChildren().isEmpty() ? - null : board.getChildren().stream().map(InnerBoardResponse::from).toList(), + board.getChildren().isEmpty() + ? null : board.getChildren().stream().map(InnerBoardResponse::from).toList(), board.getCreatedAt(), board.getUpdatedAt() ); diff --git a/src/main/java/in/koreatech/koin/domain/community/model/Article.java b/src/main/java/in/koreatech/koin/domain/community/model/Article.java index f35006e3a..41ebc9b92 100644 --- a/src/main/java/in/koreatech/koin/domain/community/model/Article.java +++ b/src/main/java/in/koreatech/koin/domain/community/model/Article.java @@ -95,13 +95,14 @@ public class Article extends BaseEntity { private String summary; public String getContentSummary() { - if (content == null) + if (content == null) { return ""; - String summary = Jsoup.parse(content).text(); - summary = summary.replace(" ", "").trim(); - summary = (summary.length() > SUMMARY_MAX_LENGTH) ? - summary.substring(SUMMARY_MIN_LENGTH, SUMMARY_MAX_LENGTH) : summary; - return summary; + } + String contentSummary = Jsoup.parse(content).text(); + contentSummary = contentSummary.replace(" ", "").trim(); + contentSummary = (contentSummary.length() > SUMMARY_MAX_LENGTH) + ? contentSummary.substring(SUMMARY_MIN_LENGTH, SUMMARY_MAX_LENGTH) : contentSummary; + return contentSummary; } @Builder From 2baaaf639a42cf97479f5d33b20bd7d2e6d933db Mon Sep 17 00:00:00 2001 From: songsunkook Date: Sat, 13 Jan 2024 13:14:29 +0900 Subject: [PATCH 18/19] =?UTF-8?q?refactor:=20=EB=AC=B8=EC=9E=90=EC=97=B4?= =?UTF-8?q?=20=ED=8C=8C=EC=8B=B1=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koreatech/koin/domain/community/model/Article.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/community/model/Article.java b/src/main/java/in/koreatech/koin/domain/community/model/Article.java index 41ebc9b92..ab00a5f68 100644 --- a/src/main/java/in/koreatech/koin/domain/community/model/Article.java +++ b/src/main/java/in/koreatech/koin/domain/community/model/Article.java @@ -99,10 +99,11 @@ public String getContentSummary() { return ""; } String contentSummary = Jsoup.parse(content).text(); - contentSummary = contentSummary.replace(" ", "").trim(); - contentSummary = (contentSummary.length() > SUMMARY_MAX_LENGTH) - ? contentSummary.substring(SUMMARY_MIN_LENGTH, SUMMARY_MAX_LENGTH) : contentSummary; - return contentSummary; + contentSummary = contentSummary.replace(" ", "").strip(); + if (contentSummary.length() < SUMMARY_MAX_LENGTH) { + return contentSummary; + } + return contentSummary.substring(SUMMARY_MIN_LENGTH, SUMMARY_MAX_LENGTH); } @Builder From 3399cbe33c118adfd54629d12fda2e3455be98b5 Mon Sep 17 00:00:00 2001 From: songsunkook Date: Sat, 13 Jan 2024 16:05:05 +0900 Subject: [PATCH 19/19] =?UTF-8?q?test:=20=EC=98=88=EC=99=B8=EC=83=81?= =?UTF-8?q?=ED=99=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/acceptance/CommunityApiTest.java | 258 +++++++++++++++++- 1 file changed, 248 insertions(+), 10 deletions(-) diff --git a/src/test/java/in/koreatech/koin/acceptance/CommunityApiTest.java b/src/test/java/in/koreatech/koin/acceptance/CommunityApiTest.java index c20707b97..dd3640d06 100644 --- a/src/test/java/in/koreatech/koin/acceptance/CommunityApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/CommunityApiTest.java @@ -1,6 +1,7 @@ package in.koreatech.koin.acceptance; import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -23,15 +24,16 @@ class CommunityApiTest extends AcceptanceTest { @Autowired private BoardRepository boardRepository; - @Test - @DisplayName("게시글들을 페이지네이션하여 조회한다.") - void getArticlesByPagination() { - // given - final long PAGE_NUMBER = 1L; - final long PAGE_LIMIT = 1L; - final long ARTICLE_COUNT = 2L; + private final long PAGE_NUMBER = 1L; + private final long PAGE_LIMIT = 1L; + private final long ARTICLE_COUNT = 2L; + + private Board board; + private Article article1, article2; - Board board = Board.builder() + @BeforeEach + void givenBeforeEach() { + board = Board.builder() .tag("FA001") .name("자유게시판") .isAnonymous(false) @@ -42,7 +44,7 @@ void getArticlesByPagination() { .seq(1L) .build(); - Article article1 = Article.builder() + article1 = Article.builder() .boardId(1L) .title("제목") .content("

내용

") @@ -58,7 +60,7 @@ void getArticlesByPagination() { .noticeArticleId(null) .build(); - Article article2 = Article.builder() + article2 = Article.builder() .boardId(1L) .title("TITLE") .content("

CONTENT

") @@ -77,6 +79,12 @@ void getArticlesByPagination() { boardRepository.save(board); articleRepository.save(article1); articleRepository.save(article2); + } + + @Test + @DisplayName("게시글들을 페이지네이션하여 조회한다.") + void getArticlesByPagination() { + // given // when then ExtractableResponse response = RestAssured @@ -127,4 +135,234 @@ void getArticlesByPagination() { } ); } + + @Test + @DisplayName("게시글들을 페이지네이션하여 조회한다. - 페이지가 0이면 1 페이지 조회") + void getArticlesByPagination_0Page() { + // given + final long PAGE_NUMBER_ZERO = 0L; + + // when then + ExtractableResponse response = RestAssured + .given() + .log().all() + .when() + .log().all() + .param("boardId", board.getId()) + .param("page", PAGE_NUMBER_ZERO) + .param("limit", PAGE_LIMIT) + .get("/articles") + .then() + .log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(response.jsonPath().getLong("articles[0].id")).isEqualTo(article2.getId()); + } + ); + } + + @Test + @DisplayName("게시글들을 페이지네이션하여 조회한다. - 페이지가 음수이면 1 페이지 조회") + void getArticlesByPagination_lessThan0Pages() { + // given + final long PAGE_NUMBER_MINUS = -10L; + + // when then + ExtractableResponse response = RestAssured + .given() + .log().all() + .when() + .log().all() + .param("boardId", board.getId()) + .param("page", PAGE_NUMBER_MINUS) + .param("limit", PAGE_LIMIT) + .get("/articles") + .then() + .log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(response.jsonPath().getLong("articles[0].id")).isEqualTo(article2.getId()); + } + ); + } + + @Test + @DisplayName("게시글들을 페이지네이션하여 조회한다. - limit가 0 이면 한 번에 1 게시글 조회") + void getArticlesByPagination_1Limit() { + // given + final long PAGE_LIMIT_ZERO = 0L; + + // when then + ExtractableResponse response = RestAssured + .given() + .log().all() + .when() + .log().all() + .param("boardId", board.getId()) + .param("page", PAGE_NUMBER) + .param("limit", PAGE_LIMIT_ZERO) + .get("/articles") + .then() + .log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(response.jsonPath().getLong("totalPage")).isEqualTo(ARTICLE_COUNT / PAGE_LIMIT); + } + ); + } + + @Test + @DisplayName("게시글들을 페이지네이션하여 조회한다. - limit가 음수이면 한 번에 1 게시글 조회") + void getArticlesByPagination_lessThan0Limit() { + // given + final long PAGE_LIMIT_ZERO = -10L; + + // when then + ExtractableResponse response = RestAssured + .given() + .log().all() + .when() + .log().all() + .param("boardId", board.getId()) + .param("page", PAGE_NUMBER) + .param("limit", PAGE_LIMIT_ZERO) + .get("/articles") + .then() + .log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(response.jsonPath().getLong("totalPage")).isEqualTo(ARTICLE_COUNT / PAGE_LIMIT); + } + ); + } + @Test + @DisplayName("게시글들을 페이지네이션하여 조회한다. - limit가 50 이상이면 한 번에 50 게시글 조회") + void getArticlesByPagination_over50Limit() { + // given + final long PAGE_LIMIT_ZERO = 100L; + final long MAX_PAGE_LIMIT = 50L; + final long ADD_ARTICLE_COUNT = 60L; + + for (int i = 0; i < ADD_ARTICLE_COUNT; i++) { + Article article = Article.builder() + .boardId(1L) + .title("제목") + .content("

내용

") + .userId(1L) + .nickname("BCSD") + .hit(14L) + .ip("123.21.234.321") + .isSolved(false) + .isDeleted(false) + .commentCount((byte)2) + .meta(null) + .isNotice(false) + .noticeArticleId(null) + .build(); + articleRepository.save(article); + }; + + // when then + ExtractableResponse response = RestAssured + .given() + .log().all() + .when() + .log().all() + .param("boardId", board.getId()) + .param("page", PAGE_NUMBER) + .param("limit", PAGE_LIMIT_ZERO) + .get("/articles") + .then() + .log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(response.jsonPath().getLong("totalPage")).isEqualTo((long)Math.ceil(((double)ARTICLE_COUNT + ADD_ARTICLE_COUNT) / MAX_PAGE_LIMIT)); + } + ); + } + + @Test + @DisplayName("게시글들을 페이지네이션하여 조회한다. - 페이지, limit가 주어지지 않으면 1 페이지 10 게시글 조회") + void getArticlesByPagination_default() { + // given + final long DEFAULT_LIMIT = 10L; + final long ADD_ARTICLE_COUNT = 10L; + final long FINAL_ARTICLE_ID = ARTICLE_COUNT + ADD_ARTICLE_COUNT; + + for (int i = 0; i < ADD_ARTICLE_COUNT; i++) { + Article article = Article.builder() + .boardId(1L) + .title("제목") + .content("

내용

") + .userId(1L) + .nickname("BCSD") + .hit(14L) + .ip("123.21.234.321") + .isSolved(false) + .isDeleted(false) + .commentCount((byte)2) + .meta(null) + .isNotice(false) + .noticeArticleId(null) + .build(); + articleRepository.save(article); + }; + + // when then + ExtractableResponse response = RestAssured + .given() + .log().all() + .when() + .log().all() + .param("boardId", board.getId()) + .get("/articles") + .then() + .log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(response.jsonPath().getLong("articles[0].id")).isEqualTo(FINAL_ARTICLE_ID); + softly.assertThat(response.jsonPath().getLong("totalPage")).isEqualTo((long)Math.ceil(((double)ARTICLE_COUNT + ADD_ARTICLE_COUNT) / DEFAULT_LIMIT)); + } + ); + } + + @Test + @DisplayName("게시글들을 페이지네이션하여 조회한다. - 페이지가 최대 페이지 수를 넘어가면 404") + void getArticlesByPagination_overMaxPageNotFound() { + // given + final long PAGE_NUMBER = 10000L; + + // when then + ExtractableResponse response = RestAssured + .given() + .log().all() + .when() + .log().all() + .param("boardId", board.getId()) + .param("page", PAGE_NUMBER) + .param("limit", PAGE_LIMIT) + .get("/articles") + .then() + .log().all() + .statusCode(HttpStatus.NOT_FOUND.value()) + .extract(); + } }