diff --git a/src/main/java/com/codeit/todo/common/exception/follow/FollowNotFoundException.java b/src/main/java/com/codeit/todo/common/exception/follow/FollowNotFoundException.java new file mode 100644 index 0000000..7fd4bbc --- /dev/null +++ b/src/main/java/com/codeit/todo/common/exception/follow/FollowNotFoundException.java @@ -0,0 +1,14 @@ +package com.codeit.todo.common.exception.follow; + +import com.codeit.todo.common.exception.EntityNotFoundException; + +public class FollowNotFoundException extends EntityNotFoundException { + private static final String ENTITY_TYPE = "follow"; + + /** + * @param request 엔티티를 찾기 위해 요청한 값 + */ + public FollowNotFoundException(String request) { + super(request, ENTITY_TYPE); + } +} diff --git a/src/main/java/com/codeit/todo/domain/Follow.java b/src/main/java/com/codeit/todo/domain/Follow.java index 3f91e4c..ab2555c 100644 --- a/src/main/java/com/codeit/todo/domain/Follow.java +++ b/src/main/java/com/codeit/todo/domain/Follow.java @@ -29,5 +29,10 @@ public Follow(User follower, User followee){ this.followee = followee; } - + public static Follow from(User follower, User followee) { + return Follow.builder() + .follower(follower) + .followee(followee) + .build(); + } } diff --git a/src/main/java/com/codeit/todo/repository/FollowRepository.java b/src/main/java/com/codeit/todo/repository/FollowRepository.java index 0246b80..75f0f9f 100644 --- a/src/main/java/com/codeit/todo/repository/FollowRepository.java +++ b/src/main/java/com/codeit/todo/repository/FollowRepository.java @@ -7,6 +7,7 @@ import org.springframework.data.repository.query.Param; import java.util.List; +import java.util.Optional; public interface FollowRepository extends JpaRepository { @Query( @@ -28,5 +29,7 @@ public interface FollowRepository extends JpaRepository { @Query("select f.followee.userId from Follow f where f.follower.userId = :userId") List findFolloweeIdsByFollowerId(@Param("userId") int userId); + + Optional findByFollower_UserIdAndFollowee_UserId(int followerId, int followeeId); } diff --git a/src/main/java/com/codeit/todo/service/follow/FollowService.java b/src/main/java/com/codeit/todo/service/follow/FollowService.java index b3a30c2..7c5862f 100644 --- a/src/main/java/com/codeit/todo/service/follow/FollowService.java +++ b/src/main/java/com/codeit/todo/service/follow/FollowService.java @@ -1,9 +1,15 @@ package com.codeit.todo.service.follow; import com.codeit.todo.web.dto.request.follow.ReadFollowRequest; +import com.codeit.todo.web.dto.response.follow.CreateFollowResponse; +import com.codeit.todo.web.dto.response.follow.DeleteFollowResponse; import com.codeit.todo.web.dto.response.follow.ReadFollowResponse; import org.springframework.data.domain.Slice; public interface FollowService { Slice readFollows(int userId, ReadFollowRequest request); + + CreateFollowResponse registerFollow(int followerId, int followeeId); + + DeleteFollowResponse cancelFollow(int followerId, int followeeId); } diff --git a/src/main/java/com/codeit/todo/service/follow/impl/FollowServiceImpl.java b/src/main/java/com/codeit/todo/service/follow/impl/FollowServiceImpl.java index f37ae49..6f90b98 100644 --- a/src/main/java/com/codeit/todo/service/follow/impl/FollowServiceImpl.java +++ b/src/main/java/com/codeit/todo/service/follow/impl/FollowServiceImpl.java @@ -1,11 +1,19 @@ package com.codeit.todo.service.follow.impl; +import com.codeit.todo.common.exception.auth.AuthorizationDeniedException; +import com.codeit.todo.common.exception.follow.FollowNotFoundException; +import com.codeit.todo.common.exception.user.UserNotFoundException; import com.codeit.todo.domain.Complete; +import com.codeit.todo.domain.Follow; +import com.codeit.todo.domain.User; import com.codeit.todo.repository.CompleteRepository; import com.codeit.todo.repository.FollowRepository; import com.codeit.todo.repository.LikesRepository; +import com.codeit.todo.repository.UserRepository; import com.codeit.todo.service.follow.FollowService; import com.codeit.todo.web.dto.request.follow.ReadFollowRequest; +import com.codeit.todo.web.dto.response.follow.CreateFollowResponse; +import com.codeit.todo.web.dto.response.follow.DeleteFollowResponse; import com.codeit.todo.web.dto.response.follow.ReadFollowResponse; import com.codeit.todo.web.dto.response.slice.CustomSlice; import lombok.RequiredArgsConstructor; @@ -26,6 +34,7 @@ public class FollowServiceImpl implements FollowService { private final FollowRepository followRepository; private final CompleteRepository completeRepository; private final LikesRepository likesRepository; + private final UserRepository userRepository; @Transactional(readOnly = true) @Override @@ -55,4 +64,38 @@ public Slice readFollows(int userId, ReadFollowRequest reque return new CustomSlice<>(responses, pageable, completes.hasNext(), nextCursor); } + + @Override + public CreateFollowResponse registerFollow(int followerId, int followeeId) { + if (followRepository.existsByFollower_FollowerIdAndFollowee_FolloweeId(followeeId, followerId)) { + throw new AuthorizationDeniedException("이미 팔로우로 등록한 회원입니다."); + } + + User follower = getUser(followerId); + User followee = getUser(followeeId); + + Follow follow = Follow.from(follower, followee); + Follow savedFollow = followRepository.save(follow); + + return CreateFollowResponse.fromEntity(savedFollow); + } + + @Override + public DeleteFollowResponse cancelFollow(int followerId, int followeeId) { + if (!followRepository.existsByFollower_FollowerIdAndFollowee_FolloweeId(followeeId, followerId)) { + throw new AuthorizationDeniedException("팔로우 내역이 존재하지 않습니다."); + } + + Follow follow = followRepository.findByFollower_UserIdAndFollowee_UserId(followerId, followeeId) + .orElseThrow(() -> new FollowNotFoundException("FollowerId : " + followerId + ", FolloweeId : " + followeeId)); + + followRepository.delete(follow); + + return DeleteFollowResponse.from(followerId, followeeId); + } + + private User getUser(int userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new UserNotFoundException(String.valueOf(userId), "User")); + } } diff --git a/src/main/java/com/codeit/todo/web/controller/FollowController.java b/src/main/java/com/codeit/todo/web/controller/FollowController.java index d4c5bb5..a162515 100644 --- a/src/main/java/com/codeit/todo/web/controller/FollowController.java +++ b/src/main/java/com/codeit/todo/web/controller/FollowController.java @@ -3,17 +3,18 @@ import com.codeit.todo.repository.CustomUserDetails; import com.codeit.todo.service.follow.FollowService; import com.codeit.todo.web.dto.request.follow.ReadFollowRequest; -import com.codeit.todo.web.dto.request.todo.ReadTodoCompleteWithGoalRequest; import com.codeit.todo.web.dto.response.Response; +import com.codeit.todo.web.dto.response.follow.CreateFollowResponse; +import com.codeit.todo.web.dto.response.follow.DeleteFollowResponse; import com.codeit.todo.web.dto.response.follow.ReadFollowResponse; +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.data.domain.Slice; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @@ -23,6 +24,10 @@ public class FollowController { private final FollowService followService; + @Operation(summary = "팔로우의 최근 등록한 인증글 목록 조회", description = "팔로우의 최근 등록한 인증글 목록 조회 API") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공") + }) @GetMapping public Response> getFollows( @AuthenticationPrincipal CustomUserDetails userDetails, @@ -32,4 +37,30 @@ public Response> getFollows( int userId = userDetails.getUserId(); return Response.ok(followService.readFollows(userId, request)); } + + @Operation(summary = "팔로우 등록", description = "팔로우 등록 API") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공") + }) + @PostMapping("{followerId}") + public Response createFollow( + @AuthenticationPrincipal CustomUserDetails userDetails, + @PathVariable int followerId + ) { + int followeeId = userDetails.getUserId(); + return Response.ok(followService.registerFollow(followerId, followeeId)); + } + + @Operation(summary = "팔로우 취소", description = "팔로우 취소 API") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "조회 성공") + }) + @DeleteMapping("{followerId}") + public Response cancelFollow( + @AuthenticationPrincipal CustomUserDetails userDetails, + @PathVariable int followerId + ) { + int followeeId = userDetails.getUserId(); + return Response.ok(followService.cancelFollow(followerId, followeeId)); + } } diff --git a/src/main/java/com/codeit/todo/web/dto/response/follow/CreateFollowResponse.java b/src/main/java/com/codeit/todo/web/dto/response/follow/CreateFollowResponse.java new file mode 100644 index 0000000..dc9f0d3 --- /dev/null +++ b/src/main/java/com/codeit/todo/web/dto/response/follow/CreateFollowResponse.java @@ -0,0 +1,15 @@ +package com.codeit.todo.web.dto.response.follow; + +import com.codeit.todo.domain.Follow; +import lombok.Builder; + +@Builder +public record CreateFollowResponse( + int followId +) { + public static CreateFollowResponse fromEntity(Follow follow) { + return CreateFollowResponse.builder() + .followId(follow.getFollowId()) + .build(); + } +} diff --git a/src/main/java/com/codeit/todo/web/dto/response/follow/DeleteFollowResponse.java b/src/main/java/com/codeit/todo/web/dto/response/follow/DeleteFollowResponse.java new file mode 100644 index 0000000..f23bc0b --- /dev/null +++ b/src/main/java/com/codeit/todo/web/dto/response/follow/DeleteFollowResponse.java @@ -0,0 +1,13 @@ +package com.codeit.todo.web.dto.response.follow; + +import lombok.Builder; + +@Builder +public record DeleteFollowResponse(int followerId, int followeeId) { + public static DeleteFollowResponse from(int followerId, int followeeId) { + return DeleteFollowResponse.builder() + .followerId(followerId) + .followeeId(followeeId) + .build(); + } +}