Skip to content

Commit

Permalink
feat : 게시글 좋아요, 좋아요 해제 구현 및 테스트코드 작성 (#31)
Browse files Browse the repository at this point in the history
* refactor : Post 좋아요 삽입 기능 구현, 동시성 이슈 해결을 위한 낙관적 Lock도입.

* refactor : Post 좋아요 삽입 기능에 대한 테스트 코드 작성

* fix : 엔드포인트 및 메소드명 수정

* feat : Post 좋아요 해제 기능 구현

* fix : 게시물 좋아요 기능 관련 api수정
  • Loading branch information
changhyun-jang authored Jul 25, 2024
1 parent db85927 commit 579dac9
Show file tree
Hide file tree
Showing 11 changed files with 443 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public enum ExceptionCode {
INCORRECT_TOKEN(UNAUTHORIZED, "올바르지 않는 토큰입니다."),
ALREADY_REGISTERED_EMAIL(CONFLICT, "이미 등록된 이메일입니다."),
NOT_FOUND_POST(NOT_FOUND, "게시물을 찾을 수 없습니다."),
ALREADY_EXIST_POSTLIKE(CONFLICT, "이미 좋아요를 누른 게시물입니다."),
NOT_EXIST_POSTLIKE(NOT_FOUND,"해당 게시물에 좋아요 기록이 없습니다."),
NOT_CORRECT_USER(UNAUTHORIZED, "작성자가 일치하지 않습니다."),
PUBLIC_KEY_GENERATION_FAILED(INTERNAL_SERVER_ERROR ,"공개 키 생성에 실패했습니다.");
;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package thisisus.school.post.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import thisisus.school.auth.config.AuthenticatedMemberId;
import thisisus.school.common.response.SuccessResonse;
import thisisus.school.post.service.PostLikeService;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/post")
public class PostLikeController {

private final PostLikeService postLikeService;

@PostMapping("/{postId}/like")
public SuccessResonse likePost(@PathVariable("postId") final Long postId,
@AuthenticatedMemberId final Long memberId) {
postLikeService.likePost(postId, memberId);
return SuccessResonse.of();
}

@DeleteMapping("{postId}/like")
public SuccessResonse disLikePost(@PathVariable("postId") final Long postId,
@AuthenticatedMemberId final Long memberId) {
postLikeService.disLikePost(postId, memberId);
return SuccessResonse.of();
}
}
77 changes: 44 additions & 33 deletions BE/school/src/main/java/thisisus/school/post/domain/Post.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;
import thisisus.school.common.BaseTimeEntity;
import thisisus.school.member.domain.Member;
import thisisus.school.post.exception.NotCorrectUserException;
Expand All @@ -26,47 +27,57 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Post extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "post_id")
private Long id;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "post_id")
private Long id;

@Column
private String title;
@Column
private String title;

@Column
private String content;
@Column
private String content;

@Column
@Enumerated(EnumType.STRING)
private PostCategory category;
@Column
@Enumerated(EnumType.STRING)
private PostCategory category;

@Column
private int likeCount;
@Column
@ColumnDefault("0")
private Integer likeCount;

@Column
private int viewCount;
@Column
@ColumnDefault("0")
private Integer viewCount;

@Column
private boolean isDelete;
@Column
private boolean isDelete;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;

public void delete() {
this.isDelete = true;
}
public void delete() {
this.isDelete = true;
}

public void update(String title, String content, PostCategory category) {
this.title = title;
this.content = content;
this.category = category;
}
public void update(String title, String content, PostCategory category) {
this.title = title;
this.content = content;
this.category = category;
}

public void checkWriter(Post post, Long memberId) {
if (post.getMember().getId() != memberId) {
throw new NotCorrectUserException();
}
}
public void checkWriter(Post post, Long memberId) {
if (post.getMember().getId() != memberId) {
throw new NotCorrectUserException();
}
}

public void increaseLikeCount() {
this.likeCount = getLikeCount() + 1;
}

public void decreaseLikeCount() {
this.likeCount = getLikeCount() - 1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package thisisus.school.post.exception;

import thisisus.school.common.exception.CustomException;
import thisisus.school.common.exception.ExceptionCode;

public class AlreadyExistPostLikeException extends CustomException {
public AlreadyExistPostLikeException() {
super(ExceptionCode.ALREADY_EXIST_POSTLIKE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package thisisus.school.post.exception;

import thisisus.school.common.exception.CustomException;
import thisisus.school.common.exception.ExceptionCode;

public class NotExistPostLikeException extends CustomException {
public NotExistPostLikeException() {
super(ExceptionCode.NOT_EXIST_POSTLIKE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package thisisus.school.post.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import thisisus.school.member.domain.Member;
import thisisus.school.post.domain.Post;
import thisisus.school.post.domain.PostLike;

import java.util.Optional;

@Repository
public interface PostLikeRepository extends JpaRepository<PostLike, Long> {
boolean existsByMemberAndPost(Member member, Post post);

PostLike findByMemberAndPost(Member member, Post post);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package thisisus.school.post.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import thisisus.school.post.domain.Post;

import javax.persistence.LockModeType;
import java.util.List;
import java.util.Optional;

public interface PostRepository extends JpaRepository<Post, Long> {
List<Post> findAllByMemberId(Long memberId);

@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<Post> findWithLockById(Long id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package thisisus.school.post.service;

public interface PostLikeService {
void likePost(Long postId, Long memberId);
void disLikePost(Long postId, Long memberId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package thisisus.school.post.service;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import thisisus.school.member.domain.Member;
import thisisus.school.member.exception.NotFoundMemberException;
import thisisus.school.member.repository.MemberRepository;
import thisisus.school.post.domain.Post;
import thisisus.school.post.domain.PostLike;
import thisisus.school.post.exception.AlreadyExistPostLikeException;
import thisisus.school.post.exception.NotExistPostLikeException;
import thisisus.school.post.exception.NotFoundPostException;
import thisisus.school.post.repository.PostLikeRepository;
import thisisus.school.post.repository.PostRepository;

@Service
@RequiredArgsConstructor
public class PostLikeServiceImpl implements PostLikeService {
private final PostLikeRepository postLikeRepository;
private final PostRepository postRepository;
private final MemberRepository memberRepository;

@Override
@Transactional
public void likePost(Long postId, Long memberId) {
Member member = memberRepository.findById(memberId)
.orElseThrow(NotFoundMemberException::new);
Post post = postRepository.findWithLockById(postId)
.orElseThrow(NotFoundPostException::new);
if (postLikeRepository.existsByMemberAndPost(member, post)) {
throw new AlreadyExistPostLikeException();
}
post.increaseLikeCount();

PostLike postLike = PostLike.builder()
.post(post)
.member(member)
.build();

postLikeRepository.save(postLike);
}

@Override
@Transactional
public void disLikePost(Long postId, Long memberId) {
Member member = memberRepository.findById(memberId)
.orElseThrow(NotFoundMemberException::new);
Post post = postRepository.findWithLockById(postId)
.orElseThrow(NotFoundPostException::new);
if (!postLikeRepository.existsByMemberAndPost(member, post)) {
throw new NotExistPostLikeException();
}
post.decreaseLikeCount();

PostLike postLike = postLikeRepository.findByMemberAndPost(member, post);
postLikeRepository.delete(postLike);
}
}
Loading

0 comments on commit 579dac9

Please sign in to comment.