Skip to content

Commit

Permalink
merge: 아티클 좋아요 기능 추가
Browse files Browse the repository at this point in the history
Feature/#1538 아티클 좋아요 기능 추가
  • Loading branch information
hong-sile authored Sep 27, 2023
2 parents 92cc7eb + 496eeec commit a622b23
Show file tree
Hide file tree
Showing 17 changed files with 462 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,25 @@ public class ArticleStepDefinitions extends AcceptanceSteps {
assertAll(
() -> assertThat(statusCode).isEqualTo(HttpStatus.OK.value())
);
//아티클 단건 조회 변경 시, 단건 조회로 bookmark 여부까지 검증해보기
//아티클 단건 조회에 북마크 여부가 추가되면 단건 조회로 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/likes", articleId),
request
);
}

@Then("아티클에 좋아요가 등록된다")
public void 아티클에_좋아요가_등록된다() {
final int statusCode = context.response.statusCode();
assertAll(
() -> assertThat(statusCode).isEqualTo(HttpStatus.OK.value())
);
//아티클 단건 조회에 좋아요 여부가 추가되면 단건 조회로 likes 여부까지 검증해보기
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@ Feature: 아티클 관련 기능
Given 아티클이 작성되어 있고
When 1번 아티클에 북마크 요청을 보내면
Then 아티클에 북마크가 등록된다

Scenario: 아티클에 좋아요를 등록하기
Given 아티클이 작성되어 있고
When 1번 아티클에 좋아요 요청을 보내면
Then 아티클에 좋아요가 등록된다
10 changes: 10 additions & 0 deletions backend/src/documentation/adoc/article.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,13 @@ 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[]
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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 {
Expand All @@ -28,7 +29,7 @@ public class ArticleDocumentation extends NewDocumentation {
.header("Authorization", "Bearer " + accessToken)
.contentType(APPLICATION_JSON)
.body(bookmarkRequest)
.when().put("/articles/{bookmark-id}/bookmark", 1L)
.when().put("/articles/{article-id}/bookmark", 1L)
.then().log().all();

//then
Expand All @@ -37,4 +38,23 @@ public class ArticleDocumentation extends NewDocumentation {
//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"));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package wooteco.prolog.article.application;

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;
Expand All @@ -14,12 +18,6 @@
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 Down Expand Up @@ -63,27 +61,33 @@ public void delete(final Long id, final LoginMember loginMember) {

@Transactional
public void bookmarkArticle(final Long id, final LoginMember loginMember,
final Boolean checked) {
final Article article = articleRepository.findFetchById(id)
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);
}

if (TRUE.equals(checked)) {
article.addBookmark(member);
} else {
article.removeBookmark(member);
}
@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) {
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()))
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()))
.map(article -> ArticleResponse.of(article, member.getId()))
.collect(toList());
}
}
35 changes: 33 additions & 2 deletions backend/src/main/java/wooteco/prolog/article/domain/Article.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package wooteco.prolog.article.domain;

import static java.lang.Boolean.TRUE;

import java.time.LocalDateTime;
import javax.persistence.Embedded;
import javax.persistence.Entity;
Expand Down Expand Up @@ -45,6 +47,9 @@ public class Article {
@Embedded
private ArticleBookmarks articleBookmarks;

@Embedded
private ArticleLikes articleLikes;

@CreatedDate
private LocalDateTime createdAt;

Expand All @@ -54,6 +59,7 @@ public Article(final Member member, final Title title, final Url url, final Imag
this.url = url;
this.imageUrl = imageUrl;
this.articleBookmarks = new ArticleBookmarks();
this.articleLikes = new ArticleLikes();
}

public void validateOwner(final Member member) {
Expand All @@ -67,12 +73,37 @@ public void update(final String title, final String url) {
this.url = new Url(url);
}

public void addBookmark(final Member member) {
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);
}

public void removeBookmark(final Member member) {
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());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package wooteco.prolog.article.domain;

import java.util.Objects;
import javax.persistence.Entity;
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 javax.persistence.Table;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "article_like")
public class ArticleLike {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "article_id")
private Article article;

private Long memberId;

private ArticleLike(final Long id, final Article article, final Long memberId) {
this.id = id;
this.article = article;
this.memberId = memberId;
}

public ArticleLike(final Article article, final Long memberId) {
this(null, article, memberId);
}

public boolean isOwner(final Long memberId) {
return Objects.equals(this.memberId, memberId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package wooteco.prolog.article.domain;

import java.util.ArrayList;
import java.util.List;
import javax.persistence.Embeddable;
import javax.persistence.OneToMany;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;

@Embeddable
public class ArticleLikes {

@OneToMany(mappedBy = "article")
@Cascade(value = {CascadeType.PERSIST, CascadeType.DELETE})
private List<ArticleLike> articleLikes;

public ArticleLikes() {
this.articleLikes = new ArrayList<>();
}

public void addLike(final ArticleLike articleLike) {
articleLikes.add(articleLike);
}

public void removeLike(final Long memberId) {
articleLikes.stream()
.filter(like -> like.isOwner(memberId))
.findAny()
.ifPresent(like -> articleLikes.remove(like));
}

public boolean isAlreadyLike(final Long memberId) {
return articleLikes.stream()
.anyMatch(like -> like.isOwner(memberId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ 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);
Optional<Article> findFetchBookmarkById(@Param("id") final Long id);

@Query("select a from Article a join fetch a.articleLikes where a.id = :id")
Optional<Article> findFetchLikeById(@Param("id") final Long id);

@Query("SELECT DISTINCT a FROM Article a " +
"JOIN GroupMember gm ON a.member.id = gm.member.id " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ public ResponseEntity<Void> bookmarkArticle(@PathVariable final Long id,
return ResponseEntity.ok().build();
}

@PutMapping("/{id}/like")
public ResponseEntity<Void> likeArticle(@PathVariable final Long id,
@AuthMemberPrincipal final LoginMember member,
@RequestBody final ArticleLikesRequest request) {
articleService.likeArticle(id, member, request.getLike());
return ResponseEntity.ok().build();
}

@GetMapping
public ResponseEntity<List<ArticleResponse>> getFilteredArticles(@AuthMemberPrincipal final LoginMember member,
@RequestParam("course") final ArticleFilterType course,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package wooteco.prolog.article.ui;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class ArticleLikesRequest {

private final Boolean like;

public ArticleLikesRequest() {
this(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ create table if not exists prolog.article_bookmark
(
id bigint auto_increment primary key,
article_id bigint not null,
member_id bigint not null
member_id bigint not null,
foreign key (member_id) references prolog.member (id),
foreign key (article_id) references prolog.article (id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
create table if not exists prolog.article_like
(
id bigint auto_increment primary key,
article_id bigint not null,
member_id bigint not null,
foreign key (member_id) references prolog.member (id),
foreign key (article_id) references prolog.article (id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci;
Loading

0 comments on commit a622b23

Please sign in to comment.