Skip to content

Commit d6bddbc

Browse files
authored
Merge pull request #39 from rhkr8521/feat/#comment-crud
[FEAT] 댓글 CRUD 및 좋아요 기능 구현
2 parents 94a412f + 87fd5ca commit d6bddbc

File tree

12 files changed

+485
-3
lines changed

12 files changed

+485
-3
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package com.rhkr8521.mapping.api.comment.controller;
2+
3+
import com.rhkr8521.mapping.api.comment.dto.CommentCreateDTO;
4+
import com.rhkr8521.mapping.api.comment.dto.CommentResponseDTO;
5+
import com.rhkr8521.mapping.api.comment.dto.CommentUpdateDTO;
6+
import com.rhkr8521.mapping.api.comment.service.CommentService;
7+
import com.rhkr8521.mapping.api.member.service.MemberService;
8+
import com.rhkr8521.mapping.common.exception.NotFoundException;
9+
import io.swagger.v3.oas.annotations.Operation;
10+
import com.rhkr8521.mapping.common.response.ApiResponse;
11+
import com.rhkr8521.mapping.common.response.ErrorStatus;
12+
import com.rhkr8521.mapping.common.response.SuccessStatus;
13+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
14+
import io.swagger.v3.oas.annotations.tags.Tag;
15+
import lombok.RequiredArgsConstructor;
16+
import org.springframework.http.ResponseEntity;
17+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
18+
import org.springframework.security.core.userdetails.UserDetails;
19+
import org.springframework.web.bind.annotation.*;
20+
21+
import java.util.List;
22+
23+
@Tag(name = "Comment", description = "댓글 관련 API 입니다.")
24+
@RestController
25+
@RequestMapping("/api/v2/comment")
26+
@RequiredArgsConstructor
27+
public class CommentController {
28+
29+
private final CommentService commentService;
30+
private final MemberService memberService;
31+
32+
@Operation(
33+
summary = "댓글 작성 API",
34+
description = "메모에 댓글을 작성합니다."
35+
)
36+
@ApiResponses({
37+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "댓글 등록 성공"),
38+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "댓글이 입력되지 않았습니다. / 메모 ID가 입력되지 않았습니다."),
39+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "메모를 찾을 수 없습니다.")
40+
})
41+
@PostMapping("/new")
42+
public ResponseEntity<ApiResponse<Void>> createComment(
43+
@AuthenticationPrincipal UserDetails userDetails,
44+
CommentCreateDTO commentCreateDTO
45+
) {
46+
47+
// Comment 누락시 예외처리
48+
if (commentCreateDTO.getComment() == null || commentCreateDTO.getComment().isEmpty()) {
49+
throw new NotFoundException(ErrorStatus.MISSING_COMMENT.getMessage());
50+
}
51+
52+
// 게시글 ID 누락시 예외처리
53+
if (commentCreateDTO.getMemoId() == null) {
54+
throw new NotFoundException(ErrorStatus.MISSING_COMMENT_MEMOID.getMessage());
55+
}
56+
57+
Long userId = memberService.getUserIdByEmail(userDetails.getUsername());
58+
commentService.createComment(commentCreateDTO, userId);
59+
60+
return ApiResponse.success_only(SuccessStatus.CREATE_COMMENT_SUCCESS);
61+
}
62+
63+
@Operation(
64+
summary = "댓글 조회 API",
65+
description = "메모에 달린 댓글을 조회합니다."
66+
)
67+
@ApiResponses({
68+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "댓글 조회 성공"),
69+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "게시글 ID가 입력되지 않았습니다."),
70+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "게시글을 찾을 수 없습니다.")
71+
})
72+
@GetMapping
73+
public ResponseEntity<ApiResponse<List<CommentResponseDTO>>> getCommentsByArticleId(
74+
@RequestParam Long memoId,
75+
@AuthenticationPrincipal UserDetails userDetails
76+
) {
77+
78+
// 메모 ID 누락시 예외처리
79+
if (memoId == null) {
80+
throw new NotFoundException(ErrorStatus.MISSING_COMMENT_MEMOID.getMessage());
81+
}
82+
83+
List<CommentResponseDTO> comments = commentService.getCommentsByMemoId(memoId,userDetails);
84+
85+
return ApiResponse.success(SuccessStatus.SEND_COMMENT_SUCCESS, comments);
86+
}
87+
88+
@Operation(
89+
summary = "댓글 수정 API",
90+
description = "게시글에 달린 댓글을 수정합니다.")
91+
@ApiResponses({
92+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "댓글 수정 성공"),
93+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "댓글 ID가 입력되지 않았습니다. / 댓글이 입력되지 않았습니다."),
94+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "댓글을 찾을 수 없습니다."),
95+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "수정 권한이 없습니다.")
96+
})
97+
@PatchMapping("/{commentId}")
98+
public ResponseEntity<ApiResponse<Void>> updateComment(
99+
@PathVariable Long commentId,
100+
@RequestBody CommentUpdateDTO commentUpdateDTO,
101+
@AuthenticationPrincipal UserDetails userDetails
102+
) {
103+
//댓글 ID 누락시 예외처리
104+
if (commentId == null) {
105+
throw new NotFoundException(ErrorStatus.MISSING_COMMENT_ID.getMessage());
106+
}
107+
108+
// Comment 누락시 예외처리
109+
if (commentUpdateDTO.getComment() == null || commentUpdateDTO.getComment().isEmpty()) {
110+
throw new NotFoundException(ErrorStatus.MISSING_COMMENT.getMessage());
111+
}
112+
113+
Long userId = memberService.getUserIdByEmail(userDetails.getUsername());
114+
commentService.updateComment(commentId, commentUpdateDTO, userId);
115+
116+
return ApiResponse.success_only(SuccessStatus.MODIFY_COMMENT_SUCCESS);
117+
}
118+
119+
@Operation(
120+
summary = "댓글 삭제 API",
121+
description = "메모에 달린 댓글을 삭제합니다.")
122+
@ApiResponses({
123+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "댓글 삭제 성공"),
124+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "댓글 ID가 입력되지 않았습니다."),
125+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "댓글을 찾을 수 없습니다."),
126+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "40₩", description = "삭제 권한이 없습니다.")
127+
})
128+
@DeleteMapping("/{commentId}")
129+
public ResponseEntity<ApiResponse<Void>> deleteComment(
130+
@PathVariable Long commentId,
131+
@AuthenticationPrincipal UserDetails userDetails
132+
) {
133+
//댓글 ID 누락시 예외처리
134+
if (commentId == null) {
135+
throw new NotFoundException(ErrorStatus.MISSING_COMMENT_ID.getMessage());
136+
}
137+
138+
Long userId = memberService.getUserIdByEmail(userDetails.getUsername());
139+
commentService.deleteComment(commentId, userId);
140+
141+
return ApiResponse.success_only(SuccessStatus.DELETE_COMMENT_SUCCESS);
142+
}
143+
144+
@Operation(
145+
summary = "댓글 좋아요 토글 API",
146+
description = "특정 댓글에 좋아요를 누르거나 취소합니다."
147+
)
148+
@ApiResponses({
149+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "좋아요 토글 성공"),
150+
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "해당 게시글을 찾을 수 없습니다."),
151+
})
152+
@PostMapping("/like/{commentId}")
153+
public ResponseEntity<ApiResponse<Void>> toggleLike(
154+
@PathVariable Long commentId,
155+
@AuthenticationPrincipal UserDetails userDetails
156+
) {
157+
Long userId = memberService.getUserIdByEmail(userDetails.getUsername());
158+
commentService.toggleLike(commentId, userId);
159+
return ApiResponse.success_only(SuccessStatus.TOGGLE_LIKE_SUCCESS);
160+
}
161+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.rhkr8521.mapping.api.comment.dto;
2+
3+
import lombok.Builder;
4+
import lombok.Getter;
5+
6+
@Getter
7+
@Builder
8+
public class CommentCreateDTO {
9+
10+
private String comment;
11+
private Long memoId;
12+
private int rating;
13+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.rhkr8521.mapping.api.comment.dto;
2+
3+
import com.rhkr8521.mapping.api.comment.entity.Comment;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
import java.time.format.DateTimeFormatter;
8+
9+
@Getter
10+
@Builder
11+
public class CommentResponseDTO {
12+
13+
private Long id;
14+
private String comment;
15+
private int rating;
16+
private int likeCnt;
17+
private String nickname;
18+
private String profileImageUrl;
19+
private String updatedAt;
20+
private boolean myLike;
21+
22+
public static CommentResponseDTO fromEntity(Comment comment, boolean myLike) {
23+
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss");
24+
25+
return CommentResponseDTO.builder()
26+
.id(comment.getId())
27+
.comment(comment.getComment())
28+
.rating(comment.getRating())
29+
.likeCnt(comment.getLikeCnt())
30+
.nickname(comment.getMember().getNickname())
31+
.profileImageUrl(comment.getMember().getImageUrl())
32+
.updatedAt(comment.getUpdatedAt().format(dateTimeFormatter))
33+
.myLike(myLike)
34+
.build();
35+
}
36+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.rhkr8521.mapping.api.comment.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
import lombok.NoArgsConstructor;
7+
8+
@Getter
9+
@Builder
10+
@NoArgsConstructor
11+
@AllArgsConstructor
12+
public class CommentUpdateDTO {
13+
14+
private String comment;
15+
private int rating;
16+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.rhkr8521.mapping.api.comment.entity;
2+
3+
import com.rhkr8521.mapping.api.member.entity.Member;
4+
import com.rhkr8521.mapping.api.memo.entity.Memo;
5+
import com.rhkr8521.mapping.common.entity.BaseTimeEntity;
6+
import jakarta.persistence.*;
7+
import lombok.*;
8+
9+
@Getter
10+
@Builder(toBuilder = true)
11+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
12+
@AllArgsConstructor(access = AccessLevel.PROTECTED)
13+
@Entity
14+
@Table(name = "comment")
15+
public class Comment extends BaseTimeEntity {
16+
17+
@Id
18+
@GeneratedValue(strategy = GenerationType.IDENTITY)
19+
@Column(name = "comment_id")
20+
private Long id;
21+
22+
@Column(columnDefinition = "TEXT")
23+
private String comment;
24+
25+
private int rating;
26+
private int likeCnt;
27+
28+
@ManyToOne(fetch = FetchType.LAZY)
29+
@JoinColumn(name = "memo_id")
30+
private Memo memo;
31+
32+
@ManyToOne(fetch = FetchType.LAZY)
33+
@JoinColumn(name = "user_id")
34+
private Member member;
35+
36+
// 좋아요 증가
37+
public Comment increaseLikeCnt() {
38+
return this.toBuilder()
39+
.likeCnt(this.likeCnt + 1)
40+
.build();
41+
}
42+
43+
// 좋아요 감소
44+
public Comment decreaseLikeCnt() {
45+
return this.toBuilder()
46+
.likeCnt(this.likeCnt - 1)
47+
.build();
48+
}
49+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.rhkr8521.mapping.api.comment.entity;
2+
3+
import com.rhkr8521.mapping.api.member.entity.Member;
4+
import jakarta.persistence.*;
5+
import lombok.*;
6+
7+
@Getter
8+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
9+
@AllArgsConstructor
10+
@Entity
11+
@Table(name = "comment_like")
12+
@Builder
13+
public class CommentLike {
14+
15+
@Id
16+
@GeneratedValue(strategy = GenerationType.IDENTITY)
17+
@Column(name = "comment_like_id")
18+
private Long id;
19+
20+
@ManyToOne(fetch = FetchType.LAZY)
21+
@JoinColumn(name = "comment_id")
22+
private Comment comment;
23+
24+
@ManyToOne(fetch = FetchType.LAZY)
25+
@JoinColumn(name = "user_id")
26+
private Member member;
27+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.rhkr8521.mapping.api.comment.repository;
2+
3+
import com.rhkr8521.mapping.api.comment.entity.CommentLike;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
import java.util.Optional;
7+
8+
public interface CommentLikeRepository extends JpaRepository<CommentLike, Long> {
9+
Optional<CommentLike> findByCommentIdAndMemberId(Long commentId, Long memberId);
10+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.rhkr8521.mapping.api.comment.repository;
2+
3+
import com.rhkr8521.mapping.api.comment.entity.Comment;
4+
import com.rhkr8521.mapping.api.memo.entity.Memo;
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
7+
import java.util.List;
8+
9+
public interface CommentRepository extends JpaRepository<Comment, Long> {
10+
List<Comment> findByMemo(Memo memo);
11+
12+
}

0 commit comments

Comments
 (0)