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

[NB-203, NB-204] 게시글 좋아요 및 좋아요 취소 기능 구현 #41

Merged
merged 6 commits into from
Aug 8, 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
10 changes: 10 additions & 0 deletions src/main/java/com/soyeon/nubim/domain/post/Post.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ public class Post extends BaseEntity {
@OrderBy("createdAt DESC")
private List<Comment> comments = new ArrayList<>();

/**
* 다른 엔티티 생성 시 매핑만을 위해 임시 Post 엔티티 생성
* 실제 Post의 값은 가지지 않으니 사용 시 주의할 것
*/
public Post(Long postId, Long userId) {
this.postId = postId;
this.user = new User(userId);
this.postTitle = "MAPPING_POST";
}

public void linkAlbum(Album album) {
this.album = album;
album.linkPost(this);
Expand Down
95 changes: 45 additions & 50 deletions src/main/java/com/soyeon/nubim/domain/post/PostService.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,6 @@ public class PostService {
private final CommentMapper commentMapper;
private final UserMapper userMapper;

public PostDetailResponseDto findPostDetailById(Long id) {
Post post = postRepository.findById(id).orElseThrow(() -> new PostNotFoundException(id));

return postMapper.toPostDetailResponseDto(post);
}

public PostSimpleResponseDto findPostSimpleById(Long id) {
Post post = postRepository.findById(id).orElseThrow(() -> new PostNotFoundException(id));

return postMapper.toPostSimpleResponseDto(post);
}

public Page<PostSimpleResponseDto> findAllPostsByUserOrderByCreatedAt(User user, Pageable pageable) {
Page<Post> postList = postRepository.findByUser(user, pageable);
return postList
.map(postMapper::toPostSimpleResponseDto);
}

public PostCreateResponseDto createPost(PostCreateRequestDto postCreateRequestDto, User authorUser) {
Album linkedAlbum = albumService.findById(postCreateRequestDto.getAlbumId());
albumService.validateAlbumOwner(linkedAlbum.getAlbumId(), authorUser.getUserId());
Expand All @@ -65,39 +47,34 @@ public PostCreateResponseDto createPost(PostCreateRequestDto postCreateRequestDt
return postMapper.toPostCreateResponseDto(post);
}

public void deleteById(Long postId, Long userId) {
validatePostOwner(postId, userId);
public Post findPostByIdOrThrow(Long id) {
return postRepository.findById(id).orElseThrow(() -> new PostNotFoundException(id));
}

Post post = findPostByIdOrThrow(postId);
post.unlinkAlbum();
public PostSimpleResponseDto findPostSimpleById(Long id) {
Post post = findPostByIdOrThrow(id);

postRepository.deleteById(postId);
return postMapper.toPostSimpleResponseDto(post);
}

public Post findPostByIdOrThrow(Long id) {
public PostDetailResponseDto findPostDetailById(Long id) {
Post post = findPostByIdOrThrow(id);

return postRepository
.findById(id)
.orElseThrow(() -> new PostNotFoundException(id));
return postMapper.toPostDetailResponseDto(post);
}

public void validatePostExist(Long postId) {
if (!postRepository.existsById(postId)) {
throw new PostNotFoundException(postId);
}
public Page<PostSimpleResponseDto> searchPostsByTitleOrContent(Pageable pageable, String query) {
return postRepository.findByPostTitleContainingOrPostContentContaining(query, query, pageable)
.map(postMapper::toPostSimpleResponseDto);
}

public void validatePostOwner(Long postId, Long userId) {
Post post = findPostByIdOrThrow(postId);
Long postOwnerId = post.getUser().getUserId();

if (!postOwnerId.equals(userId)) {
throw new UnauthorizedPostAccessException(postId);
}
public Page<PostSimpleResponseDto> findAllPostsByUserOrderByCreatedAt(User user, Pageable pageable) {
Page<Post> postList = postRepository.findByUser(user, pageable);
return postList
.map(postMapper::toPostSimpleResponseDto);
}

public Page<PostMainResponseDto> findRecentPostsOfFollowees(
User user, Pageable pageable, int recentCriteriaDays) {
public Page<PostMainResponseDto> findRecentPostsOfFollowees(User user, Pageable pageable, int recentCriteriaDays) {
return postRepository.findRecentPostsByFollowees(user, LocalDateTime.now().minusDays(recentCriteriaDays),
pageable)
.map(post -> postMapper.toPostMainResponseDto(post, findRecentCommentByPostOrNull(post)));
Expand All @@ -116,13 +93,6 @@ public Page<PostMainResponseDto> findRandomPosts(Pageable pageable, Float random
return customPage;
}

private Float getOrGenerateRandomSeed(Float seed) {
if (seed == null) {
return random.nextFloat();
}
return seed;
}

private CommentResponseDto findRecentCommentByPostOrNull(Post post) {
Comment lastCommentByPost = post.getComments().stream().findFirst().orElse(null);
if (lastCommentByPost == null) {
Expand All @@ -134,8 +104,33 @@ private CommentResponseDto findRecentCommentByPostOrNull(Post post) {
}
}

public Page<PostSimpleResponseDto> searchPostsByTitleOrContent(Pageable pageable, String query) {
return postRepository.findByPostTitleContainingOrPostContentContaining(query, query, pageable)
.map(postMapper::toPostSimpleResponseDto);
public void deleteById(Long postId, Long userId) {
Post post = findPostByIdOrThrow(postId);
validatePostOwner(post, userId);

post.unlinkAlbum();

postRepository.deleteById(postId);
}

public void validatePostExist(Long postId) {
if (!postRepository.existsById(postId)) {
throw new PostNotFoundException(postId);
}
}

public void validatePostOwner(Post post, Long userId) {
Long postOwnerId = post.getUser().getUserId();

if (!postOwnerId.equals(userId)) {
throw new UnauthorizedPostAccessException(post.getPostId());
}
}

private Float getOrGenerateRandomSeed(Float seed) {
if (seed == null) {
return random.nextFloat();
}
return seed;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
import org.springframework.web.server.ResponseStatusException;

public class PostNotFoundException extends ResponseStatusException {
public PostNotFoundException(Long albumId) {
super(HttpStatus.NOT_FOUND, "Album not found with id " + albumId);
public PostNotFoundException(Long postId) {
super(HttpStatus.NOT_FOUND, "Post not found with id " + postId);
}

}
39 changes: 39 additions & 0 deletions src/main/java/com/soyeon/nubim/domain/postlike/PostLike.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.soyeon.nubim.domain.postlike;

import com.soyeon.nubim.domain.post.Post;
import com.soyeon.nubim.domain.user.User;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PostLike {

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

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id", nullable = false)
private Post post;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.soyeon.nubim.domain.postlike;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.soyeon.nubim.domain.postlike.dto.PostLikeCreateResponse;

import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/v1/postlikes")
@RequiredArgsConstructor
public class PostLikeControllerV1 {

private final PostLikeService postLikeService;

@Operation(description = "게시글 좋아요 및 좋아요 취소. 게시글에 좋아요를 누르고, 이미 좋아요가 되어있을 시 취소한다.")
@PostMapping("/{postId}")
public ResponseEntity<PostLikeCreateResponse> togglePostLike(@PathVariable("postId") Long postId) {
PostLikeCreateResponse postLike = postLikeService.togglePostLike(postId);

return ResponseEntity.ok().body(postLike);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.soyeon.nubim.domain.postlike;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

@Repository
public interface PostLikeRepository extends JpaRepository<PostLike, Long> {

@Query("SELECT EXISTS (FROM PostLike pl WHERE pl.post.postId = :postId AND pl.user.userId = :userId)")
boolean existsPostLikeByPostAndUser(Long postId, Long userId);

@Modifying
@Query("DELETE FROM PostLike pl WHERE pl.post.postId = :postId AND pl.user.userId = :userId")
int deletePostLikeByPostAndUser(Long postId, Long userId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.soyeon.nubim.domain.postlike;

import org.springframework.stereotype.Service;

import com.soyeon.nubim.domain.post.Post;
import com.soyeon.nubim.domain.post.PostService;
import com.soyeon.nubim.domain.postlike.dto.PostLikeCreateResponse;
import com.soyeon.nubim.domain.postlike.exception.MultiplePostLikeDeleteException;
import com.soyeon.nubim.domain.user.User;
import com.soyeon.nubim.domain.user.UserService;

import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class PostLikeService {

public static final int POST_LIKE_DELETE_SUCCESS = 1;
private final UserService userService;
private final PostService postService;
private final PostLikeRepository postLikeRepository;

@Transactional
public PostLikeCreateResponse togglePostLike(Long postId) {
Long currentUserId = userService.getCurrentUserId();

postService.validatePostExist(postId);
userService.validateUserExists(currentUserId);

// 좋아요 되어 있을 시 좋아요 삭제
if (postLikeRepository.existsPostLikeByPostAndUser(postId, currentUserId)) {
int deleteResult = postLikeRepository.deletePostLikeByPostAndUser(postId, currentUserId);

if (deleteResult != POST_LIKE_DELETE_SUCCESS) {
throw new MultiplePostLikeDeleteException();
}
return new PostLikeCreateResponse("post like removed");
}

PostLike postLike = PostLike.builder()
.post(new Post(postId, currentUserId))
.user(new User(currentUserId))
.build();
postLikeRepository.save(postLike);

return new PostLikeCreateResponse("post like successfully");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.soyeon.nubim.domain.postlike.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class PostLikeCreateResponse {
private String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.soyeon.nubim.domain.postlike.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;

public class MultiplePostLikeDeleteException extends ResponseStatusException {
public MultiplePostLikeDeleteException() {
super(HttpStatus.INTERNAL_SERVER_ERROR,
"Multiple post like were unexpectedly deleted. Only one post like can be deleted");
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/soyeon/nubim/domain/user/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ public class User extends BaseEntity {
@OneToMany(mappedBy = "follower", fetch = FetchType.LAZY)
private List<UserFollow> followees = new ArrayList<>();

/**
* 다른 엔티티 생성 시 매핑만을 위한 임시 User 엔티티 생성
* 실제 User의 값은 가지지 않으니 사용 시 주의할 것
*/
public User(Long userId) {
this.userId = userId;
this.username = "MAPPING_USER";
this.nickname = "MAPPING_USER";
this.email = "MAPPING_USER@email.com";
}

public User updateNameFromOAuthProfile(String name) {
this.username = name;
return this;
Expand Down