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

Feature : 커뮤니티 댓글 API 구현 #45

Merged
merged 18 commits into from
Jan 6, 2025
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
129 changes: 129 additions & 0 deletions src/main/java/itstime/reflog/comment/controller/CommentController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package itstime.reflog.comment.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import itstime.reflog.comment.dto.CommentDto;
import itstime.reflog.comment.service.CommentService;
import itstime.reflog.common.CommonApiResponse;
import itstime.reflog.common.annotation.UserId;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@Tag(name = "COMMENT API", description = "댓글에 대한 API입니다.")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/comments")
public class CommentController {

private final CommentService commentService;

@Operation(
summary = "댓글 생성 API",
description = "새로운 댓글을 생성합니다. AccessToken 필요.",
responses = {
@ApiResponse(
responseCode = "200",
description = "댓글 생성 성공"
),
@ApiResponse(
responseCode = "404",
description = "해당 회원을 찾을 수 없음"
),
@ApiResponse(
responseCode = "500",
description = "서버 에러"
)
}
)
@PostMapping("/{communityId}")
public ResponseEntity<CommonApiResponse<Void>> createComment(
@PathVariable Long communityId,
@UserId String memberId,
@RequestBody CommentDto.CommentSaveOrUpdateRequest dto
) {
commentService.createComment(communityId, memberId, dto);
return ResponseEntity.ok(CommonApiResponse.onSuccess(null));
}

@Operation(
summary = "댓글 수정 API",
description = "댓글을 수정합니다. AccessToken 필요.",
responses = {
@ApiResponse(
responseCode = "200",
description = "댓글 수정 성공"
),
@ApiResponse(
responseCode = "404",
description = "해당 회원을 찾을 수 없음"
),
@ApiResponse(
responseCode = "500",
description = "서버 에러"
)
}
)
@PatchMapping("/{commentId}")
public ResponseEntity<CommonApiResponse<Void>> updateComment(
@PathVariable Long commentId,
@RequestBody CommentDto.CommentSaveOrUpdateRequest dto
) {
commentService.updateComment(commentId, dto);
return ResponseEntity.ok(CommonApiResponse.onSuccess(null));
}

@Operation(
summary = "댓글 삭제 API",
description = "댓글을 삭제합니다. AccessToken 필요.",
responses = {
@ApiResponse(
responseCode = "200",
description = "댓글 삭제 성공"
),
@ApiResponse(
responseCode = "404",
description = "해당 회원을 찾을 수 없음"
),
@ApiResponse(
responseCode = "500",
description = "서버 에러"
)
}
)
@DeleteMapping("/{commentId}")
public ResponseEntity<CommonApiResponse<Void>> deleteComment(
@PathVariable Long commentId
) {
commentService.deleteComment(commentId);
return ResponseEntity.ok(CommonApiResponse.onSuccess(null));
}

@Operation(
summary = "댓글 좋아요 API",
description = "댓글에 좋아요를 합니다. AccessToken 필요.",
responses = {
@ApiResponse(
responseCode = "200",
description = "댓글 좋아요 성공"
),
@ApiResponse(
responseCode = "404",
description = "해당 회원을 찾을 수 없음"
),
@ApiResponse(
responseCode = "500",
description = "서버 에러"
)
}
)
@PostMapping("/like/{commentId}")
public ResponseEntity<CommonApiResponse<Void>> toggleCommentLike(
@PathVariable Long commentId,
@UserId String memberId
) {
commentService.toggleCommentLike(commentId, memberId);
return ResponseEntity.ok(CommonApiResponse.onSuccess(null));
}
}
55 changes: 55 additions & 0 deletions src/main/java/itstime/reflog/comment/domain/Comment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package itstime.reflog.comment.domain;

