diff --git a/src/main/java/com/moongeul/backend/api/post/controller/PostController.java b/src/main/java/com/moongeul/backend/api/post/controller/PostController.java index 7bf4ec1..b111ac3 100644 --- a/src/main/java/com/moongeul/backend/api/post/controller/PostController.java +++ b/src/main/java/com/moongeul/backend/api/post/controller/PostController.java @@ -118,4 +118,28 @@ public ResponseEntity> deletePost(@AuthenticationPrincipal Use postService.deletePost(id, userDetails.getUsername()); return ApiResponse.success_only(SuccessStatus.DELETE_POST_SUCCESS); } + + @Operation( + summary = "공감 토글 API", + description = "기록(게시글)에 공감 표시 시 사용하는 공감 토글 API 입니다." + + "

[enum] 기록(게시글) 공감 유형 ->" + + "
- RELATABLE: 공감돼요" + + "
- SAME_TASTE: 취향이 같아요" + + "
- IMPRESSIVE_EXPRESSION: 표현이 인상적이에요" + + "
- WANT_TO_READ: 읽고 싶네요" + + "
- HELPFUL: 도움이 됐어요" + ) + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "기록(게시글) 공감 버튼 등록 성공"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "공감 유형은 필수 입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "해당 기록(게시글)을 찾을 수 없습니다.") + }) + @PostMapping("/like/{id}") + public ResponseEntity> LikePost(@AuthenticationPrincipal UserDetails userDetails, + @PathVariable Long id, + @Valid @RequestBody LikeDTO likeDTO) { + + postService.likePost(id, userDetails.getUsername(), likeDTO); + return ApiResponse.success_only(SuccessStatus.POST_LIKE_SUCCESS); + } } diff --git a/src/main/java/com/moongeul/backend/api/post/dto/LikeDTO.java b/src/main/java/com/moongeul/backend/api/post/dto/LikeDTO.java new file mode 100644 index 0000000..f44b703 --- /dev/null +++ b/src/main/java/com/moongeul/backend/api/post/dto/LikeDTO.java @@ -0,0 +1,29 @@ +package com.moongeul.backend.api.post.dto; + +import com.moongeul.backend.api.member.entity.Member; +import com.moongeul.backend.api.post.entity.Likes; +import com.moongeul.backend.api.post.entity.LikeType; +import com.moongeul.backend.api.post.entity.Post; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class LikeDTO { + + @NotNull(message = "공감 유형은 필수 입니다.") + private LikeType likeType; // 공감 유형 + + public Likes toEntity(Member member, Post post){ + return Likes.builder() + .member(member) + .post(post) + .likeType(this.likeType) + .build(); + } +} diff --git a/src/main/java/com/moongeul/backend/api/post/dto/PostRequestDTO.java b/src/main/java/com/moongeul/backend/api/post/dto/PostRequestDTO.java index c1abc57..006ad6b 100644 --- a/src/main/java/com/moongeul/backend/api/post/dto/PostRequestDTO.java +++ b/src/main/java/com/moongeul/backend/api/post/dto/PostRequestDTO.java @@ -65,6 +65,11 @@ public Post toEntity(Category category, Member member, Book book) { .rating(finalRating) .page(finalPage) .content(this.content) + .relatableCount(0) + .sameTasteCount(0) + .impressiveExpressionCount(0) + .wantToReadCount(0) + .helpfulCount(0) .member(member) .book(book) .build(); diff --git a/src/main/java/com/moongeul/backend/api/post/entity/LikeType.java b/src/main/java/com/moongeul/backend/api/post/entity/LikeType.java new file mode 100644 index 0000000..9503e03 --- /dev/null +++ b/src/main/java/com/moongeul/backend/api/post/entity/LikeType.java @@ -0,0 +1,17 @@ +package com.moongeul.backend.api.post.entity; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum LikeType { + + RELATABLE("공감돼요"), + SAME_TASTE("취향이 같아요"), + IMPRESSIVE_EXPRESSION("표현이 인상적이에요"), + WANT_TO_READ("읽고 싶네요"), + HELPFUL("도움이 됐어요"); + + private final String key; +} diff --git a/src/main/java/com/moongeul/backend/api/post/entity/Likes.java b/src/main/java/com/moongeul/backend/api/post/entity/Likes.java new file mode 100644 index 0000000..f141e12 --- /dev/null +++ b/src/main/java/com/moongeul/backend/api/post/entity/Likes.java @@ -0,0 +1,39 @@ +package com.moongeul.backend.api.post.entity; + +import com.moongeul.backend.api.member.entity.Member; +import com.moongeul.backend.common.entity.BaseTimeEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Builder // 빌더 패턴 사용을 위한 롬복 애너테이션 +@NoArgsConstructor // 기본 생성자 +@AllArgsConstructor // 모든 필드를 포함한 생성자 +@Table(name = "LIKES") // 데이터베이스 테이블 이름 지정 +public class Likes extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private Post post; // 어떤 게시글에 눌렀는지 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + private Member member; // 누가 눌렀는지 + + @Enumerated(EnumType.STRING) + private LikeType likeType; // 공감 유형 + + // 공감 타입 변경 메서드 + public void changeLikeType(LikeType newLikeType) { + this.likeType = newLikeType; + } + +} diff --git a/src/main/java/com/moongeul/backend/api/post/entity/Post.java b/src/main/java/com/moongeul/backend/api/post/entity/Post.java index ff03275..6d791f4 100644 --- a/src/main/java/com/moongeul/backend/api/post/entity/Post.java +++ b/src/main/java/com/moongeul/backend/api/post/entity/Post.java @@ -28,6 +28,19 @@ public class Post extends BaseTimeEntity { @Enumerated(EnumType.STRING) private PostVisibility postVisibility; // 공개여부 + // 공감 유형 별 개수 필드들 + @Column(nullable = false) + private Integer relatableCount = 0; // 공감돼요 + @Column(nullable = false) + private Integer sameTasteCount = 0; // 취향이 같아요 + @Column(nullable = false) + private Integer impressiveExpressionCount = 0; // 표현이 인상적이에요 + @Column(nullable = false) + private Integer wantToReadCount = 0; // 읽고싶네요 + @Column(nullable = false) + private Integer helpfulCount = 0; // 도움이 됐어요 + + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "category_id", nullable = true) private Category category; @@ -56,4 +69,28 @@ public void update(LocalDate readDate, this.category = category; this.book = book; } + + // 공감 개수 증가 메서드들 + public void incrementRelatableCount() { this.relatableCount++; } + public void incrementSameTasteCount() { this.sameTasteCount++; } + public void incrementImpressiveExpressionCount() { this.impressiveExpressionCount++; } + public void incrementWantToReadCount() { this.wantToReadCount++; } + public void incrementHelpfulCount() { this.helpfulCount++; } + + // 공감 개수 감소 메서드들 + public void decrementRelatableCount() { + if (this.relatableCount > 0) this.relatableCount--; + } + public void decrementSameTasteCount() { + if (this.sameTasteCount > 0) this.sameTasteCount--; + } + public void decrementImpressiveExpressionCount() { + if (this.impressiveExpressionCount > 0) this.impressiveExpressionCount--; + } + public void decrementWantToReadCount() { + if (this.wantToReadCount > 0) this.wantToReadCount--; + } + public void decrementHelpfulCount() { + if (this.helpfulCount > 0) this.helpfulCount--; + } } diff --git a/src/main/java/com/moongeul/backend/api/post/repository/LikeRepository.java b/src/main/java/com/moongeul/backend/api/post/repository/LikeRepository.java new file mode 100644 index 0000000..80458b0 --- /dev/null +++ b/src/main/java/com/moongeul/backend/api/post/repository/LikeRepository.java @@ -0,0 +1,11 @@ +package com.moongeul.backend.api.post.repository; + +import com.moongeul.backend.api.post.entity.Likes; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface LikeRepository extends JpaRepository { + + Optional findByPostIdAndMemberId(Long postId, Long memberId); +} diff --git a/src/main/java/com/moongeul/backend/api/post/service/PostService.java b/src/main/java/com/moongeul/backend/api/post/service/PostService.java index 3d819f6..1ad6681 100644 --- a/src/main/java/com/moongeul/backend/api/post/service/PostService.java +++ b/src/main/java/com/moongeul/backend/api/post/service/PostService.java @@ -9,10 +9,9 @@ import com.moongeul.backend.api.member.repository.MemberRepository; import com.moongeul.backend.api.post.dto.*; import com.moongeul.backend.api.category.entity.Category; -import com.moongeul.backend.api.post.entity.Post; +import com.moongeul.backend.api.post.entity.*; import com.moongeul.backend.api.category.repository.CategoryRepository; -import com.moongeul.backend.api.post.entity.PostVisibility; -import com.moongeul.backend.api.post.entity.Quote; +import com.moongeul.backend.api.post.repository.LikeRepository; import com.moongeul.backend.api.post.repository.PostRepository; import com.moongeul.backend.api.post.repository.QuoteRepository; import com.moongeul.backend.common.exception.NotFoundException; @@ -28,6 +27,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; @Slf4j @Service @@ -37,6 +37,7 @@ public class PostService { private final MemberRepository memberRepository; private final BookRepository bookRepository; private final PostRepository postRepository; + private final LikeRepository likeRepository; private final CategoryRepository categoryRepository; private final QuoteRepository quoteRepository; private final DoneReadBookshelfRepository doneReadBookshelfRepository; @@ -241,6 +242,60 @@ private void saveQuotes(PostRequestDTO postRequestDTO, Post post){ } } + /* 공감 토글 */ + @Transactional + public void likePost(Long postId, String email, LikeDTO likeDTO){ + + Member member = getMemberByEmail(email); + Post post = getPost(postId); + + // 사용자가 해당 게시글에 누른 공감 유형이 있다면 + Optional existingLike = likeRepository.findByPostIdAndMemberId(postId, member.getId()); + + if(existingLike.isPresent()){ + Likes currentLikes = existingLike.get(); + + // 사용자가 해당 게시글에 누른 "같은 공감 유형"이 있다면(또 누른 경우) -> 공감 삭제 + if (currentLikes.getLikeType().equals(likeDTO.getLikeType())) { + decrementLikeCount(post, currentLikes.getLikeType()); + likeRepository.delete(currentLikes); + return; + } + + // 사용자가 해당 게시글에 누른 "다른 공감 유형"이 있다면 -> 공감 유형 수정 + decrementLikeCount(post, currentLikes.getLikeType()); + currentLikes.changeLikeType(likeDTO.getLikeType()); + incrementLikeCount(post, likeDTO.getLikeType()); + return; + } + + // 처음 공감을 누르는 경우 -> 새로 저장 + likeRepository.save(likeDTO.toEntity(member, post)); + incrementLikeCount(post, likeDTO.getLikeType()); + } + + // 공감 카운트 증가 메서드 + private void incrementLikeCount(Post post, LikeType likeType) { + switch (likeType) { + case RELATABLE: post.incrementRelatableCount(); break; + case SAME_TASTE: post.incrementSameTasteCount(); break; + case IMPRESSIVE_EXPRESSION: post.incrementImpressiveExpressionCount(); break; + case WANT_TO_READ: post.incrementWantToReadCount(); break; + case HELPFUL: post.incrementHelpfulCount(); break; + } + } + + // 공감 카운트 감소 메서드 + private void decrementLikeCount(Post post, LikeType likeType) { + switch (likeType) { + case RELATABLE: post.decrementRelatableCount(); break; + case SAME_TASTE: post.decrementSameTasteCount(); break; + case IMPRESSIVE_EXPRESSION: post.decrementImpressiveExpressionCount(); break; + case WANT_TO_READ: post.decrementWantToReadCount(); break; + case HELPFUL: post.decrementHelpfulCount(); break; + } + } + /* * 단순 데이터 불러오기용 코드 메서드 - 코드 깔끔하게 하기용 diff --git a/src/main/java/com/moongeul/backend/common/response/SuccessStatus.java b/src/main/java/com/moongeul/backend/common/response/SuccessStatus.java index 22c56ca..8819845 100644 --- a/src/main/java/com/moongeul/backend/common/response/SuccessStatus.java +++ b/src/main/java/com/moongeul/backend/common/response/SuccessStatus.java @@ -39,6 +39,7 @@ public enum SuccessStatus { GET_POST_SUCCESS(HttpStatus.OK, "기록(게시글) 상세 조회 성공"), UPDATE_POST_SUCCESS(HttpStatus.OK, "기록(게시글) 수정 성공"), DELETE_POST_SUCCESS(HttpStatus.OK, "기록(게시글) 삭제 성공"), + POST_LIKE_SUCCESS(HttpStatus.OK, "기록(게시글) 공감 버튼 등록 성공"), /* CATEGORY */ GET_CATEGORY_SUCCESS(HttpStatus.OK, "카테고리 전체 조회 성공"),