From f4d5ad4c1fef7567b88171aa07d5265a6aef30df Mon Sep 17 00:00:00 2001 From: donghae-kim Date: Wed, 20 Sep 2023 16:09:01 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20article=20filter=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../article/application/ArticleService.java | 24 +++++++++++++++---- .../domain/repository/ArticleRepository.java | 18 ++++++++++++-- .../prolog/article/ui/ArticleController.java | 17 ++++++++++--- .../prolog/common/WebConverterConfig.java | 12 +++++++--- 4 files changed, 58 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java b/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java index ede45aa67..3f7ce7cfe 100644 --- a/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java +++ b/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java @@ -1,10 +1,5 @@ package wooteco.prolog.article.application; -import static java.lang.Boolean.TRUE; -import static java.util.stream.Collectors.toList; -import static wooteco.prolog.common.exception.BadRequestCode.ARTICLE_NOT_FOUND_EXCEPTION; - -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,6 +12,13 @@ import wooteco.prolog.login.ui.LoginMember; import wooteco.prolog.member.application.MemberService; import wooteco.prolog.member.domain.Member; +import wooteco.prolog.member.domain.MemberGroupType; + +import java.util.List; + +import static java.lang.Boolean.TRUE; +import static java.util.stream.Collectors.toList; +import static wooteco.prolog.common.exception.BadRequestCode.ARTICLE_NOT_FOUND_EXCEPTION; @RequiredArgsConstructor @Service @@ -79,4 +81,16 @@ public void bookmarkArticle(final Long id, final LoginMember loginMember, article.removeBookmark(member); } } + + public List filter(final LoginMember member, final MemberGroupType course, final boolean onlyBookmarked) { + if (member.isMember() && onlyBookmarked) { + return articleRepository.findArticlesByCourseAndMember(course.getGroupName(), member.getId()).stream() + .map(ArticleResponse::from) + .collect(toList()); + } + + return articleRepository.findArticlesByCourse(course.getGroupName()).stream() + .map(ArticleResponse::from) + .collect(toList()); + } } diff --git a/backend/src/main/java/wooteco/prolog/article/domain/repository/ArticleRepository.java b/backend/src/main/java/wooteco/prolog/article/domain/repository/ArticleRepository.java index df5efe865..4681d5a1f 100644 --- a/backend/src/main/java/wooteco/prolog/article/domain/repository/ArticleRepository.java +++ b/backend/src/main/java/wooteco/prolog/article/domain/repository/ArticleRepository.java @@ -1,16 +1,30 @@ package wooteco.prolog.article.domain.repository; -import java.util.List; -import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import wooteco.prolog.article.domain.Article; +import java.util.List; +import java.util.Optional; + public interface ArticleRepository extends JpaRepository { List
findAllByOrderByCreatedAtDesc(); @Query("select a from Article a join fetch a.articleBookmarks where a.id = :id") Optional
findFetchById(@Param("id") final Long id); + + @Query("SELECT DISTINCT a FROM Article a " + + "JOIN GroupMember gm ON a.member.id = gm.member.id " + + "JOIN gm.group mg " + + "WHERE mg.name LIKE %:course") + List
findArticlesByCourse(@Param("course") String course); + + @Query("SELECT DISTINCT a FROM Article a " + + "JOIN GroupMember gm ON a.member.id = gm.member.id " + + "JOIN gm.group mg " + + "JOIN a.articleBookmarks.articleBookmarks ab " + + "WHERE mg.name LIKE %:course AND ab.memberId = :memberId") + List
findArticlesByCourseAndMember(@Param("course") String course, @Param("memberId") Long memberId); } diff --git a/backend/src/main/java/wooteco/prolog/article/ui/ArticleController.java b/backend/src/main/java/wooteco/prolog/article/ui/ArticleController.java index 1cacde254..8c5fa5e26 100644 --- a/backend/src/main/java/wooteco/prolog/article/ui/ArticleController.java +++ b/backend/src/main/java/wooteco/prolog/article/ui/ArticleController.java @@ -1,7 +1,5 @@ package wooteco.prolog.article.ui; -import java.net.URI; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -11,11 +9,15 @@ import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import wooteco.prolog.article.application.ArticleService; -import wooteco.prolog.article.domain.ArticleBookmark; import wooteco.prolog.login.domain.AuthMemberPrincipal; import wooteco.prolog.login.ui.LoginMember; +import wooteco.prolog.member.domain.MemberGroupType; + +import java.net.URI; +import java.util.List; @RequiredArgsConstructor @RestController @@ -59,4 +61,13 @@ public ResponseEntity bookmarkArticle(@PathVariable final Long id, articleService.bookmarkArticle(id, member, request.getBookmark()); return ResponseEntity.ok().build(); } + + @GetMapping("/filter") + public ResponseEntity> filterArticles(@AuthMemberPrincipal final LoginMember member, + @RequestParam("course") final MemberGroupType course, + @RequestParam("onlyBookmarked") boolean onlyBookmarked) { + final List articleResponses = articleService.filter(member, course, onlyBookmarked); + + return ResponseEntity.ok(articleResponses); + } } diff --git a/backend/src/main/java/wooteco/prolog/common/WebConverterConfig.java b/backend/src/main/java/wooteco/prolog/common/WebConverterConfig.java index 932f74c6e..d7d157281 100644 --- a/backend/src/main/java/wooteco/prolog/common/WebConverterConfig.java +++ b/backend/src/main/java/wooteco/prolog/common/WebConverterConfig.java @@ -1,11 +1,13 @@ package wooteco.prolog.common; -import java.time.LocalDate; -import java.time.Month; -import java.time.format.DateTimeFormatter; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import wooteco.prolog.member.domain.MemberGroupType; + +import java.time.LocalDate; +import java.time.Month; +import java.time.format.DateTimeFormatter; @Configuration public class WebConverterConfig implements WebMvcConfigurer { @@ -18,5 +20,9 @@ public void addFormatters(FormatterRegistry registry) { registry.addConverter(String.class, LocalDate.class, source -> LocalDate.parse(source, DateTimeFormatter.BASIC_ISO_DATE) ); + + registry.addConverter(String.class, MemberGroupType.class, + source -> MemberGroupType.valueOf(source.toUpperCase()) + ); } } From 262d41446c40f206281566398a6d11ad3da733a1 Mon Sep 17 00:00:00 2001 From: donghae-kim Date: Wed, 20 Sep 2023 16:09:12 +0900 Subject: [PATCH 2/3] =?UTF-8?q?test:=20article=20filter=20test=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ArticleServiceTest.java | 63 ++++++++++++++++--- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/backend/src/test/java/wooteco/prolog/article/application/ArticleServiceTest.java b/backend/src/test/java/wooteco/prolog/article/application/ArticleServiceTest.java index a93382d44..00b94cb45 100644 --- a/backend/src/test/java/wooteco/prolog/article/application/ArticleServiceTest.java +++ b/backend/src/test/java/wooteco/prolog/article/application/ArticleServiceTest.java @@ -1,15 +1,6 @@ package wooteco.prolog.article.application; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static wooteco.prolog.login.ui.LoginMember.Authority.MEMBER; -import static wooteco.prolog.member.domain.Role.CREW; - -import java.util.Optional; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -24,12 +15,28 @@ import wooteco.prolog.article.domain.Url; import wooteco.prolog.article.domain.repository.ArticleRepository; import wooteco.prolog.article.ui.ArticleRequest; +import wooteco.prolog.article.ui.ArticleResponse; import wooteco.prolog.common.exception.BadRequestException; import wooteco.prolog.login.ui.LoginMember; import wooteco.prolog.member.application.MemberService; import wooteco.prolog.member.domain.Member; +import wooteco.prolog.member.domain.MemberGroupType; import wooteco.prolog.member.domain.Role; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static wooteco.prolog.login.ui.LoginMember.Authority.ANONYMOUS; +import static wooteco.prolog.login.ui.LoginMember.Authority.MEMBER; +import static wooteco.prolog.member.domain.Role.CREW; + @ExtendWith(MockitoExtension.class) class ArticleServiceTest { @@ -232,4 +239,40 @@ void remove() { .isTrue(); } } + + @DisplayName("비로그인 사용자가 백엔드 아티클을 필터링 한다.") + @Test + void filter() { + //given + final Member member = new Member(1L, "username", "nickname", CREW, 1L, "url"); + final Article article = new Article(member, new Title("title"), new Url("url"), new ImageUrl("imageUrl")); + final LoginMember unLoginMember = new LoginMember(1L, ANONYMOUS); + + when(articleRepository.findArticlesByCourse(any())).thenReturn(Arrays.asList(article)); + + //when + final List articleResponses = articleService.filter(unLoginMember, MemberGroupType.BACKEND, false); + + //then + verify(articleRepository).findArticlesByCourse(any()); + Assertions.assertThat(articleResponses.get(0).getTitle()).isEqualTo(article.getTitle().getTitle()); + } + + @DisplayName("로그인 유저가 북마크 백엔드 아티클을 필터링 한다.") + @Test + void filter_isBookmarked() { + //given + final Member member = new Member(1L, "username", "nickname", CREW, 1L, "url"); + final Article article = new Article(member, new Title("title"), new Url("url"), new ImageUrl("imageUrl")); + final LoginMember loginMember = new LoginMember(1L, MEMBER); + + when(articleRepository.findArticlesByCourseAndMember(any(), any())).thenReturn(Arrays.asList(article)); + + //when + final List articleResponses = articleService.filter(loginMember, MemberGroupType.BACKEND, true); + + //then + verify(articleRepository).findArticlesByCourseAndMember(any(), any()); + Assertions.assertThat(articleResponses.get(0).getTitle()).isEqualTo(article.getTitle().getTitle()); + } } From b1a13d1270aeb2d90bfaffa6a46b2b670739a304 Mon Sep 17 00:00:00 2001 From: donghae-kim Date: Wed, 20 Sep 2023 17:41:17 +0900 Subject: [PATCH 3/3] =?UTF-8?q?refactor:=20=EC=95=84=ED=8B=B0=ED=81=B4=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20response?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../article/application/ArticleService.java | 15 ++++----------- .../article/domain/ArticleFilterType.java | 17 +++++++++++++++++ .../prolog/article/ui/ArticleController.java | 16 +++++----------- .../prolog/article/ui/ArticleResponse.java | 8 +++++--- .../prolog/common/WebConverterConfig.java | 6 +++--- .../article/application/ArticleServiceTest.java | 6 +++--- 6 files changed, 37 insertions(+), 31 deletions(-) create mode 100644 backend/src/main/java/wooteco/prolog/article/domain/ArticleFilterType.java diff --git a/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java b/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java index 3f7ce7cfe..55ec503ce 100644 --- a/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java +++ b/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java @@ -4,6 +4,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import wooteco.prolog.article.domain.Article; +import wooteco.prolog.article.domain.ArticleFilterType; import wooteco.prolog.article.domain.repository.ArticleRepository; import wooteco.prolog.article.ui.ArticleRequest; import wooteco.prolog.article.ui.ArticleResponse; @@ -12,7 +13,6 @@ import wooteco.prolog.login.ui.LoginMember; import wooteco.prolog.member.application.MemberService; import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.MemberGroupType; import java.util.List; @@ -38,13 +38,6 @@ public Long create(final ArticleRequest articleRequest, final LoginMember loginM return articleRepository.save(article).getId(); } - public List getAll() { - return articleRepository.findAllByOrderByCreatedAtDesc() - .stream() - .map(ArticleResponse::from) - .collect(toList()); - } - @Transactional public void update(final Long id, final ArticleRequest articleRequest, final LoginMember loginMember) { @@ -82,15 +75,15 @@ public void bookmarkArticle(final Long id, final LoginMember loginMember, } } - public List filter(final LoginMember member, final MemberGroupType course, final boolean onlyBookmarked) { + public List getFilteredArticles(final LoginMember member, final ArticleFilterType course, final boolean onlyBookmarked) { if (member.isMember() && onlyBookmarked) { return articleRepository.findArticlesByCourseAndMember(course.getGroupName(), member.getId()).stream() - .map(ArticleResponse::from) + .map(article -> ArticleResponse.of(article,member.getId())) .collect(toList()); } return articleRepository.findArticlesByCourse(course.getGroupName()).stream() - .map(ArticleResponse::from) + .map(article -> ArticleResponse.of(article,member.getId())) .collect(toList()); } } diff --git a/backend/src/main/java/wooteco/prolog/article/domain/ArticleFilterType.java b/backend/src/main/java/wooteco/prolog/article/domain/ArticleFilterType.java new file mode 100644 index 000000000..8eddb65f6 --- /dev/null +++ b/backend/src/main/java/wooteco/prolog/article/domain/ArticleFilterType.java @@ -0,0 +1,17 @@ +package wooteco.prolog.article.domain; + +import lombok.Getter; + +@Getter +public enum ArticleFilterType { + ALL(""), + ANDROID("안드로이드"), + BACKEND("백엔드"), + FRONTEND("프론트엔드"); + + private final String groupName; + + ArticleFilterType(String groupName) { + this.groupName = groupName; + } +} diff --git a/backend/src/main/java/wooteco/prolog/article/ui/ArticleController.java b/backend/src/main/java/wooteco/prolog/article/ui/ArticleController.java index 8c5fa5e26..2ac381fd9 100644 --- a/backend/src/main/java/wooteco/prolog/article/ui/ArticleController.java +++ b/backend/src/main/java/wooteco/prolog/article/ui/ArticleController.java @@ -12,9 +12,9 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import wooteco.prolog.article.application.ArticleService; +import wooteco.prolog.article.domain.ArticleFilterType; import wooteco.prolog.login.domain.AuthMemberPrincipal; import wooteco.prolog.login.ui.LoginMember; -import wooteco.prolog.member.domain.MemberGroupType; import java.net.URI; import java.util.List; @@ -33,12 +33,6 @@ public ResponseEntity createArticles(@RequestBody final ArticleRequest art return ResponseEntity.created(URI.create("/articles/" + id)).build(); } - @GetMapping - public ResponseEntity> getArticles() { - final List allArticles = articleService.getAll(); - return ResponseEntity.ok(allArticles); - } - @PutMapping("/{id}") public ResponseEntity updateArticle(@RequestBody final ArticleRequest articleRequest, @AuthMemberPrincipal final LoginMember member, @@ -62,11 +56,11 @@ public ResponseEntity bookmarkArticle(@PathVariable final Long id, return ResponseEntity.ok().build(); } - @GetMapping("/filter") - public ResponseEntity> filterArticles(@AuthMemberPrincipal final LoginMember member, - @RequestParam("course") final MemberGroupType course, + @GetMapping + public ResponseEntity> getFilteredArticles(@AuthMemberPrincipal final LoginMember member, + @RequestParam("course") final ArticleFilterType course, @RequestParam("onlyBookmarked") boolean onlyBookmarked) { - final List articleResponses = articleService.filter(member, course, onlyBookmarked); + final List articleResponses = articleService.getFilteredArticles(member, course, onlyBookmarked); return ResponseEntity.ok(articleResponses); } diff --git a/backend/src/main/java/wooteco/prolog/article/ui/ArticleResponse.java b/backend/src/main/java/wooteco/prolog/article/ui/ArticleResponse.java index 6d79c7943..19d398ca5 100644 --- a/backend/src/main/java/wooteco/prolog/article/ui/ArticleResponse.java +++ b/backend/src/main/java/wooteco/prolog/article/ui/ArticleResponse.java @@ -16,20 +16,22 @@ public class ArticleResponse { private final String title; private final String url; private final String imageUrl; + private final boolean isBookmarked; @JsonFormat(pattern = "yyyy-MM-dd HH:mm") private final LocalDateTime createdAt; private ArticleResponse() { - this(null, null, null, null, null, null); + this(null, null, null, null, null, false, null); } - public static ArticleResponse from(final Article article) { + public static ArticleResponse of(final Article article, final Long memberId) { final Long id = article.getId(); final String nickName = article.getMember().getNickname(); final String title = article.getTitle().getTitle(); final String url = article.getUrl().getUrl(); final String imageUrl = article.getImageUrl().getUrl(); + final boolean isBookmarked = article.getArticleBookmarks().containBookmark(memberId); final LocalDateTime createdAt = article.getCreatedAt(); - return new ArticleResponse(id, nickName, title, url, imageUrl, createdAt); + return new ArticleResponse(id, nickName, title, url, imageUrl, isBookmarked, createdAt); } } diff --git a/backend/src/main/java/wooteco/prolog/common/WebConverterConfig.java b/backend/src/main/java/wooteco/prolog/common/WebConverterConfig.java index d7d157281..c0277be27 100644 --- a/backend/src/main/java/wooteco/prolog/common/WebConverterConfig.java +++ b/backend/src/main/java/wooteco/prolog/common/WebConverterConfig.java @@ -3,7 +3,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import wooteco.prolog.member.domain.MemberGroupType; +import wooteco.prolog.article.domain.ArticleFilterType; import java.time.LocalDate; import java.time.Month; @@ -21,8 +21,8 @@ public void addFormatters(FormatterRegistry registry) { source -> LocalDate.parse(source, DateTimeFormatter.BASIC_ISO_DATE) ); - registry.addConverter(String.class, MemberGroupType.class, - source -> MemberGroupType.valueOf(source.toUpperCase()) + registry.addConverter(String.class, ArticleFilterType.class, + source -> ArticleFilterType.valueOf(source.toUpperCase()) ); } } diff --git a/backend/src/test/java/wooteco/prolog/article/application/ArticleServiceTest.java b/backend/src/test/java/wooteco/prolog/article/application/ArticleServiceTest.java index 00b94cb45..1d5c4a366 100644 --- a/backend/src/test/java/wooteco/prolog/article/application/ArticleServiceTest.java +++ b/backend/src/test/java/wooteco/prolog/article/application/ArticleServiceTest.java @@ -10,6 +10,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import wooteco.prolog.article.domain.Article; import wooteco.prolog.article.domain.ArticleBookmarks; +import wooteco.prolog.article.domain.ArticleFilterType; import wooteco.prolog.article.domain.ImageUrl; import wooteco.prolog.article.domain.Title; import wooteco.prolog.article.domain.Url; @@ -20,7 +21,6 @@ import wooteco.prolog.login.ui.LoginMember; import wooteco.prolog.member.application.MemberService; import wooteco.prolog.member.domain.Member; -import wooteco.prolog.member.domain.MemberGroupType; import wooteco.prolog.member.domain.Role; import java.util.Arrays; @@ -251,7 +251,7 @@ void filter() { when(articleRepository.findArticlesByCourse(any())).thenReturn(Arrays.asList(article)); //when - final List articleResponses = articleService.filter(unLoginMember, MemberGroupType.BACKEND, false); + final List articleResponses = articleService.getFilteredArticles(unLoginMember, ArticleFilterType.BACKEND, false); //then verify(articleRepository).findArticlesByCourse(any()); @@ -269,7 +269,7 @@ void filter_isBookmarked() { when(articleRepository.findArticlesByCourseAndMember(any(), any())).thenReturn(Arrays.asList(article)); //when - final List articleResponses = articleService.filter(loginMember, MemberGroupType.BACKEND, true); + final List articleResponses = articleService.getFilteredArticles(loginMember, ArticleFilterType.BACKEND, true); //then verify(articleRepository).findArticlesByCourseAndMember(any(), any());