import itstime.reflog.comment.dto.CommentDto;
import itstime.reflog.commentLike.domain.CommentLike;
import itstime.reflog.community.domain.Community;
import itstime.reflog.member.domain.Member;
import jakarta.persistence.*;
import lombok.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Comment {

@Id
@Column(name = "comment_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String content;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "commnunity_id", nullable = false)
private Community community;

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

@OneToMany(mappedBy = "comment", cascade = CascadeType.ALL, orphanRemoval = true)
private List<CommentLike> likes = new ArrayList<>();

@ManyToOne
@JoinColumn(name = "parent_id")
private Comment parent;

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Comment> children = new ArrayList<>();

private LocalDateTime createdAt; // 생성일

private LocalDateTime updatedAt; // 수정일

public void update(CommentDto.CommentSaveOrUpdateRequest dto) {
this.content = dto.getContent();
this.updatedAt = LocalDateTime.now();
}
}
30 changes: 30 additions & 0 deletions src/main/java/itstime/reflog/comment/dto/CommentDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package itstime.reflog.comment.dto;

import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.time.LocalDateTime;

public class CommentDto {

@Getter
@AllArgsConstructor
public static class CommentSaveOrUpdateRequest {

@NotBlank(message = "댓글은 비어 있을 수 없습니다.")
private String content;

private Long parentId;
}

@Getter
@AllArgsConstructor
public static class CommentResponse {
private Long commentId;
private String name;
private String content;
private Long parentId;
private LocalDateTime createdAt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package itstime.reflog.comment.repository;

import itstime.reflog.comment.domain.Comment;
import itstime.reflog.community.domain.Community;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface CommentRepository extends JpaRepository<Comment, Long> {

// 커뮤니티 모든 댓글 조회
List<Comment> findAllByCommunityOrderByCreatedAtDesc(Community community);
}
111 changes: 111 additions & 0 deletions src/main/java/itstime/reflog/comment/service/CommentService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package itstime.reflog.comment.service;

import itstime.reflog.comment.domain.Comment;
import itstime.reflog.comment.dto.CommentDto;
import itstime.reflog.comment.repository.CommentRepository;
import itstime.reflog.commentLike.domain.CommentLike;
import itstime.reflog.commentLike.repository.CommentLikeRepository;
import itstime.reflog.common.code.status.ErrorStatus;
import itstime.reflog.common.exception.GeneralException;
import itstime.reflog.community.domain.Community;
import itstime.reflog.community.repository.CommunityRepository;
import itstime.reflog.member.domain.Member;
import itstime.reflog.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Optional;
import java.util.UUID;

@Service
@RequiredArgsConstructor
public class CommentService {

private final MemberRepository memberRepository;
private final CommunityRepository communityRepository;
private final CommentRepository commentRepository;
private final CommentLikeRepository commentLikeRepository;

@Transactional
public void createComment(Long communityId, String memberId, CommentDto.CommentSaveOrUpdateRequest dto) {
// 1. 멤버 조회
Member member = memberRepository.findByUuid(UUID.fromString(memberId))
.orElseThrow(() -> new GeneralException(ErrorStatus._MEMBER_NOT_FOUND));

// 2. 커뮤니티 조회
Community community = communityRepository.findById(communityId)
.orElseThrow(() -> new GeneralException(ErrorStatus._COMMUNITY_NOT_FOUND));

// 3. parent 댓글 확인
Comment parentComment = null;
if (dto.getParentId() != null) {
parentComment = commentRepository.findById(dto.getParentId())
.orElseThrow(() -> new GeneralException(ErrorStatus._PARENT_COMMENT_NOT_FOUND));
}

// 4. 댓글 생성
Comment comment = Comment.builder()
.content(dto.getContent())
.community(community)
.member(member)
.parent(parentComment)
.createdAt(LocalDateTime.now())
.updatedAt(LocalDateTime.now())
.build();

// 5. 댓글 저장
commentRepository.save(comment);
}

@Transactional
public void updateComment(Long commentId, CommentDto.CommentSaveOrUpdateRequest dto) {
// 1. 댓글 조회
Comment comment = commentRepository.findById(commentId)
.orElseThrow(() -> new GeneralException(ErrorStatus._COMMENT_NOT_FOUND));

// 2. 댓글 업데이트
comment.update(dto);
}

@Transactional
public void deleteComment(Long commentId) {
// 1. 댓글 조회
Comment comment = commentRepository.findById(commentId)
.orElseThrow(() -> new GeneralException(ErrorStatus._COMMENT_NOT_FOUND));

// 2. 댓글 삭제
commentRepository.delete(comment);
}

@Transactional
public void toggleCommentLike(Long commentId, String memberId) {
// 1. 댓글 조회
Comment comment = commentRepository.findById(commentId)
.orElseThrow(() -> new GeneralException(ErrorStatus._COMMENT_NOT_FOUND));

// 2. 멤버 조회
Member member = memberRepository.findByUuid(UUID.fromString(memberId))
.orElseThrow(() -> new GeneralException(ErrorStatus._MEMBER_NOT_FOUND));

// 3. 좋아요 상태 확인
Optional<CommentLike> optionalCommentLike = commentLikeRepository.findByCommentAndMember(comment, member);

if (optionalCommentLike.isPresent()) {
// 좋아요가 이미 존재하면 상태 토글
CommentLike commentLike = optionalCommentLike.get();
commentLike.update(!commentLike.isLiked());
} else {
// 좋아요가 없으면 새로 생성
CommentLike newLike = CommentLike.builder()
.comment(comment)
.member(member)
.isLiked(true)
.build();

commentLikeRepository.save(newLike);
}

}
}
34 changes: 34 additions & 0 deletions src/main/java/itstime/reflog/commentLike/domain/CommentLike.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package itstime.reflog.commentLike.domain;

import itstime.reflog.comment.domain.Comment;
import itstime.reflog.member.domain.Member;
import jakarta.persistence.*;
import lombok.*;

@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class CommentLike {

@Id
@Column(name = "commentlike_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "comment_id", nullable = false)
private Comment comment;

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

@Column(nullable = false)
private boolean isLiked;

public void update(boolean isLiked) {
this.isLiked = isLiked;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package itstime.reflog.commentLike.repository;

import itstime.reflog.comment.domain.Comment;
import itstime.reflog.commentLike.domain.CommentLike;
import itstime.reflog.member.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface CommentLikeRepository extends JpaRepository<CommentLike, Long> {
Optional<CommentLike> findByCommentAndMember(Comment comment, Member member);
}
Loading
Loading