Skip to content

Commit

Permalink
Merge pull request #1583 from woowacourse/article-develop
Browse files Browse the repository at this point in the history
Article develop main merge
  • Loading branch information
donghae-kim authored Oct 6, 2023
2 parents d3343e9 + 4159d63 commit 6581ed1
Show file tree
Hide file tree
Showing 47 changed files with 1,803 additions and 135 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package wooteco.prolog.steps;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static wooteco.prolog.fixtures.ArticleFixture.ARTICLE_REQUEST1;
import static wooteco.prolog.fixtures.ArticleFixture.ARTICLE_REQUEST2;

Expand All @@ -10,6 +11,7 @@
import java.util.List;
import org.springframework.http.HttpStatus;
import wooteco.prolog.AcceptanceSteps;
import wooteco.prolog.article.ui.ArticleBookmarkRequest;
import wooteco.prolog.article.ui.ArticleResponse;
import wooteco.prolog.article.ui.ArticleUrlResponse;

Expand All @@ -22,7 +24,7 @@ public class ArticleStepDefinitions extends AcceptanceSteps {
context.invokeHttpPostWithToken("/articles", ARTICLE_REQUEST2);
}

@Given("아티클을 작성하고")
@Given("아티클이 작성되어 있고")
@When("아티클을 작성하면")
public void 아티클을_작성하면() {
context.invokeHttpPostWithToken("/articles", ARTICLE_REQUEST1);
Expand Down Expand Up @@ -96,4 +98,57 @@ public class ArticleStepDefinitions extends AcceptanceSteps {
.usingRecursiveComparison()
.isEqualTo(expected);
}

@When("{long}번 아티클에 북마크 요청을 보내면")
public void 아티클에_북마크_요청을_보내면(final Long articleId) {
//final String articleUrl = context.response.header("Location");
final ArticleBookmarkRequest request = new ArticleBookmarkRequest(true);
context.invokeHttpPutWithToken(
String.format("/articles/%d/bookmark", articleId),
request
);
}

@Then("아티클에 북마크가 등록된다")
public void 아티클에_북마크가_등록된다() {
final int statusCode = context.response.statusCode();
assertAll(
() -> assertThat(statusCode).isEqualTo(HttpStatus.OK.value())
);
//아티클 단건 조회에 북마크 여부가 추가되면 단건 조회로 bookmark 여부까지 검증해보기
}

@When("{long}번 아티클에 좋아요 요청을 보내면")
public void 아티클에_좋아요_요청을_보내면(final Long articleId) {
//final String articleUrl = context.response.header("Location");
final ArticleBookmarkRequest request = new ArticleBookmarkRequest(true);
context.invokeHttpPutWithToken(
String.format("/articles/%d/like", articleId),
request
);
}

@Then("아티클에 좋아요가 등록된다")
public void 아티클에_좋아요가_등록된다() {
final int statusCode = context.response.statusCode();
assertAll(
() -> assertThat(statusCode).isEqualTo(HttpStatus.OK.value())
);
//아티클 단건 조회에 좋아요 여부가 추가되면 단건 조회로 likes 여부까지 검증해보기
}

@When("{long}번 아티클에 조회수 추가요청을 보내면")
public void 아티클에_조회수_추가요청을_보내면(final Long articleId) {
context.invokeHttpPost(
String.format("/articles/%d/views", articleId),""
);
}

@Then("아티클의 조회수가 증가한다")
public void 아티클의_조회수가_증가한다() {
final int statusCode = context.response.statusCode();
assertAll(
() -> assertThat(statusCode).isEqualTo(HttpStatus.OK.value())
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ Feature: 아티클 관련 기능
When 아티클을 작성하면
Then 아티클이 작성된다

Scenario: 아티클을 전체 조회하기
Given 아티클을 여러개 작성하고
When 아티클을 전체 조회 하면
Then 아티클이 전체 조회 된다
# Scenario: 아티클을 전체 조회하기
# Given 아티클을 여러개 작성하고
# When 아티클을 전체 조회 하면
# Then 아티클이 전체 조회 된다


Scenario: 아티클을 수정하기
Given 아티클을 여러개 작성하고
Expand All @@ -24,5 +25,20 @@ Feature: 아티클 관련 기능
Then 아티클이 삭제된다

Scenario: Url og태그 파싱하기
When Url을 입력하면
Then og태그를 파싱해서 반환한다.
When Url을 입력하면
Then og태그를 파싱해서 반환한다.

Scenario: 아티클을 북마크로 등록하기
Given 아티클이 작성되어 있고
When 1번 아티클에 북마크 요청을 보내면
Then 아티클에 북마크가 등록된다

Scenario: 아티클에 좋아요를 등록하기
Given 아티클이 작성되어 있고
When 1번 아티클에 좋아요 요청을 보내면
Then 아티클에 좋아요가 등록된다

Scenario: 아티클 조회수 추가하기
Given 아티클이 작성되어 있고
When 1번 아티클에 조회수 추가요청을 보내면
Then 아티클의 조회수가 증가한다
32 changes: 32 additions & 0 deletions backend/src/documentation/adoc/article.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[[article]]
== 아티클

=== 아티클 북마크 추가

==== Request

include::{snippets}/article/bookmark/http-request.adoc[]

==== Response

include::{snippets}/article/bookmark/http-response.adoc[]

=== 아티클 좋아요 추가

==== Request

include::{snippets}/article/likes/http-request.adoc[]

==== Response

include::{snippets}/article/likes/http-response.adoc[]

=== 아티클 조회수 추가

==== Request

include::{snippets}/article/views/http-request.adoc[]

==== Response

include::{snippets}/article/views/http-response.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package wooteco.prolog.docu;

import static io.netty.handler.codec.http.HttpHeaders.Values.APPLICATION_JSON;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import io.restassured.module.mockmvc.response.ValidatableMockMvcResponse;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import wooteco.prolog.NewDocumentation;
import wooteco.prolog.article.application.ArticleService;
import wooteco.prolog.article.ui.ArticleBookmarkRequest;
import wooteco.prolog.article.ui.ArticleController;
import wooteco.prolog.article.ui.ArticleLikesRequest;

@WebMvcTest(controllers = ArticleController.class)
public class ArticleDocumentation extends NewDocumentation {

@MockBean
private ArticleService articleService;

@Test
void 아티클에_북마크를_변경한다() {
//given, when
final ArticleBookmarkRequest bookmarkRequest = new ArticleBookmarkRequest(true);

final ValidatableMockMvcResponse response = given
.header("Authorization", "Bearer " + accessToken)
.contentType(APPLICATION_JSON)
.body(bookmarkRequest)
.when().put("/articles/{article-id}/bookmark", 1L)
.then().log().all();

//then
response.expect(status().isOk());

//docs
response.apply(document("article/bookmark"));
}

@Test
void 아티클에_좋아요를_변경한다() {
//given, when
final ArticleLikesRequest articleLikesRequest = new ArticleLikesRequest(true);

final ValidatableMockMvcResponse response = given
.header("Authorization", "Bearer " + accessToken)
.contentType(APPLICATION_JSON)
.body(articleLikesRequest)
.when().put("/articles/{article-id}/like", 1L)
.then().log().all();

//then
response.expect(status().isOk());

//docs
response.apply(document("article/like"));
}

@Test
void 아티클의_조회수를_증가시킨다() {
//given, when
final ValidatableMockMvcResponse response = given
.when().post("/articles/{article-id}/views", 1L)
.then().log().all();

//then
response.expect(status().isOk());

//docs
response.apply(document("article/views"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
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 Down Expand Up @@ -40,13 +41,6 @@ private void validateMemberIsCrew(final Member member) {
}
}

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 All @@ -70,4 +64,43 @@ public void delete(final Long id, final LoginMember loginMember) {
article.validateOwner(member);
articleRepository.delete(article);
}

@Transactional
public void bookmarkArticle(final Long id, final LoginMember loginMember,
final Boolean isBookmark) {
final Article article = articleRepository.findFetchBookmarkById(id)
.orElseThrow(() -> new BadRequestException(ARTICLE_NOT_FOUND_EXCEPTION));
final Member member = memberService.findById(loginMember.getId());
article.setBookmark(member, isBookmark);
}

@Transactional
public void likeArticle(final Long id, final LoginMember loginMember, final Boolean isLike) {
final Article article = articleRepository.findFetchLikeById(id)
.orElseThrow(() -> new BadRequestException(ARTICLE_NOT_FOUND_EXCEPTION));
final Member member = memberService.findById(loginMember.getId());
article.setLike(member, isLike);
}

public List<ArticleResponse> getFilteredArticles(final LoginMember member,
final ArticleFilterType course,
final boolean onlyBookmarked) {
if (member.isMember()) {
return articleRepository.findArticlesByCourseAndMember(course.getGroupName(),
member.getId(), onlyBookmarked).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());
}

@Transactional
public void updateViewCount(final Long id) {
final Article article = articleRepository.findById(id)
.orElseThrow(() -> new BadRequestException(ARTICLE_NOT_FOUND_EXCEPTION));
article.updateViewCount();
}
}
62 changes: 61 additions & 1 deletion backend/src/main/java/wooteco/prolog/article/domain/Article.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,19 @@
import wooteco.prolog.common.exception.BadRequestException;
import wooteco.prolog.member.domain.Member;

import javax.persistence.*;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import java.time.LocalDateTime;

import static java.lang.Boolean.TRUE;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
Expand All @@ -35,14 +45,26 @@ public class Article {
@Embedded
private ImageUrl imageUrl;

@Embedded
private ArticleBookmarks articleBookmarks;

@Embedded
private ArticleLikes articleLikes;

@CreatedDate
private LocalDateTime createdAt;

@Embedded
private ViewCount views;

public Article(final Member member, final Title title, final Url url, final ImageUrl imageUrl) {
this.member = member;
this.title = title;
this.url = url;
this.imageUrl = imageUrl;
this.articleBookmarks = new ArticleBookmarks();
this.articleLikes = new ArticleLikes();
this.views = new ViewCount();
}

public void validateOwner(final Member member) {
Expand All @@ -55,4 +77,42 @@ public void update(final String title, final String url) {
this.title = new Title(title);
this.url = new Url(url);
}

public void setBookmark(final Member member, final Boolean isBookmark) {
if (TRUE.equals(isBookmark)) {
addBookmark(member);
} else {
removeBookmark(member);
}
}

private void addBookmark(final Member member) {
final ArticleBookmark articleBookmark = new ArticleBookmark(this, member.getId());
articleBookmarks.addBookmark(articleBookmark);
}

private void removeBookmark(final Member member) {
articleBookmarks.removeBookmark(member.getId());
}

public void setLike(final Member member, final Boolean isLike) {
if (TRUE.equals(isLike)) {
addLike(member);
} else {
removeLike(member);
}
}

private void addLike(final Member member) {
final ArticleLike articleLike = new ArticleLike(this, member.getId());
articleLikes.addLike(articleLike);
}

private void removeLike(final Member member) {
articleLikes.removeLike(member.getId());
}

public void updateViewCount() {
this.views.increase();
}
}
Loading

0 comments on commit 6581ed1

Please sign in to comment.