diff --git a/src/main/java/com/codeit/todo/common/exception/comment/CommentNotFoundException.java b/src/main/java/com/codeit/todo/common/exception/comment/CommentNotFoundException.java new file mode 100644 index 0000000..ebea797 --- /dev/null +++ b/src/main/java/com/codeit/todo/common/exception/comment/CommentNotFoundException.java @@ -0,0 +1,14 @@ +package com.codeit.todo.common.exception.comment; + +import com.codeit.todo.common.exception.EntityNotFoundException; + +public class CommentNotFoundException extends EntityNotFoundException { + private static final String ENTITY_TYPE = "comment"; + + /** + * @param request 엔티티를 찾기 위해 요청한 값 + */ + public CommentNotFoundException(String request) { + super(request, ENTITY_TYPE); + } +} diff --git a/src/main/java/com/codeit/todo/domain/Comment.java b/src/main/java/com/codeit/todo/domain/Comment.java index 2eaa922..e9a9e63 100644 --- a/src/main/java/com/codeit/todo/domain/Comment.java +++ b/src/main/java/com/codeit/todo/domain/Comment.java @@ -2,6 +2,7 @@ import jakarta.persistence.*; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -28,4 +29,17 @@ public class Comment { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "complete_id", nullable = false) private Complete complete; + + @Builder + public Comment(int commentId, String content, LocalDateTime createdAt, User user, Complete complete) { + this.commentId = commentId; + this.content = content; + this.createdAt = createdAt; + this.user = user; + this.complete = complete; + } + + public void update(String content){ + this.content = content; + } } diff --git a/src/main/java/com/codeit/todo/repository/CommentRepository.java b/src/main/java/com/codeit/todo/repository/CommentRepository.java new file mode 100644 index 0000000..84749d3 --- /dev/null +++ b/src/main/java/com/codeit/todo/repository/CommentRepository.java @@ -0,0 +1,9 @@ +package com.codeit.todo.repository; + + +import com.codeit.todo.domain.Comment; +import org.springframework.data.jpa.repository.JpaRepository; +public interface CommentRepository extends JpaRepository { + +} + diff --git a/src/main/java/com/codeit/todo/service/comment/CommentService.java b/src/main/java/com/codeit/todo/service/comment/CommentService.java new file mode 100644 index 0000000..120caeb --- /dev/null +++ b/src/main/java/com/codeit/todo/service/comment/CommentService.java @@ -0,0 +1,15 @@ +package com.codeit.todo.service.comment; + +import com.codeit.todo.web.dto.request.comment.CreateCommentRequest; +import com.codeit.todo.web.dto.request.comment.UpdateCommentRequest; +import com.codeit.todo.web.dto.response.comment.CreateCommentResponse; +import com.codeit.todo.web.dto.response.comment.DeleteCommentResponse; +import com.codeit.todo.web.dto.response.comment.UpdateCommentResponse; + +public interface CommentService { + CreateCommentResponse saveComment(int userId, CreateCommentRequest request); + + UpdateCommentResponse updateComment(int userId, int commentId, UpdateCommentRequest request); + + DeleteCommentResponse deleteComment(int userId, int commentId); +} diff --git a/src/main/java/com/codeit/todo/service/comment/impl/CommentServiceImpl.java b/src/main/java/com/codeit/todo/service/comment/impl/CommentServiceImpl.java new file mode 100644 index 0000000..2f12f02 --- /dev/null +++ b/src/main/java/com/codeit/todo/service/comment/impl/CommentServiceImpl.java @@ -0,0 +1,74 @@ +package com.codeit.todo.service.comment.impl; + +import com.codeit.todo.common.exception.auth.AuthorizationDeniedException; +import com.codeit.todo.common.exception.comment.CommentNotFoundException; +import com.codeit.todo.common.exception.complete.CompleteNotFoundException; +import com.codeit.todo.domain.Comment; +import com.codeit.todo.domain.Complete; +import com.codeit.todo.domain.User; +import com.codeit.todo.repository.CommentRepository; +import com.codeit.todo.repository.CompleteRepository; +import com.codeit.todo.service.comment.CommentService; +import com.codeit.todo.service.user.UserService; +import com.codeit.todo.service.user.impl.UserServiceImpl; +import com.codeit.todo.web.dto.request.comment.CreateCommentRequest; +import com.codeit.todo.web.dto.request.comment.UpdateCommentRequest; +import com.codeit.todo.web.dto.response.comment.CreateCommentResponse; +import com.codeit.todo.web.dto.response.comment.DeleteCommentResponse; +import com.codeit.todo.web.dto.response.comment.UpdateCommentResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CommentServiceImpl implements CommentService { + + private final UserServiceImpl userServiceImpl; + private final CompleteRepository completeRepository; + private final CommentRepository commentRepository; + + @Transactional + @Override + public CreateCommentResponse saveComment(int userId, CreateCommentRequest request) { + + User user = userServiceImpl.getUser(userId); + Complete complete = completeRepository.findById(request.completeId()) + .orElseThrow(()-> new CompleteNotFoundException(String.valueOf(request.completeId()))); + + Comment comment = request.toEntity(request.content(), user, complete); + Comment savedComment = commentRepository.save(comment); + return CreateCommentResponse.fromEntity(savedComment); + } + + @Transactional + @Override + public UpdateCommentResponse updateComment(int userId, int commentId, UpdateCommentRequest request) { + Comment comment = getComment(userId, commentId); + comment.update(request.content()); + + return UpdateCommentResponse.fromEntity(comment); + } + + @Transactional + @Override + public DeleteCommentResponse deleteComment(int userId, int commentId) { + Comment comment = getComment(userId, commentId); + commentRepository.delete(comment); + return DeleteCommentResponse.from(commentId); + } + + private Comment getComment(int userId, int commentId){ + Comment comment = commentRepository.findById(commentId) + .orElseThrow(()-> new CommentNotFoundException(String.valueOf(commentId))); + + if(comment.getUser().getUserId() != userId){ + log.error("댓글 작업 거부됨. 요청 유저 ID : {}", userId); + throw new AuthorizationDeniedException("댓글 작업에 대한 권한이 없습니다."); + } + + return comment; + } +} diff --git a/src/main/java/com/codeit/todo/service/user/impl/UserServiceImpl.java b/src/main/java/com/codeit/todo/service/user/impl/UserServiceImpl.java index c8200d9..b7d0cbe 100644 --- a/src/main/java/com/codeit/todo/service/user/impl/UserServiceImpl.java +++ b/src/main/java/com/codeit/todo/service/user/impl/UserServiceImpl.java @@ -162,7 +162,7 @@ public ReadMyPageResponse findUserInfoAndFollows(int userId) { return ReadMyPageResponse.from(followerCount, followeeCount); } - private User getUser(int userId){ + public User getUser(int userId){ User user = userRepository.findById(userId) .orElseThrow(()-> new UserNotFoundException(String.valueOf(userId), "User")); return user; diff --git a/src/main/java/com/codeit/todo/web/controller/CommentController.java b/src/main/java/com/codeit/todo/web/controller/CommentController.java new file mode 100644 index 0000000..c6f42f6 --- /dev/null +++ b/src/main/java/com/codeit/todo/web/controller/CommentController.java @@ -0,0 +1,65 @@ +package com.codeit.todo.web.controller; + +import com.codeit.todo.repository.CustomUserDetails; +import com.codeit.todo.service.comment.CommentService; +import com.codeit.todo.web.dto.request.comment.CreateCommentRequest; +import com.codeit.todo.web.dto.request.comment.UpdateCommentRequest; +import com.codeit.todo.web.dto.response.Response; +import com.codeit.todo.web.dto.response.comment.CreateCommentResponse; +import com.codeit.todo.web.dto.response.comment.DeleteCommentResponse; +import com.codeit.todo.web.dto.response.comment.UpdateCommentResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/comments") +public class CommentController { + + private final CommentService commentService; + + @Operation(summary = "댓글 생성", description = "새로운 댓글 생성 API") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "댓글 생성 성공") + }) + @PostMapping + public Response createComment( + @AuthenticationPrincipal CustomUserDetails customUserDetails, + @Valid @RequestBody CreateCommentRequest request + ){ + int userId = customUserDetails.getUserId(); + return Response.ok(commentService.saveComment(userId, request)); + } + + @Operation(summary = "댓글 수정", description = "댓글 내용 수정 API") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "수정 성공") + }) + @PutMapping("{commentId}") + public Response updateComment( + @PathVariable int commentId, + @AuthenticationPrincipal CustomUserDetails customUserDetails, + @Valid @RequestBody UpdateCommentRequest request + ){ + int userId = customUserDetails.getUserId(); + return Response.ok(commentService.updateComment(userId, commentId, request)); + } + + @Operation(summary = "댓글 삭제", description = "댓글 삭제 API") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "삭제 성공") + }) + @DeleteMapping("{commentId}") + public Response deleteComment( + @PathVariable int commentId, + @AuthenticationPrincipal CustomUserDetails customUserDetails + ){ + int userId = customUserDetails.getUserId(); + return Response.ok(commentService.deleteComment(userId, commentId)); + } +} diff --git a/src/main/java/com/codeit/todo/web/dto/request/comment/CreateCommentRequest.java b/src/main/java/com/codeit/todo/web/dto/request/comment/CreateCommentRequest.java new file mode 100644 index 0000000..4827095 --- /dev/null +++ b/src/main/java/com/codeit/todo/web/dto/request/comment/CreateCommentRequest.java @@ -0,0 +1,22 @@ +package com.codeit.todo.web.dto.request.comment; + +import com.codeit.todo.domain.*; +import jakarta.validation.constraints.NotNull; + + +import java.time.LocalDateTime; + +public record CreateCommentRequest( + int completeId, + @NotNull + String content +) { + public Comment toEntity(String content, User user, Complete complete) { + return Comment.builder() + .content(content) + .createdAt(LocalDateTime.now()) + .user(user) + .complete(complete) + .build(); + } +} diff --git a/src/main/java/com/codeit/todo/web/dto/request/comment/UpdateCommentRequest.java b/src/main/java/com/codeit/todo/web/dto/request/comment/UpdateCommentRequest.java new file mode 100644 index 0000000..903457d --- /dev/null +++ b/src/main/java/com/codeit/todo/web/dto/request/comment/UpdateCommentRequest.java @@ -0,0 +1,10 @@ +package com.codeit.todo.web.dto.request.comment; + +import com.codeit.todo.domain.Comment; +import com.codeit.todo.domain.Complete; +import com.codeit.todo.domain.User; +import jakarta.validation.constraints.NotNull; + +import java.time.LocalDateTime; + +public record UpdateCommentRequest( @NotNull String content) {} diff --git a/src/main/java/com/codeit/todo/web/dto/response/comment/CreateCommentResponse.java b/src/main/java/com/codeit/todo/web/dto/response/comment/CreateCommentResponse.java new file mode 100644 index 0000000..c7b24a4 --- /dev/null +++ b/src/main/java/com/codeit/todo/web/dto/response/comment/CreateCommentResponse.java @@ -0,0 +1,16 @@ +package com.codeit.todo.web.dto.response.comment; + +import com.codeit.todo.domain.Comment; +import com.codeit.todo.domain.Goal; +import lombok.Builder; + +@Builder +public record CreateCommentResponse(int commentId ) { + + public static CreateCommentResponse fromEntity(Comment savedComment) { + return CreateCommentResponse.builder() + .commentId(savedComment.getCommentId()) + .build(); + } + +} diff --git a/src/main/java/com/codeit/todo/web/dto/response/comment/DeleteCommentResponse.java b/src/main/java/com/codeit/todo/web/dto/response/comment/DeleteCommentResponse.java new file mode 100644 index 0000000..12dae5f --- /dev/null +++ b/src/main/java/com/codeit/todo/web/dto/response/comment/DeleteCommentResponse.java @@ -0,0 +1,15 @@ +package com.codeit.todo.web.dto.response.comment; + +import com.codeit.todo.domain.Comment; +import lombok.Builder; + +@Builder +public record DeleteCommentResponse(int commentId) { + + public static DeleteCommentResponse from(int commentId) { + return DeleteCommentResponse.builder() + .commentId(commentId) + .build(); + } + +} diff --git a/src/main/java/com/codeit/todo/web/dto/response/comment/UpdateCommentResponse.java b/src/main/java/com/codeit/todo/web/dto/response/comment/UpdateCommentResponse.java new file mode 100644 index 0000000..4617bfd --- /dev/null +++ b/src/main/java/com/codeit/todo/web/dto/response/comment/UpdateCommentResponse.java @@ -0,0 +1,15 @@ +package com.codeit.todo.web.dto.response.comment; + +import com.codeit.todo.domain.Comment; +import lombok.Builder; + +@Builder +public record UpdateCommentResponse(int commentId) { + + public static UpdateCommentResponse fromEntity(Comment comment) { + return UpdateCommentResponse.builder() + .commentId(comment.getCommentId()) + .build(); + } + +}