Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
1b26e36
[refactor] status 접근제어자 필드 수정 (#103)
hd0rable Aug 2, 2025
d921cb2
[refactor] status 접근제어자 필드 수정 (#103)
hd0rable Aug 2, 2025
e033c78
[feat] 댓글 삭제 관련 댓글 도메인 책임 메서드 추가 (#103)
hd0rable Aug 2, 2025
bf1d222
[feat] 댓글 삭제 컨트롤러 작성(#103)
hd0rable Aug 2, 2025
fe7a0e6
[feat] 댓글 조회 시 active한 댓글만 찾도록 수정(#103)
hd0rable Aug 2, 2025
bd6144f
[feat] decreaseCommentCount() 추가 (#103)
hd0rable Aug 2, 2025
94be498
[feat] CommentDeleteUseCase 작성 (#103)
hd0rable Aug 2, 2025
3b98312
[feat] CommentDeleteUseCase 구현체 CommentDeleteService 작성(#103)
hd0rable Aug 2, 2025
bb07074
[feat] updateFrom시에 상태 변경 추가(#103)
hd0rable Aug 2, 2025
19d2ffa
[feat] findByCommentIdAndStatus 작성 (#103)
hd0rable Aug 2, 2025
c71bf52
[feat] CommentLikeCommandPersistenceAdapter.deleteAllByCommentId 추가 (…
hd0rable Aug 2, 2025
5340f2a
[feat] CommentLikeCommandPort.deleteAllByCommentId 추가 (#103)
hd0rable Aug 2, 2025
7ba26f3
[feat] CommentLikeJpaRepository .deleteAllByCommentId 추가 (#103)
hd0rable Aug 2, 2025
dc0784b
[fix] CommentLikeQueryPersistenceAdapter .isLikedCommentByUser 오류 수정 …
hd0rable Aug 2, 2025
9c00a8e
[feat] 댓글 관련 에러 코드 추가 (#103)
hd0rable Aug 2, 2025
5c52ef1
[feat] 피드 댓글 관련 함수 추가 (#103)
hd0rable Aug 2, 2025
5bf535f
[feat] 피드 댓글 테스트 관련 함수 추가 (#103)
hd0rable Aug 2, 2025
f89a4f4
[feat] 기록 댓글 관련 함수 추가 (#103)
hd0rable Aug 2, 2025
dbac3eb
[feat] 댓글 삭제 관련 스웨거 문서 에러코드 추가 (#103)
hd0rable Aug 2, 2025
9be50ed
[feat] 투표 댓글 관련 함수 추가 (#103)
hd0rable Aug 2, 2025
71b471f
[test] 댓글 단위 테스트코드 수정 (#103)
hd0rable Aug 2, 2025
d443b2d
[test] 투표 단위 테스트코드 작성 (#103)
hd0rable Aug 2, 2025
60859af
[test] 답글 생성 테스트 팩토리 메서드 추가 (#103)
hd0rable Aug 2, 2025
d5b0981
[test] 기록 단위 테스트코드 작성 (#103)
hd0rable Aug 2, 2025
0d65540
[test] 피드 단위 도메인 테스트 코드 작성 (#103)
hd0rable Aug 2, 2025
c756b3c
[test] 댓글 삭제관련 댓글 단위 테스트코드 추가 (#103)
hd0rable Aug 2, 2025
76c08bc
[test] 댓글 삭제 통합 테스트 코드 작성 (#103)
hd0rable Aug 2, 2025
c92b45d
[feat] PostQueryService.updatePost 추가 (#103)
hd0rable Aug 2, 2025
f4d54d0
[refactor] 피드 수정 관련 request dto 설명 추가 (#103)
hd0rable Aug 2, 2025
d9cdc8b
[refactor] 피드 수정 스웨거 관련 설명 추가 (#103)
hd0rable Aug 2, 2025
27a609d
[refactor] 개발 환경 별로 다른 db반환값에 따른 분기처리 코드 추가 (#103)
hd0rable Aug 2, 2025
96544bf
[refactor] 개발 환경 별로 다른 db반환값에 따른 분기처리 코드 추가 (#103)
hd0rable Aug 2, 2025
7e3bdb8
[feat] PostQueryService.updatePost 추가 (#103)
hd0rable Aug 2, 2025
b7ccf95
[refactor] 안쓰는 의존성 삭제 (#103)
hd0rable Aug 2, 2025
8ef7fbe
[refactor] status 접근 제어자 타입 수정 (#103)
hd0rable Aug 4, 2025
fc288ef
[refactor] 안쓰는 도메인 함수 삭제 (#103)
hd0rable Aug 4, 2025
0305e48
[refactor] postId반환하도록 수정 (#103)
hd0rable Aug 4, 2025
07e1dae
[feat] getId() 추가 (#103)
hd0rable Aug 4, 2025
d2412da
[feat] CommentDeleteResponse dto 작성(#103)
hd0rable Aug 4, 2025
58cc5d5
[feat] CommentCommandPort.delete 작성 (#103)
hd0rable Aug 4, 2025
116ec80
[feat] CommentCommandPersistenceAdapter.delete 구현 (#103)
hd0rable Aug 4, 2025
318a5e7
[refactor] postId반환하도록 수정 (#103)
hd0rable Aug 4, 2025
ae2074f
[feat] Comment @SQLDelete 추가 (#103)
hd0rable Aug 4, 2025
84320d6
[feat] CommentLikeJpaEntity에 cascade = CascadeType.ALL 속성 추가 (#103)
hd0rable Aug 4, 2025
cb298d1
[refactor] 네이티브 쿼리 jpql로 수정 (#103)
hd0rable Aug 4, 2025
eed90c7
[refactor] 네이티브 쿼리 jpql로 수정 (#103)
hd0rable Aug 4, 2025
2896725
[rename] 헬퍼서비스 이름 변경 및 어노테이션 변경 (#103)
hd0rable Aug 4, 2025
7fcdb6b
[refactor] 안쓰는 에러코드 수정 (#103)
hd0rable Aug 4, 2025
b6af8ae
[refactor] 안쓰는 에러코드 수정 (#103)
hd0rable Aug 4, 2025
2d8ed70
[test] 테스트 코드 수정 (#103)
hd0rable Aug 4, 2025
4f50afe
[refactor] 댓글 좋아요 삭제 먼저 후 댓글 삭제 (#103)
hd0rable Aug 4, 2025
a59f1d1
[test] 테스트 코드 수정 (#103)
hd0rable Aug 4, 2025
ad84e71
Merge remote-tracking branch 'origin/develop' into feat/#103-comment-…
hd0rable Aug 5, 2025
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 @@ -6,21 +6,19 @@
import jakarta.validation.Valid;
import konkuk.thip.comment.adapter.in.web.request.CommentCreateRequest;
import konkuk.thip.comment.adapter.in.web.request.CommentIsLikeRequest;
import konkuk.thip.comment.adapter.in.web.response.CommentDeleteResponse;
import konkuk.thip.comment.adapter.in.web.response.CommentIdResponse;
import konkuk.thip.comment.adapter.in.web.response.CommentIsLikeResponse;
import konkuk.thip.comment.application.port.in.CommentCreateUseCase;
import konkuk.thip.comment.application.port.in.CommentDeleteUseCase;
import konkuk.thip.comment.application.port.in.CommentLikeUseCase;
import konkuk.thip.common.dto.BaseResponse;
import konkuk.thip.common.security.annotation.UserId;
import konkuk.thip.common.swagger.annotation.ExceptionDescription;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import static konkuk.thip.common.swagger.SwaggerResponseDescription.CHANGE_COMMENT_LIKE_STATE;
import static konkuk.thip.common.swagger.SwaggerResponseDescription.COMMENT_CREATE;
import static konkuk.thip.common.swagger.SwaggerResponseDescription.*;

@Tag(name = "Comment Command API", description = "댓글 상태변경 관련 API")
@RestController
Expand All @@ -29,6 +27,7 @@ public class CommentCommandController {

private final CommentCreateUseCase commentCreateUseCase;
private final CommentLikeUseCase commentLikeUseCase;
private final CommentDeleteUseCase commentDeleteUseCase;

/**
* 댓글/답글 작성
Expand Down Expand Up @@ -63,4 +62,16 @@ public BaseResponse<CommentIsLikeResponse> likeComment(
return BaseResponse.ok(CommentIsLikeResponse.of(commentLikeUseCase.changeLikeStatusComment(request.toCommand(userId, commentId))));
}

@Operation(
summary = "댓글 삭제",
description = "사용자가 댓글을 삭제합니다."
)
@ExceptionDescription(COMMENT_DELETE)
@DeleteMapping("/comments/{commentId}")
public BaseResponse<CommentDeleteResponse> deleteComment(
@Parameter(description = "삭제하려는 댓글 ID", example = "1") @PathVariable("commentId") final Long commentId,
@Parameter(hidden = true) @UserId final Long userId) {
return BaseResponse.ok(CommentDeleteResponse.of(commentDeleteUseCase.deleteComment(commentId,userId)));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package konkuk.thip.comment.adapter.in.web.response;

public record CommentDeleteResponse(Long postId) {
public static CommentDeleteResponse of(Long postId) {
return new CommentDeleteResponse(postId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
import konkuk.thip.post.adapter.out.jpa.PostJpaEntity;
import konkuk.thip.user.adapter.out.jpa.UserJpaEntity;
import lombok.*;
import org.hibernate.annotations.SQLDelete;

@Entity
@Table(name = "comments")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@SQLDelete(sql = "UPDATE comments SET status = 'INACTIVE' WHERE comment_id = ?")
public class CommentJpaEntity extends BaseJpaEntity {

@Id
Expand Down Expand Up @@ -53,6 +55,7 @@ public class CommentJpaEntity extends BaseJpaEntity {
public CommentJpaEntity updateFrom(Comment comment) {
this.reportCount = comment.getReportCount();
this.likeCount = comment.getLikeCount();
this.status = comment.getStatus();
return this;
}
Comment thread
hd0rable marked this conversation as resolved.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.util.Optional;

import static konkuk.thip.common.entity.StatusType.ACTIVE;
import static konkuk.thip.common.exception.code.ErrorCode.*;

@Repository
Expand Down Expand Up @@ -67,18 +68,25 @@ private PostJpaEntity findPostJpaEntity(PostType postType, Long postId) {

@Override
public Optional<Comment> findById(Long id) {
return commentJpaRepository.findById(id)
return commentJpaRepository.findByCommentIdAndStatus(id, ACTIVE)
Comment thread
hd0rable marked this conversation as resolved.
.map(commentMapper::toDomainEntity);
}

@Override
public void update(Comment comment) {

CommentJpaEntity commentJpaEntity = commentJpaRepository.findById(comment.getId()).orElseThrow(
() -> new EntityNotFoundException(COMMENT_NOT_FOUND)
);

commentJpaRepository.save(commentJpaEntity.updateFrom(comment));
}

@Override
public void delete(Comment comment) {
CommentJpaEntity commentJpaEntity = commentJpaRepository.findById(comment.getId()).orElseThrow(
() -> new EntityNotFoundException(COMMENT_NOT_FOUND)
);
commentJpaRepository.delete(commentJpaEntity);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,10 @@ public void save(Long userId, Long commentId) {
public void delete(Long userId, Long commentId) {
commentLikeJpaRepository.deleteByUserIdAndCommentId(userId, commentId);
}

@Override
public void deleteAllByCommentId(Long commentId) {
commentLikeJpaRepository.deleteAllByCommentId(commentId);
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package konkuk.thip.comment.adapter.out.persistence.repository;

import konkuk.thip.comment.adapter.out.jpa.CommentJpaEntity;
import konkuk.thip.common.entity.StatusType;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface CommentJpaRepository extends JpaRepository<CommentJpaEntity, Long> {
int countByPostJpaEntity_PostId(Long postId);
Optional<CommentJpaEntity> findByCommentIdAndStatus(Long commentId, StatusType status);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,22 @@
import java.util.List;

public interface CommentLikeJpaRepository extends JpaRepository<CommentLikeJpaEntity, Long> {
@Query(value = "SELECT * FROM comment_likes WHERE user_id = :userId", nativeQuery = true)
List<CommentLikeJpaEntity> findAllByUserId(Long userId);

@Query("SELECT cl FROM CommentLikeJpaEntity cl WHERE cl.userJpaEntity.userId = :userId")
List<CommentLikeJpaEntity> findAllByUserId(@Param("userId") Long userId);


@Modifying
@Query(value = "DELETE FROM comment_likes WHERE user_id = :userId AND comment_id = :commentId", nativeQuery = true)
@Query("DELETE FROM CommentLikeJpaEntity cl WHERE cl.userJpaEntity.userId = :userId AND cl.commentJpaEntity.commentId = :commentId")
void deleteByUserIdAndCommentId(@Param("userId") Long userId, @Param("commentId") Long commentId);

@Query(value = "SELECT EXISTS(SELECT 1 FROM comment_likes WHERE user_id = :userId AND comment_id = :commentId)", nativeQuery = true)
@Query("SELECT CASE WHEN COUNT(cl) > 0 THEN true ELSE false END " +
"FROM CommentLikeJpaEntity cl " +
"WHERE cl.userJpaEntity.userId = :userId AND cl.commentJpaEntity.commentId = :commentId")
boolean existsByUserIdAndCommentId(@Param("userId") Long userId, @Param("commentId") Long commentId);

@Modifying
@Query("DELETE FROM CommentLikeJpaEntity cl WHERE cl.commentJpaEntity.commentId = :commentId")
void deleteAllByCommentId(@Param("commentId") Long commentId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package konkuk.thip.comment.application.port.in;


public interface CommentDeleteUseCase {
Long deleteComment(Long commentId, Long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ default Comment getByIdOrThrow(Long id) {

void update(Comment comment);

void delete(Comment comment);

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
public interface CommentLikeCommandPort {
void save(Long userId, Long commentId);
void delete(Long userId, Long commentId);
void deleteAllByCommentId(Long commentId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,8 @@
import konkuk.thip.comment.domain.Comment;
import konkuk.thip.common.exception.InvalidStateException;
import konkuk.thip.common.post.CommentCountUpdatable;
import konkuk.thip.common.post.service.PostQueryService;
import konkuk.thip.feed.application.port.out.FeedCommandPort;
import konkuk.thip.feed.domain.Feed;
import konkuk.thip.common.post.service.PostHandler;
import konkuk.thip.common.post.PostType;
import konkuk.thip.record.application.port.out.RecordCommandPort;
import konkuk.thip.record.domain.Record;
import konkuk.thip.vote.application.port.out.VoteCommandPort;
import konkuk.thip.vote.domain.Vote;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -27,11 +21,8 @@
public class CommentCreateService implements CommentCreateUseCase {

private final CommentCommandPort commentCommandPort;
private final FeedCommandPort feedCommandPort;
private final RecordCommandPort recordCommandPort;
private final VoteCommandPort voteCommandPort;

private final PostQueryService postQueryService;
private final PostHandler postHandler;
private final CommentAuthorizationValidator commentAuthorizationValidator;

@Override
Expand All @@ -43,7 +34,7 @@ public Long createComment(CommentCreateCommand command) {
PostType type = PostType.from(command.postType());

// 2. 게시물 타입에 맞게 조회
CommentCountUpdatable post = postQueryService.findPost(type, command.postId());
CommentCountUpdatable post = postHandler.findPost(type, command.postId());
// 2-1. 게시글 타입에 따른 댓글 생성 권한 검증
commentAuthorizationValidator.validateUserCanAccessPostForComment(type, post, command.userId());

Expand All @@ -53,11 +44,13 @@ public Long createComment(CommentCreateCommand command) {
// 3. 댓글 생성
Long commentId = createCommentDomain(command);

//TODO 게시물의 댓글 수 증가/감소 동시성 제어 로직 추가해야됨

// 4. 게시글 댓글 수 증가
// 4-1. 도메인 게시물 댓글 수 증가
post.increaseCommentCount();
// 4-2 Jpa엔티티 게시물 댓글 수 증가
updatePost(type, post);
postHandler.updatePost(type, post);

return commentId;
}
Expand Down Expand Up @@ -85,12 +78,4 @@ private Long createCommentDomain(CommentCreateCommand command) {
return commentCommandPort.save(comment);
}

private void updatePost(PostType type, CommentCountUpdatable post) {
switch (type) {
case FEED -> feedCommandPort.update((Feed) post);
case RECORD -> recordCommandPort.update((Record) post);
case VOTE -> voteCommandPort.updateVote((Vote) post);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package konkuk.thip.comment.application.service;

import jakarta.transaction.Transactional;
import konkuk.thip.comment.application.port.in.CommentDeleteUseCase;
import konkuk.thip.comment.application.port.out.CommentCommandPort;
import konkuk.thip.comment.application.port.out.CommentLikeCommandPort;
import konkuk.thip.comment.application.service.validator.CommentAuthorizationValidator;
import konkuk.thip.comment.domain.Comment;
import konkuk.thip.common.post.CommentCountUpdatable;
import konkuk.thip.common.post.service.PostHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class CommentDeleteService implements CommentDeleteUseCase {

private final CommentCommandPort commentCommandPort;
private final CommentLikeCommandPort commentLikeCommandPort;

private final PostHandler postHandler;
private final CommentAuthorizationValidator commentAuthorizationValidator;

@Override
@Transactional
public Long deleteComment(Long commentId, Long userId) {

// 1. 댓글 조회 및 권한 검증
Comment comment = commentCommandPort.getByIdOrThrow(commentId);
// 1-1. 게시글 타입에 따른 댓글 삭제 권한 검증
CommentCountUpdatable post = postHandler.findPost(comment.getPostType(), comment.getTargetPostId());
commentAuthorizationValidator.validateUserCanAccessPostForComment(comment.getPostType(), post, userId);

// 2. 댓글 삭제 권한 검증 및 소프트 딜리트
comment.validateDeletable(userId);
// 2-1. 댓글 좋아요 삭제
commentLikeCommandPort.deleteAllByCommentId(commentId);

// 3. 댓글 삭제
commentCommandPort.delete(comment);

//TODO 게시물의 댓글 수 증가/감소 동시성 제어 로직 추가해야됨
Comment thread
hd0rable marked this conversation as resolved.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

이거까지 고려해도 좋지요~~


// 4. 게시글 댓글 수 감소
// 4-1. 도메인 게시물 댓글 수 감소
post.decreaseCommentCount();
// 4-2 Jpa엔티티 게시물 댓글 수 감소
postHandler.updatePost(comment.getPostType(), post);

return post.getId();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import konkuk.thip.comment.application.service.validator.CommentAuthorizationValidator;
import konkuk.thip.comment.domain.Comment;
import konkuk.thip.common.post.CommentCountUpdatable;
import konkuk.thip.common.post.service.PostQueryService;
import konkuk.thip.common.post.service.PostHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

Expand All @@ -22,7 +22,7 @@ public class CommentLikeService implements CommentLikeUseCase {
private final CommentLikeQueryPort commentLikeQueryPort;
private final CommentLikeCommandPort commentLikeCommandPort;

private final PostQueryService postQueryService;
private final PostHandler postHandler;
private final CommentAuthorizationValidator commentAuthorizationValidator;

@Override
Expand All @@ -32,7 +32,7 @@ public CommentIsLikeResult changeLikeStatusComment(CommentIsLikeCommand command)
// 1. 댓글 조회 및 검증 (존재 여부)
Comment comment = commentCommandPort.getByIdOrThrow(command.commentId());
// 1-1. 게시글 타입에 따른 댓글 좋아요 권한 검증
CommentCountUpdatable post = postQueryService.findPost(comment.getPostType(), comment.getTargetPostId());
CommentCountUpdatable post = postHandler.findPost(comment.getPostType(), comment.getTargetPostId());
commentAuthorizationValidator.validateUserCanAccessPostForComment(comment.getPostType(), post, command.userId());

// 2. 유저가 해당 댓글에 대해 좋아요 했는지 조회
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/konkuk/thip/comment/domain/Comment.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,15 @@ public void validateCanUnlike(boolean alreadyLiked) {
throw new InvalidStateException(COMMENT_NOT_LIKED_CANNOT_CANCEL);
}
}

private boolean validateCreator(Long userId) {
return this.creatorId.equals(userId);
}

public void validateDeletable(Long userId) {
if (!validateCreator(userId)) {
throw new InvalidStateException(COMMENT_DELETE_FORBIDDEN);
}
}

}
2 changes: 1 addition & 1 deletion src/main/java/konkuk/thip/common/entity/BaseJpaEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ public abstract class BaseJpaEntity {

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private StatusType status = StatusType.ACTIVE;
protected StatusType status = StatusType.ACTIVE;
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ public enum ErrorCode implements ResponseCode {
COMMENT_LIKE_COUNT_UNDERFLOW(HttpStatus.BAD_REQUEST, 190002, "좋아요 수는 0 이하로 감소할 수 없습니다."),
COMMENT_ALREADY_LIKED(HttpStatus.BAD_REQUEST, 190003, "사용자가 이미 좋아요한 댓글입니다."),
COMMENT_NOT_LIKED_CANNOT_CANCEL(HttpStatus.BAD_REQUEST, 190004, "사용자가 좋아요하지 않은 댓글은 좋아요 취소 할 수 없습니다."),
COMMENT_DELETE_FORBIDDEN(HttpStatus.FORBIDDEN, 190005, "댓글 삭제 권한이 없습니다."),
COMMENT_COUNT_UNDERFLOW(HttpStatus.BAD_REQUEST, 190007, "댓글 수는 0 이하로 감소할 수 없습니다."),

;

private final HttpStatus httpStatus;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@

public interface CommentCountUpdatable {
void increaseCommentCount();
void decreaseCommentCount();
Long getId();
}
Loading