From dda6919f818ee246d3e9d457a13be7cc5fa4bf97 Mon Sep 17 00:00:00 2001 From: kpeel5839 <89840550+kpeel5839@users.noreply.github.com> Date: Fri, 9 Aug 2024 01:35:50 +0900 Subject: [PATCH] =?UTF-8?q?#67,75=20-=20Cursor=20Based=20Pagination?= =?UTF-8?q?=EC=9D=84=20=EB=8F=84=EC=9E=85=ED=95=98=EA=B3=A0,=20Sent=20Vote?= =?UTF-8?q?=20=EC=8A=A4=ED=8E=99=EC=9D=84=20=EB=B3=80=EA=B2=BD=ED=95=9C?= =?UTF-8?q?=EB=8B=A4.=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Vote쪽 Sent Votes 로직 수정 * refactor: SentVotes 스펙 변경 완료 * test: 스펙 변경 이후 테스트 수정 * test: CompleteBallotTest 추가 * feat: Notification 목록 조회에 Cursor Based Pagination 적용 * feat: CursorBased Pagination 적용 * test: 테스트 수정 * refactor: 사용하지 않는 메서드 제거 * test: Fixture 추가 * test: CursorBased Repository Test 추가 * refactor: VoteId 추가 및 테스트 보강 * chore: UserSettings CustomUrlFilter에 추가 * refactor: CursorUtils 사용 --- .../wespot/config/security/CustomUrlFilter.kt | 1 + .../notification/NotificationController.kt | 8 +- .../com/wespot/report/ReportController.kt | 1 - .../kotlin/com/wespot/vote/VoteController.kt | 25 +++--- .../mysql/NotificationJpaRepositoryTest.kt | 51 +++++++++++ .../service/InquiryNotificationServiceTest.kt | 16 ++-- .../wespot/vote/domain/CompleteBallotTest.kt | 84 +++++++++++++++++++ .../kotlin/com/wespot/vote/domain/VoteTest.kt | 83 ++++-------------- .../vote/fixture/VoteJpaEntityFixture.kt | 14 ++++ .../mysql/VoteJpaRepositoryTest.kt | 71 ++++++++++++++++ .../vote/service/ReceivedVoteServiceTest.kt | 36 ++++---- .../vote/service/SentVoteServiceTest.kt | 82 +++++------------- .../notification/dto/NotificationResponses.kt | 5 +- .../port/in/InquiryNotificationUseCase.kt | 2 +- .../notification/port/out/NotificationPort.kt | 2 +- .../service/InquiryNotificationService.kt | 12 ++- .../service/NotificationFinder.kt | 9 +- .../vote/dto/response/VoteUserResponse.kt | 2 + .../received/ReceivedVotesResponse.kt | 2 + .../received/ReceivedVotesResponses.kt | 7 +- .../dto/response/sent/SentVotesResponse.kt | 10 +-- .../dto/response/sent/SentVotesResponses.kt | 12 +-- .../response/sent/SentVotesResultsResponse.kt | 13 +-- .../dto/response/sent/SingleVoteResponse.kt | 20 +++++ .../top1/VoteDetailResultResponseOfTop1.kt | 5 +- .../response/top1/VoteUserResponseOfTop1.kt | 25 ------ .../top5/VoteDetailResultResponseOfTop5.kt | 5 +- .../response/top5/VoteUserResponseOfTop5.kt | 26 ------ .../vote/port/in/ReceivedVoteUseCase.kt | 2 +- .../wespot/vote/port/in/SentVoteUseCase.kt | 6 +- .../com/wespot/vote/port/out/VotePort.kt | 4 +- .../vote/service/ReceivedVoteService.kt | 14 +++- .../wespot/vote/service/SentVoteService.kt | 42 ++++------ .../vote/service/helper/VoteServiceHelper.kt | 19 +---- .../kotlin/com/wespot/vote/CompleteBallot.kt | 52 ++++++++++++ .../src/main/kotlin/com/wespot/vote/Vote.kt | 32 +++---- .../notification/NotificationJpaRepository.kt | 17 +++- .../NotificationPersistenceAdapter.kt | 4 +- .../com/wespot/vote/VoteJpaRepository.kt | 17 +++- .../com/wespot/vote/VotePersistenceAdapter.kt | 27 +++--- 40 files changed, 522 insertions(+), 343 deletions(-) create mode 100644 app/src/test/kotlin/com/wespot/notification/infrastructure/mysql/NotificationJpaRepositoryTest.kt create mode 100644 app/src/test/kotlin/com/wespot/vote/domain/CompleteBallotTest.kt create mode 100644 app/src/test/kotlin/com/wespot/vote/infrastructure/mysql/VoteJpaRepositoryTest.kt create mode 100644 core/src/main/kotlin/com/wespot/vote/dto/response/sent/SingleVoteResponse.kt delete mode 100644 core/src/main/kotlin/com/wespot/vote/dto/response/top1/VoteUserResponseOfTop1.kt delete mode 100644 core/src/main/kotlin/com/wespot/vote/dto/response/top5/VoteUserResponseOfTop5.kt create mode 100644 domain/src/main/kotlin/com/wespot/vote/CompleteBallot.kt diff --git a/app/src/main/kotlin/com/wespot/config/security/CustomUrlFilter.kt b/app/src/main/kotlin/com/wespot/config/security/CustomUrlFilter.kt index 866de3de..94e897cf 100644 --- a/app/src/main/kotlin/com/wespot/config/security/CustomUrlFilter.kt +++ b/app/src/main/kotlin/com/wespot/config/security/CustomUrlFilter.kt @@ -33,6 +33,7 @@ class CustomUrlFilter( "/api/v1/users/backgrounds", "/api/v1/users/characters", "/api/v1/users/search", + "/api/v1/users/settings", "/api/v1/messages", "/api/v1/messages/send", "/api/v1/messages/status/me", diff --git a/app/src/main/kotlin/com/wespot/notification/NotificationController.kt b/app/src/main/kotlin/com/wespot/notification/NotificationController.kt index 7096e0c6..c0e91360 100644 --- a/app/src/main/kotlin/com/wespot/notification/NotificationController.kt +++ b/app/src/main/kotlin/com/wespot/notification/NotificationController.kt @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PatchMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @@ -17,8 +18,11 @@ class NotificationController( ) { @GetMapping - fun getNotifications(): ResponseEntity { - val notifications = inquiryNotificationUseCase.getNotifications() + fun getNotifications( + @RequestParam(required = false) cursorId: Long?, + @RequestParam limit: Long + ): ResponseEntity { + val notifications = inquiryNotificationUseCase.getNotifications(cursorId, limit) return ResponseEntity.ok(notifications) } diff --git a/app/src/main/kotlin/com/wespot/report/ReportController.kt b/app/src/main/kotlin/com/wespot/report/ReportController.kt index c5d0418e..9c1b0628 100644 --- a/app/src/main/kotlin/com/wespot/report/ReportController.kt +++ b/app/src/main/kotlin/com/wespot/report/ReportController.kt @@ -5,7 +5,6 @@ import com.wespot.report.dto.ReportResponse import com.wespot.report.port.`in`.SavedReportUseCase import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping diff --git a/app/src/main/kotlin/com/wespot/vote/VoteController.kt b/app/src/main/kotlin/com/wespot/vote/VoteController.kt index 39b3088e..d960bcdb 100644 --- a/app/src/main/kotlin/com/wespot/vote/VoteController.kt +++ b/app/src/main/kotlin/com/wespot/vote/VoteController.kt @@ -5,7 +5,6 @@ import com.wespot.vote.dto.response.SaveVoteResponse import com.wespot.vote.dto.response.VoteItems import com.wespot.vote.dto.response.received.ReceivedVoteResponse import com.wespot.vote.dto.response.received.ReceivedVotesResponses -import com.wespot.vote.dto.response.sent.SentVoteResponse import com.wespot.vote.dto.response.sent.SentVotesResponses import com.wespot.vote.dto.response.top1.VoteResultResponsesOfTop1 import com.wespot.vote.dto.response.top5.VoteResultResponsesOfTop5 @@ -67,8 +66,11 @@ class VoteController( } @GetMapping("/received") - fun getReceivedVotes(): ResponseEntity { - val responses = receivedVoteUseCase.getReceivedVotes() + fun getReceivedVotes( + @RequestParam(required = false) cursorId: Long?, + @RequestParam limit: Long + ): ResponseEntity { + val responses = receivedVoteUseCase.getReceivedVotes(cursorId, limit) return ResponseEntity.ok(responses) } @@ -84,20 +86,13 @@ class VoteController( } @GetMapping("/sent") - fun getSentVotes(): ResponseEntity { - val responses = sentVoteUseCase.getSentVotes() + fun getSentVotes( + @RequestParam(required = false) cursorId: Long?, + @RequestParam limit: Long + ): ResponseEntity { + val responses = sentVoteUseCase.getSentVotes(cursorId, limit) return ResponseEntity.ok(responses) } - @GetMapping("/sent/options/{optionId}") - fun getSentVote( - @PathVariable optionId: Long, - @RequestParam date: LocalDate - ): ResponseEntity { - val response = sentVoteUseCase.getSentVote(optionId, date) - - return ResponseEntity.ok(response) - } - } diff --git a/app/src/test/kotlin/com/wespot/notification/infrastructure/mysql/NotificationJpaRepositoryTest.kt b/app/src/test/kotlin/com/wespot/notification/infrastructure/mysql/NotificationJpaRepositoryTest.kt new file mode 100644 index 00000000..cfc912c1 --- /dev/null +++ b/app/src/test/kotlin/com/wespot/notification/infrastructure/mysql/NotificationJpaRepositoryTest.kt @@ -0,0 +1,51 @@ +package com.wespot.notification.infrastructure.mysql + +import com.wespot.notification.NotificationJpaEntity +import com.wespot.notification.NotificationJpaRepository +import com.wespot.notification.NotificationMapper +import com.wespot.notification.NotificationType +import com.wespot.notification.fixtrue.NotificationFixture +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest + +@DataJpaTest +class NotificationJpaRepositoryTest @Autowired constructor( + private val notificationJpaRepository: NotificationJpaRepository +) { + + @Test + fun `알림 목록 조회에 Cursor Based Pagination을 도입한다`() { + // given + val notification1 = createSavedNotification() + val notification2 = createSavedNotification() + val notification3 = createSavedNotification() + val notification4 = createSavedNotification() + val notification5 = createSavedNotification() + val userId = notification1.userId + + // when + val firstSearch = + notificationJpaRepository.findAllByUserIdOrderByBaseEntityCreatedAtDesc(userId, Long.MAX_VALUE, 3) + val secondSearch = + notificationJpaRepository.findAllByUserIdOrderByBaseEntityCreatedAtDesc(userId, firstSearch[2].id, 4) + + // then + firstSearch.size shouldBe 3 + firstSearch[0] shouldBe notification5 + firstSearch[1] shouldBe notification4 + firstSearch[2] shouldBe notification3 + secondSearch.size shouldBe 2 + secondSearch[0] shouldBe notification2 + secondSearch[1] shouldBe notification1 + } + + private fun createSavedNotification(): NotificationJpaEntity { + val notification = NotificationFixture.createWithType(NotificationType.MESSAGE) + val notificationJpaEntity = NotificationMapper.mapToJpaEntity(notification) + + return notificationJpaRepository.save(notificationJpaEntity) + } + +} diff --git a/app/src/test/kotlin/com/wespot/notification/service/InquiryNotificationServiceTest.kt b/app/src/test/kotlin/com/wespot/notification/service/InquiryNotificationServiceTest.kt index 02524607..5de39340 100644 --- a/app/src/test/kotlin/com/wespot/notification/service/InquiryNotificationServiceTest.kt +++ b/app/src/test/kotlin/com/wespot/notification/service/InquiryNotificationServiceTest.kt @@ -41,12 +41,16 @@ class InquiryNotificationServiceTest @Autowired constructor( ) // when - val responses = inquiryNotificationService.getNotifications().notifications + val responses1 = inquiryNotificationService.getNotifications(null, 1) + val responses2= inquiryNotificationService.getNotifications(responses1.notifications[0].id, 1) // then - responses.size shouldBe 2 - responses[1].id shouldBe notifications[0].id - responses[0].id shouldBe savedNotification.id + responses1.notifications.size shouldBe 1 + responses1.notifications[0].id shouldBe savedNotification.id + responses1.hasNext shouldBe true + responses2.notifications.size shouldBe 1 + responses2.notifications[0].id shouldBe notifications[0].id + responses2.hasNext shouldBe false } @Test @@ -72,9 +76,9 @@ class InquiryNotificationServiceTest @Autowired constructor( ) // when - val responses = inquiryNotificationService.getNotifications().notifications + val responses = inquiryNotificationService.getNotifications(null, 100).notifications inquiryNotificationService.readNotification(responses[0].id) - val actual = inquiryNotificationService.getNotifications().notifications + val actual = inquiryNotificationService.getNotifications(null, 100).notifications // then actual.size shouldBe 2 diff --git a/app/src/test/kotlin/com/wespot/vote/domain/CompleteBallotTest.kt b/app/src/test/kotlin/com/wespot/vote/domain/CompleteBallotTest.kt new file mode 100644 index 00000000..2394f11e --- /dev/null +++ b/app/src/test/kotlin/com/wespot/vote/domain/CompleteBallotTest.kt @@ -0,0 +1,84 @@ +package com.wespot.vote.domain + +import com.wespot.user.fixture.UserFixture +import com.wespot.vote.CompleteBallot +import com.wespot.vote.fixture.BallotFixture +import com.wespot.vote.fixture.VoteFixture +import com.wespot.voteoption.fixture.VoteOptionFixture +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.throwable.shouldHaveMessage + +class CompleteBallotTest : BehaviorSpec({ + + given("완전한 투표지를 만들 때") { + val ballot = + BallotFixture.createByVoteAndVoteOptionAndSenderAndReceiver(1, 1, 1, 2) + val validSender = UserFixture.createWithId(1) + val invalidSender = UserFixture.createWithId(2) + val validReceiver = UserFixture.createWithId(2) + val invalidReceiver = UserFixture.createWithId(1) + val validVote = VoteFixture.createWithIdAndVoteNumberAndBallots(1, 0, emptyList()) + val invalidVote = VoteFixture.createWithIdAndVoteNumberAndBallots(2, 0, emptyList()) + val validVoteOption = VoteOptionFixture.createWithId(1) + val invalidVoteOption = VoteOptionFixture.createWithId(2) + `when`("입력된 값이 투표지와 동일하지 않다면") { + val shouldThrow1 = shouldThrow { + CompleteBallot.of( + invalidVote, + validVoteOption, + validSender, + validReceiver, + ballot + ) + } + val shouldThrow2 = shouldThrow { + CompleteBallot.of( + validVote, + invalidVoteOption, + validSender, + validReceiver, + ballot + ) + } + val shouldThrow3 = shouldThrow { + CompleteBallot.of( + validVote, + validVoteOption, + invalidSender, + validReceiver, + ballot + ) + } + val shouldThrow4 = shouldThrow { + CompleteBallot.of( + validVote, + validVoteOption, + validSender, + invalidReceiver, + ballot + ) + } + then("예외가 발생한다.") { + shouldThrow1 shouldHaveMessage "입력된 투표가 잘못되었습니다." + shouldThrow2 shouldHaveMessage "입력된 선택지가 잘못되었습니다." + shouldThrow3 shouldHaveMessage "입력된 송신자가 잘못되었습니다." + shouldThrow4 shouldHaveMessage "입력된 수신자가 잘못되었습니다." + } + } + `when`("정상적인 값이 입력된다면") { + val completeBallot = CompleteBallot.of(validVote, validVoteOption, validSender, validReceiver, ballot) + then("정상적으로 생성된다.") { + completeBallot.vote shouldBe validVote + completeBallot.voteOption shouldBe validVoteOption + completeBallot.sender shouldBe validSender + completeBallot.receiver shouldBe validReceiver + completeBallot.createdAt shouldBe ballot.createdAt + completeBallot.updatedAt shouldBe ballot.updatedAt + completeBallot.isReceiverRead shouldBe ballot.isReceiverRead + } + } + } + +}) diff --git a/app/src/test/kotlin/com/wespot/vote/domain/VoteTest.kt b/app/src/test/kotlin/com/wespot/vote/domain/VoteTest.kt index b8a4c5a9..e1fcc31f 100644 --- a/app/src/test/kotlin/com/wespot/vote/domain/VoteTest.kt +++ b/app/src/test/kotlin/com/wespot/vote/domain/VoteTest.kt @@ -161,7 +161,7 @@ class VoteTest() : BehaviorSpec({ val vote = VoteFixture.createWithVoteNumberAndBallots(0, ballots) val me = users[0] val voteUsers = vote.findUsersForVote(users, me) - val userCounts= voteUsers.map { it.id }.toSet() + val userCounts = voteUsers.map { it.id }.toSet() val doesNotContainsMe = voteUsers.stream().allMatch { it.id != users[0].id } then("정상적으로 반환한다.") { voteUsers.size shouldBe 5 @@ -491,75 +491,22 @@ class VoteTest() : BehaviorSpec({ it.createdAt ) } - val userReceivedVote = vote.getUserSentVotes(users[0]) + val userSentVotes = vote.getUserSentVotes(users[0], users) then("결과를 정상적으로 반환한다.") { - userReceivedVote.size shouldBe 3 - userReceivedVote[voteOptions[0]]!!.size shouldBe 2 - userReceivedVote[voteOptions[0]]!!.size shouldBe 2 - userReceivedVote[voteOptions[0]]!![0].receiverId shouldBe 2 - userReceivedVote[voteOptions[0]]!![0].senderId shouldBe 1 - userReceivedVote[voteOptions[0]]!![1].receiverId shouldBe 3 - userReceivedVote[voteOptions[0]]!![1].senderId shouldBe 1 - userReceivedVote[voteOptions[1]]!!.size shouldBe 1 - userReceivedVote[voteOptions[1]]!![0].senderId shouldBe 1 - userReceivedVote[voteOptions[1]]!![0].receiverId shouldBe 4 - userReceivedVote[voteOptions[2]]!!.size shouldBe 1 - userReceivedVote[voteOptions[2]]!![0].senderId shouldBe 1 - userReceivedVote[voteOptions[2]]!![0].receiverId shouldBe 5 - } - } - - `when`("개별 조회하는 경우, 오늘의 질문지가 아니면") { - val users = createUserByCount(5) - val voteOptions = createVoteOptionByCount(10) - val vote = Vote.of(voteIdentifier, voteOptions, null) - ballots.forEach { - vote.addBallot( - it.voteOptionId, - UserFixture.createWithId(it.senderId), - UserFixture.createWithId(it.receiverId), - it.createdAt - ) - } - - then("예외가 발생한다.") { - val shouldThrow = shouldThrow { - vote.getUserSentVote( - voteOptions[5], - users[0] - ) - } - shouldThrow shouldHaveMessage "오늘 제공된 질문지만 선택해 투표할 수 있습니다." - } - } - `when`("개별 조회하는 경우") { - val users = createUserByCount(5) - val voteOptions = createVoteOptionByCount(10) - val vote = Vote.of(voteIdentifier, voteOptions, null) - ballots.forEach { - vote.addBallot( - it.voteOptionId, - UserFixture.createWithId(it.senderId), - UserFixture.createWithId(it.receiverId), - it.createdAt - ) - } - val userSentVoteByFirstVoteOption = vote.getUserSentVote(voteOptions[0], users[0]) - val userSentVoteBySecondVoteOption = vote.getUserSentVote(voteOptions[1], users[0]) - - then("결과를 정상적으로 반환한다.") { - userSentVoteByFirstVoteOption.size shouldBe 2 - userSentVoteByFirstVoteOption[0].voteOptionId shouldBe 1 - userSentVoteByFirstVoteOption[0].senderId shouldBe 1 - userSentVoteByFirstVoteOption[0].receiverId shouldBe 2 - userSentVoteByFirstVoteOption[1].voteOptionId shouldBe 1 - userSentVoteByFirstVoteOption[1].senderId shouldBe 1 - userSentVoteByFirstVoteOption[1].receiverId shouldBe 3 - userSentVoteBySecondVoteOption.size shouldBe 1 - userSentVoteBySecondVoteOption[0].voteOptionId shouldBe 2 - userSentVoteBySecondVoteOption[0].senderId shouldBe 1 - userSentVoteBySecondVoteOption[0].receiverId shouldBe 4 + userSentVotes.size shouldBe 4 + userSentVotes[0].voteOption shouldBe voteOptions[0] + userSentVotes[0].sender shouldBe users[0] + userSentVotes[0].receiver shouldBe users[1] + userSentVotes[1].voteOption shouldBe voteOptions[0] + userSentVotes[1].sender shouldBe users[0] + userSentVotes[1].receiver shouldBe users[2] + userSentVotes[2].voteOption shouldBe voteOptions[1] + userSentVotes[2].sender shouldBe users[0] + userSentVotes[2].receiver shouldBe users[3] + userSentVotes[3].voteOption shouldBe voteOptions[2] + userSentVotes[3].sender shouldBe users[0] + userSentVotes[3].receiver shouldBe users[4] } } } diff --git a/app/src/test/kotlin/com/wespot/vote/fixture/VoteJpaEntityFixture.kt b/app/src/test/kotlin/com/wespot/vote/fixture/VoteJpaEntityFixture.kt index 60bd280b..241381d5 100644 --- a/app/src/test/kotlin/com/wespot/vote/fixture/VoteJpaEntityFixture.kt +++ b/app/src/test/kotlin/com/wespot/vote/fixture/VoteJpaEntityFixture.kt @@ -27,4 +27,18 @@ object VoteJpaEntityFixture { date = LocalDate.now(), ) + fun createWithSchoolIdAndGradeAndClassNumberAndDate( + schoolId: Long, + grade: Int, + classNumber: Int, + date: LocalDate + ) = VoteJpaEntity( + id = 0L, + schoolId = schoolId, + grade = grade, + classNumber = classNumber, + voteNumber = 0, + date = date, + ) + } diff --git a/app/src/test/kotlin/com/wespot/vote/infrastructure/mysql/VoteJpaRepositoryTest.kt b/app/src/test/kotlin/com/wespot/vote/infrastructure/mysql/VoteJpaRepositoryTest.kt new file mode 100644 index 00000000..f4e9e7b8 --- /dev/null +++ b/app/src/test/kotlin/com/wespot/vote/infrastructure/mysql/VoteJpaRepositoryTest.kt @@ -0,0 +1,71 @@ +package com.wespot.vote.infrastructure.mysql + +import com.wespot.vote.VoteJpaRepository +import com.wespot.vote.fixture.VoteJpaEntityFixture +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import java.time.LocalDate + +@DataJpaTest +class VoteJpaRepositoryTest @Autowired constructor( + private val voteJpaRepository: VoteJpaRepository +) { + + @Test + fun `최근 투표 목록을 받아올 때 Cursor Based Pagination을 활용한다`() { + // given + val now = LocalDate.now() + val vote1 = + voteJpaRepository.save(VoteJpaEntityFixture.createWithSchoolIdAndGradeAndClassNumberAndDate(1, 1, 1, now.minusDays(4))) + val vote2 = voteJpaRepository.save( + VoteJpaEntityFixture.createWithSchoolIdAndGradeAndClassNumberAndDate( + 1, + 1, + 1, + now.minusDays(3) + ) + ) + val vote3 = voteJpaRepository.save( + VoteJpaEntityFixture.createWithSchoolIdAndGradeAndClassNumberAndDate( + 1, + 1, + 1, + now.minusDays(2) + ) + ) + val vote4 = voteJpaRepository.save( + VoteJpaEntityFixture.createWithSchoolIdAndGradeAndClassNumberAndDate( + 1, + 1, + 1, + now.minusDays(1) + ) + ) + val vote5 = voteJpaRepository.save( + VoteJpaEntityFixture.createWithSchoolIdAndGradeAndClassNumberAndDate( + 1, + 1, + 1, + now + ) + ) + + // when + val firstSearch = + voteJpaRepository.findAllBySchoolIdAndGradeAndClassNumberOrderByDateDesc(1, 1, 1, Long.MAX_VALUE, 3) + val secondSearch = + voteJpaRepository.findAllBySchoolIdAndGradeAndClassNumberOrderByDateDesc(1, 1, 1, firstSearch[2].id, 3) + + // then + firstSearch.size shouldBe 3 + firstSearch[0] shouldBe vote5 + firstSearch[1] shouldBe vote4 + firstSearch[2] shouldBe vote3 + secondSearch.size shouldBe 2 + secondSearch[0] shouldBe vote2 + secondSearch[1] shouldBe vote1 + } + +} diff --git a/app/src/test/kotlin/com/wespot/vote/service/ReceivedVoteServiceTest.kt b/app/src/test/kotlin/com/wespot/vote/service/ReceivedVoteServiceTest.kt index 9bad05a2..e441e327 100644 --- a/app/src/test/kotlin/com/wespot/vote/service/ReceivedVoteServiceTest.kt +++ b/app/src/test/kotlin/com/wespot/vote/service/ReceivedVoteServiceTest.kt @@ -107,20 +107,28 @@ class ReceivedVoteServiceTest @Autowired constructor( ) // when - val receivedVotes = receivedVoteService.getReceivedVotes() + val receivedVotes1 = receivedVoteService.getReceivedVotes(null, 100) + val receivedVotes2 = receivedVoteService.getReceivedVotes(null, 1) + val receivedVotes3 = receivedVoteService.getReceivedVotes(receivedVotes2.voteData[0].voteId, 1) // then - receivedVotes.voteData.size shouldBe 2 - receivedVotes.voteData[0].date shouldBe plusOneMinute.toLocalDate().toString() - receivedVotes.voteData[0].receivedVoteResults.size shouldBe 2 - receivedVotes.voteData[0].receivedVoteResults[0].voteOption.id shouldBe voteOptions[5].id - receivedVotes.voteData[0].receivedVoteResults[0].voteCount shouldBe 1 - receivedVotes.voteData[0].receivedVoteResults[0].isNew shouldBe true - receivedVotes.voteData[0].receivedVoteResults[1].voteOption.id shouldBe voteOptions[6].id - receivedVotes.voteData[0].receivedVoteResults[1].voteCount shouldBe 1 - receivedVotes.voteData[0].receivedVoteResults[1].isNew shouldBe true - receivedVotes.voteData[1].date shouldBe now.minusDays(1).toLocalDate().toString() - receivedVotes.voteData[1].receivedVoteResults.size shouldBe 0 + receivedVotes1.voteData.size shouldBe 2 + receivedVotes1.voteData[0].date shouldBe plusOneMinute.toLocalDate().toString() + receivedVotes1.voteData[0].receivedVoteResults.size shouldBe 2 + receivedVotes1.voteData[0].receivedVoteResults[0].voteOption.id shouldBe voteOptions[5].id + receivedVotes1.voteData[0].receivedVoteResults[0].voteCount shouldBe 1 + receivedVotes1.voteData[0].receivedVoteResults[0].isNew shouldBe true + receivedVotes1.voteData[0].receivedVoteResults[1].voteOption.id shouldBe voteOptions[6].id + receivedVotes1.voteData[0].receivedVoteResults[1].voteCount shouldBe 1 + receivedVotes1.voteData[0].receivedVoteResults[1].isNew shouldBe true + receivedVotes1.voteData[1].date shouldBe now.minusDays(1).toLocalDate().toString() + receivedVotes1.voteData[1].receivedVoteResults.size shouldBe 0 + receivedVotes2.voteData.size shouldBe 1 + receivedVotes2.voteData[0].voteId shouldBe vote1!!.id + receivedVotes2.hasNext shouldBe true + receivedVotes3.voteData.size shouldBe 1 + receivedVotes3.voteData[0].voteId shouldBe vote2!!.id + receivedVotes3.hasNext shouldBe false } @Test @@ -282,9 +290,9 @@ class ReceivedVoteServiceTest @Autowired constructor( ) // when - val firstReceivedVotes = receivedVoteService.getReceivedVotes() + val firstReceivedVotes = receivedVoteService.getReceivedVotes(null, 100) receivedVoteService.getReceivedVote(voteOptions[5].id, now.toLocalDate()) - val secondReceivedVotes = receivedVoteService.getReceivedVotes() + val secondReceivedVotes = receivedVoteService.getReceivedVotes(null, 100) // then firstReceivedVotes.voteData[0].receivedVoteResults[0].isNew shouldBe true diff --git a/app/src/test/kotlin/com/wespot/vote/service/SentVoteServiceTest.kt b/app/src/test/kotlin/com/wespot/vote/service/SentVoteServiceTest.kt index 5ffc7919..26a1c9c0 100644 --- a/app/src/test/kotlin/com/wespot/vote/service/SentVoteServiceTest.kt +++ b/app/src/test/kotlin/com/wespot/vote/service/SentVoteServiceTest.kt @@ -106,71 +106,27 @@ class SentVoteServiceTest @Autowired constructor( ) // when - val sentVotes = sentVoteService.getSentVotes() + val sentVotes1 = sentVoteService.getSentVotes(null, 100) + val sentVotes2 = sentVoteService.getSentVotes(null, 1) + val sentVotes3 = sentVoteService.getSentVotes(sentVotes1.voteData[0].voteId, 1) // then - sentVotes.voteData.size shouldBe 2 - sentVotes.voteData[0].date shouldBe now.toLocalDate().toString() - sentVotes.voteData[0].sentVoteResults.size shouldBe 2 - sentVotes.voteData[0].sentVoteResults[0].voteCount shouldBe 1 - sentVotes.voteData[0].sentVoteResults[1].voteCount shouldBe 1 - sentVotes.voteData[1].date shouldBe now.minusDays(1).toLocalDate().toString() - sentVotes.voteData[1].sentVoteResults.size shouldBe 0 - } - - @Test - fun `본인이 보낸 투표를 개별 조회한다`() { - // given - val now = LocalDateTime.now() - val plusOneMinute = now.plusMinutes(1) - val loginUser = UserMapper.mapToDomainEntity(users[0]) - UserFixture.setSecurityContextUser(loginUser) - ballotJpaRepository.save( - BallotMapper.mapToJpaEntity( - Ballot.of( - vote1!!.id, - vote1!!.voteIdentifier.date, - voteOptions[5].id, - users[0].id, - users[1].id, - now - ) - ) - ) - ballotJpaRepository.save( - BallotMapper.mapToJpaEntity( - Ballot.of( - vote1!!.id, - vote1!!.voteIdentifier.date, - voteOptions[5].id, - users[1].id, - users[0].id, - plusOneMinute - ) - ) - ) - ballotJpaRepository.save( - BallotMapper.mapToJpaEntity( - Ballot.of( - vote1!!.id, - vote1!!.voteIdentifier.date, - voteOptions[6].id, - users[0].id, - users[2].id, - plusOneMinute - ) - ) - ) - - // when - val sentVoteByFirstVoteOption = sentVoteService.getSentVote(voteOptions[5].id, now.toLocalDate()) - val sentVoteBySecondVoteOption = sentVoteService.getSentVote(voteOptions[6].id, now.toLocalDate()) - - // then - sentVoteByFirstVoteOption.voteResult.voteUsers.size shouldBe 1 - sentVoteByFirstVoteOption.voteResult.voteOption.id shouldBe voteOptions[5].id - sentVoteBySecondVoteOption.voteResult.voteUsers.size shouldBe 1 - sentVoteBySecondVoteOption.voteResult.voteOption.id shouldBe voteOptions[6].id + sentVotes1.voteData.size shouldBe 2 + sentVotes1.voteData[0].date shouldBe now.toLocalDate().toString() + sentVotes1.voteData[0].sentVoteResults.size shouldBe 2 + sentVotes1.voteData[0].sentVoteResults[0].vote.voteOption.id shouldBe voteOptions[5].id + sentVotes1.voteData[0].sentVoteResults[1].vote.voteOption.id shouldBe voteOptions[7].id + sentVotes1.voteData[0].sentVoteResults[0].vote.user.id shouldBe users[1].id + sentVotes1.voteData[0].sentVoteResults[1].vote.user.id shouldBe users[2].id + sentVotes1.voteData[1].date shouldBe now.minusDays(1).toLocalDate().toString() + sentVotes1.voteData[1].sentVoteResults.size shouldBe 0 + sentVotes1.hasNext shouldBe false + sentVotes2.voteData.size shouldBe 1 + sentVotes2.voteData[0].voteId shouldBe vote1!!.id + sentVotes2.hasNext shouldBe true + sentVotes3.voteData.size shouldBe 1 + sentVotes3.voteData[0].voteId shouldBe vote2!!.id + sentVotes3.hasNext shouldBe false } } diff --git a/core/src/main/kotlin/com/wespot/notification/dto/NotificationResponses.kt b/core/src/main/kotlin/com/wespot/notification/dto/NotificationResponses.kt index f6081462..ce528aea 100644 --- a/core/src/main/kotlin/com/wespot/notification/dto/NotificationResponses.kt +++ b/core/src/main/kotlin/com/wespot/notification/dto/NotificationResponses.kt @@ -4,16 +4,17 @@ import com.wespot.notification.Notification data class NotificationResponses( val notifications: List, + val hasNext: Boolean ) { companion object { - fun from(notifications: List): NotificationResponses { + fun from(notifications: List, hasNext: Boolean): NotificationResponses { val response: List = notifications.stream() .map { NotificationResponse.from(it) } .toList() - return NotificationResponses(response) + return NotificationResponses(response, hasNext) } } diff --git a/core/src/main/kotlin/com/wespot/notification/port/in/InquiryNotificationUseCase.kt b/core/src/main/kotlin/com/wespot/notification/port/in/InquiryNotificationUseCase.kt index abf7e3d1..ef0b0613 100644 --- a/core/src/main/kotlin/com/wespot/notification/port/in/InquiryNotificationUseCase.kt +++ b/core/src/main/kotlin/com/wespot/notification/port/in/InquiryNotificationUseCase.kt @@ -4,7 +4,7 @@ import com.wespot.notification.dto.NotificationResponses interface InquiryNotificationUseCase { - fun getNotifications(): NotificationResponses + fun getNotifications(cursorId: Long?, limit: Long): NotificationResponses fun readNotification(readNotificationId: Long) diff --git a/core/src/main/kotlin/com/wespot/notification/port/out/NotificationPort.kt b/core/src/main/kotlin/com/wespot/notification/port/out/NotificationPort.kt index a1fc8617..5d03fc4d 100644 --- a/core/src/main/kotlin/com/wespot/notification/port/out/NotificationPort.kt +++ b/core/src/main/kotlin/com/wespot/notification/port/out/NotificationPort.kt @@ -13,7 +13,7 @@ interface NotificationPort { fun findById(id: Long): Notification? - fun findAllByUserIdOrderByCreatedAtDesc(userId: Long): List + fun findAllByUserIdOrderByCreatedAtDesc(userId: Long, cursorId: Long, limit: Long): List fun findAllFromDateYesterday(today: LocalDate): List diff --git a/core/src/main/kotlin/com/wespot/notification/service/InquiryNotificationService.kt b/core/src/main/kotlin/com/wespot/notification/service/InquiryNotificationService.kt index 0c06300d..1a882d88 100644 --- a/core/src/main/kotlin/com/wespot/notification/service/InquiryNotificationService.kt +++ b/core/src/main/kotlin/com/wespot/notification/service/InquiryNotificationService.kt @@ -1,5 +1,6 @@ package com.wespot.notification.service +import com.wespot.CursorUtils import com.wespot.auth.service.SecurityUtils import com.wespot.notification.dto.NotificationResponses import com.wespot.notification.port.`in`.InquiryNotificationUseCase @@ -15,11 +16,16 @@ class InquiryNotificationService( ) : InquiryNotificationUseCase { @Transactional(readOnly = true) - override fun getNotifications(): NotificationResponses { + override fun getNotifications(cursorId: Long?, limit: Long): NotificationResponses { val loginUser = SecurityUtils.getLoginUser(userPort) - val notifications = NotificationFinder.findAllByUserIdOrderByCreatedAtDesc(notificationPort, loginUser.id) + val notifications = NotificationFinder.findAllByUserIdOrderByCreatedAtDesc( + notificationPort = notificationPort, + userId = loginUser.id, + cursorId = CursorUtils.getEffectiveCursorId(cursorId), + limit = limit + 1 + ) - return NotificationResponses.from(notifications) + return NotificationResponses.from(notifications.take(limit.toInt()), notifications.size.toLong() == limit + 1) } @Transactional diff --git a/core/src/main/kotlin/com/wespot/notification/service/NotificationFinder.kt b/core/src/main/kotlin/com/wespot/notification/service/NotificationFinder.kt index 3889d70c..0ae0cc68 100644 --- a/core/src/main/kotlin/com/wespot/notification/service/NotificationFinder.kt +++ b/core/src/main/kotlin/com/wespot/notification/service/NotificationFinder.kt @@ -15,8 +15,13 @@ object NotificationFinder { ?: throw IllegalArgumentException("ID에 해당하는 알림이 없습니다.") } - fun findAllByUserIdOrderByCreatedAtDesc(notificationPort: NotificationPort, userId: Long): List { - return notificationPort.findAllByUserIdOrderByCreatedAtDesc(userId) + fun findAllByUserIdOrderByCreatedAtDesc( + notificationPort: NotificationPort, + userId: Long, + cursorId: Long, + limit: Long + ): List { + return notificationPort.findAllByUserIdOrderByCreatedAtDesc(userId, cursorId, limit) } fun findAllFromDateYesterday(notificationPort: NotificationPort, today: LocalDate): List { diff --git a/core/src/main/kotlin/com/wespot/vote/dto/response/VoteUserResponse.kt b/core/src/main/kotlin/com/wespot/vote/dto/response/VoteUserResponse.kt index 5a0f412b..bb063823 100644 --- a/core/src/main/kotlin/com/wespot/vote/dto/response/VoteUserResponse.kt +++ b/core/src/main/kotlin/com/wespot/vote/dto/response/VoteUserResponse.kt @@ -6,6 +6,7 @@ import com.wespot.user.dto.response.ProfileResponse data class VoteUserResponse( val id: Long, val name: String, + val introduction: String, val profile: ProfileResponse ) { @@ -15,6 +16,7 @@ data class VoteUserResponse( return VoteUserResponse( id = user.id, name = user.name, + introduction = user.introduction, profile = user.profile.let { ProfileResponse.from(it) } ) } diff --git a/core/src/main/kotlin/com/wespot/vote/dto/response/received/ReceivedVotesResponse.kt b/core/src/main/kotlin/com/wespot/vote/dto/response/received/ReceivedVotesResponse.kt index 541f02aa..f3feaa78 100644 --- a/core/src/main/kotlin/com/wespot/vote/dto/response/received/ReceivedVotesResponse.kt +++ b/core/src/main/kotlin/com/wespot/vote/dto/response/received/ReceivedVotesResponse.kt @@ -5,6 +5,7 @@ import com.wespot.vote.VoteRecord import com.wespot.voteoption.VoteOption data class ReceivedVotesResponse( + val voteId: Long, val date: String, val receivedVoteResults: List ) { @@ -13,6 +14,7 @@ data class ReceivedVotesResponse( fun of(vote: Vote, voteResults: Map): ReceivedVotesResponse { return ReceivedVotesResponse( + vote.id, vote.voteIdentifier.date.toString(), voteResults.map { ReceivedVotesResultResponses.of(it.key, it.value) } .toList() diff --git a/core/src/main/kotlin/com/wespot/vote/dto/response/received/ReceivedVotesResponses.kt b/core/src/main/kotlin/com/wespot/vote/dto/response/received/ReceivedVotesResponses.kt index 2d32fb05..4bf5f288 100644 --- a/core/src/main/kotlin/com/wespot/vote/dto/response/received/ReceivedVotesResponses.kt +++ b/core/src/main/kotlin/com/wespot/vote/dto/response/received/ReceivedVotesResponses.kt @@ -5,15 +5,16 @@ import com.wespot.vote.VoteRecord import com.wespot.voteoption.VoteOption data class ReceivedVotesResponses( - val voteData: List + val voteData: List, + val hasNext: Boolean ) { companion object { - fun of(voteResults: Map>): ReceivedVotesResponses { + fun of(voteResults: Map>, hasNext: Boolean): ReceivedVotesResponses { val voteData = voteResults .map { ReceivedVotesResponse.of(it.key, it.value) } .toList() - return ReceivedVotesResponses(voteData) + return ReceivedVotesResponses(voteData, hasNext) } } diff --git a/core/src/main/kotlin/com/wespot/vote/dto/response/sent/SentVotesResponse.kt b/core/src/main/kotlin/com/wespot/vote/dto/response/sent/SentVotesResponse.kt index f917ac54..680232d9 100644 --- a/core/src/main/kotlin/com/wespot/vote/dto/response/sent/SentVotesResponse.kt +++ b/core/src/main/kotlin/com/wespot/vote/dto/response/sent/SentVotesResponse.kt @@ -1,21 +1,21 @@ package com.wespot.vote.dto.response.sent -import com.wespot.vote.Ballot +import com.wespot.vote.CompleteBallot import com.wespot.vote.Vote -import com.wespot.voteoption.VoteOption data class SentVotesResponse( + val voteId: Long, val date: String, val sentVoteResults: List ) { companion object { - fun of(vote: Vote, voteResults: Map>): SentVotesResponse { + fun of(vote: Vote, ballots: List): SentVotesResponse { return SentVotesResponse( + vote.id, vote.voteIdentifier.date.toString(), - voteResults.map { SentVotesResultsResponse.of(it.key, it.value.size) } - .toList() + ballots.map { SentVotesResultsResponse.of(it) } ) } diff --git a/core/src/main/kotlin/com/wespot/vote/dto/response/sent/SentVotesResponses.kt b/core/src/main/kotlin/com/wespot/vote/dto/response/sent/SentVotesResponses.kt index 10c50abc..55a2cd02 100644 --- a/core/src/main/kotlin/com/wespot/vote/dto/response/sent/SentVotesResponses.kt +++ b/core/src/main/kotlin/com/wespot/vote/dto/response/sent/SentVotesResponses.kt @@ -1,19 +1,19 @@ package com.wespot.vote.dto.response.sent -import com.wespot.vote.Ballot +import com.wespot.vote.CompleteBallot import com.wespot.vote.Vote -import com.wespot.voteoption.VoteOption data class SentVotesResponses( - val voteData: List + val voteData: List, + val hasNext: Boolean ) { companion object { - fun from(voteResults: Map>>): SentVotesResponses { - val voteData = voteResults + fun from(voteResult: Map>, hasNext: Boolean): SentVotesResponses { + val voteData = voteResult .map { SentVotesResponse.of(it.key, it.value) } .toList() - return SentVotesResponses(voteData) + return SentVotesResponses(voteData, hasNext) } } diff --git a/core/src/main/kotlin/com/wespot/vote/dto/response/sent/SentVotesResultsResponse.kt b/core/src/main/kotlin/com/wespot/vote/dto/response/sent/SentVotesResultsResponse.kt index 45f61669..69232049 100644 --- a/core/src/main/kotlin/com/wespot/vote/dto/response/sent/SentVotesResultsResponse.kt +++ b/core/src/main/kotlin/com/wespot/vote/dto/response/sent/SentVotesResultsResponse.kt @@ -1,20 +1,15 @@ package com.wespot.vote.dto.response.sent -import com.wespot.vote.dto.response.VoteOptionResponse -import com.wespot.voteoption.VoteOption +import com.wespot.vote.CompleteBallot data class SentVotesResultsResponse( - val voteOption: VoteOptionResponse, - val voteCount: Int + val vote: SingleVoteResponse, ) { companion object { - fun of(voteOption: VoteOption, voteCount: Int): SentVotesResultsResponse { - return SentVotesResultsResponse( - VoteOptionResponse.from(voteOption), - voteCount - ) + fun of(ballot: CompleteBallot): SentVotesResultsResponse { + return SentVotesResultsResponse(SingleVoteResponse.of(ballot.voteOption, ballot.receiver)) } } diff --git a/core/src/main/kotlin/com/wespot/vote/dto/response/sent/SingleVoteResponse.kt b/core/src/main/kotlin/com/wespot/vote/dto/response/sent/SingleVoteResponse.kt new file mode 100644 index 00000000..379b3107 --- /dev/null +++ b/core/src/main/kotlin/com/wespot/vote/dto/response/sent/SingleVoteResponse.kt @@ -0,0 +1,20 @@ +package com.wespot.vote.dto.response.sent + +import com.wespot.user.User +import com.wespot.vote.dto.response.VoteOptionResponse +import com.wespot.vote.dto.response.VoteUserResponse +import com.wespot.voteoption.VoteOption + +data class SingleVoteResponse( + val voteOption: VoteOptionResponse, + val user: VoteUserResponse +) { + companion object { + fun of(voteOption: VoteOption, user: User): SingleVoteResponse { + return SingleVoteResponse( + VoteOptionResponse.from(voteOption), + VoteUserResponse.from(user) + ) + } + } +} diff --git a/core/src/main/kotlin/com/wespot/vote/dto/response/top1/VoteDetailResultResponseOfTop1.kt b/core/src/main/kotlin/com/wespot/vote/dto/response/top1/VoteDetailResultResponseOfTop1.kt index 74a02243..cf26441d 100644 --- a/core/src/main/kotlin/com/wespot/vote/dto/response/top1/VoteDetailResultResponseOfTop1.kt +++ b/core/src/main/kotlin/com/wespot/vote/dto/response/top1/VoteDetailResultResponseOfTop1.kt @@ -1,9 +1,10 @@ package com.wespot.vote.dto.response.top1 import com.wespot.vote.VoteRecord +import com.wespot.vote.dto.response.VoteUserResponse data class VoteDetailResultResponseOfTop1( - val user: VoteUserResponseOfTop1, + val user: VoteUserResponse, val voteCount: Int ) { @@ -11,7 +12,7 @@ data class VoteDetailResultResponseOfTop1( fun from(voteRecords: VoteRecord): VoteDetailResultResponseOfTop1 { return VoteDetailResultResponseOfTop1( - VoteUserResponseOfTop1.from(voteRecords.user), + VoteUserResponse.from(voteRecords.user), voteRecords.voteCount ) } diff --git a/core/src/main/kotlin/com/wespot/vote/dto/response/top1/VoteUserResponseOfTop1.kt b/core/src/main/kotlin/com/wespot/vote/dto/response/top1/VoteUserResponseOfTop1.kt deleted file mode 100644 index db02d018..00000000 --- a/core/src/main/kotlin/com/wespot/vote/dto/response/top1/VoteUserResponseOfTop1.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.wespot.vote.dto.response.top1 - -import com.wespot.user.User - -data class VoteUserResponseOfTop1( - val id: Long, - val name: String, - val introduction: String, - val iconUrl: String, -) { - - companion object { - - fun from(user: User): VoteUserResponseOfTop1 { - return VoteUserResponseOfTop1( - user.id, - user.name, - user.introduction, - user.profile.iconUrl - ) - } - - } - -} diff --git a/core/src/main/kotlin/com/wespot/vote/dto/response/top5/VoteDetailResultResponseOfTop5.kt b/core/src/main/kotlin/com/wespot/vote/dto/response/top5/VoteDetailResultResponseOfTop5.kt index 525ef613..2c3bf29f 100644 --- a/core/src/main/kotlin/com/wespot/vote/dto/response/top5/VoteDetailResultResponseOfTop5.kt +++ b/core/src/main/kotlin/com/wespot/vote/dto/response/top5/VoteDetailResultResponseOfTop5.kt @@ -1,9 +1,10 @@ package com.wespot.vote.dto.response.top5 import com.wespot.vote.VoteRecord +import com.wespot.vote.dto.response.VoteUserResponse data class VoteDetailResultResponseOfTop5( - val user: VoteUserResponseOfTop5, + val user: VoteUserResponse, val voteCount: Int ) { @@ -11,7 +12,7 @@ data class VoteDetailResultResponseOfTop5( fun from(voteRecords: VoteRecord): VoteDetailResultResponseOfTop5 { return VoteDetailResultResponseOfTop5( - VoteUserResponseOfTop5.from(voteRecords.user), + VoteUserResponse.from(voteRecords.user), voteRecords.voteCount ) } diff --git a/core/src/main/kotlin/com/wespot/vote/dto/response/top5/VoteUserResponseOfTop5.kt b/core/src/main/kotlin/com/wespot/vote/dto/response/top5/VoteUserResponseOfTop5.kt deleted file mode 100644 index e92e17eb..00000000 --- a/core/src/main/kotlin/com/wespot/vote/dto/response/top5/VoteUserResponseOfTop5.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.wespot.vote.dto.response.top5 - -import com.wespot.user.User -import com.wespot.vote.dto.response.VoteProfileResponse - -data class VoteUserResponseOfTop5( - val id: Long, - val name: String, - val introduction: String, - val profile: VoteProfileResponse -) { - - companion object { - - fun from(user: User): VoteUserResponseOfTop5 { - return VoteUserResponseOfTop5( - user.id, - user.name, - user.introduction, - VoteProfileResponse.from(user.profile) - ) - } - - } - -} diff --git a/core/src/main/kotlin/com/wespot/vote/port/in/ReceivedVoteUseCase.kt b/core/src/main/kotlin/com/wespot/vote/port/in/ReceivedVoteUseCase.kt index b47c59a3..ac4bcadc 100644 --- a/core/src/main/kotlin/com/wespot/vote/port/in/ReceivedVoteUseCase.kt +++ b/core/src/main/kotlin/com/wespot/vote/port/in/ReceivedVoteUseCase.kt @@ -6,7 +6,7 @@ import java.time.LocalDate interface ReceivedVoteUseCase { - fun getReceivedVotes(): ReceivedVotesResponses + fun getReceivedVotes(cursorId: Long?, limit: Long): ReceivedVotesResponses fun getReceivedVote(optionId: Long, date: LocalDate): ReceivedVoteResponse diff --git a/core/src/main/kotlin/com/wespot/vote/port/in/SentVoteUseCase.kt b/core/src/main/kotlin/com/wespot/vote/port/in/SentVoteUseCase.kt index b24ad4bc..0a42c1ef 100644 --- a/core/src/main/kotlin/com/wespot/vote/port/in/SentVoteUseCase.kt +++ b/core/src/main/kotlin/com/wespot/vote/port/in/SentVoteUseCase.kt @@ -1,13 +1,9 @@ package com.wespot.vote.port.`in` -import com.wespot.vote.dto.response.sent.SentVoteResponse import com.wespot.vote.dto.response.sent.SentVotesResponses -import java.time.LocalDate interface SentVoteUseCase { - fun getSentVotes(): SentVotesResponses - - fun getSentVote(optionId: Long, date: LocalDate): SentVoteResponse + fun getSentVotes(cursorId: Long?, limit: Long): SentVotesResponses } diff --git a/core/src/main/kotlin/com/wespot/vote/port/out/VotePort.kt b/core/src/main/kotlin/com/wespot/vote/port/out/VotePort.kt index 3e0fa360..4e40308c 100644 --- a/core/src/main/kotlin/com/wespot/vote/port/out/VotePort.kt +++ b/core/src/main/kotlin/com/wespot/vote/port/out/VotePort.kt @@ -26,7 +26,9 @@ interface VotePort { fun findAllBySchoolIdAndGradeAndClassNumberByOrderByDateDesc( schoolId: Long, grade: Int, - classNumber: Int + classNumber: Int, + cursorId: Long, + limit: Long ): List diff --git a/core/src/main/kotlin/com/wespot/vote/service/ReceivedVoteService.kt b/core/src/main/kotlin/com/wespot/vote/service/ReceivedVoteService.kt index b99a8e50..2798f1d8 100644 --- a/core/src/main/kotlin/com/wespot/vote/service/ReceivedVoteService.kt +++ b/core/src/main/kotlin/com/wespot/vote/service/ReceivedVoteService.kt @@ -1,5 +1,6 @@ package com.wespot.vote.service +import com.wespot.CursorUtils import com.wespot.user.User import com.wespot.user.port.out.UserPort import com.wespot.vote.ReceivedVoteCalculateService @@ -24,12 +25,17 @@ class ReceivedVoteService( private val receivedVoteCalculateService: ReceivedVoteCalculateService ) : ReceivedVoteUseCase { - override fun getReceivedVotes(): ReceivedVotesResponses { + override fun getReceivedVotes(cursorId: Long?, limit: Long): ReceivedVotesResponses { val user = VoteServiceHelper.findLoginUser(userPort) - val votes = VoteServiceHelper.findVotesOrderByDateDesc(votePort, user) - val voteResults = votes.associateWith { getUserReceivedVotesByVote(it, user) } + val votes = VoteServiceHelper.findVotesOrderByDateDesc( + votePort = votePort, + user = user, + cursorId = CursorUtils.getEffectiveCursorId(cursorId), + limit = limit + 1 + ) + val voteResults = votes.take(limit.toInt()).associateWith { getUserReceivedVotesByVote(it, user) } - return ReceivedVotesResponses.of(voteResults = voteResults) + return ReceivedVotesResponses.of(voteResults = voteResults, votes.size.toLong() == limit + 1) } private fun getUserReceivedVotesByVote( diff --git a/core/src/main/kotlin/com/wespot/vote/service/SentVoteService.kt b/core/src/main/kotlin/com/wespot/vote/service/SentVoteService.kt index 060d52a0..f7b99c35 100644 --- a/core/src/main/kotlin/com/wespot/vote/service/SentVoteService.kt +++ b/core/src/main/kotlin/com/wespot/vote/service/SentVoteService.kt @@ -1,52 +1,42 @@ package com.wespot.vote.service +import com.wespot.CursorUtils import com.wespot.user.User import com.wespot.user.port.out.UserPort -import com.wespot.vote.Ballot +import com.wespot.vote.CompleteBallot import com.wespot.vote.Vote -import com.wespot.vote.dto.response.sent.SentVoteResponse import com.wespot.vote.dto.response.sent.SentVotesResponses import com.wespot.vote.port.`in`.SentVoteUseCase -import com.wespot.vote.port.out.VoteOptionPort import com.wespot.vote.port.out.VotePort import com.wespot.vote.service.helper.VoteServiceHelper -import com.wespot.voteoption.VoteOption import org.springframework.stereotype.Service -import java.time.LocalDate @Service class SentVoteService( private val votePort: VotePort, private val userPort: UserPort, - private val voteOptionPort: VoteOptionPort ) : SentVoteUseCase { - override fun getSentVotes(): SentVotesResponses { + override fun getSentVotes(cursorId: Long?, limit: Long): SentVotesResponses { val user = VoteServiceHelper.findLoginUser(userPort) - val votes = VoteServiceHelper.findVotesOrderByDateDesc(votePort, user) - val voteResults = votes.associateWith { getSentVotes(it, user) } + val votes = VoteServiceHelper.findVotesOrderByDateDesc( + votePort = votePort, + user = user, + cursorId = CursorUtils.getEffectiveCursorId(cursorId), + limit = limit + 1 + ) + val classmates = VoteServiceHelper.findClassmatesByUser(userPort, user) + val voteResults = votes.take(limit.toInt()).associateWith { getSentVotes(it, user, classmates) } - return SentVotesResponses.from(voteResults) + return SentVotesResponses.from(voteResults, votes.size.toLong() == limit + 1) } private fun getSentVotes( vote: Vote, - user: User - ): Map> { - return vote.getUserSentVotes(user = user) - } - - override fun getSentVote(optionId: Long, date: LocalDate): SentVoteResponse { - val user = VoteServiceHelper.findLoginUser(userPort) - val vote = VoteServiceHelper.findVoteByUser(votePort, user, date) - val voteOption = VoteServiceHelper.findVoteOptionById(voteOptionPort, optionId) - - return SentVoteResponse.of( - voteOption = voteOption, - users = vote.getUserSentVote(voteOption, user) - .map { VoteServiceHelper.findUser(userPort, it.receiverId) } - .toList() - ) + user: User, + classmates: List + ): List { + return vote.getUserSentVotes(user = user, classmates = classmates) } } diff --git a/core/src/main/kotlin/com/wespot/vote/service/helper/VoteServiceHelper.kt b/core/src/main/kotlin/com/wespot/vote/service/helper/VoteServiceHelper.kt index bdb9972d..e34b9740 100644 --- a/core/src/main/kotlin/com/wespot/vote/service/helper/VoteServiceHelper.kt +++ b/core/src/main/kotlin/com/wespot/vote/service/helper/VoteServiceHelper.kt @@ -4,7 +4,6 @@ import com.wespot.auth.service.SecurityUtils import com.wespot.user.User import com.wespot.user.port.out.UserPort import com.wespot.vote.Vote -import com.wespot.vote.VoteOptionsByVoteDate import com.wespot.vote.port.out.VoteOptionPort import com.wespot.vote.port.out.VotePort import com.wespot.voteoption.VoteOption @@ -43,24 +42,14 @@ object VoteServiceHelper { ?: throw IllegalArgumentException("ID에 해당하는 선택지가 존재하지 않습니다.") } - fun findVotesOrderByDateDesc(votePort: VotePort, user: User): List { + fun findVotesOrderByDateDesc(votePort: VotePort, user: User, cursorId: Long, limit: Long): List { return votePort.findAllBySchoolIdAndGradeAndClassNumberByOrderByDateDesc( schoolId = user.schoolId, grade = user.grade, - classNumber = user.classNumber + classNumber = user.classNumber, + cursorId = cursorId, + limit = limit ) } - fun findVoteOptionOnVoteOptions( - voteOptions: VoteOptionsByVoteDate, - optionId: Long - ): VoteOption { - val voteOption = voteOptions.voteOptionsByVoteDate - .map { it.voteOption } - .find { it.id == optionId } - ?: throw IllegalArgumentException("오늘의 선택지가 아닙니다.") - - return voteOption - } - } diff --git a/domain/src/main/kotlin/com/wespot/vote/CompleteBallot.kt b/domain/src/main/kotlin/com/wespot/vote/CompleteBallot.kt new file mode 100644 index 00000000..1770fd46 --- /dev/null +++ b/domain/src/main/kotlin/com/wespot/vote/CompleteBallot.kt @@ -0,0 +1,52 @@ +package com.wespot.vote + +import com.wespot.user.User +import com.wespot.voteoption.VoteOption +import java.time.LocalDateTime + +data class CompleteBallot( + val id: Long, + val vote: Vote, + val voteOption: VoteOption, + val sender: User, + val receiver: User, + val createdAt: LocalDateTime, + val updatedAt: LocalDateTime, + var isReceiverRead: Boolean, +) { + + companion object { + fun of( + vote: Vote, + voteOption: VoteOption, + sender: User, + receiver: User, + ballot: Ballot + ): CompleteBallot { + validate(ballot, vote, voteOption, sender, receiver) + return CompleteBallot( + id = ballot.id, + vote = vote, + voteOption = voteOption, + sender = sender, + receiver = receiver, + createdAt = ballot.createdAt, + updatedAt = ballot.updatedAt, + isReceiverRead = ballot.isReceiverRead + ) + } + + private fun validate( + ballot: Ballot, + vote: Vote, + voteOption: VoteOption, + sender: User, + receiver: User + ) { + require(ballot.voteId == vote.id) { "입력된 투표가 잘못되었습니다." } + require(ballot.voteOptionId == voteOption.id) { "입력된 선택지가 잘못되었습니다." } + require(ballot.senderId == sender.id) { "입력된 송신자가 잘못되었습니다." } + require(ballot.receiverId == receiver.id) { "입력된 수신자가 잘못되었습니다." } + } + } +} diff --git a/domain/src/main/kotlin/com/wespot/vote/Vote.kt b/domain/src/main/kotlin/com/wespot/vote/Vote.kt index e1cf98e8..fa874342 100644 --- a/domain/src/main/kotlin/com/wespot/vote/Vote.kt +++ b/domain/src/main/kotlin/com/wespot/vote/Vote.kt @@ -163,29 +163,21 @@ data class Vote( fun getUserSentVotes( user: User, - ): Map> { + classmates: List + ): List { val ballots = ballots.findSentBallotsByUser(user.id) - - return voteOptionsByVoteDate.voteOptionsByVoteDate - .filter { containVoteOptionOnBallots(ballots, it.voteOption) } + val classmateGroup = classmates.associateBy { it.id } + val voteOptionGroup = voteOptionsByVoteDate.voteOptionsByVoteDate .map { it.voteOption } - .associateWith { voteOption -> ballots.filter { voteOption.id == it.voteOptionId } } - } + .associateBy { it.id } - private fun containVoteOptionOnBallots( - ballots: List, - voteOption: VoteOption - ) = ballots.stream() - .anyMatch { it.voteOptionId == voteOption.id } - - fun getUserSentVote( - voteOption: VoteOption, - user: User, - ): List { - voteOptionsByVoteDate.validateVoteOption(voteOption.id) - - return ballots.findSentBallotsByUser(user.id) - .filter { it.voteOptionId == voteOption.id } + return ballots.filter { voteOptionGroup.containsKey(it.voteOptionId) } + .filter { classmateGroup.containsKey(it.receiverId) } + .map { + CompleteBallot.of( + this, voteOptionGroup[it.voteOptionId]!!, user, classmateGroup[it.receiverId]!!, it + ) + }.toList() } fun getNumberOfSender(): Int { diff --git a/infrastructure/mysql/src/main/kotlin/com/wespot/notification/NotificationJpaRepository.kt b/infrastructure/mysql/src/main/kotlin/com/wespot/notification/NotificationJpaRepository.kt index 3b8e5b27..70e6ff70 100644 --- a/infrastructure/mysql/src/main/kotlin/com/wespot/notification/NotificationJpaRepository.kt +++ b/infrastructure/mysql/src/main/kotlin/com/wespot/notification/NotificationJpaRepository.kt @@ -1,11 +1,26 @@ package com.wespot.notification import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query import java.time.LocalDateTime interface NotificationJpaRepository : JpaRepository { - fun findAllByUserIdOrderByBaseEntityCreatedAtDesc(userId: Long): List + @Query( + """ + SELECT n + FROM NotificationJpaEntity n + WHERE n.userId = :userId + AND n.id < :cursorId + ORDER BY n.baseEntity.createdAt DESC + LIMIT :limit + """ + ) + fun findAllByUserIdOrderByBaseEntityCreatedAtDesc( + userId: Long, + cursorId: Long, + limit: Long + ): List fun findAllByBaseEntityCreatedAtBetween( createdAtStart: LocalDateTime, diff --git a/infrastructure/mysql/src/main/kotlin/com/wespot/notification/NotificationPersistenceAdapter.kt b/infrastructure/mysql/src/main/kotlin/com/wespot/notification/NotificationPersistenceAdapter.kt index a8303194..133ad215 100644 --- a/infrastructure/mysql/src/main/kotlin/com/wespot/notification/NotificationPersistenceAdapter.kt +++ b/infrastructure/mysql/src/main/kotlin/com/wespot/notification/NotificationPersistenceAdapter.kt @@ -33,8 +33,8 @@ class NotificationPersistenceAdapter( ?.let { NotificationMapper.mapToDomainEntity(it) } } - override fun findAllByUserIdOrderByCreatedAtDesc(userId: Long): List { - return notificationJpaRepository.findAllByUserIdOrderByBaseEntityCreatedAtDesc(userId) + override fun findAllByUserIdOrderByCreatedAtDesc(userId: Long, cursorId: Long, limit: Long): List { + return notificationJpaRepository.findAllByUserIdOrderByBaseEntityCreatedAtDesc(userId, cursorId, limit) .map { NotificationMapper.mapToDomainEntity(it) } } diff --git a/infrastructure/mysql/src/main/kotlin/com/wespot/vote/VoteJpaRepository.kt b/infrastructure/mysql/src/main/kotlin/com/wespot/vote/VoteJpaRepository.kt index 0ebd9865..93d15c1e 100644 --- a/infrastructure/mysql/src/main/kotlin/com/wespot/vote/VoteJpaRepository.kt +++ b/infrastructure/mysql/src/main/kotlin/com/wespot/vote/VoteJpaRepository.kt @@ -1,6 +1,7 @@ package com.wespot.vote import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query import java.time.LocalDate interface VoteJpaRepository : JpaRepository { @@ -19,10 +20,24 @@ interface VoteJpaRepository : JpaRepository { date: LocalDate ): VoteJpaEntity? + @Query( + """ + SELECT v + FROM VoteJpaEntity v + WHERE v.schoolId = :schoolId + AND v.grade = :grade + AND v.classNumber = :classNumber + AND v.id < :cursorId + ORDER BY v.date DESC + LIMIT :limit + """ + ) fun findAllBySchoolIdAndGradeAndClassNumberOrderByDateDesc( schoolId: Long, grade: Int, - classNumber: Int + classNumber: Int, + cursorId: Long, + limit: Long ): List } diff --git a/infrastructure/mysql/src/main/kotlin/com/wespot/vote/VotePersistenceAdapter.kt b/infrastructure/mysql/src/main/kotlin/com/wespot/vote/VotePersistenceAdapter.kt index 1a9379cb..1949e789 100644 --- a/infrastructure/mysql/src/main/kotlin/com/wespot/vote/VotePersistenceAdapter.kt +++ b/infrastructure/mysql/src/main/kotlin/com/wespot/vote/VotePersistenceAdapter.kt @@ -90,18 +90,23 @@ class VotePersistenceAdapter( override fun findAllBySchoolIdAndGradeAndClassNumberByOrderByDateDesc( schoolId: Long, grade: Int, - classNumber: Int + classNumber: Int, + cursorId: Long, + limit: Long ): List { - return voteJpaRepository.findAllBySchoolIdAndGradeAndClassNumberOrderByDateDesc(schoolId, grade, classNumber) - .stream() - .map { - VoteMapper.mapToDomainEntity( - it, - findAllByVoteId(it.date, it.id), - findBallotsByVote(it.id) - ) - } - .toList() + return voteJpaRepository.findAllBySchoolIdAndGradeAndClassNumberOrderByDateDesc( + schoolId, + grade, + classNumber, + cursorId, + limit + ).map { + VoteMapper.mapToDomainEntity( + it, + findAllByVoteId(it.date, it.id), + findBallotsByVote(it.id) + ) + }.toList() } }