From 7ce42e13c7469ff0c36a0a942bf22b4655735889 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 17:01:26 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=EC=B9=9C=EA=B5=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=9A=94=EC=B2=AD=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/FriendRequestController.java | 36 +++++++++++++++++ .../everymoment/entity/FriendRequest.java | 36 +++++++++++++++++ .../everymoment/exception/ErrorCode.java | 8 +++- .../repository/FriendRequestRepository.java | 8 ++++ .../service/FriendRequestService.java | 40 +++++++++++++++++++ 5 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java create mode 100644 src/main/java/com/potatocake/everymoment/entity/FriendRequest.java create mode 100644 src/main/java/com/potatocake/everymoment/repository/FriendRequestRepository.java create mode 100644 src/main/java/com/potatocake/everymoment/service/FriendRequestService.java diff --git a/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java b/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java new file mode 100644 index 0000000..34053c7 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java @@ -0,0 +1,36 @@ +package com.potatocake.everymoment.controller; + +import com.potatocake.everymoment.dto.SuccessResponse; +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.exception.GlobalException; +import com.potatocake.everymoment.security.MemberDetails; +import com.potatocake.everymoment.service.FriendRequestService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RequestMapping("/api/members/{memberId}/friend-requests") +@RestController +public class FriendRequestController { + + private final FriendRequestService friendRequestService; + + @PostMapping + public ResponseEntity sendFriendRequest(@PathVariable Long memberId, + @AuthenticationPrincipal MemberDetails memberDetails) { + if (memberDetails.getId().equals(memberId)) { + throw new GlobalException(ErrorCode.SELF_FRIEND_REQUEST); + } + + friendRequestService.sendFriendRequest(memberDetails.getId(), memberId); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } + +} diff --git a/src/main/java/com/potatocake/everymoment/entity/FriendRequest.java b/src/main/java/com/potatocake/everymoment/entity/FriendRequest.java new file mode 100644 index 0000000..5ec955d --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/entity/FriendRequest.java @@ -0,0 +1,36 @@ +package com.potatocake.everymoment.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class FriendRequest { + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private Member sender; + + @ManyToOne(fetch = FetchType.LAZY) + private Member receiver; + + @Builder + public FriendRequest(Long id, Member sender, Member receiver) { + this.id = id; + this.sender = sender; + this.receiver = receiver; + } + +} diff --git a/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java b/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java index ccee02c..0e76866 100644 --- a/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java +++ b/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java @@ -50,7 +50,13 @@ public enum ErrorCode { METHOD_NOT_ALLOWED("지원하지 않는 HTTP 메소드입니다.", HttpStatus.METHOD_NOT_ALLOWED), /* CategoryService */ - CATEGORY_NOT_OWNER("본인의 카테고리만 수정할 수 있습니다.", HttpStatus.FORBIDDEN); + CATEGORY_NOT_OWNER("본인의 카테고리만 수정할 수 있습니다.", HttpStatus.FORBIDDEN), + + /* FriendRequestController */ + SELF_FRIEND_REQUEST("자기 자신에게 친구 요청을 보낼 수 없습니다.", BAD_REQUEST), + + /* FriendRequestService */ + FRIEND_REQUEST_ALREADY_EXISTS("이미 친구 요청을 보냈습니다.", CONFLICT); private final String message; private final HttpStatus status; diff --git a/src/main/java/com/potatocake/everymoment/repository/FriendRequestRepository.java b/src/main/java/com/potatocake/everymoment/repository/FriendRequestRepository.java new file mode 100644 index 0000000..0557d7f --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/repository/FriendRequestRepository.java @@ -0,0 +1,8 @@ +package com.potatocake.everymoment.repository; + +import com.potatocake.everymoment.entity.FriendRequest; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface FriendRequestRepository extends JpaRepository { + boolean existsBySenderIdAndReceiverId(Long senderId, Long receiverId); +} diff --git a/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java b/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java new file mode 100644 index 0000000..d6c55f7 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java @@ -0,0 +1,40 @@ +package com.potatocake.everymoment.service; + +import com.potatocake.everymoment.entity.FriendRequest; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.exception.GlobalException; +import com.potatocake.everymoment.repository.FriendRequestRepository; +import com.potatocake.everymoment.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional +@Service +public class FriendRequestService { + + private final FriendRequestRepository friendRequestRepository; + private final MemberRepository memberRepository; + + public void sendFriendRequest(Long senderId, Long receiverId) { + boolean isAlreadySend = friendRequestRepository.existsBySenderIdAndReceiverId(senderId, receiverId); + + if (isAlreadySend) { + throw new GlobalException(ErrorCode.FRIEND_REQUEST_ALREADY_EXISTS); + } + + Member sender = memberRepository.findById(senderId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); + + Member receiver = memberRepository.findById(receiverId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); + + friendRequestRepository.save(FriendRequest.builder() + .sender(sender) + .receiver(receiver) + .build()); + } + +} From 1b9bd3234bf5320826416e65365013cdff220ba4 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 17:19:08 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=EC=B9=9C=EA=B5=AC=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EC=88=98=EB=9D=BD=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/FriendRequestController.java | 13 ++++++-- .../everymoment/exception/ErrorCode.java | 3 +- .../service/FriendRequestService.java | 33 +++++++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java b/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java index 34053c7..65ce16c 100644 --- a/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java +++ b/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java @@ -10,17 +10,15 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor -@RequestMapping("/api/members/{memberId}/friend-requests") @RestController public class FriendRequestController { private final FriendRequestService friendRequestService; - @PostMapping + @PostMapping("/api/members/{memberId}/friend-requests") public ResponseEntity sendFriendRequest(@PathVariable Long memberId, @AuthenticationPrincipal MemberDetails memberDetails) { if (memberDetails.getId().equals(memberId)) { @@ -33,4 +31,13 @@ public ResponseEntity sendFriendRequest(@PathVariable Long memb .body(SuccessResponse.ok()); } + @PostMapping("/api/friend-requests/{requestId}/accept") + public ResponseEntity acceptFriendRequest(@PathVariable Long requestId, + @AuthenticationPrincipal MemberDetails memberDetails) { + friendRequestService.acceptFriendRequest(requestId, memberDetails.getId()); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } + } diff --git a/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java b/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java index 0e76866..365d29a 100644 --- a/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java +++ b/src/main/java/com/potatocake/everymoment/exception/ErrorCode.java @@ -56,7 +56,8 @@ public enum ErrorCode { SELF_FRIEND_REQUEST("자기 자신에게 친구 요청을 보낼 수 없습니다.", BAD_REQUEST), /* FriendRequestService */ - FRIEND_REQUEST_ALREADY_EXISTS("이미 친구 요청을 보냈습니다.", CONFLICT); + FRIEND_REQUEST_ALREADY_EXISTS("이미 친구 요청을 보냈습니다.", CONFLICT), + FRIEND_REQUEST_NOT_FOUND("존재하지 않는 친구 요청입니다.", NOT_FOUND); private final String message; private final HttpStatus status; diff --git a/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java b/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java index d6c55f7..1a8fa6d 100644 --- a/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java +++ b/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java @@ -1,9 +1,11 @@ package com.potatocake.everymoment.service; +import com.potatocake.everymoment.entity.Friend; import com.potatocake.everymoment.entity.FriendRequest; import com.potatocake.everymoment.entity.Member; import com.potatocake.everymoment.exception.ErrorCode; import com.potatocake.everymoment.exception.GlobalException; +import com.potatocake.everymoment.repository.FriendRepository; import com.potatocake.everymoment.repository.FriendRequestRepository; import com.potatocake.everymoment.repository.MemberRepository; import lombok.RequiredArgsConstructor; @@ -17,6 +19,7 @@ public class FriendRequestService { private final FriendRequestRepository friendRequestRepository; private final MemberRepository memberRepository; + private final FriendRepository friendRepository; public void sendFriendRequest(Long senderId, Long receiverId) { boolean isAlreadySend = friendRequestRepository.existsBySenderIdAndReceiverId(senderId, receiverId); @@ -37,4 +40,34 @@ public void sendFriendRequest(Long senderId, Long receiverId) { .build()); } + public void acceptFriendRequest(Long requestId, Long memberId) { + FriendRequest friendRequest = findAndValidateFriendRequest(requestId, memberId); + + Friend friend1 = createFriend(friendRequest.getSender(), friendRequest.getReceiver()); + Friend friend2 = createFriend(friendRequest.getReceiver(), friendRequest.getSender()); + + friendRepository.save(friend1); + friendRepository.save(friend2); + + friendRequestRepository.delete(friendRequest); + } + + private FriendRequest findAndValidateFriendRequest(Long requestId, Long memberId) { + FriendRequest friendRequest = friendRequestRepository.findById(requestId) + .orElseThrow(() -> new GlobalException(ErrorCode.FRIEND_REQUEST_NOT_FOUND)); + + if (!friendRequest.getReceiver().getId().equals(memberId)) { + throw new GlobalException(ErrorCode.FRIEND_REQUEST_NOT_FOUND); + } + + return friendRequest; + } + + private Friend createFriend(Member member, Member friend) { + return Friend.builder() + .memberId(member) + .friendId(friend) + .build(); + } + } From 12567c82f9b0f1c936a5ef3b981c8fdd6d072c22 Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 17:22:36 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=EC=B9=9C=EA=B5=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EA=B1=B0=EC=A0=88=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/FriendRequestController.java | 10 ++++++++++ .../everymoment/service/FriendRequestService.java | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java b/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java index 65ce16c..f7975a0 100644 --- a/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java +++ b/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java @@ -8,6 +8,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; @@ -40,4 +41,13 @@ public ResponseEntity acceptFriendRequest(@PathVariable Long re .body(SuccessResponse.ok()); } + @DeleteMapping("/api/friend-requests/{requestId}/reject") + public ResponseEntity rejectFriendRequest(@PathVariable Long requestId, + @AuthenticationPrincipal MemberDetails memberDetails) { + friendRequestService.rejectFriendRequest(requestId, memberDetails.getId()); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } + } diff --git a/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java b/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java index 1a8fa6d..20b3a77 100644 --- a/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java +++ b/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java @@ -52,6 +52,12 @@ public void acceptFriendRequest(Long requestId, Long memberId) { friendRequestRepository.delete(friendRequest); } + public void rejectFriendRequest(Long requestId, Long memberId) { + FriendRequest friendRequest = findAndValidateFriendRequest(requestId, memberId); + + friendRequestRepository.delete(friendRequest); + } + private FriendRequest findAndValidateFriendRequest(Long requestId, Long memberId) { FriendRequest friendRequest = friendRequestRepository.findById(requestId) .orElseThrow(() -> new GlobalException(ErrorCode.FRIEND_REQUEST_NOT_FOUND)); From 6b4c9eab5db438126aba5825f76053acbac08ded Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 18:18:44 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=EC=B9=9C=EA=B5=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=9A=94=EC=B2=AD=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/FriendRequestController.java | 17 ++++++++- .../response/FriendRequestPageRequest.java | 16 ++++++++ .../dto/response/FriendRequestResponse.java | 13 +++++++ .../repository/FriendRepository.java | 2 + .../repository/FriendRequestRepository.java | 7 ++++ .../service/FriendRequestService.java | 38 +++++++++++++++++++ .../everymoment/service/MemberService.java | 6 ++- .../everymoment/util/IdExtractor.java | 8 ++++ .../everymoment/util/PagingUtil.java | 14 +++---- .../everymoment/util/PagingUtilTest.java | 7 ++-- 10 files changed, 115 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/potatocake/everymoment/dto/response/FriendRequestPageRequest.java create mode 100644 src/main/java/com/potatocake/everymoment/dto/response/FriendRequestResponse.java create mode 100644 src/main/java/com/potatocake/everymoment/util/IdExtractor.java diff --git a/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java b/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java index f7975a0..f960fbc 100644 --- a/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java +++ b/src/main/java/com/potatocake/everymoment/controller/FriendRequestController.java @@ -1,6 +1,7 @@ package com.potatocake.everymoment.controller; import com.potatocake.everymoment.dto.SuccessResponse; +import com.potatocake.everymoment.dto.response.FriendRequestPageRequest; import com.potatocake.everymoment.exception.ErrorCode; import com.potatocake.everymoment.exception.GlobalException; import com.potatocake.everymoment.security.MemberDetails; @@ -9,8 +10,10 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @@ -19,6 +22,18 @@ public class FriendRequestController { private final FriendRequestService friendRequestService; + @GetMapping("/api/friend-requests") + public ResponseEntity> getFriendRequests( + @RequestParam(required = false) Long key, + @RequestParam(defaultValue = "10") int size, + @AuthenticationPrincipal MemberDetails memberDetails) { + FriendRequestPageRequest friendRequests = friendRequestService.getFriendRequests(key, size, + memberDetails.getId()); + + return ResponseEntity.ok() + .body(SuccessResponse.ok(friendRequests)); + } + @PostMapping("/api/members/{memberId}/friend-requests") public ResponseEntity sendFriendRequest(@PathVariable Long memberId, @AuthenticationPrincipal MemberDetails memberDetails) { @@ -49,5 +64,5 @@ public ResponseEntity rejectFriendRequest(@PathVariable Long re return ResponseEntity.ok() .body(SuccessResponse.ok()); } - + } diff --git a/src/main/java/com/potatocake/everymoment/dto/response/FriendRequestPageRequest.java b/src/main/java/com/potatocake/everymoment/dto/response/FriendRequestPageRequest.java new file mode 100644 index 0000000..1562918 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/dto/response/FriendRequestPageRequest.java @@ -0,0 +1,16 @@ +package com.potatocake.everymoment.dto.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@Builder +@Getter +public class FriendRequestPageRequest { + + private List friendRequests; + private Long next; + +} diff --git a/src/main/java/com/potatocake/everymoment/dto/response/FriendRequestResponse.java b/src/main/java/com/potatocake/everymoment/dto/response/FriendRequestResponse.java new file mode 100644 index 0000000..77f1771 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/dto/response/FriendRequestResponse.java @@ -0,0 +1,13 @@ +package com.potatocake.everymoment.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class FriendRequestResponse { + + private Long id; + private Long senderId; + +} diff --git a/src/main/java/com/potatocake/everymoment/repository/FriendRepository.java b/src/main/java/com/potatocake/everymoment/repository/FriendRepository.java index 5eb96f7..70794c6 100644 --- a/src/main/java/com/potatocake/everymoment/repository/FriendRepository.java +++ b/src/main/java/com/potatocake/everymoment/repository/FriendRepository.java @@ -8,7 +8,9 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor; public interface FriendRepository extends JpaRepository, JpaSpecificationExecutor { + Optional findByMemberIdAndFriendId(Member member, Member friend); List findAllFriendIdsByMemberId(Member member); + } diff --git a/src/main/java/com/potatocake/everymoment/repository/FriendRequestRepository.java b/src/main/java/com/potatocake/everymoment/repository/FriendRequestRepository.java index 0557d7f..154c385 100644 --- a/src/main/java/com/potatocake/everymoment/repository/FriendRequestRepository.java +++ b/src/main/java/com/potatocake/everymoment/repository/FriendRequestRepository.java @@ -1,8 +1,15 @@ package com.potatocake.everymoment.repository; import com.potatocake.everymoment.entity.FriendRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.ScrollPosition; +import org.springframework.data.domain.Window; import org.springframework.data.jpa.repository.JpaRepository; public interface FriendRequestRepository extends JpaRepository { + boolean existsBySenderIdAndReceiverId(Long senderId, Long receiverId); + + Window findByReceiverId(Long receiverId, ScrollPosition scrollPosition, Pageable pageable); + } diff --git a/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java b/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java index 20b3a77..9aeff97 100644 --- a/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java +++ b/src/main/java/com/potatocake/everymoment/service/FriendRequestService.java @@ -1,5 +1,9 @@ package com.potatocake.everymoment.service; +import static org.springframework.data.domain.Sort.Direction.DESC; + +import com.potatocake.everymoment.dto.response.FriendRequestPageRequest; +import com.potatocake.everymoment.dto.response.FriendRequestResponse; import com.potatocake.everymoment.entity.Friend; import com.potatocake.everymoment.entity.FriendRequest; import com.potatocake.everymoment.entity.Member; @@ -8,7 +12,12 @@ import com.potatocake.everymoment.repository.FriendRepository; import com.potatocake.everymoment.repository.FriendRequestRepository; import com.potatocake.everymoment.repository.MemberRepository; +import com.potatocake.everymoment.util.PagingUtil; +import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.ScrollPosition; +import org.springframework.data.domain.Window; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,6 +29,19 @@ public class FriendRequestService { private final FriendRequestRepository friendRequestRepository; private final MemberRepository memberRepository; private final FriendRepository friendRepository; + private final PagingUtil pagingUtil; + + @Transactional(readOnly = true) + public FriendRequestPageRequest getFriendRequests(Long key, int size, Long memberId) { + Window window = fetchFriendRequestWindow(key, size, memberId); + List requests = convertToFriendRequestResponses(window.getContent()); + Long nextKey = pagingUtil.getNextKey(window, FriendRequest::getId); + + return FriendRequestPageRequest.builder() + .friendRequests(requests) + .next(nextKey) + .build(); + } public void sendFriendRequest(Long senderId, Long receiverId) { boolean isAlreadySend = friendRequestRepository.existsBySenderIdAndReceiverId(senderId, receiverId); @@ -76,4 +98,20 @@ private Friend createFriend(Member member, Member friend) { .build(); } + private Window fetchFriendRequestWindow(Long key, int size, Long receiverId) { + ScrollPosition scrollPosition = pagingUtil.createScrollPosition(key); + Pageable pageable = pagingUtil.createPageable(size, DESC); + + return friendRequestRepository.findByReceiverId(receiverId, scrollPosition, pageable); + } + + private List convertToFriendRequestResponses(List requests) { + return requests.stream() + .map(request -> FriendRequestResponse.builder() + .id(request.getId()) + .senderId(request.getSender().getId()) + .build()) + .toList(); + } + } diff --git a/src/main/java/com/potatocake/everymoment/service/MemberService.java b/src/main/java/com/potatocake/everymoment/service/MemberService.java index 11b2db6..f93bfef 100644 --- a/src/main/java/com/potatocake/everymoment/service/MemberService.java +++ b/src/main/java/com/potatocake/everymoment/service/MemberService.java @@ -1,5 +1,7 @@ package com.potatocake.everymoment.service; +import static org.springframework.data.domain.Sort.Direction.ASC; + import com.potatocake.everymoment.dto.response.MemberDetailResponse; import com.potatocake.everymoment.dto.response.MemberResponse; import com.potatocake.everymoment.dto.response.MemberSearchResponse; @@ -32,7 +34,7 @@ public class MemberService { public MemberSearchResponse searchMembers(String nickname, String email, Long key, int size) { Window window = fetchMemberWindow(nickname, email, key, size); List members = convertToMemberResponses(window.getContent()); - Long nextKey = pagingUtil.getNextKey(window); + Long nextKey = pagingUtil.getNextKey(window, Member::getId); return MemberSearchResponse.builder() .members(members) @@ -76,7 +78,7 @@ public void updateMemberInfo(Long id, MultipartFile profileImage, String nicknam private Window fetchMemberWindow(String nickname, String email, Long key, int size) { ScrollPosition scrollPosition = pagingUtil.createScrollPosition(key); - Pageable pageable = pagingUtil.createPageable(size); + Pageable pageable = pagingUtil.createPageable(size, ASC); String searchNickname = (nickname == null) ? "" : nickname; String searchEmail = (email == null) ? "" : email; diff --git a/src/main/java/com/potatocake/everymoment/util/IdExtractor.java b/src/main/java/com/potatocake/everymoment/util/IdExtractor.java new file mode 100644 index 0000000..b2c6c2a --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/util/IdExtractor.java @@ -0,0 +1,8 @@ +package com.potatocake.everymoment.util; + +@FunctionalInterface +public interface IdExtractor { + + Long extractId(T item); + +} diff --git a/src/main/java/com/potatocake/everymoment/util/PagingUtil.java b/src/main/java/com/potatocake/everymoment/util/PagingUtil.java index 90c5df3..1c33922 100644 --- a/src/main/java/com/potatocake/everymoment/util/PagingUtil.java +++ b/src/main/java/com/potatocake/everymoment/util/PagingUtil.java @@ -1,6 +1,5 @@ package com.potatocake.everymoment.util; -import com.potatocake.everymoment.entity.Member; import java.util.Map; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -16,14 +15,15 @@ public ScrollPosition createScrollPosition(Long key) { return key == null ? ScrollPosition.offset() : ScrollPosition.forward(Map.of("id", key)); } - public Pageable createPageable(int size) { - return PageRequest.of(0, size, Sort.by(Sort.Direction.ASC, "id")); + public Pageable createPageable(int size, Sort.Direction direction) { + return PageRequest.of(0, size, Sort.by(direction, "id")); } - public Long getNextKey(Window window) { - return window.hasNext() - ? ((Member) window.getContent().get(window.getContent().size() - 1)).getId() - : null; + public Long getNextKey(Window window, IdExtractor idExtractor) { + if (!window.hasNext() || window.getContent().isEmpty()) { + return null; + } + return idExtractor.extractId(window.getContent().get(window.getContent().size() - 1)); } } diff --git a/src/test/java/com/potatocake/everymoment/util/PagingUtilTest.java b/src/test/java/com/potatocake/everymoment/util/PagingUtilTest.java index eb110d9..4808d11 100644 --- a/src/test/java/com/potatocake/everymoment/util/PagingUtilTest.java +++ b/src/test/java/com/potatocake/everymoment/util/PagingUtilTest.java @@ -1,6 +1,7 @@ package com.potatocake.everymoment.util; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.data.domain.Sort.Direction.ASC; import com.potatocake.everymoment.entity.Member; import java.util.List; @@ -35,7 +36,7 @@ void should_CreateScrollPosition_When_KeyIsNull() { @DisplayName("페이지 정보가 성공적으로 생성된다.") void should_CreatePageable_When_ValidSizeProvided() { // when - Pageable pageable = pagingUtil.createPageable(10); + Pageable pageable = pagingUtil.createPageable(10, ASC); // then assertThat(pageable).isNotNull(); @@ -50,7 +51,7 @@ void should_ReturnNextKey_When_WindowHasNext() { Window window = Window.from(members, ScrollPosition::offset, true); // when - Long nextKey = pagingUtil.getNextKey(window); + Long nextKey = pagingUtil.getNextKey(window, Member::getId); // then assertThat(nextKey).isNotNull(); @@ -65,7 +66,7 @@ void should_ReturnNull_When_WindowHasNoNext() { Window window = Window.from(members, ScrollPosition::offset, false); // when - Long nextKey = pagingUtil.getNextKey(window); + Long nextKey = pagingUtil.getNextKey(window, Member::getId); // then assertThat(nextKey).isNull(); From 312b235152bd208937604d6f2d138e956471805e Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 18:37:43 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80/=EC=82=AD=EC=A0=9C=20(=ED=86=A0=EA=B8=80)=20?= =?UTF-8?q?API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/LikeController.java | 30 ++++++++++++ .../potatocake/everymoment/entity/Like.java | 36 ++++++++++++++ .../repository/LikeRepository.java | 11 +++++ .../everymoment/service/LikeService.java | 47 +++++++++++++++++++ 4 files changed, 124 insertions(+) create mode 100644 src/main/java/com/potatocake/everymoment/controller/LikeController.java create mode 100644 src/main/java/com/potatocake/everymoment/entity/Like.java create mode 100644 src/main/java/com/potatocake/everymoment/repository/LikeRepository.java create mode 100644 src/main/java/com/potatocake/everymoment/service/LikeService.java diff --git a/src/main/java/com/potatocake/everymoment/controller/LikeController.java b/src/main/java/com/potatocake/everymoment/controller/LikeController.java new file mode 100644 index 0000000..739aa72 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/controller/LikeController.java @@ -0,0 +1,30 @@ +package com.potatocake.everymoment.controller; + +import com.potatocake.everymoment.dto.SuccessResponse; +import com.potatocake.everymoment.security.MemberDetails; +import com.potatocake.everymoment.service.LikeService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RequestMapping("/api/diaries/{diaryId}/likes") +@RestController +public class LikeController { + + private final LikeService likeService; + + @PostMapping + public ResponseEntity toggleLike(@PathVariable Long diaryId, + @AuthenticationPrincipal MemberDetails memberDetails) { + likeService.toggleLike(memberDetails.getId(), diaryId); + + return ResponseEntity.ok() + .body(SuccessResponse.ok()); + } + +} diff --git a/src/main/java/com/potatocake/everymoment/entity/Like.java b/src/main/java/com/potatocake/everymoment/entity/Like.java new file mode 100644 index 0000000..8f8da95 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/entity/Like.java @@ -0,0 +1,36 @@ +package com.potatocake.everymoment.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.NoArgsConstructor; + +@Table(name = "likes") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class Like { + + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + private Diary diary; + + @Builder + public Like(Long id, Member member, Diary diary) { + this.id = id; + this.member = member; + this.diary = diary; + } + +} diff --git a/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java b/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java new file mode 100644 index 0000000..c93a17d --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java @@ -0,0 +1,11 @@ +package com.potatocake.everymoment.repository; + +import com.potatocake.everymoment.entity.Like; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface LikeRepository extends JpaRepository { + + Optional findByMemberIdAndDiaryId(Long memberId, Long diaryId); + +} diff --git a/src/main/java/com/potatocake/everymoment/service/LikeService.java b/src/main/java/com/potatocake/everymoment/service/LikeService.java new file mode 100644 index 0000000..e7f3894 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/service/LikeService.java @@ -0,0 +1,47 @@ +package com.potatocake.everymoment.service; + +import com.potatocake.everymoment.entity.Diary; +import com.potatocake.everymoment.entity.Like; +import com.potatocake.everymoment.entity.Member; +import com.potatocake.everymoment.exception.ErrorCode; +import com.potatocake.everymoment.exception.GlobalException; +import com.potatocake.everymoment.repository.DiaryRepository; +import com.potatocake.everymoment.repository.LikeRepository; +import com.potatocake.everymoment.repository.MemberRepository; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional +@Service +public class LikeService { + + private final LikeRepository likeRepository; + private final DiaryRepository diaryRepository; + private final MemberRepository memberRepository; + + public void toggleLike(Long memberId, Long diaryId) { + Diary diary = diaryRepository.findById(diaryId) + .orElseThrow(() -> new GlobalException(ErrorCode.DIARY_NOT_FOUND)); + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.MEMBER_NOT_FOUND)); + + Optional existingLike = likeRepository.findByMemberIdAndDiaryId(memberId, diaryId); + + if (existingLike.isPresent()) { + // 이미 좋아요가 존재하면 삭제 (좋아요 취소) + likeRepository.delete(existingLike.get()); + } else { + // 좋아요가 없으면 새로 생성 (좋아요 추가) + Like likeEntity = Like.builder() + .diary(diary) + .member(member) + .build(); + + likeRepository.save(likeEntity); + } + } + +} From 92bf07992970e1680b1c52466d2fbf48fc251f3a Mon Sep 17 00:00:00 2001 From: JunHyeongChoi Date: Wed, 2 Oct 2024 18:46:28 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat:=20=ED=8A=B9=EC=A0=95=20=EC=9D=BC?= =?UTF-8?q?=EA=B8=B0=EC=9D=98=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=88=98=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../everymoment/controller/LikeController.java | 10 ++++++++++ .../everymoment/dto/response/LikeCountResponse.java | 12 ++++++++++++ .../everymoment/repository/LikeRepository.java | 3 +++ .../potatocake/everymoment/service/LikeService.java | 13 +++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 src/main/java/com/potatocake/everymoment/dto/response/LikeCountResponse.java diff --git a/src/main/java/com/potatocake/everymoment/controller/LikeController.java b/src/main/java/com/potatocake/everymoment/controller/LikeController.java index 739aa72..229497e 100644 --- a/src/main/java/com/potatocake/everymoment/controller/LikeController.java +++ b/src/main/java/com/potatocake/everymoment/controller/LikeController.java @@ -1,11 +1,13 @@ package com.potatocake.everymoment.controller; import com.potatocake.everymoment.dto.SuccessResponse; +import com.potatocake.everymoment.dto.response.LikeCountResponse; import com.potatocake.everymoment.security.MemberDetails; import com.potatocake.everymoment.service.LikeService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -18,6 +20,14 @@ public class LikeController { private final LikeService likeService; + @GetMapping + public ResponseEntity> getLikeCount(@PathVariable Long diaryId) { + LikeCountResponse likeCount = likeService.getLikeCount(diaryId); + + return ResponseEntity.ok() + .body(SuccessResponse.ok(likeCount)); + } + @PostMapping public ResponseEntity toggleLike(@PathVariable Long diaryId, @AuthenticationPrincipal MemberDetails memberDetails) { diff --git a/src/main/java/com/potatocake/everymoment/dto/response/LikeCountResponse.java b/src/main/java/com/potatocake/everymoment/dto/response/LikeCountResponse.java new file mode 100644 index 0000000..0db2204 --- /dev/null +++ b/src/main/java/com/potatocake/everymoment/dto/response/LikeCountResponse.java @@ -0,0 +1,12 @@ +package com.potatocake.everymoment.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class LikeCountResponse { + + private Long likeCount; + +} diff --git a/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java b/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java index c93a17d..dc383b1 100644 --- a/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java +++ b/src/main/java/com/potatocake/everymoment/repository/LikeRepository.java @@ -1,5 +1,6 @@ package com.potatocake.everymoment.repository; +import com.potatocake.everymoment.entity.Diary; import com.potatocake.everymoment.entity.Like; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -8,4 +9,6 @@ public interface LikeRepository extends JpaRepository { Optional findByMemberIdAndDiaryId(Long memberId, Long diaryId); + Long countByDiary(Diary diary); + } diff --git a/src/main/java/com/potatocake/everymoment/service/LikeService.java b/src/main/java/com/potatocake/everymoment/service/LikeService.java index e7f3894..5e9bc3d 100644 --- a/src/main/java/com/potatocake/everymoment/service/LikeService.java +++ b/src/main/java/com/potatocake/everymoment/service/LikeService.java @@ -1,5 +1,6 @@ package com.potatocake.everymoment.service; +import com.potatocake.everymoment.dto.response.LikeCountResponse; import com.potatocake.everymoment.entity.Diary; import com.potatocake.everymoment.entity.Like; import com.potatocake.everymoment.entity.Member; @@ -22,6 +23,18 @@ public class LikeService { private final DiaryRepository diaryRepository; private final MemberRepository memberRepository; + @Transactional(readOnly = true) + public LikeCountResponse getLikeCount(Long diaryId) { + Diary diary = diaryRepository.findById(diaryId) + .orElseThrow(() -> new GlobalException(ErrorCode.DIARY_NOT_FOUND)); + + Long likeCount = likeRepository.countByDiary(diary); + + return LikeCountResponse.builder() + .likeCount(likeCount) + .build(); + } + public void toggleLike(Long memberId, Long diaryId) { Diary diary = diaryRepository.findById(diaryId) .orElseThrow(() -> new GlobalException(ErrorCode.DIARY_NOT_FOUND));