From 0358fde2524984bd20c5ebe2ecc4d2c28792bbfa Mon Sep 17 00:00:00 2001 From: aisensiy Date: Thu, 17 Aug 2017 14:27:29 +0800 Subject: [PATCH] list recent article --- src/main/java/io/spring/api/ArticlesApi.java | 12 ++++ src/main/java/io/spring/application/Page.java | 33 ++++++++++ .../application/article/ArticleDataList.java | 20 ++++++ .../article/ArticleQueryService.java | 11 ++++ .../article/ArticleReadService.java | 9 +++ .../java/io/spring/core/article/Article.java | 8 ++- .../resources/mapper/ArticleReadService.xml | 53 ++++++++++++++++ src/test/java/io/spring/TestHelper.java | 39 ++++++++++++ .../java/io/spring/api/ArticlesApiTest.java | 35 +---------- .../io/spring/api/ListArticleApiTest.java | 46 ++++++++++++++ .../article/ArticleQueryServiceTest.java | 61 +++++++++++++++++++ 11 files changed, 293 insertions(+), 34 deletions(-) create mode 100644 src/main/java/io/spring/application/Page.java create mode 100644 src/main/java/io/spring/application/article/ArticleDataList.java create mode 100644 src/test/java/io/spring/TestHelper.java create mode 100644 src/test/java/io/spring/api/ListArticleApiTest.java diff --git a/src/main/java/io/spring/api/ArticlesApi.java b/src/main/java/io/spring/api/ArticlesApi.java index 0dc3057..437e332 100644 --- a/src/main/java/io/spring/api/ArticlesApi.java +++ b/src/main/java/io/spring/api/ArticlesApi.java @@ -2,6 +2,7 @@ package io.spring.api; import com.fasterxml.jackson.annotation.JsonRootName; import io.spring.api.exception.InvalidRequestException; +import io.spring.application.Page; import io.spring.application.article.ArticleQueryService; import io.spring.core.article.Article; import io.spring.core.article.ArticleRepository; @@ -13,9 +14,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; 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 javax.validation.Valid; @@ -52,6 +55,15 @@ public class ArticlesApi { put("article", articleQueryService.findById(article.getId(), user).get()); }}); } + + @GetMapping + public ResponseEntity getArticles(@RequestParam(value = "offset", defaultValue = "0") int offset, + @RequestParam(value = "limit", defaultValue = "20") int limit, + @RequestParam(value = "tag", required = false) String tag, + @RequestParam(value = "favorited", required = false) String favoritedBy, + @RequestParam(value = "author", required = false) String author) { + return ResponseEntity.ok(articleQueryService.findRecentArticles(tag, author, favoritedBy, new Page(offset, limit))); + } } @Getter diff --git a/src/main/java/io/spring/application/Page.java b/src/main/java/io/spring/application/Page.java new file mode 100644 index 0000000..536c04a --- /dev/null +++ b/src/main/java/io/spring/application/Page.java @@ -0,0 +1,33 @@ +package io.spring.application; + +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Data +@Getter +@NoArgsConstructor +public class Page { + private static final int MAX_LIMIT = 100; + private int offset = 0; + private int limit = 20; + + public Page(int offset, int limit) { + setOffset(offset); + setLimit(limit); + } + + private void setOffset(int offset) { + if (offset > 0) { + this.offset = offset; + } + } + + private void setLimit(int limit) { + if (limit > MAX_LIMIT) { + this.limit = MAX_LIMIT; + } else if (limit > 0) { + this.limit = limit; + } + } +} diff --git a/src/main/java/io/spring/application/article/ArticleDataList.java b/src/main/java/io/spring/application/article/ArticleDataList.java new file mode 100644 index 0000000..828c326 --- /dev/null +++ b/src/main/java/io/spring/application/article/ArticleDataList.java @@ -0,0 +1,20 @@ +package io.spring.application.article; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; + +import java.util.List; + +@Getter +public class ArticleDataList { + @JsonProperty("articles") + private final List articleDatas; + @JsonProperty("articlesCount") + private final int count; + + public ArticleDataList(List articleDatas, int count) { + + this.articleDatas = articleDatas; + this.count = count; + } +} diff --git a/src/main/java/io/spring/application/article/ArticleQueryService.java b/src/main/java/io/spring/application/article/ArticleQueryService.java index c6add11..79b3ebf 100644 --- a/src/main/java/io/spring/application/article/ArticleQueryService.java +++ b/src/main/java/io/spring/application/article/ArticleQueryService.java @@ -1,10 +1,13 @@ package io.spring.application.article; +import io.spring.application.Page; import io.spring.application.profile.UserRelationshipQueryService; import io.spring.core.user.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; @Service @@ -54,4 +57,12 @@ public class ArticleQueryService { user.getId(), articleData.getProfileData().getId())); } + + public ArticleDataList findRecentArticles(String tag, String author, String favoritedBy, Page page) { + List articleIds = articleReadService.queryArticles(tag, author, favoritedBy, page); + int articleCount = articleReadService.countArticle(tag, author, favoritedBy); + return new ArticleDataList( + articleIds.size() == 0 ? new ArrayList<>() : articleReadService.findArticles(articleIds), + articleCount); + } } diff --git a/src/main/java/io/spring/application/article/ArticleReadService.java b/src/main/java/io/spring/application/article/ArticleReadService.java index 5cb359f..8ed9043 100644 --- a/src/main/java/io/spring/application/article/ArticleReadService.java +++ b/src/main/java/io/spring/application/article/ArticleReadService.java @@ -1,13 +1,22 @@ package io.spring.application.article; +import io.spring.application.Page; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Component; +import java.util.List; + @Component @Mapper public interface ArticleReadService { ArticleData findById(@Param("id") String id); ArticleData findBySlug(@Param("slug") String slug); + + List queryArticles(@Param("tag") String tag, @Param("author") String author, @Param("favoritedBy") String favoritedBy, @Param("page") Page page); + + int countArticle(@Param("tag") String tag, @Param("author") String author, @Param("favoritedBy") String favoritedBy); + + List findArticles(@Param("articleIds") List articleIds); } diff --git a/src/main/java/io/spring/core/article/Article.java b/src/main/java/io/spring/core/article/Article.java index 4b9ae9e..aed678f 100644 --- a/src/main/java/io/spring/core/article/Article.java +++ b/src/main/java/io/spring/core/article/Article.java @@ -27,6 +27,10 @@ public class Article { private DateTime updatedAt; public Article(String title, String description, String body, String[] tagList, String userId) { + this(title, description, body, tagList, userId, new DateTime()); + } + + public Article(String title, String description, String body, String[] tagList, String userId, DateTime createdAt) { this.id = UUID.randomUUID().toString(); this.slug = toSlug(title); this.title = title; @@ -34,8 +38,8 @@ public class Article { this.body = body; this.tags = Arrays.stream(tagList).collect(toSet()).stream().map(Tag::new).collect(toList()); this.userId = userId; - this.createdAt = new DateTime(); - this.updatedAt = new DateTime(); + this.createdAt = createdAt; + this.updatedAt = createdAt; } public void update(String title, String description, String body) { diff --git a/src/main/resources/mapper/ArticleReadService.xml b/src/main/resources/mapper/ArticleReadService.xml index 883e1df..84c0ea3 100644 --- a/src/main/resources/mapper/ArticleReadService.xml +++ b/src/main/resources/mapper/ArticleReadService.xml @@ -33,6 +33,59 @@ where A.slug = #{slug} + + + + + + + diff --git a/src/test/java/io/spring/TestHelper.java b/src/test/java/io/spring/TestHelper.java new file mode 100644 index 0000000..809c34e --- /dev/null +++ b/src/test/java/io/spring/TestHelper.java @@ -0,0 +1,39 @@ +package io.spring; + +import io.spring.application.article.ArticleData; +import io.spring.application.profile.ProfileData; +import io.spring.core.article.Article; +import io.spring.core.user.User; +import org.joda.time.DateTime; + +import java.util.ArrayList; +import java.util.Arrays; + +public class TestHelper { + public static ArticleData articleDataFixture(String seed, User user) { + DateTime now = new DateTime(); + return new ArticleData( + seed + "id", + "title-" + seed, + "title " + seed, + "desc " + seed, + "body " + seed, false, 0, now, now, new ArrayList<>(), + new ProfileData(user.getId(), user.getUsername(), user.getBio(), user.getImage(), false)); + } + + public static ArticleData getArticleDataFromArticleAndUser(Article article, User user) { + DateTime time = new DateTime(); + return new ArticleData( + article.getId(), + article.getSlug(), + article.getTitle(), + article.getDescription(), + article.getBody(), + false, + 0, + time, + time, + Arrays.asList("joda"), + new ProfileData(user.getId(), user.getUsername(), user.getBio(), user.getImage(), false)); + } +} diff --git a/src/test/java/io/spring/api/ArticlesApiTest.java b/src/test/java/io/spring/api/ArticlesApiTest.java index 3bf7472..7b36c5d 100644 --- a/src/test/java/io/spring/api/ArticlesApiTest.java +++ b/src/test/java/io/spring/api/ArticlesApiTest.java @@ -1,6 +1,7 @@ package io.spring.api; import io.restassured.RestAssured; +import io.spring.TestHelper; import io.spring.application.article.ArticleData; import io.spring.application.article.ArticleQueryService; import io.spring.application.profile.ProfileData; @@ -42,17 +43,9 @@ public class ArticlesApiTest extends TestWithCurrentUser { @MockBean private ArticleQueryService articleQueryService; - protected String email; - protected String username; - protected String defaultAvatar; - @Before public void setUp() throws Exception { RestAssured.port = port; - email = "john@jacob.com"; - username = "johnjacob"; - defaultAvatar = "https://static.productionready.io/images/smiley-cyrus.jpg"; - userFixture(); } @Test @@ -124,18 +117,7 @@ public class ArticlesApiTest extends TestWithCurrentUser { Article article = new Article("Test New Article", "Desc", "Body", new String[]{"java", "spring", "jpg"}, user.getId()); DateTime time = new DateTime(); - ArticleData articleData = new ArticleData( - article.getId(), - article.getSlug(), - article.getTitle(), - article.getDescription(), - article.getBody(), - false, - 0, - time, - time, - Arrays.asList("joda"), - new ProfileData(user.getId(), user.getUsername(), user.getBio(), user.getImage(), false)); + ArticleData articleData = TestHelper.getArticleDataFromArticleAndUser(article, user); when(articleQueryService.findBySlug(eq(slug), eq(null))).thenReturn(Optional.of(articleData)); @@ -168,18 +150,7 @@ public class ArticlesApiTest extends TestWithCurrentUser { Article article = new Article(title, description, body, new String[]{"java", "spring", "jpg"}, user.getId()); DateTime time = new DateTime(); - ArticleData articleData = new ArticleData( - article.getId(), - article.getSlug(), - article.getTitle(), - article.getDescription(), - article.getBody(), - false, - 0, - time, - time, - Arrays.asList("joda"), - new ProfileData(user.getId(), user.getUsername(), user.getBio(), user.getImage(), false)); + ArticleData articleData = TestHelper.getArticleDataFromArticleAndUser(article, user); when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article)); when(articleQueryService.findBySlug(eq(article.getSlug()), eq(user))).thenReturn(Optional.of(articleData)); diff --git a/src/test/java/io/spring/api/ListArticleApiTest.java b/src/test/java/io/spring/api/ListArticleApiTest.java new file mode 100644 index 0000000..4d6296c --- /dev/null +++ b/src/test/java/io/spring/api/ListArticleApiTest.java @@ -0,0 +1,46 @@ +package io.spring.api; + +import io.restassured.RestAssured; +import io.spring.application.Page; +import io.spring.application.article.ArticleDataList; +import io.spring.application.article.ArticleQueryService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.junit4.SpringRunner; + +import static io.spring.TestHelper.articleDataFixture; +import static java.util.Arrays.asList; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.when; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@RunWith(SpringRunner.class) +public class ListArticleApiTest extends TestWithCurrentUser { + @MockBean + private ArticleQueryService articleQueryService; + + @LocalServerPort + private int port; + + @Before + public void setUp() throws Exception { + RestAssured.port = port; + userFixture(); + } + + @Test + public void should_get_default_article_list() throws Exception { + ArticleDataList articleDataList = new ArticleDataList( + asList(articleDataFixture("1", user), articleDataFixture("2", user)), 2); + when(articleQueryService.findRecentArticles(eq(null), eq(null), eq(null), eq(new Page(0, 20)))).thenReturn(articleDataList); + RestAssured.when() + .get("/articles") + .prettyPeek() + .then() + .statusCode(200); + } +} diff --git a/src/test/java/io/spring/application/article/ArticleQueryServiceTest.java b/src/test/java/io/spring/application/article/ArticleQueryServiceTest.java index 15b1814..bc2bad7 100644 --- a/src/test/java/io/spring/application/article/ArticleQueryServiceTest.java +++ b/src/test/java/io/spring/application/article/ArticleQueryServiceTest.java @@ -1,5 +1,6 @@ package io.spring.application.article; +import io.spring.application.Page; import io.spring.core.article.Article; import io.spring.core.article.ArticleRepository; import io.spring.core.favorite.ArticleFavorite; @@ -9,6 +10,7 @@ import io.spring.core.user.UserRepository; import io.spring.infrastructure.article.MyBatisArticleRepository; import io.spring.infrastructure.favorite.MyBatisArticleFavoriteRepository; import io.spring.infrastructure.user.MyBatisUserRepository; +import org.joda.time.DateTime; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -75,4 +77,63 @@ public class ArticleQueryServiceTest { assertThat(articleData.getFavoritesCount(), is(1)); assertThat(articleData.isFavorited(), is(true)); } + + @Test + public void should_get_default_article_list() throws Exception { + Article anotherArticle = new Article("new article", "desc", "body", new String[]{"test"}, user.getId(), new DateTime().minusHours(1)); + articleRepository.save(anotherArticle); + + ArticleDataList recentArticles = queryService.findRecentArticles(null, null, null, new Page()); + assertThat(recentArticles.getCount(), is(2)); + assertThat(recentArticles.getArticleDatas().size(), is(2)); + assertThat(recentArticles.getArticleDatas().get(0).getId(), is(article.getId())); + + ArticleDataList nodata = queryService.findRecentArticles(null, null, null, new Page(2, 10)); + assertThat(nodata.getCount(), is(2)); + assertThat(nodata.getArticleDatas().size(), is(0)); + } + + @Test + public void should_query_article_by_author() throws Exception { + User anotherUser = new User("other@email.com", "other", "123", "", ""); + userRepository.save(anotherUser); + + Article anotherArticle = new Article("new article", "desc", "body", new String[]{"test"}, anotherUser.getId()); + articleRepository.save(anotherArticle); + + ArticleDataList recentArticles = queryService.findRecentArticles(null, user.getId(), null, new Page()); + assertThat(recentArticles.getArticleDatas().size(), is(1)); + assertThat(recentArticles.getCount(), is(1)); + } + + @Test + public void should_query_article_by_favorite() throws Exception { + User anotherUser = new User("other@email.com", "other", "123", "", ""); + userRepository.save(anotherUser); + + Article anotherArticle = new Article("new article", "desc", "body", new String[]{"test"}, anotherUser.getId()); + articleRepository.save(anotherArticle); + + ArticleFavorite articleFavorite = new ArticleFavorite(article.getId(), anotherUser.getId()); + articleFavoriteRepository.save(articleFavorite); + + ArticleDataList recentArticles = queryService.findRecentArticles(null, null, anotherUser.getId(), new Page()); + assertThat(recentArticles.getArticleDatas().size(), is(1)); + assertThat(recentArticles.getCount(), is(1)); + assertThat(recentArticles.getArticleDatas().get(0).getId(), is(article.getId())); + } + + @Test + public void should_query_article_by_tag() throws Exception { + Article anotherArticle = new Article("new article", "desc", "body", new String[]{"test"}, user.getId()); + articleRepository.save(anotherArticle); + + ArticleDataList recentArticles = queryService.findRecentArticles("spring", null, null, new Page()); + assertThat(recentArticles.getArticleDatas().size(), is(1)); + assertThat(recentArticles.getCount(), is(1)); + assertThat(recentArticles.getArticleDatas().get(0).getId(), is(article.getId())); + + ArticleDataList notag = queryService.findRecentArticles("notag", null, null, new Page()); + assertThat(notag.getCount(), is(0)); + } } \ No newline at end of file