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..55ec503ce 100644 --- a/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java +++ b/backend/src/main/java/wooteco/prolog/article/application/ArticleService.java @@ -1,14 +1,10 @@ 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; 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; @@ -18,6 +14,12 @@ import wooteco.prolog.member.application.MemberService; import wooteco.prolog.member.domain.Member; +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 @Transactional(readOnly = true) @@ -36,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) { @@ -79,4 +74,16 @@ public void bookmarkArticle(final Long id, final LoginMember loginMember, article.removeBookmark(member); } } + + 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(article -> ArticleResponse.of(article,member.getId())) + .collect(toList()); + } + + return articleRepository.findArticlesByCourse(course.getGroupName()).stream() + .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/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..2ac381fd9 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,12 +9,16 @@ 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.article.domain.ArticleFilterType; import wooteco.prolog.login.domain.AuthMemberPrincipal; import wooteco.prolog.login.ui.LoginMember; +import java.net.URI; +import java.util.List; + @RequiredArgsConstructor @RestController @RequestMapping("/articles") @@ -31,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, @@ -59,4 +55,13 @@ public ResponseEntity bookmarkArticle(@PathVariable final Long id, articleService.bookmarkArticle(id, member, request.getBookmark()); return ResponseEntity.ok().build(); } + + @GetMapping + public ResponseEntity> getFilteredArticles(@AuthMemberPrincipal final LoginMember member, + @RequestParam("course") final ArticleFilterType course, + @RequestParam("onlyBookmarked") boolean 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 932f74c6e..c0277be27 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.article.domain.ArticleFilterType; + +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, 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 a93382d44..1d5c4a366 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; @@ -19,17 +10,33 @@ 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; 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.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.getFilteredArticles(unLoginMember, ArticleFilterType.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.getFilteredArticles(loginMember, ArticleFilterType.BACKEND, true); + + //then + verify(articleRepository).findArticlesByCourseAndMember(any(), any()); + Assertions.assertThat(articleResponses.get(0).getTitle()).isEqualTo(article.getTitle().getTitle()); + } }