Skip to content

Commit

Permalink
Merge pull request #110 from TripComeTrue/feature/#9-set-up-tripRecor…
Browse files Browse the repository at this point in the history
…d-comment

[feat] 여행 후기 댓글, 대댓글(TripRecordComment) CRDU 기능 구현
  • Loading branch information
meena2003 authored Jan 24, 2024
2 parents 8e186c3 + 8e9bca4 commit 0ff0e60
Show file tree
Hide file tree
Showing 14 changed files with 458 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.haejwo.tripcometrue.domain.comment.triprecord.controller;

import com.haejwo.tripcometrue.domain.comment.triprecord.dto.request.CommentRequestDto;
import com.haejwo.tripcometrue.domain.comment.triprecord.dto.response.TripRecordCommentListResponseDto;
import com.haejwo.tripcometrue.domain.comment.triprecord.service.TripRecordCommentService;
import com.haejwo.tripcometrue.global.springsecurity.PrincipalDetails;
import com.haejwo.tripcometrue.global.util.ResponseDTO;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import static org.springframework.data.domain.Sort.Direction;

@RestController
@RequiredArgsConstructor
@RequestMapping("/v1/trip-records")
public class TripRecordCommentController {

private final TripRecordCommentService commentService;

@PostMapping("/{tripRecordId}/comments")
public ResponseEntity<ResponseDTO<Void>> registerComment(
@AuthenticationPrincipal PrincipalDetails principalDetails,
@PathVariable Long tripRecordId,
@RequestBody @Valid CommentRequestDto requestDto
) {

commentService.saveComment(principalDetails, tripRecordId, requestDto);
return ResponseEntity.ok(ResponseDTO.ok());
}

@PostMapping("/comments/{tripRecordCommentId}/reply-comments")
public ResponseEntity<ResponseDTO<Void>> registerReplyComment(
@AuthenticationPrincipal PrincipalDetails principalDetails,
@PathVariable Long tripRecordCommentId,
@RequestBody @Valid CommentRequestDto requestDto
) {

commentService.saveReplyComment(principalDetails, tripRecordCommentId, requestDto);
return ResponseEntity.ok(ResponseDTO.ok());
}

@GetMapping("/{tripRecordId}/comments")
public ResponseEntity<ResponseDTO<TripRecordCommentListResponseDto>> getCommentList(
@AuthenticationPrincipal PrincipalDetails principalDetails,
@PathVariable Long tripRecordId,
@PageableDefault(sort = "createdAt", direction = Direction.DESC) Pageable pageable
) {

TripRecordCommentListResponseDto responseDto =
commentService.getCommentList(principalDetails, tripRecordId, pageable);
return ResponseEntity.ok(ResponseDTO.okWithData(responseDto));
}

@DeleteMapping("/comments/{tripRecordCommentId}")
public ResponseEntity<ResponseDTO<Void>> deleteComment(
@AuthenticationPrincipal PrincipalDetails principalDetails,
@PathVariable Long tripRecordCommentId
) {

commentService.removeComment(principalDetails, tripRecordCommentId);
return ResponseEntity.ok(ResponseDTO.ok());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.haejwo.tripcometrue.domain.comment.triprecord.controller;

import com.haejwo.tripcometrue.domain.comment.triprecord.exception.TripRecordCommentNotFoundException;
import com.haejwo.tripcometrue.global.util.ResponseDTO;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TripRecordCommentControllerAdvice {

@ExceptionHandler(TripRecordCommentNotFoundException.class)
public ResponseEntity<ResponseDTO<Void>> handleTripRecordCommentNotFoundException(TripRecordCommentNotFoundException e) {
HttpStatus status = e.getErrorCode().getHttpStatus();

return ResponseEntity
.status(status)
.body(ResponseDTO.errorWithMessage(status, e.getMessage()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.haejwo.tripcometrue.domain.comment.triprecord.dto.request;

import com.haejwo.tripcometrue.domain.comment.triprecord.entity.TripRecordComment;
import com.haejwo.tripcometrue.domain.member.entity.Member;
import com.haejwo.tripcometrue.domain.triprecord.entity.TripRecord;
import jakarta.validation.constraints.NotNull;
import org.hibernate.validator.constraints.Length;

public record CommentRequestDto(

@NotNull(message = "본문은 필수로 입력해야 합니다.")
@Length(min = 1, max = 500, message = "작성 허용 범위는 최소 1자 또는 최대 500자 입니다.")
String content

) {

public static TripRecordComment toComment(Member member, TripRecord tripRecord, CommentRequestDto requestDto) {
return TripRecordComment.builder()
.member(member)
.tripRecord(tripRecord)
.content(requestDto.content)
.build();
}

public static TripRecordComment toReplyComment(
Member member,
TripRecord tripRecord,
TripRecordComment tripRecordComment,
CommentRequestDto requestDto
) {

return TripRecordComment.builder()
.member(member)
.tripRecord(tripRecord)
.parentComment(tripRecordComment)
.content(requestDto.content)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.haejwo.tripcometrue.domain.comment.triprecord.dto.response;

import com.haejwo.tripcometrue.domain.comment.triprecord.entity.TripRecordComment;
import com.haejwo.tripcometrue.domain.member.entity.Member;
import org.springframework.data.domain.Slice;

import java.util.List;
import java.util.Objects;

public record TripRecordCommentListResponseDto(

int totalCount,
List<TripRecordCommentResponseDto> comments

) {

public static TripRecordCommentListResponseDto fromData(int totalCount, Slice<TripRecordComment> tripRecordComments, Member loginMember) {
return new TripRecordCommentListResponseDto(
totalCount,
tripRecordComments.map(tripRecordComment -> {
if (tripRecordComment.getParentComment() == null) { //최상위 댓글만 포함
return TripRecordCommentResponseDto.fromEntity(tripRecordComment, loginMember);
}
return null;
})
.filter(Objects::nonNull)
.toList()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.haejwo.tripcometrue.domain.comment.triprecord.dto.response;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.haejwo.tripcometrue.domain.comment.triprecord.entity.TripRecordComment;
import com.haejwo.tripcometrue.domain.member.entity.Member;

import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;

public record TripRecordCommentResponseDto(

Long commentId,
Long memberId,
String profileUrl,
String nickname,
boolean isWriter,

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH-mm-ss")
LocalDateTime createdAt,

List<TripRecordCommentResponseDto> replyComments

) {

public static TripRecordCommentResponseDto fromEntity(TripRecordComment tripRecordComment, Member loginMember) {
return new TripRecordCommentResponseDto(
tripRecordComment.getId(),
tripRecordComment.getMember().getId(),
tripRecordComment.getMember().getProfileImage(),
tripRecordComment.getMember().getMemberBase().getNickname(),
isWriter(tripRecordComment, loginMember),
tripRecordComment.getCreatedAt(),
getReplyComments(tripRecordComment, loginMember) //자식 댓글 리스트에 담기
);
}

private static boolean isWriter(TripRecordComment tripRecordComment, Member loginMember) {
return Objects.equals(tripRecordComment.getMember(), loginMember);
}

private static List<TripRecordCommentResponseDto> getReplyComments(TripRecordComment tripRecordComment, Member loginMember) {
if (tripRecordComment.getParentComment() == null) {
return tripRecordComment.getChildComments().stream()
.map(comment -> TripRecordCommentResponseDto.fromEntity(comment, loginMember))
.sorted(Comparator.comparing(TripRecordCommentResponseDto::createdAt).reversed())
.toList();
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.haejwo.tripcometrue.domain.comment.triprecord.entity;

import com.haejwo.tripcometrue.domain.member.entity.Member;
import com.haejwo.tripcometrue.domain.triprecord.entity.TripRecord;
import com.haejwo.tripcometrue.global.entity.BaseTimeEntity;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

import static jakarta.persistence.CascadeType.REMOVE;
import static jakarta.persistence.FetchType.LAZY;

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class TripRecordComment extends BaseTimeEntity {

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

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

@ManyToOne(fetch = LAZY)
@JoinColumn(name = "trip_record_id")
private TripRecord tripRecord;

@ManyToOne(fetch = LAZY)
@JoinColumn(name = "parent_comment_id")
private TripRecordComment parentComment;

@OneToMany(mappedBy = "parentComment", cascade = REMOVE, orphanRemoval = true)
private List<TripRecordComment> childComments = new ArrayList<>();

@Column(nullable = false)
private String content;

@Builder
public TripRecordComment(Member member, TripRecord tripRecord, TripRecordComment parentComment, String content) {
this.member = member;
this.tripRecord = tripRecord;
this.parentComment = parentComment;
this.content = content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.haejwo.tripcometrue.domain.comment.triprecord.exception;

import com.haejwo.tripcometrue.global.exception.ApplicationException;
import com.haejwo.tripcometrue.global.exception.ErrorCode;

public class TripRecordCommentNotFoundException extends ApplicationException {
private static final ErrorCode ERROR_CODE = ErrorCode.TRIP_RECORD_COMMENT_NOT_FOUND;

public TripRecordCommentNotFoundException() {
super(ERROR_CODE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.haejwo.tripcometrue.domain.comment.triprecord.repository;

import com.haejwo.tripcometrue.domain.comment.triprecord.entity.TripRecordComment;
import com.haejwo.tripcometrue.domain.triprecord.entity.TripRecord;
import io.lettuce.core.dynamic.annotation.Param;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

public interface TripRecordCommentRepository extends JpaRepository<TripRecordComment, Long> {

Slice<TripRecordComment> findByTripRecord(TripRecord tripRecord, Pageable pageable);

@Modifying
@Query("delete from TripRecordComment trc where trc.parentComment.id = :tripRecordCommentId")
int deleteChildrenByTripRecordCommentId(@Param("tripRecordCommentId") Long tripRecordCommentId);

@Modifying
@Query("delete from TripRecordComment trc where trc.id = :tripRecordCommentId")
int deleteParentByTripRecordCommentId(@Param("tripRecordCommentId") Long tripRecordCommentId);
}
Loading

0 comments on commit 0ff0e60

Please sign in to comment.