Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] 게시글과 댓글의 좋아요, 좋아요 취소 api 생성 #23

Merged
merged 15 commits into from
Mar 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ public enum ArticleFailureCode implements FailureCode{
/**
* 404 NOT FOUND
*/
ARTICLE_NOT_FOUND(HttpStatus.NOT_FOUND, "뉴스를 찾을 수 없습니다");
ARTICLE_NOT_FOUND(HttpStatus.NOT_FOUND, "뉴스를 찾을 수 없습니다"),
ARTICLE_HEART_NOT_FOUND(HttpStatus.NOT_FOUND, "기사 좋아요가 없습니다"),

/**
* 409 CONFLICT
*/
ARTICLE_HEART_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 좋아요한 게시물입니다");

private final HttpStatus httpStatus;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ public enum CommentFailureCode implements FailureCode{
/**
* 404 Not Found
**/
COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "댓글을 찾을 수 없습니다");
COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "댓글을 찾을 수 없습니다"),
COMMENT_LIKE_NOT_FOUND(HttpStatus.NOT_FOUND, "댓글 좋아요가 없습니다."),
/**
* 409 Conflict
**/
COMMENT_HEART_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 좋아요한 댓글입니다");

private final HttpStatus httpStatus;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ public enum ArticleSuccessCode implements SuccessCode{
/**
* 200 OK
**/
GET_ARTICLES_SUCCESS(HttpStatus.OK, "기사 조회에 성공했습니다");
GET_ARTICLES_SUCCESS(HttpStatus.OK, "기사 조회에 성공했습니다"),
/**
* 201 Created
**/
ARTICLE_LIKE_SUCCESS(HttpStatus.OK, "기사 좋아요 성공"),
/**
* 204 No Content
**/
ARTICLE_LIKE_CANCEL_SUCCESS(HttpStatus.OK, "기사 좋아요 취소 성공");

private final HttpStatus httpStatus;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ public enum CommentSuccessCode implements SuccessCode{
* 201 Created
**/
COMMENT_CREATED(HttpStatus.CREATED, "댓글 작성 성공"),

COMMENT_LIKE_SUCCESS(HttpStatus.CREATED, "댓글 좋아요 성공"),
/**
* 204 No Content
**/
COMMENT_DELETED_SUCCESS(HttpStatus.NO_CONTENT, "댓글 삭제 성공"),
COMMENT_UPDATED(HttpStatus.NO_CONTENT, "댓글 수정 성공");
COMMENT_UPDATED(HttpStatus.NO_CONTENT, "댓글 수정 성공"),
COMMENT_LIKE_CANCEL_SUCCESS(HttpStatus.NO_CONTENT, "댓글 좋아요 취소 성공");

private final HttpStatus httpStatus;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.newsnack.www.newsnackserver.common.exception;

import com.newsnack.www.newsnackserver.common.code.failure.FailureCode;
import lombok.Getter;

@Getter
public class ArticleException extends RuntimeException{

private final FailureCode failureCode;

public ArticleException(FailureCode failureCode) {
super("[AuthException] : " + failureCode.getMessage());
super("[ArticleException] : " + failureCode.getMessage());
this.failureCode = failureCode;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.newsnack.www.newsnackserver.common.exception;

import com.newsnack.www.newsnackserver.common.code.failure.FailureCode;
import lombok.Getter;

@Getter
public class CommentException extends RuntimeException {
private final FailureCode failureCode;

public CommentException(FailureCode failureCode) {
super("[CommentException] : " + failureCode.getMessage());
this.failureCode = failureCode;
}

public int getHttpStatusCode() {
return failureCode.getHttpStatus().value();
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.newsnack.www.newsnackserver.common.handler;

import com.newsnack.www.newsnackserver.common.exception.AuthException;
import com.newsnack.www.newsnackserver.common.exception.MemberException;
import com.newsnack.www.newsnackserver.common.exception.NewSnackException;
import com.newsnack.www.newsnackserver.common.exception.*;
import com.newsnack.www.newsnackserver.common.response.NewSnackResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -50,12 +48,28 @@ public ResponseEntity<NewSnackResponse<?>> handleMemberException(MemberException
return ResponseEntity.status(e.getHttpStatusCode())
.body(NewSnackResponse.error(e.getFailureCode()));
}

@ExceptionHandler(ArticleException.class)
public ResponseEntity<NewSnackResponse<?>> handleMemberException(ArticleException e) {
log.info("handleMemberException() in NewSnackControllerAdvice throw MemberException : {}", e.getMessage());
return ResponseEntity.status(e.getHttpStatusCode())
.body(NewSnackResponse.error(e.getFailureCode()));
}

@ExceptionHandler(CommentException.class)
public ResponseEntity<NewSnackResponse<?>> handleCommentException(CommentException e) {
log.info("handleCommentException() in NewSnackControllerAdvice throw CommentException : {}", e.getMessage());
return ResponseEntity.status(e.getHttpStatusCode())
.body(NewSnackResponse.error(e.getFailureCode()));
}

@ExceptionHandler(NewSnackException.class)
public ResponseEntity<NewSnackResponse<?>> handleNewSnackException(NewSnackException e) {
log.info("handleNewSnackException() in NewSnackControllerAdvice throw NewSnackException : {}", e.getMessage());
return ResponseEntity.status(e.getHttpStatusCode())
.body(NewSnackResponse.error(e.getFailureCode()));
}

@ExceptionHandler(Exception.class)
public ResponseEntity<NewSnackResponse<?>> handleException(Exception e) {
log.error("handleException() in NewSnackControllerAdvice throw Exception : {} ,{}", e.getClass() ,e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package com.newsnack.www.newsnackserver.controller;

import com.newsnack.www.newsnackserver.annotation.MemberId;
import com.newsnack.www.newsnackserver.common.exception.ArticleException;
import com.newsnack.www.newsnackserver.dto.response.ArticleIndividualResponse;
import com.newsnack.www.newsnackserver.dto.response.ArticleMainPageResponse;
import com.newsnack.www.newsnackserver.dto.response.ArticleResponse;
import com.newsnack.www.newsnackserver.service.ArticleService;
import com.newsnack.www.newsnackserver.common.code.failure.ArticleFailureCode;
import com.newsnack.www.newsnackserver.common.code.success.ArticleSuccessCode;
import com.newsnack.www.newsnackserver.common.exception.NewSnackException;
import com.newsnack.www.newsnackserver.common.response.NewSnackResponse;
import com.newsnack.www.newsnackserver.domain.article.model.LocationCategory;
import com.newsnack.www.newsnackserver.domain.article.model.SearchOrder;
import com.newsnack.www.newsnackserver.controller.parameter.SearchOrder;
import com.newsnack.www.newsnackserver.domain.article.model.SectionCategory;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
Expand All @@ -30,7 +30,7 @@ public NewSnackResponse<List<ArticleResponse>> getArticles(@RequestParam SearchO
@RequestParam(required = false)SectionCategory sectionCategory,
@RequestParam(required = false)LocationCategory locationCategory) {
if (order == null || page < 0 || (sectionCategory != null && locationCategory != null) ) {
throw new NewSnackException(ArticleFailureCode.INVALID_PARAMETER);
throw new ArticleException(ArticleFailureCode.INVALID_PARAMETER);
}
return NewSnackResponse.success(ArticleSuccessCode.GET_ARTICLES_SUCCESS, articleService.getArticles(order, sectionCategory, locationCategory, page));
}
Expand All @@ -44,4 +44,15 @@ public NewSnackResponse<ArticleIndividualResponse> getArticle(@PathVariable Long
public NewSnackResponse<List<ArticleMainPageResponse>> getMainArticles() {
return NewSnackResponse.success(ArticleSuccessCode.GET_ARTICLES_SUCCESS, articleService.getMainArticles());
}

@PostMapping("/{articleId}/likes")
public NewSnackResponse<?> likeArticle(@PathVariable Long articleId, @MemberId Long memberId) {
articleService.likeArticle(articleId, memberId);
return NewSnackResponse.success(ArticleSuccessCode.ARTICLE_LIKE_SUCCESS);
}
@DeleteMapping("/{articleId}/likes")
public NewSnackResponse<?> deleteArticleLike(@PathVariable Long articleId, @MemberId Long memberId) {
articleService.cancelArticleLike(articleId, memberId);
return NewSnackResponse.success(ArticleSuccessCode.ARTICLE_LIKE_CANCEL_SUCCESS);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.newsnack.www.newsnackserver.annotation.MemberId;
import com.newsnack.www.newsnackserver.common.code.success.CommentSuccessCode;
import com.newsnack.www.newsnackserver.common.response.NewSnackResponse;
import com.newsnack.www.newsnackserver.domain.article.model.SearchOrder;
import com.newsnack.www.newsnackserver.controller.parameter.SearchOrder;
import com.newsnack.www.newsnackserver.dto.CommentRequest;
import com.newsnack.www.newsnackserver.dto.response.CommentResponse;
import com.newsnack.www.newsnackserver.service.CommentService;
Expand Down Expand Up @@ -46,4 +46,14 @@ public NewSnackResponse<?> deleteComment(@PathVariable Long commentId, @MemberId
return NewSnackResponse.success(CommentSuccessCode.COMMENT_DELETED_SUCCESS);
}

@PostMapping("/comments/{commentId}/likes")//comment likecount 갱실 분실 문제 해결하기
public NewSnackResponse<?> likeComment(@PathVariable Long commentId, @MemberId Long memberId) {
commentService.likeComment(commentId, memberId);
return NewSnackResponse.success(CommentSuccessCode.COMMENT_LIKE_SUCCESS);
}
@DeleteMapping("/comments/{commentId}/likes")//나중에 comment에 likecount 넣게 되면 -> comment.likecount++-- 하고 동시성 해결하기.
public NewSnackResponse<?> cancelCommentLike(@PathVariable Long commentId, @MemberId Long memberId) {
commentService.cancelCommentLike(commentId, memberId);
return NewSnackResponse.success(CommentSuccessCode.COMMENT_LIKE_CANCEL_SUCCESS);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.newsnack.www.newsnackserver.domain.article.model;
package com.newsnack.www.newsnackserver.controller.parameter;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Formula;

@Entity
@Getter
Expand All @@ -30,6 +29,13 @@ public class Article extends BaseTimeEntity {
@Enumerated(EnumType.STRING)
private LocationCategory locationCategory;

@Formula("(select count(*) from article_heart where article_heart.article_id = id)")
private int heartCount;

public void increaseHeartCount() {
this.heartCount++;
}

public void decreaseHeartCount() {
this.heartCount--;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.newsnack.www.newsnackserver.domain.member.model.Member;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

Expand All @@ -23,4 +24,10 @@ public class ArticleHeart {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "article_id")
private Article article;

@Builder
public ArticleHeart(Member member, Article article) {
this.member = member;
this.article = article;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.newsnack.www.newsnackserver.domain.articleheart.repository;

import com.newsnack.www.newsnackserver.domain.article.model.Article;
import com.newsnack.www.newsnackserver.domain.articleheart.model.ArticleHeart;
import com.newsnack.www.newsnackserver.domain.member.model.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface ArticleHeartJpaRepository extends JpaRepository<ArticleHeart, Long> {
boolean existsByArticleIdAndMemberId(Long articleId, Long memberId);
Optional<ArticleHeart> findByArticleAndMember(Article article, Member member);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Formula;

import java.util.ArrayList;
import java.util.List;
Expand All @@ -36,7 +35,6 @@ public class Comment extends BaseTimeEntity {

private String content;

@Formula("(select count(*) from comment_heart where comment_heart.comment_id = id)")
private int heartCount;

@Builder
Expand All @@ -46,6 +44,14 @@ public Comment(Article article, Member member, String content) {
this.content = content;
}

public void increaseHeartCount() {
this.heartCount++;
}

public void decreaseHeartCount() {
this.heartCount--;
}

public void updateContent(String content) {
this.content = content;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.newsnack.www.newsnackserver.domain.member.model.Member;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

Expand All @@ -24,4 +25,10 @@ public class CommentHeart extends BaseTimeEntity {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;

@Builder
public CommentHeart(Comment comment, Member member) {
this.comment = comment;
this.member = member;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package com.newsnack.www.newsnackserver.domain.commentheart.repository;

import com.newsnack.www.newsnackserver.domain.comment.model.Comment;
import com.newsnack.www.newsnackserver.domain.commentheart.model.CommentHeart;
import com.newsnack.www.newsnackserver.domain.member.model.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface CommentHeartJpaRepository extends JpaRepository<CommentHeart, Long> {
Optional<CommentHeart> findByCommentAndMember(Comment comment, Member member);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
import com.newsnack.www.newsnackserver.common.code.failure.ArticleFailureCode;
import com.newsnack.www.newsnackserver.common.exception.ArticleException;
import com.newsnack.www.newsnackserver.domain.article.model.Article;
import com.newsnack.www.newsnackserver.domain.articleheart.model.ArticleHeart;
import com.newsnack.www.newsnackserver.domain.articleheart.repository.ArticleHeartJpaRepository;
import com.newsnack.www.newsnackserver.domain.member.model.Member;
import com.newsnack.www.newsnackserver.domain.member.repository.MemberJpaRepository;
import com.newsnack.www.newsnackserver.dto.response.ArticleIndividualResponse;
import com.newsnack.www.newsnackserver.dto.response.ArticleMainPageResponse;
import com.newsnack.www.newsnackserver.dto.response.ArticleResponse;
import com.newsnack.www.newsnackserver.domain.article.model.LocationCategory;
import com.newsnack.www.newsnackserver.domain.article.model.SearchOrder;
import com.newsnack.www.newsnackserver.controller.parameter.SearchOrder;
import com.newsnack.www.newsnackserver.domain.article.model.SectionCategory;
import com.newsnack.www.newsnackserver.domain.article.repository.ArticleRepository;
import lombok.RequiredArgsConstructor;
Expand All @@ -28,6 +31,7 @@ public class ArticleService {

private final ArticleRepository articleRepository;
private final ArticleHeartJpaRepository articleHeartJpaRepository;
private final MemberJpaRepository memberJpaRepository;

public List<ArticleResponse> getArticles(SearchOrder order, SectionCategory sectionCategory, LocationCategory locationCategory, Integer page) {

Expand Down Expand Up @@ -67,4 +71,35 @@ public List<ArticleMainPageResponse> getMainArticles() {
LocalDateTime oneWeekAgo = LocalDateTime.now().minusDays(7);
return articleRepository.findTop5ByCreatedAtAfterOrderByHeartCountDescIdDesc(oneWeekAgo).stream().map(ArticleMainPageResponse::from).collect(Collectors.toList());
}

@Transactional
public void likeArticle(Long articleId, Long memberId) {
Article article = articleRepository.getReferenceById(articleId);
Member member = memberJpaRepository.getReferenceById(memberId);
articleHeartJpaRepository.findByArticleAndMember(article, member).ifPresentOrElse(
(articleHeart) -> {
throw new ArticleException(ArticleFailureCode.ARTICLE_HEART_ALREADY_EXISTS);
},
() -> {
articleHeartJpaRepository.save(ArticleHeart.builder().member(member).article(article).build());
article.increaseHeartCount();
}
);
}

@Transactional
public void cancelArticleLike(Long articleId, Long memberId) {
Article article = articleRepository.getReferenceById(articleId);
Member member = memberJpaRepository.getReferenceById(memberId);
articleHeartJpaRepository.findByArticleAndMember(article, member).ifPresentOrElse(
(articleHeart) -> {
articleHeartJpaRepository.delete(articleHeart);
article.decreaseHeartCount();
},
() -> {
throw new ArticleException(ArticleFailureCode.ARTICLE_HEART_NOT_FOUND);
}
);
}
}

Loading
Loading