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
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.back.domain.board.controller;

import com.back.domain.board.dto.CommentRequest;
import com.back.domain.board.dto.CommentResponse;
import com.back.domain.board.service.CommentService;
import com.back.global.common.dto.RsData;
import com.back.global.security.user.CustomUserDetails;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/posts/{postId}/comments")
@RequiredArgsConstructor
public class CommentController implements CommentControllerDocs {
private final CommentService commentService;

// 댓글 생성
@PostMapping
public ResponseEntity<RsData<CommentResponse>> createComment(
@PathVariable Long postId,
@RequestBody @Valid CommentRequest request,
@AuthenticationPrincipal CustomUserDetails user
) {
CommentResponse response = commentService.createComment(postId, request, user.getUserId());
return ResponseEntity
.status(HttpStatus.CREATED)
.body(RsData.success(
"댓글이 생성되었습니다.",
response
));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package com.back.domain.board.controller;

import com.back.domain.board.dto.CommentRequest;
import com.back.domain.board.dto.CommentResponse;
import com.back.global.common.dto.RsData;
import com.back.global.security.user.CustomUserDetails;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

@Tag(name = "Comment API", description = "댓글 관련 API")
public interface CommentControllerDocs {

@Operation(
summary = "댓글 생성",
description = "로그인한 사용자가 특정 게시글에 댓글을 작성합니다."
)
@ApiResponses({
@ApiResponse(
responseCode = "201",
description = "댓글 생성 성공",
content = @Content(
mediaType = "application/json",
examples = @ExampleObject(value = """
{
"success": true,
"code": "SUCCESS_200",
"message": "댓글이 생성되었습니다.",
"data": {
"commentId": 25,
"postId": 101,
"author": {
"id": 5,
"nickname": "홍길동"
},
"content": "좋은 글 감사합니다!",
"createdAt": "2025-09-22T11:30:00",
"updatedAt": "2025-09-22T11:30:00"
}
}
""")
)
),
@ApiResponse(
responseCode = "400",
description = "잘못된 요청 (필드 누락 등)",
content = @Content(
mediaType = "application/json",
examples = @ExampleObject(value = """
{
"success": false,
"code": "COMMON_400",
"message": "잘못된 요청입니다.",
"data": null
}
""")
)
),
@ApiResponse(
responseCode = "401",
description = "인증 실패 (토큰 없음/잘못됨/만료)",
content = @Content(
mediaType = "application/json",
examples = {
@ExampleObject(name = "토큰 없음", value = """
{
"success": false,
"code": "AUTH_001",
"message": "인증이 필요합니다.",
"data": null
}
"""),
@ExampleObject(name = "잘못된 토큰", value = """
{
"success": false,
"code": "AUTH_002",
"message": "유효하지 않은 액세스 토큰입니다.",
"data": null
}
"""),
@ExampleObject(name = "만료된 토큰", value = """
{
"success": false,
"code": "AUTH_004",
"message": "만료된 액세스 토큰입니다.",
"data": null
}
""")
}
)
),
@ApiResponse(
responseCode = "404",
description = "존재하지 않는 사용자 또는 게시글",
content = @Content(
mediaType = "application/json",
examples = {
@ExampleObject(name = "존재하지 않는 사용자", value = """
{
"success": false,
"code": "USER_001",
"message": "존재하지 않는 사용자입니다.",
"data": null
}
"""),
@ExampleObject(name = "존재하지 않는 게시글", value = """
{
"success": false,
"code": "POST_001",
"message": "존재하지 않는 게시글입니다.",
"data": null
}
""")
}
)
),
@ApiResponse(
responseCode = "500",
description = "서버 내부 오류",
content = @Content(
mediaType = "application/json",
examples = @ExampleObject(value = """
{
"success": false,
"code": "COMMON_500",
"message": "서버 오류가 발생했습니다.",
"data": null
}
""")
)
)
})
ResponseEntity<RsData<CommentResponse>> createComment(
@PathVariable Long postId,
@RequestBody CommentRequest request,
@AuthenticationPrincipal CustomUserDetails user
);
}
13 changes: 13 additions & 0 deletions src/main/java/com/back/domain/board/dto/CommentRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.back.domain.board.dto;

import jakarta.validation.constraints.NotBlank;

/**
* 댓글 작성 및 수정을 위한 요청 DTO
*
* @param content 댓글 내용
*/
public record CommentRequest(
@NotBlank String content
) {
}
35 changes: 35 additions & 0 deletions src/main/java/com/back/domain/board/dto/CommentResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.back.domain.board.dto;

import com.back.domain.board.entity.Comment;

import java.time.LocalDateTime;

/**
* 댓글 응답 DTO
*
* @param commentId 댓글 Id
* @param postId 게시글 Id
* @param author 작성자 정보
* @param content 댓글 내용
* @param createdAt 댓글 생성 일시
* @param updatedAt 댓글 수정 일시
*/
public record CommentResponse(
Long commentId,
Long postId,
AuthorResponse author,
String content,
LocalDateTime createdAt,
LocalDateTime updatedAt
) {
public static CommentResponse from(Comment comment) {
return new CommentResponse(
comment.getId(),
comment.getPost().getId(),
AuthorResponse.from(comment.getUser()),
comment.getContent(),
comment.getCreatedAt(),
comment.getUpdatedAt()
);
}
}
7 changes: 7 additions & 0 deletions src/main/java/com/back/domain/board/entity/Comment.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,11 @@ public class Comment extends BaseEntity {

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

// -------------------- 생성자 --------------------
public Comment(Post post, User user, String content) {
this.post = post;
this.user = user;
this.content = content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.back.domain.board.repository;

import com.back.domain.board.entity.Comment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CommentRepository extends JpaRepository<Comment, Long> {
}
48 changes: 48 additions & 0 deletions src/main/java/com/back/domain/board/service/CommentService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.back.domain.board.service;

import com.back.domain.board.dto.CommentRequest;
import com.back.domain.board.dto.CommentResponse;
import com.back.domain.board.entity.Comment;
import com.back.domain.board.entity.Post;
import com.back.domain.board.repository.CommentRepository;
import com.back.domain.board.repository.PostRepository;
import com.back.domain.user.entity.User;
import com.back.domain.user.repository.UserRepository;
import com.back.global.exception.CustomException;
import com.back.global.exception.ErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional
public class CommentService {
private final CommentRepository commentRepository;
private final UserRepository userRepository;
private final PostRepository postRepository;

/**
* 댓글 생성 서비스
* 1. User 조회
* 2. Post 조회
* 3. Comment 생성
* 4. Comment 저장 및 CommentResponse 반환
*/
public CommentResponse createComment(Long postId, CommentRequest request, Long userId) {
// User 조회
User user = userRepository.findById(userId)
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));

// Post 조회
Post post = postRepository.findById(postId)
.orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND));

// Comment 생성
Comment comment = new Comment(post, user, request.content());

// Comment 저장 및 응답 반환
commentRepository.save(comment);
return CommentResponse.from(comment);
}
}
Loading