Skip to content

Commit

Permalink
Merge pull request #64 from real-world-study/jinyoungchoi95-issue63
Browse files Browse the repository at this point in the history
[jinyoungchoi95-issue63] Article 단일조회 /수정 / 삭제 기능 구현
  • Loading branch information
jinyoungchoi95 committed Oct 25, 2021
2 parents ade24bb + 880edc0 commit 3d2a189
Show file tree
Hide file tree
Showing 20 changed files with 787 additions and 36 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package com.study.realworld.article.controller;

import com.study.realworld.article.controller.request.ArticleCreateRequest;
import com.study.realworld.article.controller.request.ArticleUpdateRequest;
import com.study.realworld.article.controller.response.ArticleResponse;
import com.study.realworld.article.domain.Article;
import com.study.realworld.article.domain.Slug;
import com.study.realworld.article.service.ArticleService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
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;
Expand All @@ -21,11 +27,30 @@ public ArticleController(ArticleService articleService) {
this.articleService = articleService;
}

@GetMapping("/articles/{slug}")
public ResponseEntity<ArticleResponse> getArticle(@PathVariable String slug) {
Article article = articleService.findBySlug(Slug.of(slug));
return ResponseEntity.ok().body(ArticleResponse.fromArticle(article));
}

@PostMapping("/articles")
public ResponseEntity<ArticleResponse> createArticle(@RequestBody ArticleCreateRequest request,
@AuthenticationPrincipal Long loginId) {
Article article = articleService.createArticle(loginId, request.toArticleContent());
return ResponseEntity.ok().body(ArticleResponse.fromArticle(article));
}

@PutMapping("/articles/{slug}")
public ResponseEntity<ArticleResponse> updateArticle(@PathVariable String slug,
@RequestBody ArticleUpdateRequest request,
@AuthenticationPrincipal Long loginId) {
Article article = articleService.updateArticle(loginId, Slug.of(slug), request.toArticleUpdateModel());
return ResponseEntity.ok().body(ArticleResponse.fromArticle(article));
}

