diff --git a/src/main/java/io/spring/api/ArticleApi.java b/src/main/java/io/spring/api/ArticleApi.java index 0e39610..de25b87 100644 --- a/src/main/java/io/spring/api/ArticleApi.java +++ b/src/main/java/io/spring/api/ArticleApi.java @@ -1,25 +1,37 @@ package io.spring.api; +import com.fasterxml.jackson.annotation.JsonRootName; +import io.spring.api.exception.NoAuthorizationException; import io.spring.api.exception.ResourceNotFoundException; +import io.spring.application.AuthorizationService; import io.spring.application.article.ArticleData; import io.spring.application.article.ArticleQueryService; +import io.spring.core.article.ArticleRepository; import io.spring.core.user.User; +import lombok.Getter; +import lombok.NoArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +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.RestController; +import javax.validation.Valid; + @RestController @RequestMapping(path = "/articles/{slug}") public class ArticleApi { private ArticleQueryService articleQueryService; + private ArticleRepository articleRepository; @Autowired - public ArticleApi(ArticleQueryService articleQueryService) { + public ArticleApi(ArticleQueryService articleQueryService, ArticleRepository articleRepository) { this.articleQueryService = articleQueryService; + this.articleRepository = articleRepository; } @GetMapping @@ -27,4 +39,30 @@ public class ArticleApi { @AuthenticationPrincipal User user) { return articleQueryService.findBySlug(slug, user).map(ResponseEntity::ok).orElseThrow(ResourceNotFoundException::new); } + + @PutMapping + public ResponseEntity updateArticle(@PathVariable("slug") String slug, + @AuthenticationPrincipal User user, + @Valid @RequestBody UpdateArticleParam updateArticleParam) { + return articleRepository.findBySlug(slug).map(article -> { + if (!AuthorizationService.canUpdateArticle(user, article)) { + throw new NoAuthorizationException(); + } + article.update( + updateArticleParam.getTitle(), + updateArticleParam.getDescription(), + updateArticleParam.getBody()); + articleRepository.save(article); + return ResponseEntity.ok(articleQueryService.findBySlug(slug, user).get()); + }).orElseThrow(ResourceNotFoundException::new); + } +} + +@Getter +@NoArgsConstructor +@JsonRootName("article") +class UpdateArticleParam { + private String title = ""; + private String body = ""; + private String description = ""; } diff --git a/src/main/java/io/spring/api/ArticlesApi.java b/src/main/java/io/spring/api/ArticlesApi.java index 1a7bb00..5a954f4 100644 --- a/src/main/java/io/spring/api/ArticlesApi.java +++ b/src/main/java/io/spring/api/ArticlesApi.java @@ -41,8 +41,6 @@ public class ArticlesApi { } Article article = new Article( - articleRepository.toSlug( - newArticleParam.getTitle()), newArticleParam.getTitle(), newArticleParam.getDescription(), newArticleParam.getBody(), diff --git a/src/main/java/io/spring/api/exception/NoAuthorizationException.java b/src/main/java/io/spring/api/exception/NoAuthorizationException.java new file mode 100644 index 0000000..60544d2 --- /dev/null +++ b/src/main/java/io/spring/api/exception/NoAuthorizationException.java @@ -0,0 +1,8 @@ +package io.spring.api.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.FORBIDDEN) +public class NoAuthorizationException extends RuntimeException { +} diff --git a/src/main/java/io/spring/application/AuthorizationService.java b/src/main/java/io/spring/application/AuthorizationService.java new file mode 100644 index 0000000..be22cda --- /dev/null +++ b/src/main/java/io/spring/application/AuthorizationService.java @@ -0,0 +1,10 @@ +package io.spring.application; + +import io.spring.core.article.Article; +import io.spring.core.user.User; + +public class AuthorizationService { + public static boolean canUpdateArticle(User user, Article article) { + return user.getId().equals(article.getUserId()); + } +} diff --git a/src/main/java/io/spring/core/article/Article.java b/src/main/java/io/spring/core/article/Article.java index 70adc9c..4b9ae9e 100644 --- a/src/main/java/io/spring/core/article/Article.java +++ b/src/main/java/io/spring/core/article/Article.java @@ -26,9 +26,9 @@ public class Article { private DateTime createdAt; private DateTime updatedAt; - public Article(String slug, String title, String description, String body, String[] tagList, String userId) { + public Article(String title, String description, String body, String[] tagList, String userId) { this.id = UUID.randomUUID().toString(); - this.slug = slug; + this.slug = toSlug(title); this.title = title; this.description = description; this.body = body; @@ -38,4 +38,20 @@ public class Article { this.updatedAt = new DateTime(); } + public void update(String title, String description, String body) { + if (!"".equals(title)) { + this.title = title; + this.slug = toSlug(title); + } + if (!"".equals(description)) { + this.description = description; + } + if (!"".equals(body)) { + this.body = body; + } + } + + private String toSlug(String title) { + return title.toLowerCase().replace(' ', '-'); + } } diff --git a/src/main/java/io/spring/core/article/ArticleRepository.java b/src/main/java/io/spring/core/article/ArticleRepository.java index 375f972..9eaa8ed 100644 --- a/src/main/java/io/spring/core/article/ArticleRepository.java +++ b/src/main/java/io/spring/core/article/ArticleRepository.java @@ -3,9 +3,10 @@ package io.spring.core.article; import java.util.Optional; public interface ArticleRepository { - String toSlug(String title); void save(Article article); Optional
findById(String id); + + Optional
findBySlug(String slug); } diff --git a/src/main/java/io/spring/infrastructure/article/ArticleMapper.java b/src/main/java/io/spring/infrastructure/article/ArticleMapper.java index 37e6008..370fa9f 100644 --- a/src/main/java/io/spring/infrastructure/article/ArticleMapper.java +++ b/src/main/java/io/spring/infrastructure/article/ArticleMapper.java @@ -18,4 +18,8 @@ public interface ArticleMapper { void insertTag(@Param("tag") Tag tag); void insertArticleTagRelation(@Param("articleId") String articleId, @Param("tagId") String tagId); + + Article findBySlug(@Param("slug") String slug); + + void update(@Param("article") Article article); } diff --git a/src/main/java/io/spring/infrastructure/article/MyBatisArticleRepository.java b/src/main/java/io/spring/infrastructure/article/MyBatisArticleRepository.java index ae06730..8f6f0ec 100644 --- a/src/main/java/io/spring/infrastructure/article/MyBatisArticleRepository.java +++ b/src/main/java/io/spring/infrastructure/article/MyBatisArticleRepository.java @@ -16,12 +16,15 @@ public class MyBatisArticleRepository implements ArticleRepository { } @Override - public String toSlug(String title) { - return title.toLowerCase().replace(' ', '-'); + public void save(Article article) { + if (articleMapper.findById(article.getId()) == null) { + createNew(article); + } else { + articleMapper.update(article); + } } - @Override - public void save(Article article) { + private void createNew(Article article) { articleMapper.insert(article); for (Tag tag : article.getTags()) { if (!articleMapper.findTag(tag.getName())) { @@ -35,4 +38,9 @@ public class MyBatisArticleRepository implements ArticleRepository { public Optional
findById(String id) { return Optional.ofNullable(articleMapper.findById(id)); } + + @Override + public Optional
findBySlug(String slug) { + return Optional.ofNullable(articleMapper.findBySlug(slug)); + } } diff --git a/src/main/resources/mapper/ArticleMapper.xml b/src/main/resources/mapper/ArticleMapper.xml index 8cd04d4..c2bd3e6 100644 --- a/src/main/resources/mapper/ArticleMapper.xml +++ b/src/main/resources/mapper/ArticleMapper.xml @@ -19,27 +19,45 @@ insert into article_tags (article_id, tag_id) values(#{articleId}, #{tagId}) - + where A.id = #{id} + diff --git a/src/test/java/io/spring/api/ArticlesApiTest.java b/src/test/java/io/spring/api/ArticlesApiTest.java index 38f4d1f..03ca3a0 100644 --- a/src/test/java/io/spring/api/ArticlesApiTest.java +++ b/src/test/java/io/spring/api/ArticlesApiTest.java @@ -6,6 +6,7 @@ import io.spring.application.article.ArticleQueryService; import io.spring.application.profile.ProfileData; import io.spring.core.article.Article; import io.spring.core.article.ArticleRepository; +import io.spring.core.user.User; import org.joda.time.DateTime; import org.junit.Before; import org.junit.Test; @@ -22,7 +23,6 @@ import java.util.Optional; import static io.restassured.RestAssured.given; import static org.hamcrest.core.IsEqual.equalTo; -import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; @@ -64,8 +64,6 @@ public class ArticlesApiTest extends TestWithCurrentUser { String[] tagList = {"reactjs", "angularjs", "dragons"}; Map param = prepareParam(title, description, body, tagList); - when(articleRepository.toSlug(eq(title))).thenReturn(slug); - ArticleData articleData = new ArticleData( "123", slug, @@ -102,14 +100,11 @@ public class ArticlesApiTest extends TestWithCurrentUser { @Test public void should_get_error_message_with_wrong_parameter() throws Exception { String title = "How to train your dragon"; - String slug = "how-to-train-your-dragon"; String description = "Ever wonder how?"; String body = ""; String[] tagList = {"reactjs", "angularjs", "dragons"}; Map param = prepareParam(title, description, body, tagList); - when(articleRepository.toSlug(eq(title))).thenReturn(slug); - given() .contentType("application/json") .header("Authorization", "Token " + token) @@ -122,21 +117,10 @@ public class ArticlesApiTest extends TestWithCurrentUser { } - private HashMap prepareParam(final String title, final String description, final String body, final String[] tagList) { - return new HashMap() {{ - put("article", new HashMap() {{ - put("title", title); - put("description", description); - put("body", body); - put("tagList", tagList); - }}); - }}; - } - @Test public void should_read_article_success() throws Exception { String slug = "test-new-article"; - Article article = new Article(slug, "Test New Article", "Desc", "Body", new String[]{"java", "spring", "jpg"}, user.getId()); + Article article = new Article("Test New Article", "Desc", "Body", new String[]{"java", "spring", "jpg"}, user.getId()); DateTime time = new DateTime(); ArticleData articleData = new ArticleData( @@ -172,4 +156,100 @@ public class ArticlesApiTest extends TestWithCurrentUser { .then() .statusCode(404); } + + @Test + public void should_update_article_content_success() throws Exception { + String title = "new-title"; + String body = "new body"; + String description = "new description"; + Map updateParam = prepareUpdateParam(title, body, description); + + 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)); + + when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article)); + when(articleQueryService.findBySlug(eq(article.getSlug()), eq(user))).thenReturn(Optional.of(articleData)); + + given() + .contentType("application/json") + .header("Authorization", "Token " + token) + .body(updateParam) + .when() + .put("/articles/{slug}", article.getSlug()) + .then() + .statusCode(200) + .body("article.slug", equalTo(articleData.getSlug())); + } + + @Test + public void should_get_403_if_not_author_to_update_article() throws Exception { + String title = "new-title"; + String body = "new body"; + String description = "new description"; + Map updateParam = prepareUpdateParam(title, body, description); + + User anotherUser = new User("test@test.com", "test", "123123", "", ""); + + Article article = new Article(title, description, body, new String[]{"java", "spring", "jpg"}, anotherUser.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(anotherUser.getId(), anotherUser.getUsername(), anotherUser.getBio(), anotherUser.getImage(), false)); + + when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article)); + when(articleQueryService.findBySlug(eq(article.getSlug()), eq(user))).thenReturn(Optional.of(articleData)); + + given() + .contentType("application/json") + .header("Authorization", "Token " + token) + .body(updateParam) + .when() + .put("/articles/{slug}", article.getSlug()) + .then() + .statusCode(403); + } + + private HashMap prepareUpdateParam(final String title, final String body, final String description) { + return new HashMap() {{ + put("article", new HashMap() {{ + put("title", title); + put("body", body); + put("description", description); + }}); + }}; + } + + private HashMap prepareParam(final String title, final String description, final String body, final String[] tagList) { + return new HashMap() {{ + put("article", new HashMap() {{ + put("title", title); + put("description", description); + put("body", body); + put("tagList", tagList); + }}); + }}; + } } \ No newline at end of file diff --git a/src/test/java/io/spring/application/article/ArticleQueryServiceTest.java b/src/test/java/io/spring/application/article/ArticleQueryServiceTest.java index 5b51079..ca42db8 100644 --- a/src/test/java/io/spring/application/article/ArticleQueryServiceTest.java +++ b/src/test/java/io/spring/application/article/ArticleQueryServiceTest.java @@ -37,7 +37,7 @@ public class ArticleQueryServiceTest { User user = new User("aisensiy@gmail.com", "aisensiy", "123", "", ""); userRepository.save(user); - Article article = new Article("test", "test", "desc", "body", new String[]{"java", "spring"}, user.getId()); + Article article = new Article("test", "desc", "body", new String[]{"java", "spring"}, user.getId()); articleRepository.save(article); Optional optional = queryService.findById(article.getId(), user); diff --git a/src/test/java/io/spring/infrastructure/article/MyBatisArticleRepositoryTest.java b/src/test/java/io/spring/infrastructure/article/MyBatisArticleRepositoryTest.java index 0b15b4c..8702838 100644 --- a/src/test/java/io/spring/infrastructure/article/MyBatisArticleRepositoryTest.java +++ b/src/test/java/io/spring/infrastructure/article/MyBatisArticleRepositoryTest.java @@ -17,6 +17,7 @@ import org.springframework.test.context.junit4.SpringRunner; import java.util.Optional; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.*; @MybatisTest @@ -29,17 +30,18 @@ public class MyBatisArticleRepositoryTest { @Autowired private UserRepository userRepository; private User user; + private Article article; @Before public void setUp() throws Exception { user = new User("aisensiy@gmail.com", "aisensiy", "123", "bio", "default"); userRepository.save(user); + article = new Article("test", "desc", "body", new String[]{"java", "spring"}, user.getId()); } @Test public void should_create_and_fetch_article_success() throws Exception { - Article article = new Article("test", "test", "desc", "body", new String[]{"java", "spring"}, user.getId()); articleRepository.save(article); Optional
optional = articleRepository.findById(article.getId()); assertThat(optional.isPresent(), is(true)); @@ -47,4 +49,19 @@ public class MyBatisArticleRepositoryTest { assertThat(optional.get().getTags().contains(new Tag("java")), is(true)); assertThat(optional.get().getTags().contains(new Tag("spring")), is(true)); } + + @Test + public void should_update_and_fetch_article_success() throws Exception { + articleRepository.save(article); + + String newTitle = "new test 2"; + article.update(newTitle, "", ""); + articleRepository.save(article); + System.out.println(article.getSlug()); + Optional
optional = articleRepository.findBySlug(article.getSlug()); + assertThat(optional.isPresent(), is(true)); + Article fetched = optional.get(); + assertThat(fetched.getTitle(), is(newTitle)); + assertThat(fetched.getBody(), not("")); + } } \ No newline at end of file