diff --git a/src/main/java/com/codeit/todo/repository/CompleteRepository.java b/src/main/java/com/codeit/todo/repository/CompleteRepository.java index 444820d..c1165e4 100644 --- a/src/main/java/com/codeit/todo/repository/CompleteRepository.java +++ b/src/main/java/com/codeit/todo/repository/CompleteRepository.java @@ -1,10 +1,20 @@ package com.codeit.todo.repository; import com.codeit.todo.domain.Complete; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; public interface CompleteRepository extends JpaRepository { List findByTodo_TodoId(int todoId); + + @Query("select c from Complete c where c.todo.goal.user.userId in :userIds order by c.createdAt desc") + Slice findByFollowees(@Param("userIds") List userIds, Pageable pageable); + + @Query("select c from Complete c where c.todo.goal.user.userId in :userIds and c.completeId > :completeId order by c.createdAt desc") + Slice findByFolloweesAfterCompleteId(@Param("userIds")List followeeIds, @Param("completeId") Integer completeId, Pageable pageable); } diff --git a/src/main/java/com/codeit/todo/repository/FollowRepository.java b/src/main/java/com/codeit/todo/repository/FollowRepository.java index f6864ad..0246b80 100644 --- a/src/main/java/com/codeit/todo/repository/FollowRepository.java +++ b/src/main/java/com/codeit/todo/repository/FollowRepository.java @@ -6,6 +6,8 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.List; + public interface FollowRepository extends JpaRepository { @Query( "SELECT COUNT(f) > 0 " + @@ -24,5 +26,7 @@ public interface FollowRepository extends JpaRepository { int countByFollowee(@Param("userId") int userId); + @Query("select f.followee.userId from Follow f where f.follower.userId = :userId") + List findFolloweeIdsByFollowerId(@Param("userId") int userId); } diff --git a/src/main/java/com/codeit/todo/service/follow/FollowService.java b/src/main/java/com/codeit/todo/service/follow/FollowService.java new file mode 100644 index 0000000..b3a30c2 --- /dev/null +++ b/src/main/java/com/codeit/todo/service/follow/FollowService.java @@ -0,0 +1,9 @@ +package com.codeit.todo.service.follow; + +import com.codeit.todo.web.dto.request.follow.ReadFollowRequest; +import com.codeit.todo.web.dto.response.follow.ReadFollowResponse; +import org.springframework.data.domain.Slice; + +public interface FollowService { + Slice readFollows(int userId, ReadFollowRequest request); +} 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 new file mode 100644 index 0000000..f37ae49 --- /dev/null +++ b/src/main/java/com/codeit/todo/service/follow/impl/FollowServiceImpl.java @@ -0,0 +1,58 @@ +package com.codeit.todo.service.follow.impl; + +import com.codeit.todo.domain.Complete; +import com.codeit.todo.repository.CompleteRepository; +import com.codeit.todo.repository.FollowRepository; +import com.codeit.todo.repository.LikesRepository; +import com.codeit.todo.service.follow.FollowService; +import com.codeit.todo.web.dto.request.follow.ReadFollowRequest; +import com.codeit.todo.web.dto.response.follow.ReadFollowResponse; +import com.codeit.todo.web.dto.response.slice.CustomSlice; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Objects; + +@Service +@RequiredArgsConstructor +@Transactional +public class FollowServiceImpl implements FollowService { + + private final FollowRepository followRepository; + private final CompleteRepository completeRepository; + private final LikesRepository likesRepository; + + @Transactional(readOnly = true) + @Override + public Slice readFollows(int userId, ReadFollowRequest request) { + List followeeIds = followRepository.findFolloweeIdsByFollowerId(userId); + + Pageable pageable = PageRequest.of(0, request.size()); + + Slice completes; + if (Objects.isNull(request.lastCompleteId()) || request.lastCompleteId() <= 0) { + completes = completeRepository.findByFollowees(followeeIds, pageable); + } else { + completes = completeRepository.findByFolloweesAfterCompleteId(followeeIds, request.lastCompleteId(), pageable); + } + + List responses = completes.stream() + .map(complete -> { + Boolean likeStatus = likesRepository.existsByUser_UserIdAndComplete_CompleteId(userId, complete.getCompleteId()); + + return ReadFollowResponse.from(complete, likeStatus); + }) + .toList(); + + Integer nextCursor = completes.hasNext() + ? completes.getContent().get(completes.getContent().size() - 1).getCompleteId() + : null; + + return new CustomSlice<>(responses, pageable, completes.hasNext(), nextCursor); + } +} diff --git a/src/main/java/com/codeit/todo/web/controller/FollowController.java b/src/main/java/com/codeit/todo/web/controller/FollowController.java new file mode 100644 index 0000000..d4c5bb5 --- /dev/null +++ b/src/main/java/com/codeit/todo/web/controller/FollowController.java @@ -0,0 +1,35 @@ +package com.codeit.todo.web.controller; + +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.ReadFollowResponse; +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; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/follows") +public class FollowController { + + private final FollowService followService; + + + @GetMapping + public Response> getFollows( + @AuthenticationPrincipal CustomUserDetails userDetails, + @Valid @ModelAttribute ReadFollowRequest request + ) { + + int userId = userDetails.getUserId(); + return Response.ok(followService.readFollows(userId, request)); + } +} diff --git a/src/main/java/com/codeit/todo/web/dto/request/follow/ReadFollowRequest.java b/src/main/java/com/codeit/todo/web/dto/request/follow/ReadFollowRequest.java new file mode 100644 index 0000000..5303368 --- /dev/null +++ b/src/main/java/com/codeit/todo/web/dto/request/follow/ReadFollowRequest.java @@ -0,0 +1,12 @@ +package com.codeit.todo.web.dto.request.follow; + +import jakarta.validation.constraints.Min; +import lombok.Builder; + +@Builder +public record ReadFollowRequest( + Integer lastCompleteId, + @Min(1) + int size +) { +} diff --git a/src/main/java/com/codeit/todo/web/dto/response/follow/ReadFollowResponse.java b/src/main/java/com/codeit/todo/web/dto/response/follow/ReadFollowResponse.java new file mode 100644 index 0000000..407ffbf --- /dev/null +++ b/src/main/java/com/codeit/todo/web/dto/response/follow/ReadFollowResponse.java @@ -0,0 +1,29 @@ +package com.codeit.todo.web.dto.response.follow; + +import com.codeit.todo.domain.Complete; +import lombok.Builder; + +import java.time.LocalDateTime; + +@Builder +public record ReadFollowResponse( + int completeId, + String completePic, + LocalDateTime createdAt, + Boolean likeStatus, + int likeCount, + int commentCount +) { + + + public static ReadFollowResponse from(Complete complete, Boolean likeStatus) { + return ReadFollowResponse.builder() + .completeId(complete.getCompleteId()) + .completePic(complete.getCompletePic()) + .createdAt(complete.getCreatedAt()) + .likeStatus(likeStatus) + .likeCount(complete.getLikes().size()) + .commentCount(complete.getComments().size()) + .build(); + } +}