댓글 시스템 (Comment System) - fitpassTeam/fitpass GitHub Wiki

댓글 시스템 (Comment System)

개요

FitPass의 댓글 시스템은 게시물에 대한 실시간 댓글 작성과 대댓글(답글) 기능을 제공하며, 계층형 구조로 효율적인 댓글 관리를 지원합니다.

주요 기능

  • 계층형 댓글: 부모 댓글과 자식 댓글(대댓글) 구조로 체계적 관리
  • 실시간 작성: 게시물별 댓글 즉시 작성 및 조회
  • 권한 관리: 댓글 작성자 및 게시글 작성자 권한 기반 수정/삭제
  • 유연한 구조: 1000자 제한 내에서 자유로운 댓글 작성
  • 연관 관계: 게시물과 사용자 간의 안전한 연관 관계 관리

댓글 엔티티 구조

필드 타입 제약조건 설명
id Long PK, AUTO_INCREMENT 댓글 고유 ID
content String NOT NULL, MAX 1000자 댓글 내용
post_id Long FK, NOT NULL 게시물 ID
user_id Long FK, NOT NULL 작성자 ID
parent_id Long FK, NULLABLE 부모 댓글 ID (대댓글인 경우)

댓글 플로우

사용자 댓글 작성 → 게시물 연관 → 댓글 저장
↓
부모 댓글 설정 (대댓글인 경우)
↓
계층형 구조로 조회 및 표시
↓
작성자/게시글 작성자만 수정/삭제 가능

댓글 조회 로직

계층형 댓글 조회

사용자 요청 → 게시물 ID로 부모 댓글 조회 → 자식 댓글 매핑 → 계층형 응답 반환

부모 댓글 필터링

private List<Comment> getParentComments(Long postId) {
    return commentRepository.findByPostIdAndParentIsNull(postId);
}
  • 해당 게시물의 부모 댓글(parent_id가 null)만 조회
  • 자식 댓글들은 엔티티 매핑을 통해 자동 로딩
  • 최종 계층형 구조로 응답 반환

댓글 작성 프로세스

엔티티 조회 → 부모 댓글 확인 → 댓글 생성 → 저장

@Transactional
public void createComment(Long postId, Long userId, String content, Long parentId) {
    // 1. 필수 엔티티 조회 및 검증
    Post post = postRepository.findByIdOrElseThrow(postId);
    User user = userRepository.findByIdOrElseThrow(userId);
    
    // 2. 부모 댓글 확인 (대댓글인 경우)
    Comment parent = null;
    if (parentId != null) {
        parent = commentRepository.findByIdOrElseThrow(parentId);
    }
    
    // 3. 댓글 생성 및 저장
    Comment comment = Comment.of(post, user, content, parent);
    commentRepository.save(comment);
}

댓글 생성 검증

  • 게시물 존재: 존재하는 게시물에만 댓글 작성 가능
  • 사용자 인증: 로그인한 사용자만 댓글 작성 가능
  • 부모 댓글: 대댓글인 경우 부모 댓글 존재 확인
  • 내용 검증: 1000자 이내의 유효한 내용 필수

댓글 수정

수정 권한 검증

  • 작성자 확인: 댓글 작성자 본인만 수정 가능
  • 게시물 존재: 댓글이 속한 게시물 존재 확인
  • 댓글 존재: 수정할 댓글 존재 확인
@Transactional
public void updateComment(Long commentId, String content, Long userId, Long postId) {
    Post post = postRepository.findByIdOrElseThrow(postId);
    Comment comment = commentRepository.findByIdOrElseThrow(commentId);
    User user = userRepository.findByIdOrElseThrow(userId);
    
    // 권한 검증: 작성자 본인만 수정 가능
    if(!comment.getUser().equals(user)){
        throw new BaseException(ExceptionCode.NOT_GYM_OWNER);
    }
    
    comment.update(content);
}

댓글 삭제

삭제 권한

  • 댓글 작성자: 본인이 작성한 댓글 삭제 가능
  • 게시글 작성자: 본인 게시글의 모든 댓글 삭제 가능

삭제 방식

  • 계층형 삭제: 부모 댓글 삭제 시 자식 댓글들 함께 삭제 (CASCADE)
  • 즉시 삭제: 별도의 상태 변경 없이 즉시 물리적 삭제
public void deleteComment(Long commentId, Long userId, Long postId) {
    Post post = postRepository.findByIdOrElseThrow(postId);
    Comment comment = commentRepository.findByIdOrElseThrow(commentId);
    User user = userRepository.findByIdOrElseThrow(userId);
    
    // 권한 검증: 댓글 작성자 또는 게시글 작성자
    if (!comment.getUser().getId().equals(userId) && 
        !comment.getPost().getUser().getId().equals(userId)) {
        throw new BaseException(ExceptionCode.NOT_HAS_AUTHORITY);
    }
    
    commentRepository.delete(comment);
}

API 엔드포인트

댓글 CRUD 작업

Method Endpoint 설명 권한
POST /posts/{postId}/comments 댓글 작성 USER
GET /posts/{postId}/comments 댓글 목록 조회 ALL
PATCH /posts/{postId}/comments/{commentId} 댓글 수정 USER (작성자)
DELETE /posts/{postId}/comments/{commentId} 댓글 삭제 USER (작성자, 게시글 작성자)

요청/응답 예시

댓글 작성 요청

{
    "content": "좋은 글 감사합니다!",
    "parentId": null
}

대댓글 작성 요청

{
    "content": "저도 동감입니다.",
    "parentId": 123
}

댓글 조회 응답

{
    "statusCode": 200,
    "message": "댓글 조회가 성공적으로 완료되었습니다.",
    "data": [
        {
            "id": 101,
            "content": "좋은 글 감사합니다!",
            "name": "홍길동",
            "writerId": 12,
            "postOwnerId": 10,
            "children": [
                {
                    "id": 102,
                    "content": "저도 동감입니다.",
                    "name": "김철수",
                    "writerId": 15,
                    "postOwnerId": 10,
                    "children": []
                }
            ]
        }
    ]
}

보안 및 검증

권한 검증

  • 작성자 확인: 모든 CUD 작업에서 댓글 소유자 검증
  • 게시글 작성자: 삭제 시 게시글 작성자 추가 권한 부여
  • Cross-cutting 보안: 다른 사용자 댓글 접근 차단

데이터 무결성

  • 외래키 제약: post_id, user_id, parent_id 참조 무결성
  • 계층 구조: 부모-자식 관계의 일관성 보장
  • 내용 검증: 1000자 이내 댓글 내용 제한

예외 처리

  • 엔티티 부재: 게시물, 사용자, 댓글 미존재 시 예외 발생
  • 권한 부족: 수정/삭제 권한 없을 시 예외 발생
  • 잘못된 요청: 유효하지 않은 데이터 요청 시 예외 발생

데이터베이스 설계

ERD 관계

User (1) ─── (N) Comment (N) ─── (1) Post
               │
               └── parent_id (self-reference)

인덱스 최적화

  • 복합 인덱스: (post_id, parent_id) - 부모 댓글 조회 최적화
  • 단일 인덱스: user_id - 사용자별 댓글 조회 최적화
  • 계층 쿼리: parent IS NULL 조건 최적화
⚠️ **GitHub.com Fallback** ⚠️