@DeleteMapping("/articles/{slug}")
public void deleteArticle(@PathVariable String slug, @AuthenticationPrincipal Long loginId) {
articleService.deleteArticleByAuthorAndSlug(loginId, Slug.of(slug));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.study.realworld.article.controller.request;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.study.realworld.article.domain.Body;
import com.study.realworld.article.domain.Description;
import com.study.realworld.article.domain.Title;
import com.study.realworld.article.service.model.ArticleUpdateModel;
import java.util.Optional;

@JsonTypeName(value = "article")
@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME)
public class ArticleUpdateRequest {

@JsonProperty("title")
private String title;

@JsonProperty("description")
private String description;

@JsonProperty("body")
private String body;

ArticleUpdateRequest() {
}

public ArticleUpdateModel toArticleUpdateModel() {
return new ArticleUpdateModel(
Optional.ofNullable(title).map(Title::of).orElse(null),
Optional.ofNullable(description).map(Description::of).orElse(null),
Optional.ofNullable(body).map(Body::of).orElse(null)
);
}

}
20 changes: 19 additions & 1 deletion src/main/java/com/study/realworld/article/domain/Article.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import com.study.realworld.global.domain.BaseTimeEntity;
import com.study.realworld.tag.domain.Tag;
import com.study.realworld.user.domain.User;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Objects;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
Expand All @@ -29,7 +31,7 @@ public class Article extends BaseTimeEntity {
private ArticleContent articleContent;

@JoinColumn(name = "user_id", nullable = false, foreignKey = @ForeignKey(name = "fk_article_to_user_id"))
@ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
private User author;

protected Article() {
Expand Down Expand Up @@ -68,6 +70,22 @@ public User author() {
return author;
}

public void changeTitle(Title title) {
articleContent.changeTitle(title);
}

public void changeDescription(Description description) {
articleContent.changeDescription(description);
}

public void changeBody(Body body) {
articleContent.changeBody(body);
}

public void deleteArticle() {
saveDeletedTime(OffsetDateTime.now());
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import javax.persistence.CascadeType;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
Expand All @@ -25,7 +26,7 @@ public class ArticleContent {
@JoinTable(name = "article_tag",
joinColumns = @JoinColumn(name = "article_id"),
inverseJoinColumns = @JoinColumn(name = "tag_id"))
@ManyToMany(cascade = CascadeType.PERSIST)
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
private List<Tag> tags;

public ArticleContent() {
Expand Down Expand Up @@ -58,6 +59,18 @@ public List<Tag> tags() {
return tags;
}

public void changeTitle(Title title) {
slugTitle.changeTitle(title);
}

public void changeDescription(Description description) {
this.description = description;
}

public void changeBody(Body body) {
this.body = body;
}

public ArticleContent refreshTags(List<Tag> tags) {
this.tags = tags;
return this;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package com.study.realworld.article.domain;

import com.study.realworld.user.domain.User;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ArticleRepository extends JpaRepository<Article, Long> {

Optional<Article> findByArticleContentSlugTitleSlug(Slug slug);

Optional<Article> findByAuthorAndArticleContentSlugTitleSlug(User author, Slug slug);

}
2 changes: 1 addition & 1 deletion src/main/java/com/study/realworld/article/domain/Slug.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ private Slug(String slug) {
this.slug = slug;
}

static Slug of(String slug) {
public static Slug of(String slug) {
checkSlug(slug);

return new Slug(slug);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ public Title title() {
return title;
}

public void changeTitle(Title title) {
SlugTitle changeSlugTitle = SlugTitle.of(title);
this.title = changeSlugTitle.title;
this.slug = changeSlugTitle.slug;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import com.study.realworld.article.domain.Article;
import com.study.realworld.article.domain.ArticleContent;
import com.study.realworld.article.domain.ArticleRepository;
import com.study.realworld.article.domain.Slug;
import com.study.realworld.article.service.model.ArticleUpdateModel;
import com.study.realworld.global.exception.BusinessException;
import com.study.realworld.global.exception.ErrorCode;
import com.study.realworld.tag.service.TagService;
import com.study.realworld.user.domain.User;
import com.study.realworld.user.service.UserService;
Expand All @@ -22,6 +26,12 @@ public ArticleService(ArticleRepository articleRepository, UserService userServi
this.tagService = tagService;
}

@Transactional(readOnly = true)
public Article findBySlug(Slug slug) {
return articleRepository.findByArticleContentSlugTitleSlug(slug)
.orElseThrow(() -> new BusinessException(ErrorCode.ARTICLE_NOT_FOUND_BY_SLUG));
}

@Transactional
public Article createArticle(Long userId, ArticleContent articleContent) {
User author = userService.findById(userId);
Expand All @@ -31,4 +41,29 @@ public Article createArticle(Long userId, ArticleContent articleContent) {
return articleRepository.save(article);
}

@Transactional
public Article updateArticle(Long userId, Slug slug, ArticleUpdateModel updateArticle) {
User author = userService.findById(userId);
Article article = findByAuthorAndSlug(author, slug);

updateArticle.getTitle().ifPresent(article::changeTitle);
updateArticle.getDescription().ifPresent(article::changeDescription);
updateArticle.getBody().ifPresent(article::changeBody);

return article;
}

@Transactional
public void deleteArticleByAuthorAndSlug(Long userId, Slug slug) {
User author = userService.findById(userId);
Article article = findByAuthorAndSlug(author, slug);

article.deleteArticle();
}

private Article findByAuthorAndSlug(User author, Slug slug) {
return articleRepository.findByAuthorAndArticleContentSlugTitleSlug(author, slug)
.orElseThrow(() -> new BusinessException(ErrorCode.ARTICLE_NOT_FOUND_BY_AUTHOR_AND_SLUG));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.study.realworld.article.service.model;

import com.study.realworld.article.domain.Body;
import com.study.realworld.article.domain.Description;
import com.study.realworld.article.domain.Title;
import java.util.Optional;

public class ArticleUpdateModel {

private final Title title;
private final Description description;
private final Body body;

public ArticleUpdateModel(Title title, Description description, Body body) {
this.title = title;
this.description = description;
this.body = body;
}

public Optional<Title> getTitle() {
return Optional.ofNullable(title);
}

public Optional<Description> getDescription() {
return Optional.ofNullable(description);
}

public Optional<Body> getBody() {
return Optional.ofNullable(body);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.study.realworld.global.config;

import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.HttpMethod.POST;

import com.study.realworld.security.JwtAuthenticationEntryPoint;
Expand Down Expand Up @@ -61,6 +62,7 @@ protected void configure(HttpSecurity http) throws Exception {

.authorizeRequests()
.antMatchers(POST, "/api/users", "/api/users/login").permitAll()
.antMatchers(GET, "/api/articles/{slug}").permitAll()
.anyRequest().authenticated()
.and()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public enum ErrorCode {
INVALID_DESCRIPTION_LENGTH(HttpStatus.BAD_REQUEST, "description length must by less than 255 characters."),
INVALID_BODY_NULL(HttpStatus.BAD_REQUEST, "body must be provided"),

ARTICLE_NOT_FOUND_BY_AUTHOR_AND_SLUG(HttpStatus.BAD_REQUEST, "article is not found by author and slug"),
ARTICLE_NOT_FOUND_BY_SLUG(HttpStatus.BAD_REQUEST, "article is not found by slug"),
;

private final HttpStatus httpStatus;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Embeddable;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
Expand All @@ -17,7 +18,7 @@ public class FollowingUsers {
@JoinTable(name = "follow",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "follower_id"))
@ManyToMany(cascade = CascadeType.ALL)
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<User> followingUsers = new HashSet<>();

protected FollowingUsers() {
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ spring:
use_sql_comments: true
hibernate:
ddl-auto: create
open-in-view: false
open-in-view: true
datasource:
platform: h2
driver-class-name: org.h2.Driver
Expand Down

0 comments on commit 3d2a189

Please sign in to comment.