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
12 changes: 12 additions & 0 deletions documents/Undabang SQL/Undabang Database DDL.sql
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ DROP TABLE IF EXISTS notification_types;
DROP TABLE IF EXISTS fcm_tokens;

DROP TABLE IF EXISTS comment_likes;
DROP TABLE IF EXISTS comment_tags;
DROP TABLE IF EXISTS comments;
DROP TABLE IF EXISTS feed_likes;
DROP TABLE IF EXISTS feed_pictures;
Expand Down Expand Up @@ -660,6 +661,17 @@ CREATE TABLE comments
CONSTRAINT FK_comments_feed FOREIGN KEY (feed_id) REFERENCES feeds (feed_id)
);

CREATE TABLE comment_tags
(
comment_tag_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '댓글 태그 식별자',
comment_id BIGINT NOT NULL,
tagged_member_id CHAR(36) NOT NULL COMMENT '태그된 회원 UUID',
comment_tag_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT FK_comment_tags_comment FOREIGN KEY (comment_id) REFERENCES comments (comment_id),
CONSTRAINT FK_comment_tags_member FOREIGN KEY (tagged_member_id) REFERENCES members (member_id)
);

CREATE TABLE comment_likes
(
comment_like_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '피드 댓글 좋아요 식별자',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.project200.undabang.comment.dto.record;

import java.util.UUID;

public record TaggedMemberRecord(
UUID memberId,
String memberNickname) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import jakarta.validation.constraints.NotBlank;

import java.util.UUID;

public record CreateCommentRequest(
@NotBlank(message = "댓글 내용은 필수입니다.") String content,
Long parentCommentId) {
Long parentCommentId,
UUID taggedMemberId) {
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.project200.undabang.comment.dto.response;

import com.project200.undabang.comment.dto.record.TaggedMemberRecord;

import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
Expand All @@ -14,5 +16,6 @@ public record CommentResponse(
Integer likesCount,
Boolean commentIsLiked,
LocalDateTime createdAt,
TaggedMemberRecord taggedMember,
List<CommentResponse> children) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.project200.undabang.comment.entity;

import com.project200.undabang.member.entity.Member;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.ColumnDefault;

import java.time.LocalDateTime;

@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Entity
@Table(name = "comment_tags")
public class CommentTag {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "comment_tag_id", nullable = false, updatable = false)
private Long id;

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

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "tagged_member_id", nullable = false, updatable = false)
private Member taggedMember;

@Builder.Default
@ColumnDefault("CURRENT_TIMESTAMP")
@Column(name = "comment_tag_created_at", nullable = false, updatable = false)
private LocalDateTime createdAt = LocalDateTime.now();

public static CommentTag of(Comment comment, Member taggedMember) {
return CommentTag.builder()
.comment(comment)
.taggedMember(taggedMember)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.project200.undabang.comment.repository;

import com.project200.undabang.comment.entity.CommentTag;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CommentTagRepository extends JpaRepository<CommentTag, Long> {
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.project200.undabang.comment.repository.impl;

import com.project200.undabang.comment.dto.record.TaggedMemberRecord;
import com.project200.undabang.comment.dto.response.CommentResponse;
import com.project200.undabang.comment.entity.QComment;
import com.project200.undabang.comment.entity.QCommentTag;
import com.project200.undabang.comment.repository.CommentRepositoryCustom;
import com.project200.undabang.common.entity.QPicture;
import com.project200.undabang.like.entity.QCommentLike;
Expand Down Expand Up @@ -35,6 +37,8 @@ public class CommentRepositoryImpl implements CommentRepositoryCustom {
private final QPicture picture = QPicture.picture;
private final QCommentLike commentLike = QCommentLike.commentLike;
private final QMemberBlock memberBlock = QMemberBlock.memberBlock;
private final QCommentTag commentTag = QCommentTag.commentTag;
private final QMember taggedMember = new QMember("taggedMember");

@Override
public List<CommentResponse> findCommentsWithChildrenByFeedId(Long feedId, Member currentMember) {
Expand All @@ -50,11 +54,16 @@ public List<CommentResponse> findCommentsWithChildrenByFeedId(Long feedId, Membe
comment.likesCount,
isCommentLikedExpression(currentMember),
comment.createdAt,
Projections.constructor(TaggedMemberRecord.class,
taggedMember.memberId,
taggedMember.memberNickname),
Expressions.constant(Collections.<CommentResponse>emptyList())))
.from(comment)
.leftJoin(comment.member, member)
.leftJoin(member.memberPicture, memberPicture)
.leftJoin(memberPicture.picture, picture)
.leftJoin(commentTag).on(commentTag.comment.eq(comment))
.leftJoin(commentTag.taggedMember, taggedMember)
.where(
comment.feed.id.eq(feedId),
comment.parent.isNull(),
Expand Down Expand Up @@ -84,11 +93,16 @@ public List<CommentResponse> findCommentsWithChildrenByFeedId(Long feedId, Membe
comment.likesCount,
isCommentLikedExpression(currentMember),
comment.createdAt,
Projections.constructor(TaggedMemberRecord.class,
taggedMember.memberId,
taggedMember.memberNickname),
Expressions.constant(Collections.<CommentResponse>emptyList())))
.from(comment)
.leftJoin(comment.member, member)
.leftJoin(member.memberPicture, memberPicture)
.leftJoin(memberPicture.picture, picture)
.leftJoin(commentTag).on(commentTag.comment.eq(comment))
.leftJoin(commentTag.taggedMember, taggedMember)
.where(
comment.parent.id.in(parentIds),
comment.deletedAt.isNull(),
Expand All @@ -111,6 +125,7 @@ public List<CommentResponse> findCommentsWithChildrenByFeedId(Long feedId, Membe
parent.likesCount(),
parent.commentIsLiked(),
parent.createdAt(),
parent.taggedMember(),
childrenMap.getOrDefault(parent.commentId(), new ArrayList<>())))
.collect(Collectors.toList());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import com.project200.undabang.comment.dto.request.CreateCommentRequest;
import com.project200.undabang.comment.dto.response.CreateCommentResponse;
import com.project200.undabang.comment.entity.Comment;
import com.project200.undabang.comment.entity.CommentTag;
import com.project200.undabang.comment.repository.CommentRepository;
import com.project200.undabang.comment.repository.CommentTagRepository;
import com.project200.undabang.comment.service.CommentCommandService;
import com.project200.undabang.common.context.UserContextHolder;
import com.project200.undabang.common.web.exception.CustomException;
Expand All @@ -24,6 +26,7 @@
public class CommentCommandServiceImpl implements CommentCommandService {

private final CommentRepository commentRepository;
private final CommentTagRepository commentTagRepository;
private final FeedRepository feedRepository;
private final MemberRepository memberRepository;

Expand All @@ -49,6 +52,14 @@ public CreateCommentResponse createComment(Long feedId, CreateCommentRequest req
Comment comment = Comment.create(member, feed, parentComment, request);
Comment savedComment = commentRepository.save(comment);

// 태그된 멤버가 있으면 CommentTag 생성
if (request.taggedMemberId() != null) {
Member taggedMember = memberRepository
.findByMemberIdAndMemberDeletedAtNull(request.taggedMemberId())
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND));
commentTagRepository.save(CommentTag.of(savedComment, taggedMember));
}

// 피드 댓글 수 증가
feed.incrementCommentsCount();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ public enum ErrorCode {
COMMENT_DELETE_FORBIDDEN(403, "COMMENT_DELETE_FORBIDDEN", "댓글 삭제 권한이 없습니다."),
COMMENT_PARENT_NOT_FOUND(404, "COMMENT_PARENT_NOT_FOUND", "부모 댓글을 찾을 수 없습니다."),

// 댓글 태그 관련 에러
COMMENT_TAG_NOT_ALLOWED(400, "COMMENT_TAG_NOT_ALLOWED", "대댓글에서만 태그 기능을 사용할 수 있습니다."),

// 피드 관련 에러
FEED_NOT_FOUND(404, "FEED_NOT_FOUND", "존재하지 않는 피드입니다."),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void createComment_success() throws Exception {
// given
UUID testMemberId = UUID.randomUUID();
Long feedId = 1L;
CreateCommentRequest request = new CreateCommentRequest("댓글 내용입니다.", null);
CreateCommentRequest request = new CreateCommentRequest("댓글 내용입니다.", null, null);
CreateCommentResponse responseDto = new CreateCommentResponse(1L);

BDDMockito
Expand All @@ -69,7 +69,8 @@ void createComment_success() throws Exception {
parameterWithName("feedId").attributes(getTypeFormat(JsonFieldType.NUMBER)).description("댓글을 작성할 피드의 고유 식별자(ID) 입니다.")),
requestFields(
fieldWithPath("content").type(JsonFieldType.STRING).description("작성할 댓글의 내용입니다."),
fieldWithPath("parentCommentId").type(JsonFieldType.NUMBER).description("해당 댓글이 대댓글인 경우, 부모 댓글의 고유 식별자(ID)입니다.").optional()),
fieldWithPath("parentCommentId").type(JsonFieldType.NUMBER).description("해당 댓글이 대댓글인 경우, 부모 댓글의 고유 식별자(ID)입니다.").optional(),
fieldWithPath("taggedMemberId").type(JsonFieldType.STRING).description("태그할 멤버의 고유 식별자(ID)입니다.").optional()),
responseFields(commonResponseFields(fieldWithPath("data.commentId").type(JsonFieldType.NUMBER).description("작성된 댓글의 고유 식별자(ID) 입니다.")))))
.andReturn().getResponse().getContentAsString();

Expand All @@ -86,7 +87,7 @@ void createReply_success() throws Exception {
UUID testMemberId = UUID.randomUUID();
Long feedId = 1L;
Long parentCommentId = 1L;
CreateCommentRequest request = new CreateCommentRequest("대댓글 내용입니다.", parentCommentId);
CreateCommentRequest request = new CreateCommentRequest("대댓글 내용입니다.", parentCommentId, null);
CreateCommentResponse responseDto = new CreateCommentResponse(2L);

BDDMockito
Expand Down Expand Up @@ -114,7 +115,7 @@ void createComment_Failed_FeedNotFound() throws Exception {
// given
UUID testMemberId = UUID.randomUUID();
Long feedId = 999L;
CreateCommentRequest request = new CreateCommentRequest("댓글 내용입니다.", null);
CreateCommentRequest request = new CreateCommentRequest("댓글 내용입니다.", null, null);

BDDMockito
.given(commentCommandService.createComment(BDDMockito.eq(feedId),
Expand All @@ -141,7 +142,7 @@ void createReply_Failed_ParentNotFound() throws Exception {
// given
UUID testMemberId = UUID.randomUUID();
Long feedId = 1L;
CreateCommentRequest request = new CreateCommentRequest("대댓글 내용입니다.", 999L);
CreateCommentRequest request = new CreateCommentRequest("대댓글 내용입니다.", 999L, null);

BDDMockito
.given(commentCommandService.createComment(BDDMockito.eq(feedId),
Expand All @@ -167,7 +168,7 @@ void createReply_Failed_ParentNotFound() throws Exception {
void createComment_Failed_Not_Having_Token() throws Exception {
// given
Long feedId = 1L;
CreateCommentRequest request = new CreateCommentRequest("댓글 내용입니다.", null);
CreateCommentRequest request = new CreateCommentRequest("댓글 내용입니다.", null, null);

// when & then
mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/feeds/{feedId}/comments", feedId)
Expand Down Expand Up @@ -201,7 +202,8 @@ void deleteComment_success() throws Exception {
.andDo(document.document(
requestHeaders(HEADER_ACCESS_TOKEN),
pathParameters(
parameterWithName("commentId").attributes(getTypeFormat(JsonFieldType.NUMBER))
parameterWithName("commentId").attributes(
getTypeFormat(JsonFieldType.NUMBER))
.description("삭제할 댓글 ID")),
responseFields(commonResponseFieldsOnly())));

Expand Down
Loading