Skip to content
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 @@ -3,26 +3,15 @@
import com.example.solidconnection.comment.domain.Comment;
import com.example.solidconnection.post.domain.Post;
import com.example.solidconnection.siteuser.domain.SiteUser;
import jakarta.annotation.Nullable;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Getter;

@Getter
public class CommentCreateRequest {

@NotBlank(message = "댓글 내용은 빈 값일 수 없습니다.")
@Size(min = 1, max = 255, message = "댓글 내용은 최소 1자 이상, 최대 255자 이하여야 합니다.")
String content;

@Nullable
Long parentId;

public CommentCreateRequest(String content, @Nullable Long parentId) {
this.content = content;
this.parentId = parentId;
}

public record CommentCreateRequest(
@NotBlank(message = "댓글 내용은 빈 값일 수 없습니다.")
@Size(min = 1, max = 255, message = "댓글 내용은 최소 1자 이상, 최대 255자 이하여야 합니다.")
String content,
Long parentId
) {
public Comment toEntity(SiteUser siteUser, Post post, Comment parentComment) {

Comment comment = new Comment(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,10 @@ public CommentCreateResponse createComment(String email, Long postId, CommentCre
SiteUser siteUser = siteUserRepository.getByEmail(email);
Post post = postRepository.getById(postId);

Comment parentComment = Optional.ofNullable(commentCreateRequest.getParentId())
.map(commentRepository::getById)
.orElse(null);
Comment parentComment = null;
if (commentCreateRequest.parentId() != null) {
parentComment = commentRepository.getById(commentCreateRequest.parentId());
}
Comment createdComment = commentRepository.save(commentCreateRequest.toEntity(siteUser, post, parentComment));

return CommentCreateResponse.from(createdComment);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public enum ErrorCode {
INVALID_COMMENT_ID(HttpStatus.BAD_REQUEST.value(), "존재하지 않는 댓글입니다."),
INVALID_COMMENT_ACCESS(HttpStatus.BAD_REQUEST.value(), "자신의 댓글만 제어할 수 있습니다."),
CAN_NOT_UPDATE_DEPRECATED_COMMENT(HttpStatus.BAD_REQUEST.value(),"이미 삭제된 댓글을 수정할 수 없습니다."),
INVALID_POST_LIKE(HttpStatus.BAD_REQUEST.value(), "존재하지 않는 게시글 좋아요입니다."),
DUPLICATE_POST_LIKE(HttpStatus.BAD_REQUEST.value(), "이미 좋아요한 게시글입니다."),

// general
JSON_PARSING_FAILED(HttpStatus.BAD_REQUEST.value(), "JSON 파싱을 할 수 없습니다."),
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,26 @@ public ResponseEntity<?> deletePostById(
PostDeleteResponse postDeleteResponse = postService.deletePostById(principal.getName(), code, postId);
return ResponseEntity.ok().body(postDeleteResponse);
}

@PostMapping(value = "/{code}/posts/{post_id}/like")
public ResponseEntity<?> likePost(
Principal principal,
@PathVariable("code") String code,
@PathVariable("post_id") Long postId
) {

PostLikeResponse postLikeResponse = postService.likePost(principal.getName(), code, postId);
return ResponseEntity.ok().body(postLikeResponse);
}

@DeleteMapping(value = "/{code}/posts/{post_id}/like")
public ResponseEntity<?> dislikePost(
Principal principal,
@PathVariable("code") String code,
@PathVariable("post_id") Long postId
) {

PostDislikeResponse postDislikeResponse = postService.dislikePost(principal.getName(), code, postId);
return ResponseEntity.ok().body(postDislikeResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.example.solidconnection.comment.domain.Comment;
import com.example.solidconnection.entity.PostImage;
import com.example.solidconnection.entity.common.BaseEntity;
import com.example.solidconnection.entity.mapping.PostLike;
import com.example.solidconnection.post.dto.PostUpdateRequest;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.type.PostCategory;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.example.solidconnection.post.domain;

import com.example.solidconnection.siteuser.domain.SiteUser;
import jakarta.persistence.*;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor
@EqualsAndHashCode(of = "id")
public class PostLike {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

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

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "site_user_id")
private SiteUser siteUser;

public void setPostAndSiteUser(Post post, SiteUser siteUser) {

if (this.post != null) {
this.post.getPostLikeList().remove(this);
}
this.post = post;
post.getPostLikeList().add(this);

if (this.siteUser != null) {
this.siteUser.getPostLikeList().remove(this);
}
this.siteUser = siteUser;
siteUser.getPostLikeList().add(this);
}

public void resetPostAndSiteUser() {

if (this.post != null) {
this.post.getPostLikeList().remove(this);
}
this.post = null;

if (this.siteUser != null) {
this.siteUser.getPostLikeList().remove(this);
}
this.siteUser = null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.solidconnection.post.dto;

import com.example.solidconnection.post.domain.Post;

public record PostDislikeResponse(
Long likeCount,
Boolean isLiked
) {
public static PostDislikeResponse from(Post post) {
return new PostDislikeResponse(
post.getLikeCount(),
false
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public record PostFindResponse(
Integer commentCount,
String postCategory,
Boolean isOwner,
Boolean isLiked,
LocalDateTime createdAt,
LocalDateTime updatedAt,
PostFindBoardResponse postFindBoardResponse,
Expand All @@ -27,7 +28,7 @@ public record PostFindResponse(
List<PostFindPostImageResponse> postFindPostImageResponses
) {

public static PostFindResponse from(Post post, Boolean isOwner, PostFindBoardResponse postFindBoardResponse,
public static PostFindResponse from(Post post, Boolean isOwner, Boolean isLiked, PostFindBoardResponse postFindBoardResponse,
PostFindSiteUserResponse postFindSiteUserResponse,
List<PostFindCommentResponse> postFindCommentResponses,
List<PostFindPostImageResponse> postFindPostImageResponses
Expand All @@ -42,6 +43,7 @@ public static PostFindResponse from(Post post, Boolean isOwner, PostFindBoardRes
postFindCommentResponses.size(),
String.valueOf(post.getCategory()),
isOwner,
isLiked,
post.getCreatedAt(),
post.getUpdatedAt(),
postFindBoardResponse,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.example.solidconnection.post.dto;

import com.example.solidconnection.post.domain.Post;

public record PostLikeResponse(
Long likeCount,
Boolean isLiked
Comment on lines +5 to +7
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[반영 안 해도 되는 개인적 생각]
래퍼 클래스는 "null 이 올 가능성이 있을 때" 만 사용하는 것이 좋다 생각해요. 메모리상으로도 효율적이고, 다른 개발자들에게 null 의 가능성을 알려준다는 것도 중요하다 생각해서요!

Copy link
Member Author

@leesewon00 leesewon00 Aug 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오,, 해당부분은 제가 크게 놓치고 있었던 내용이네요,,
래퍼클래스의 장점 때문에 무분별하게 사용하고 있었던 것 같은데 null이 올 가능성이 없을때 등 불필요한 상황에서의 래퍼클래스 사용은 지양하는게 좋다는 생각이 드네요. 감사합니다!



) {
public static PostLikeResponse from(Post post) {
return new PostLikeResponse(
post.getLikeCount(),
true
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.example.solidconnection.post.repository;

import com.example.solidconnection.custom.exception.CustomException;
import com.example.solidconnection.post.domain.PostLike;
import com.example.solidconnection.post.domain.Post;
import com.example.solidconnection.siteuser.domain.SiteUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_LIKE;

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

Optional<PostLike> findPostLikeByPostAndSiteUser(Post post, SiteUser siteUser);

default PostLike getByPostAndSiteUser(Post post, SiteUser siteUser) {
return findPostLikeByPostAndSiteUser(post, siteUser)
.orElseThrow(() -> new CustomException(INVALID_POST_LIKE));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import com.example.solidconnection.post.domain.Post;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.Optional;
Expand All @@ -25,4 +28,14 @@ default Post getById(Long id) {
return findById(id)
.orElseThrow(() -> new CustomException(INVALID_POST_ID));
}

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("UPDATE Post p SET p.likeCount = p.likeCount - 1 " +
"WHERE p.id = :postId AND p.likeCount > 0")
void decreaseLikeCount(@Param("postId") Long postId);

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("UPDATE Post p SET p.likeCount = p.likeCount + 1 " +
"WHERE p.id = :postId")
void increaseLikeCount(@Param("postId") Long postId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import com.example.solidconnection.dto.*;
import com.example.solidconnection.board.domain.Board;
import com.example.solidconnection.entity.PostImage;
import com.example.solidconnection.post.domain.PostLike;
import com.example.solidconnection.post.repository.PostLikeRepository;
import com.example.solidconnection.post.domain.Post;
import com.example.solidconnection.post.dto.*;
import com.example.solidconnection.post.repository.PostRepository;
Expand All @@ -24,6 +26,7 @@
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.EnumUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

Expand All @@ -41,6 +44,7 @@ public class PostService {
private final CommentService commentService;
private final RedisService redisService;
private final RedisUtils redisUtils;
private final PostLikeRepository postLikeRepository;

private String validateCode(String code) {
try {
Expand Down Expand Up @@ -71,8 +75,8 @@ private void validateQuestion(Post post) {
}
}

private void validatePostCategory(String category){
if(!EnumUtils.isValidEnum(PostCategory.class, category)){
private void validatePostCategory(String category) {
if (!EnumUtils.isValidEnum(PostCategory.class, category) || category.equals(PostCategory.전체.toString())) {
throw new CustomException(INVALID_POST_CATEGORY);
}
}
Expand All @@ -81,6 +85,11 @@ private Boolean getIsOwner(Post post, String email) {
return post.getSiteUser().getEmail().equals(email);
}

private Boolean getIsLiked(Post post, SiteUser siteUser) {
return postLikeRepository.findPostLikeByPostAndSiteUser(post, siteUser)
.isPresent();
}

@Transactional
public PostCreateResponse createPost(String email, String code, PostCreateRequest postCreateRequest,
List<MultipartFile> imageFile) {
Expand Down Expand Up @@ -146,20 +155,22 @@ public PostFindResponse findPostById(String email, String code, Long postId) {
String boardCode = validateCode(code);

Post post = postRepository.getByIdUsingEntityGraph(postId);
SiteUser siteUser = siteUserRepository.getByEmail(email);
Boolean isOwner = getIsOwner(post, email);
Boolean isLiked = getIsLiked(post, siteUser);

PostFindBoardResponse boardPostFindResultDTO = PostFindBoardResponse.from(post.getBoard());
PostFindSiteUserResponse siteUserPostFindResultDTO = PostFindSiteUserResponse.from(post.getSiteUser());
List<PostFindPostImageResponse> postImageFindResultDTOList = PostFindPostImageResponse.from(post.getPostImageList());
List<PostFindCommentResponse> commentFindResultDTOList = commentService.findCommentsByPostId(email, postId);

// caching && 어뷰징 방지
if (redisService.isPresent(redisUtils.getValidatePostViewCountRedisKey(email,postId))) {
if (redisService.isPresent(redisUtils.getValidatePostViewCountRedisKey(email, postId))) {
redisService.increaseViewCount(redisUtils.getPostViewCountRedisKey(postId));
}

return PostFindResponse.from(
post, isOwner, boardPostFindResultDTO, siteUserPostFindResultDTO, commentFindResultDTOList, postImageFindResultDTOList);
post, isOwner, isLiked, boardPostFindResultDTO, siteUserPostFindResultDTO, commentFindResultDTOList, postImageFindResultDTOList);
}

@Transactional
Expand All @@ -178,4 +189,41 @@ public PostDeleteResponse deletePostById(String email, String code, Long postId)

return new PostDeleteResponse(postId);
}

@Transactional(isolation = Isolation.READ_COMMITTED)
public PostLikeResponse likePost(String email, String code, Long postId) {

String boardCode = validateCode(code);
Post post = postRepository.getById(postId);
SiteUser siteUser = siteUserRepository.getByEmail(email);
validateDuplicatePostLike(post, siteUser);

PostLike postLike = new PostLike();
postLike.setPostAndSiteUser(post, siteUser);
postLikeRepository.save(postLike);
postRepository.increaseLikeCount(post.getId());

return PostLikeResponse.from(postRepository.getById(postId)); // 실시간성을 위한 재조회
}

private void validateDuplicatePostLike(Post post, SiteUser siteUser) {
if (postLikeRepository.findPostLikeByPostAndSiteUser(post, siteUser).isPresent()) {
throw new CustomException(DUPLICATE_POST_LIKE);
}
}

@Transactional(isolation = Isolation.READ_COMMITTED)
public PostDislikeResponse dislikePost(String email, String code, Long postId) {

String boardCode = validateCode(code);
Post post = postRepository.getById(postId);
SiteUser siteUser = siteUserRepository.getByEmail(email);

PostLike postLike = postLikeRepository.getByPostAndSiteUser(post, siteUser);
postLike.resetPostAndSiteUser();
postLikeRepository.deleteById(postLike.getId());
postRepository.decreaseLikeCount(post.getId());

return PostDislikeResponse.from(postRepository.getById(postId)); // 실시간성을 위한 재조회
}
}
Loading