diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/controller/NotificationController.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/controller/NotificationController.kt index b19e80d7..6cf2a7b0 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/controller/NotificationController.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/controller/NotificationController.kt @@ -1,6 +1,8 @@ package com.depromeet.whatnow.api.notification.controller +import com.depromeet.whatnow.api.notification.dto.HighlightsResponse import com.depromeet.whatnow.api.notification.dto.NotificationResponse +import com.depromeet.whatnow.api.notification.usecase.NotificationHighlightsReadUseCase import com.depromeet.whatnow.api.notification.usecase.NotificationReadUseCase import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.security.SecurityRequirement @@ -9,6 +11,7 @@ import org.springdoc.api.annotations.ParameterObject import org.springframework.data.domain.Pageable import org.springframework.data.web.PageableDefault import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @@ -18,13 +21,26 @@ import org.springframework.web.bind.annotation.RestController @SecurityRequirement(name = "access-token") class NotificationController( val notificationReadUseCase: NotificationReadUseCase, + val notificationHighlightsReadUseCase: NotificationHighlightsReadUseCase, ) { @Operation(summary = "자신의 알림 조회") @GetMapping fun getMyNotifications( - @ParameterObject @PageableDefault + @ParameterObject + @PageableDefault(size = 10) pageable: Pageable, ): NotificationResponse { return notificationReadUseCase.execute(pageable) } + + @Operation(summary = "하이라이트 조회", description = "약속 ID로 하이라이트를 조회합니다") + @GetMapping("/highlights/promises/{promise-id}") + fun getHighlights( + @PathVariable("promise-id") promiseId: Long, + @ParameterObject + @PageableDefault(size = 10) + pageable: Pageable, + ): HighlightsResponse { + return notificationHighlightsReadUseCase.execute(promiseId, pageable) + } } diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/dto/ArrivalNotificationResponse.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/dto/ArrivalNotificationResponse.kt index a1e22894..e475eb18 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/dto/ArrivalNotificationResponse.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/dto/ArrivalNotificationResponse.kt @@ -8,7 +8,7 @@ class ArrivalNotificationResponse( val promiseId: Long, val senderUserId: Long, override val createdAt: LocalDateTime, -) : NotificationAbstract(NotificationType.START_SHARING, createdAt) { +) : NotificationAbstract(NotificationType.ARRIVAL, createdAt) { companion object { fun from(notification: ArrivalNotification): ArrivalNotificationResponse { return ArrivalNotificationResponse( diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/dto/EndSharingNotificationResponse.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/dto/EndSharingNotificationResponse.kt index 195bb92c..97f1d455 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/dto/EndSharingNotificationResponse.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/dto/EndSharingNotificationResponse.kt @@ -7,7 +7,7 @@ import java.time.LocalDateTime class EndSharingNotificationResponse( val promiseId: Long, override val createdAt: LocalDateTime, -) : NotificationAbstract(NotificationType.START_SHARING, createdAt) { +) : NotificationAbstract(NotificationType.END_SHARING, createdAt) { companion object { fun from(notification: EndSharingNotification): EndSharingNotificationResponse { return EndSharingNotificationResponse( diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/dto/HighlightsResponse.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/dto/HighlightsResponse.kt new file mode 100644 index 00000000..46afe5ff --- /dev/null +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/dto/HighlightsResponse.kt @@ -0,0 +1,5 @@ +package com.depromeet.whatnow.api.notification.dto + +data class HighlightsResponse( + val highlights: List, +) diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/usecase/NotificationHighlightsReadUseCase.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/usecase/NotificationHighlightsReadUseCase.kt new file mode 100644 index 00000000..9ad0189e --- /dev/null +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/notification/usecase/NotificationHighlightsReadUseCase.kt @@ -0,0 +1,46 @@ +package com.depromeet.whatnow.api.notification.usecase + +import com.depromeet.whatnow.annotation.UseCase +import com.depromeet.whatnow.api.notification.dto.ArrivalNotificationResponse +import com.depromeet.whatnow.api.notification.dto.HighlightsResponse +import com.depromeet.whatnow.config.security.SecurityUtils +import com.depromeet.whatnow.domains.notification.domain.ArrivalNotification +import com.depromeet.whatnow.domains.notification.exception.NotHighlightsTypeException +import com.depromeet.whatnow.domains.notification.service.NotificationDomainService +import org.springframework.data.domain.Pageable + +@UseCase +class NotificationHighlightsReadUseCase( + val notificationDomainService: NotificationDomainService, +) { + fun execute(promiseId: Long, pageable: Pageable): HighlightsResponse { + val userId = SecurityUtils.currentUserId + val highlights = notificationDomainService.getNotificationHighlights(promiseId, userId, pageable) + .map { notification -> + when (notification) { + is ArrivalNotification -> { + ArrivalNotificationResponse.from(notification) + } + // TODO: 만났다 이벤트 생성 된 이후 MEET Type Notification 추가 + else -> throw NotHighlightsTypeException.EXCEPTION + } + }.toList() + return HighlightsResponse(highlights) + } + + fun executeTop3(promiseId: Long): HighlightsResponse { + val listMap = notificationDomainService.getNotificationHighlightsTop3(promiseId) + .map { notification -> + when (notification) { + is ArrivalNotification -> + ArrivalNotificationResponse.from(notification) + // TODO: 만났다 이벤트 생성 된 이후 MEET Type Notification 추가 + else -> throw NotHighlightsTypeException.EXCEPTION + } + } + .groupBy { it.senderUserId } + + val result = listMap.values.map { it.first() } + return HighlightsResponse(result.sortedByDescending { it.createdAt }) + } +} diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/dto/PromiseDetailDto.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/dto/PromiseDetailDto.kt index 121d83d6..59628622 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/dto/PromiseDetailDto.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/dto/PromiseDetailDto.kt @@ -1,5 +1,6 @@ package com.depromeet.whatnow.api.promise.dto +import com.depromeet.whatnow.api.notification.dto.HighlightsResponse import com.depromeet.whatnow.common.vo.CoordinateVo import com.depromeet.whatnow.domains.promise.domain.Promise import java.time.LocalDateTime @@ -12,11 +13,9 @@ data class PromiseDetailDto( val endTime: LocalDateTime, // 유저의 마지막 위치 리스트 val promiseUsers: List, - // TODO : 약속 기록 사진 기능 추가시 함께 추가할게요. val promiseImageUrls: List?, val timeOverLocations: List, - // TODO : highlight 기능 추가시 함께 추가할게요. ( 최대 3개 제한 ) -// x val highlights: List, + val highlights: HighlightsResponse, ) { companion object { fun of( @@ -24,6 +23,7 @@ data class PromiseDetailDto( promiseUsers: List, promiseImageUrls: List, timeOverLocations: List, + highlights: HighlightsResponse, ): PromiseDetailDto { return PromiseDetailDto( promiseId = promise.id!!, @@ -34,6 +34,7 @@ data class PromiseDetailDto( promiseUsers = promiseUsers, promiseImageUrls = promiseImageUrls, timeOverLocations = timeOverLocations, + highlights = highlights, ) } } diff --git a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCase.kt b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCase.kt index d82f53f8..ed5b79f9 100644 --- a/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCase.kt +++ b/Whatnow-Api/src/main/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCase.kt @@ -2,6 +2,7 @@ package com.depromeet.whatnow.api.promise.usecase import com.depromeet.whatnow.annotation.UseCase import com.depromeet.whatnow.api.interaction.dto.InteractionDto +import com.depromeet.whatnow.api.notification.usecase.NotificationHighlightsReadUseCase import com.depromeet.whatnow.api.promise.dto.LocationCapture import com.depromeet.whatnow.api.promise.dto.PromiseDetailDto import com.depromeet.whatnow.api.promise.dto.PromiseFindDto @@ -29,6 +30,7 @@ class PromiseReadUseCase( val userRepository: UserRepository, val interactionAdapter: InteractionAdapter, val promiseImageAdapter: PromiseImageAdapter, + val notificationHighlightsReadUseCase: NotificationHighlightsReadUseCase, ) { /** * method desc: 유저가 참여한 약속들을 약속 종류(BEFORE, PAST)에 따라 분리해서 조회 @@ -127,6 +129,8 @@ class PromiseReadUseCase( .sortedByDescending { it.createdAt } .map { it.uri } + val highlights = notificationHighlightsReadUseCase.executeTop3(promise.id!!) + val timeOverLocations = promiseUsers.mapNotNull { promiseUser -> promiseUser.userLocation?.let { location -> LocationCapture(userId = promiseUser.userId, coordinateVo = location) @@ -139,6 +143,7 @@ class PromiseReadUseCase( promiseUsers = promiseUserInfoVos, timeOverLocations = timeOverLocations, promiseImageUrls = promiseImagesUrls, + highlights = highlights, ), ) } diff --git a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCaseTest.kt b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCaseTest.kt index 4737b6e0..24eecc9b 100644 --- a/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCaseTest.kt +++ b/Whatnow-Api/src/test/kotlin/com/depromeet/whatnow/api/promise/usecase/PromiseReadUseCaseTest.kt @@ -1,5 +1,8 @@ package com.depromeet.whatnow.api.promise.usecase +import com.depromeet.whatnow.api.notification.dto.ArrivalNotificationResponse +import com.depromeet.whatnow.api.notification.dto.HighlightsResponse +import com.depromeet.whatnow.api.notification.usecase.NotificationHighlightsReadUseCase import com.depromeet.whatnow.common.vo.CoordinateVo import com.depromeet.whatnow.common.vo.PlaceVo import com.depromeet.whatnow.domains.image.adapter.PromiseImageAdapter @@ -25,7 +28,6 @@ import org.junit.jupiter.api.extension.ExtendWith import org.mockito.InjectMocks import org.mockito.Mock import org.mockito.Mockito.`when` -import org.mockito.MockitoAnnotations import org.mockito.junit.jupiter.MockitoExtension import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.core.authority.SimpleGrantedAuthority @@ -53,20 +55,14 @@ class PromiseReadUseCaseTest { @Mock private lateinit var promiseImageAdapter: PromiseImageAdapter + @Mock + private lateinit var notificationHighlightsReadUseCase: NotificationHighlightsReadUseCase + @InjectMocks private lateinit var promiseReadUseCase: PromiseReadUseCase @BeforeEach fun setUp() { - MockitoAnnotations.openMocks(this) - promiseReadUseCase = PromiseReadUseCase( - userRepository = userRepository, - promiseUserAdaptor = promiseUserAdaptor, - promiseAdaptor = promiseAdaptor, - userAdapter = userAdapter, - interactionAdapter = interactionAdapter, - promiseImageAdapter = promiseImageAdapter, - ) val securityContext = SecurityContextHolder.createEmptyContext() val authentication = UsernamePasswordAuthenticationToken("1", null, setOf(SimpleGrantedAuthority("ROLE_USER"))) securityContext.authentication = authentication @@ -145,6 +141,11 @@ class PromiseReadUseCaseTest { Interaction(InteractionType.POOP, 1, 2, 12), Interaction(InteractionType.STEP, 1, 2, 2934), ) + val highlightsResponse = HighlightsResponse( + listOf( + ArrivalNotificationResponse(1, 2, LocalDateTime.now()), + ), + ) `when`(promiseUserAdaptor.findByUserId(userId)).thenReturn(promiseUsers) `when`(promiseAdaptor.queryPromises(listOf(1, 2))).thenReturn(promises) @@ -152,6 +153,8 @@ class PromiseReadUseCaseTest { // `when`(interactionAdapter.queryAllInteraction(1, 1)).thenReturn(interactions) `when`(interactionAdapter.queryAllInteraction(1, 1)).thenReturn(interactionsPromise1) `when`(interactionAdapter.queryAllInteraction(2, 1)).thenReturn(interactionsPromise2) + `when`(notificationHighlightsReadUseCase.executeTop3(1)).thenReturn(highlightsResponse) + `when`(notificationHighlightsReadUseCase.executeTop3(2)).thenReturn(highlightsResponse) // When val result = promiseReadUseCase.findPromiseDetailByStatus(PromiseType.BEFORE) diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/interactionhistory/domain/InteractionHistory.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/interactionhistory/domain/InteractionHistory.kt index 0814f0b9..10c235e2 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/interactionhistory/domain/InteractionHistory.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/interactionhistory/domain/InteractionHistory.kt @@ -6,6 +6,8 @@ import com.depromeet.whatnow.domains.interaction.domain.InteractionType import com.depromeet.whatnow.events.domainEvent.InteractionHistoryRegisterEvent import javax.persistence.Column import javax.persistence.Entity +import javax.persistence.EnumType +import javax.persistence.Enumerated import javax.persistence.GeneratedValue import javax.persistence.GenerationType import javax.persistence.Id @@ -17,6 +19,7 @@ import javax.persistence.Table class InteractionHistory( var promiseId: Long, + @Enumerated(EnumType.STRING) var interactionType: InteractionType, var userId: Long, diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/adapter/NotificationAdapter.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/adapter/NotificationAdapter.kt index 0f555c29..492b62c7 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/adapter/NotificationAdapter.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/adapter/NotificationAdapter.kt @@ -17,4 +17,12 @@ class NotificationAdapter( fun getMyNotifications(userId: Long, pageable: Pageable): Slice { return notificationRepository.findAllByTargetUserIdOrderByCreatedAtDesc(userId, pageable) } + + fun getNotificationHighlights(promiseId: Long, userId: Long, pageable: Pageable): Slice { + return notificationRepository.findAllHighlights(promiseId, userId, pageable) + } + + fun getNotificationHighlightsTop3(promiseId: Long): List { + return notificationRepository.findAllHighlightsTop3(promiseId) + } } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/exception/NotHighlightsTypeException.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/exception/NotHighlightsTypeException.kt new file mode 100644 index 00000000..f12b2840 --- /dev/null +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/exception/NotHighlightsTypeException.kt @@ -0,0 +1,11 @@ +package com.depromeet.whatnow.domains.notification.exception + +import com.depromeet.whatnow.exception.WhatnowCodeException + +class NotHighlightsTypeException : WhatnowCodeException( + NotificationErrorCode.NOT_HIGH_LIGHTS_TYPE, +) { + companion object { + val EXCEPTION: WhatnowCodeException = NotHighlightsTypeException() + } +} diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/exception/NotificationErrorCode.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/exception/NotificationErrorCode.kt index d49dbbc8..7b508b1f 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/exception/NotificationErrorCode.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/exception/NotificationErrorCode.kt @@ -10,6 +10,9 @@ enum class NotificationErrorCode(val status: Int, val code: String, val reason: @ExplainError("알수 없는 알림 유형 일 경우") UNKNOWN_NOTIFICATION_TYPE(BAD_REQUEST, "NOTIFICATION_400_1", "알수 없는 알림 유형 입니다."), + + @ExplainError("하이라이트 유형이 아닐 경우") + NOT_HIGH_LIGHTS_TYPE(BAD_REQUEST, "NOTIFICATION_400_2", "하이라이트 유형이 아닙니다."), ; override val errorReason: ErrorReason diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/repository/NotificationRepository.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/repository/NotificationRepository.kt index 7d0569b6..97a1e4c3 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/repository/NotificationRepository.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/repository/NotificationRepository.kt @@ -4,7 +4,26 @@ import com.depromeet.whatnow.domains.notification.domain.Notification 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 interface NotificationRepository : JpaRepository { fun findAllByTargetUserIdOrderByCreatedAtDesc(targetUserId: Long, pageable: Pageable): Slice + + @Query( + "SELECT * " + + "FROM tbl_notification " + + "WHERE promise_id = :promiseId AND target_user_id = :userId AND notification_type IN ('ARRIVAL') " + + "ORDER BY created_at DESC", + nativeQuery = true, + ) + fun findAllHighlights(promiseId: Long, userId: Long, pageable: Pageable): Slice + + @Query( + "SELECT * " + + "FROM tbl_notification " + + "WHERE promise_id = :promiseId AND notification_type IN ('ARRIVAL') " + + "ORDER BY created_at", + nativeQuery = true, + ) + fun findAllHighlightsTop3(promiseId: Long): List } diff --git a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/service/NotificationDomainService.kt b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/service/NotificationDomainService.kt index c6ad87b4..baf1773e 100644 --- a/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/service/NotificationDomainService.kt +++ b/Whatnow-Domain/src/main/kotlin/com/depromeet/whatnow/domains/notification/service/NotificationDomainService.kt @@ -58,4 +58,16 @@ class NotificationDomainService( fun getMyNotifications(userId: Long, pageable: Pageable): Slice { return notificationAdapter.getMyNotifications(userId, pageable) } + + fun getNotificationHighlights( + promiseId: Long, + userId: Long, + pageable: Pageable, + ): Slice { + return notificationAdapter.getNotificationHighlights(promiseId, userId, pageable) + } + + fun getNotificationHighlightsTop3(promiseId: Long): List { + return notificationAdapter.getNotificationHighlightsTop3(promiseId) + } }