Skip to content

Commit 54826fe

Browse files
authored
Merge branch 'main' into fix/infra-and-cd
2 parents 29addc2 + 6630398 commit 54826fe

File tree

28 files changed

+545
-177
lines changed

28 files changed

+545
-177
lines changed

back/build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ dependencies {
7878
// AI Services - WebFlux for non-blocking HTTP clients
7979
implementation("org.springframework.boot:spring-boot-starter-webflux")
8080
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
81+
82+
// macOS Netty 네이티브 DNS 리졸버 (WebFlux 필요)
83+
val isMacOS: Boolean = System.getProperty("os.name").startsWith("Mac OS X")
84+
val architecture = System.getProperty("os.arch").lowercase()
85+
if (isMacOS && architecture == "aarch64") {
86+
developmentOnly("io.netty:netty-resolver-dns-native-macos:4.1.68.Final:osx-aarch_64")
87+
}
8188
}
8289

8390
tasks.withType<Test> {

back/src/main/java/com/back/domain/comment/controller/CommentController.java

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.back.domain.comment.dto.CommentResponse;
55
import com.back.domain.comment.enums.CommentSortType;
66
import com.back.domain.comment.service.CommentService;
7+
import com.back.domain.user.entity.User;
78
import com.back.global.common.PageResponse;
89
import com.back.global.security.CustomUserDetails;
910
import io.swagger.v3.oas.annotations.Operation;
@@ -20,72 +21,62 @@
2021
import org.springframework.security.core.annotation.AuthenticationPrincipal;
2122
import org.springframework.web.bind.annotation.*;
2223

23-
@Tag(name = "Comment", description = "댓글 관련 API")
2424
@RestController
2525
@RequestMapping("/api/v1/posts/{postId}/comments")
2626
@RequiredArgsConstructor
27+
@Tag(name = "Comment", description = "댓글 관련 API")
2728
public class CommentController {
2829

2930
private final CommentService commentService;
3031

31-
// 댓글 생성
3232
@PostMapping
3333
@Operation(summary = "댓글 생성", description = "새 댓글을 생성합니다.")
34-
public ResponseEntity<CommentResponse> createPost(
35-
@io.swagger.v3.oas.annotations.parameters.RequestBody(
36-
description = "생성할 댓글 정보",
37-
required = true
38-
)
34+
public ResponseEntity<CommentResponse> createComment(
3935
@RequestBody @Valid CommentRequest request,
40-
@Parameter(description = "조회할 게시글 ID", required = true) @PathVariable("postId") Long postId,
36+
@PathVariable("postId") Long postId,
4137
@AuthenticationPrincipal CustomUserDetails cs
4238
) {
43-
CommentResponse response = commentService.createComment(cs.getUser().getId(), postId, request);
39+
User user = cs.getUser();
40+
CommentResponse response = commentService.createComment(user, postId, request);
4441
return ResponseEntity.status(HttpStatus.CREATED).body(response);
4542
}
4643

4744
@GetMapping
4845
@Operation(summary = "댓글 목록 조회", description = "게시글 목록을 조회합니다.")
49-
public ResponseEntity<PageResponse<CommentResponse>> getPosts(
50-
@Parameter(description = "페이지 정보") Pageable pageable,
51-
@Parameter(description = "조회할 게시글 ID", required = true) @PathVariable("postId") Long postId,
52-
@Parameter(description = "정렬 조건 LATEST or LIKES") @RequestParam(defaultValue = "LATEST") CommentSortType sortType,
46+
public ResponseEntity<PageResponse<CommentResponse>> getComments(
47+
Pageable pageable,
48+
@PathVariable("postId") Long postId,
49+
@RequestParam(defaultValue = "LATEST") CommentSortType sortType,
5350
@AuthenticationPrincipal CustomUserDetails cs) {
5451

55-
Sort sort = Sort.by(Sort.Direction.DESC, sortType.getProperty());
56-
57-
Pageable sortedPageable = PageRequest.of(
58-
pageable.getPageNumber(),
59-
pageable.getPageSize(),
60-
sort
61-
);
52+
User user = cs != null ? cs.getUser() : null;
6253

63-
Long userId = (cs != null && cs.getUser() != null) ? cs.getUser().getId() : null;
54+
Sort sort = Sort.by(Sort.Direction.DESC, sortType.getProperty());
55+
Pageable sortedPageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort);
6456

65-
Page<CommentResponse> responses = commentService.getComments(userId, postId, sortedPageable);
57+
Page<CommentResponse> responses = commentService.getComments(user, postId, sortedPageable);
6658
return ResponseEntity.ok(PageResponse.of(responses));
6759
}
6860

69-
7061
@PutMapping("/{commentId}")
7162
@Operation(summary = "댓글 수정", description = "자신의 댓글을 수정합니다.")
7263
public ResponseEntity<Long> updateComment(
73-
@Parameter(description = "수정할 댓글 ID", required = true) @PathVariable Long commentId,
74-
@io.swagger.v3.oas.annotations.parameters.RequestBody(
75-
description = "수정할 댓글 정보",
76-
required = true
77-
)
64+
@PathVariable Long commentId,
7865
@RequestBody @Valid CommentRequest request,
7966
@AuthenticationPrincipal CustomUserDetails cs) {
80-
return ResponseEntity.ok(commentService.updateComment(cs.getUser().getId(), commentId, request));
67+
68+
User user = cs.getUser();
69+
return ResponseEntity.ok(commentService.updateComment(user, commentId, request));
8170
}
8271

8372
@DeleteMapping("/{commentId}")
8473
@Operation(summary = "댓글 삭제", description = "자신의 댓글을 삭제합니다.")
85-
public ResponseEntity<Void> deletePost(
86-
@Parameter(description = "삭제할 댓글 ID", required = true) @PathVariable Long commentId,
74+
public ResponseEntity<Void> deleteComment(
75+
@PathVariable Long commentId,
8776
@AuthenticationPrincipal CustomUserDetails cs) {
88-
commentService.deleteComment(cs.getUser().getId(), commentId);
89-
return ResponseEntity.ok(null);
77+
78+
User user = cs.getUser();
79+
commentService.deleteComment(user, commentId);
80+
return ResponseEntity.ok().build();
9081
}
91-
}
82+
}

back/src/main/java/com/back/domain/comment/entity/Comment.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ public class Comment extends BaseEntity {
5050
@LastModifiedDate
5151
private LocalDateTime updatedAt;
5252

53-
public void checkUser(Long userId) {
54-
if (!user.getId().equals(userId))
53+
public void checkUser(Long targetUserId) {
54+
if (!targetUserId.equals(user.getId()))
5555
throw new ApiException(ErrorCode.UNAUTHORIZED_USER);
5656
}
5757

back/src/main/java/com/back/domain/comment/repository/CommentRepository.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ public interface CommentRepository extends JpaRepository<Comment, Long> {
2424

2525
int countByUserId(Long userId);
2626

27-
@Lock(LockModeType.PESSIMISTIC_WRITE)
28-
@Query("SELECT c FROM Comment c WHERE c.id = :commentId")
29-
Optional<Comment> findByIdWithLock(@Param("commentId") Long commentId);
27+
// @Lock(LockModeType.PESSIMISTIC_WRITE)
28+
// @Query("SELECT c FROM Comment c WHERE c.id = :commentId")
29+
// Optional<Comment> findByIdWithLock(@Param("commentId") Long commentId);
3030

3131
@EntityGraph(attributePaths = {"post"})
3232
Page<Comment> findByUserIdOrderByCreatedDateDesc(Long userId, Pageable pageable);

back/src/main/java/com/back/domain/comment/service/CommentService.java

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,33 +31,27 @@
3131
@Transactional(readOnly = true)
3232
public class CommentService {
3333

34-
private final UserRepository userRepository;
3534
private final PostRepository postRepository;
3635
private final CommentRepository commentRepository;
3736
private final CommentLikeRepository commentLikeRepository;
3837

39-
public CommentResponse createComment(Long userId, Long postId, CommentRequest request) {
40-
User user = userRepository.findById(userId)
41-
.orElseThrow(() -> new ApiException(ErrorCode.USER_NOT_FOUND));
42-
38+
public CommentResponse createComment(User user, Long postId, CommentRequest request) {
4339
Post post = postRepository.findById(postId)
4440
.orElseThrow(() -> new ApiException(ErrorCode.POST_NOT_FOUND));
41+
4542
CommentMappers.CommentCtxMapper ctxMapper = new CommentMappers.CommentCtxMapper(user, post);
4643
Comment savedComment = commentRepository.save(ctxMapper.toEntity(request));
4744
return ctxMapper.toResponse(savedComment);
4845
}
4946

50-
public Page<CommentResponse> getComments(Long userId, Long postId, Pageable pageable) {
51-
User user = userId != null
52-
? userRepository.findById(userId).orElse(null)
53-
: null;
54-
47+
public Page<CommentResponse> getComments(User user, Long postId, Pageable pageable) {
5548
Post post = postRepository.findById(postId)
5649
.orElseThrow(() -> new ApiException(ErrorCode.POST_NOT_FOUND));
50+
5751
Page<Comment> commentsPage = commentRepository.findCommentsByPostId(postId, pageable);
5852

59-
Set<Long> userLikedComments = userId != null
60-
? getUserLikedComments(userId, commentsPage)
53+
Set<Long> userLikedComments = user != null
54+
? getUserLikedComments(user, commentsPage)
6155
: Collections.emptySet();
6256

6357
return commentsPage.map(comment -> CommentMappers.toCommentResponse(
@@ -68,29 +62,30 @@ public Page<CommentResponse> getComments(Long userId, Long postId, Pageable page
6862
}
6963

7064
@Transactional
71-
public Long updateComment(Long userId, Long commentId, CommentRequest request) {
65+
public Long updateComment(User user, Long commentId, CommentRequest request) {
7266
Comment comment = commentRepository.findById(commentId)
7367
.orElseThrow(() -> new ApiException(ErrorCode.COMMENT_NOT_FOUND));
74-
comment.checkUser(userId);
68+
69+
comment.checkUser(user.getId());
7570
comment.updateContent(request.content());
7671
return comment.getId();
7772
}
7873

7974
@Transactional
80-
public void deleteComment(Long userId, Long commentId) {
75+
public void deleteComment(User user, Long commentId) {
8176
Comment comment = commentRepository.findById(commentId)
8277
.orElseThrow(() -> new ApiException(ErrorCode.COMMENT_NOT_FOUND));
83-
comment.checkUser(userId);
78+
79+
comment.checkUser(user.getId());
8480
commentRepository.delete(comment);
8581
}
8682

87-
// 특정 사용자가 한 게시글 내 댓글에서 좋아요를 누른 댓글 ID 집합 조회
88-
private Set<Long> getUserLikedComments(Long userId, Page<Comment> comments) {
83+
private Set<Long> getUserLikedComments(User user, Page<Comment> comments) {
8984
Set<Long> commentIds = comments.getContent()
9085
.stream()
9186
.map(Comment::getId)
9287
.collect(Collectors.toSet());
9388

94-
return commentLikeRepository.findLikedCommentsIdsByUserAndCommentIds(userId, commentIds);
89+
return commentLikeRepository.findLikedCommentsIdsByUserAndCommentIds(user.getId(), commentIds);
9590
}
9691
}

back/src/main/java/com/back/domain/like/controller/LikeController.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,33 +15,34 @@
1515
@RequiredArgsConstructor
1616
@RequestMapping("/api/v1/posts")
1717
public class LikeController {
18+
1819
private final LikeService likeService;
1920

2021
@PostMapping("/{postId}/likes")
2122
public ResponseEntity<Void> addLike(@PathVariable Long postId, @AuthenticationPrincipal CustomUserDetails cs) {
22-
likeService.addLike(cs.getUser().getId(), postId);
23-
return ResponseEntity.status(HttpStatus.CREATED).body(null);
23+
likeService.addLike(cs.getUser(), postId);
24+
return ResponseEntity.status(HttpStatus.CREATED).build();
2425
}
2526

2627
@DeleteMapping("/{postId}/likes")
2728
public ResponseEntity<Void> removeLike(@PathVariable Long postId, @AuthenticationPrincipal CustomUserDetails cs) {
28-
likeService.removeLike(postId, cs.getUser().getId());
29-
return ResponseEntity.ok(null);
29+
likeService.removeLike(cs.getUser(), postId);
30+
return ResponseEntity.ok().build();
3031
}
3132

3233
@PostMapping("/{postId}/comments/{commentId}/likes")
3334
public ResponseEntity<Void> addCommentLike(@PathVariable Long postId,
3435
@PathVariable Long commentId,
3536
@AuthenticationPrincipal CustomUserDetails cs) {
36-
likeService.addCommentLike(cs.getUser().getId(), postId, commentId);
37-
return ResponseEntity.status(HttpStatus.CREATED).body(null);
37+
likeService.addCommentLike(cs.getUser(), postId, commentId);
38+
return ResponseEntity.status(HttpStatus.CREATED).build();
3839
}
3940

4041
@DeleteMapping("/{postId}/comments/{commentId}/likes")
4142
public ResponseEntity<Void> removeCommentLike(@PathVariable Long postId,
4243
@PathVariable Long commentId,
4344
@AuthenticationPrincipal CustomUserDetails cs) {
44-
likeService.removeCommentLike(cs.getUser().getId(), postId, commentId);
45-
return ResponseEntity.ok(null);
45+
likeService.removeCommentLike(cs.getUser(), postId, commentId);
46+
return ResponseEntity.ok().build();
4647
}
4748
}

back/src/main/java/com/back/domain/like/service/LikeService.java

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@
1010
import com.back.domain.post.repository.PostRepository;
1111
import com.back.domain.user.entity.User;
1212
import com.back.domain.user.repository.UserRepository;
13+
import com.back.global.common.WithLock;
1314
import com.back.global.exception.ApiException;
1415
import com.back.global.exception.ErrorCode;
1516
import lombok.RequiredArgsConstructor;
17+
import org.springframework.core.Ordered;
1618
import org.springframework.stereotype.Service;
1719
import org.springframework.transaction.annotation.Transactional;
20+
import org.springframework.transaction.interceptor.TransactionAspectSupport;
21+
import org.springframework.transaction.interceptor.TransactionInterceptor;
1822

1923
/**
2024
* 좋아요 관련 비즈니스 로직을 처리하는 서비스.
@@ -28,29 +32,33 @@ public class LikeService {
2832
private final CommentLikeRepository commentLikeRepository;
2933
private final CommentRepository commentRepository;
3034
private final PostRepository postRepository;
31-
private final UserRepository userRepository;
3235

3336
@Transactional
34-
public void addLike(Long userId, Long postId) {
35-
Post post = postRepository.findByIdWithLock(postId)
37+
@WithLock(key = "'post:' + #postId")
38+
public void addLike(User user, Long postId) {
39+
Post post = postRepository.findById(postId)
3640
.orElseThrow(() -> new ApiException(ErrorCode.POST_NOT_FOUND));
3741

38-
if (postLikeRepository.existsByPostIdAndUserId(postId, userId)) {
42+
if (postLikeRepository.existsByPostIdAndUserId(postId, user.getId())) {
3943
throw new ApiException(ErrorCode.POST_ALREADY_LIKED);
4044
}
4145

42-
PostLike postLike = createPostLike(post, userId);
46+
PostLike postLike = PostLike.builder()
47+
.post(post)
48+
.user(user)
49+
.build();
4350

4451
postLikeRepository.save(postLike);
4552
post.incrementLikeCount();
4653
}
4754

4855
@Transactional
49-
public void removeLike(Long postId, Long userId) {
50-
Post post = postRepository.findByIdWithLock(postId)
56+
@WithLock(key = "'post:' + #postId")
57+
public void removeLike(User user, Long postId) {
58+
Post post = postRepository.findById(postId)
5159
.orElseThrow(() -> new ApiException(ErrorCode.POST_NOT_FOUND));
5260

53-
boolean deleted = postLikeRepository.deleteByPostIdAndUserId(postId, userId) > 0;
61+
boolean deleted = postLikeRepository.deleteByPostIdAndUserId(postId, user.getId()) > 0;
5462

5563
if (!deleted) {
5664
throw new ApiException(ErrorCode.LIKE_NOT_FOUND);
@@ -60,47 +68,36 @@ public void removeLike(Long postId, Long userId) {
6068
}
6169

6270
@Transactional
63-
public void addCommentLike(Long userId, Long postId, Long commentId) {
64-
Comment comment = commentRepository.findByIdWithLock(commentId)
71+
@WithLock(key = "'comment:' + #commentId")
72+
public void addCommentLike(User user, Long postId, Long commentId) {
73+
Comment comment = commentRepository.findById(commentId)
6574
.orElseThrow(() -> new ApiException(ErrorCode.COMMENT_NOT_FOUND));
6675

67-
if (commentLikeRepository.existsByCommentIdAndUserId(commentId, userId)) {
76+
if (commentLikeRepository.existsByCommentIdAndUserId(commentId, user.getId())) {
6877
throw new ApiException(ErrorCode.COMMENT_ALREADY_LIKED);
6978
}
7079

71-
CommentLike commentLike = createCommentLike(comment, userId);
80+
CommentLike commentLike = CommentLike.builder()
81+
.comment(comment)
82+
.user(user)
83+
.build();
7284

7385
commentLikeRepository.save(commentLike);
7486
comment.incrementLikeCount();
7587
}
7688

7789
@Transactional
78-
public void removeCommentLike(Long userId, Long postId, Long commentId) {
79-
Comment comment = commentRepository.findByIdWithLock(commentId)
90+
@WithLock(key = "'comment:' + #commentId")
91+
public void removeCommentLike(User user, Long postId, Long commentId) {
92+
Comment comment = commentRepository.findById(commentId)
8093
.orElseThrow(() -> new ApiException(ErrorCode.COMMENT_NOT_FOUND));
8194

82-
boolean deleted = commentLikeRepository.deleteByCommentIdAndUserId(commentId, userId) > 0;
95+
boolean deleted = commentLikeRepository.deleteByCommentIdAndUserId(commentId, user.getId()) > 0;
8396

8497
if (!deleted) {
8598
throw new ApiException(ErrorCode.LIKE_NOT_FOUND);
8699
}
87100

88101
comment.decrementLikeCount();
89102
}
90-
91-
private PostLike createPostLike(Post post, Long userId) {
92-
User userReference = userRepository.getReferenceById(userId);
93-
return PostLike.builder()
94-
.post(post)
95-
.user(userReference)
96-
.build();
97-
}
98-
99-
private CommentLike createCommentLike(Comment comment, Long userId) {
100-
User userReference = userRepository.getReferenceById(userId);
101-
return CommentLike.builder()
102-
.comment(comment)
103-
.user(userReference)
104-
.build();
105-
}
106103
}

0 commit comments

Comments
 (0)