Skip to content

Commit

Permalink
Merge pull request #1550 from woowacourse/feat/1540-article-filtering
Browse files Browse the repository at this point in the history
feat: 아티클 필터링 기능 추가
  • Loading branch information
donghae-kim authored Sep 20, 2023
2 parents 7a63321 + b1a13d1 commit 92cc7eb
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 39 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)
Expand All @@ -36,13 +38,6 @@ public Long create(final ArticleRequest articleRequest, final LoginMember loginM
return articleRepository.save(article).getId();
}

public List<ArticleResponse> getAll() {
return articleRepository.findAllByOrderByCreatedAtDesc()
.stream()
.map(ArticleResponse::from)
.collect(toList());
}

@Transactional
public void update(final Long id, final ArticleRequest articleRequest,
final LoginMember loginMember) {
Expand Down Expand Up @@ -79,4 +74,16 @@ public void bookmarkArticle(final Long id, final LoginMember loginMember,
article.removeBookmark(member);
}
}

public List<ArticleResponse> 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());
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<Article, Long> {

List<Article> findAllByOrderByCreatedAtDesc();

@Query("select a from Article a join fetch a.articleBookmarks where a.id = :id")
Optional<Article> 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<Article> 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<Article> findArticlesByCourseAndMember(@Param("course") String course, @Param("memberId") Long memberId);
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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")
Expand All @@ -31,12 +33,6 @@ public ResponseEntity<Void> createArticles(@RequestBody final ArticleRequest art
return ResponseEntity.created(URI.create("/articles/" + id)).build();
}

@GetMapping
public ResponseEntity<List<ArticleResponse>> getArticles() {
final List<ArticleResponse> allArticles = articleService.getAll();
return ResponseEntity.ok(allArticles);
}

@PutMapping("/{id}")
public ResponseEntity<Void> updateArticle(@RequestBody final ArticleRequest articleRequest,
@AuthMemberPrincipal final LoginMember member,
Expand All @@ -59,4 +55,13 @@ public ResponseEntity<Void> bookmarkArticle(@PathVariable final Long id,
articleService.bookmarkArticle(id, member, request.getBookmark());
return ResponseEntity.ok().build();
}

@GetMapping
public ResponseEntity<List<ArticleResponse>> getFilteredArticles(@AuthMemberPrincipal final LoginMember member,
@RequestParam("course") final ArticleFilterType course,
@RequestParam("onlyBookmarked") boolean onlyBookmarked) {
final List<ArticleResponse> articleResponses = articleService.getFilteredArticles(member, course, onlyBookmarked);

return ResponseEntity.ok(articleResponses);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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())
);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {

Expand Down Expand Up @@ -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<ArticleResponse> 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<ArticleResponse> articleResponses = articleService.getFilteredArticles(loginMember, ArticleFilterType.BACKEND, true);

//then
verify(articleRepository).findArticlesByCourseAndMember(any(), any());
Assertions.assertThat(articleResponses.get(0).getTitle()).isEqualTo(article.getTitle().getTitle());
}
}

0 comments on commit 92cc7eb

Please sign in to comment.