Skip to content

Commit

Permalink
[DPMBE-127] 알림 하이라이트를 조회한다 (#228)
Browse files Browse the repository at this point in the history
* feat: 알림 하이라이트를 조회한다

* fix: 하이라이트조회 endpoint PathVariable 파라미터명 변경 promiseId -> promsie-id

* fix: PageDefault에 size=10 명시적 표기

* style: spotless
  • Loading branch information
kdomo authored Jul 21, 2023
1 parent 8cba323 commit f4d1e07
Show file tree
Hide file tree
Showing 14 changed files with 148 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

Expand All @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.depromeet.whatnow.api.notification.dto

data class HighlightsResponse(
val highlights: List<NotificationAbstract>,
)
Original file line number Diff line number Diff line change
@@ -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 })
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -12,18 +13,17 @@ data class PromiseDetailDto(
val endTime: LocalDateTime,
// 유저의 마지막 위치 리스트
val promiseUsers: List<PromiseUserInfoVo>,
// TODO : 약속 기록 사진 기능 추가시 함께 추가할게요.
val promiseImageUrls: List<String>?,
val timeOverLocations: List<LocationCapture>,
// TODO : highlight 기능 추가시 함께 추가할게요. ( 최대 3개 제한 )
// x val highlights: List<NotificationDto>,
val highlights: HighlightsResponse,
) {
companion object {
fun of(
promise: Promise,
promiseUsers: List<PromiseUserInfoVo>,
promiseImageUrls: List<String>,
timeOverLocations: List<LocationCapture>,
highlights: HighlightsResponse,
): PromiseDetailDto {
return PromiseDetailDto(
promiseId = promise.id!!,
Expand All @@ -34,6 +34,7 @@ data class PromiseDetailDto(
promiseUsers = promiseUsers,
promiseImageUrls = promiseImageUrls,
timeOverLocations = timeOverLocations,
highlights = highlights,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -29,6 +30,7 @@ class PromiseReadUseCase(
val userRepository: UserRepository,
val interactionAdapter: InteractionAdapter,
val promiseImageAdapter: PromiseImageAdapter,
val notificationHighlightsReadUseCase: NotificationHighlightsReadUseCase,
) {
/**
* method desc: 유저가 참여한 약속들을 약속 종류(BEFORE, PAST)에 따라 분리해서 조회
Expand Down Expand Up @@ -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)
Expand All @@ -139,6 +143,7 @@ class PromiseReadUseCase(
promiseUsers = promiseUserInfoVos,
timeOverLocations = timeOverLocations,
promiseImageUrls = promiseImagesUrls,
highlights = highlights,
),
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -145,13 +141,20 @@ 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)
`when`(userAdapter.queryUsers(listOf(1))).thenReturn(users)
// `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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -17,6 +19,7 @@ import javax.persistence.Table
class InteractionHistory(
var promiseId: Long,

@Enumerated(EnumType.STRING)
var interactionType: InteractionType,

var userId: Long,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,12 @@ class NotificationAdapter(
fun getMyNotifications(userId: Long, pageable: Pageable): Slice<Notification> {
return notificationRepository.findAllByTargetUserIdOrderByCreatedAtDesc(userId, pageable)
}

fun getNotificationHighlights(promiseId: Long, userId: Long, pageable: Pageable): Slice<Notification> {
return notificationRepository.findAllHighlights(promiseId, userId, pageable)
}

fun getNotificationHighlightsTop3(promiseId: Long): List<Notification> {
return notificationRepository.findAllHighlightsTop3(promiseId)
}
}
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Notification, Long> {
fun findAllByTargetUserIdOrderByCreatedAtDesc(targetUserId: Long, pageable: Pageable): Slice<Notification>

@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<Notification>

@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<Notification>
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,16 @@ class NotificationDomainService(
fun getMyNotifications(userId: Long, pageable: Pageable): Slice<Notification> {
return notificationAdapter.getMyNotifications(userId, pageable)
}

fun getNotificationHighlights(
promiseId: Long,
userId: Long,
pageable: Pageable,
): Slice<Notification> {
return notificationAdapter.getNotificationHighlights(promiseId, userId, pageable)
}

fun getNotificationHighlightsTop3(promiseId: Long): List<Notification> {
return notificationAdapter.getNotificationHighlightsTop3(promiseId)
}
}

0 comments on commit f4d1e07

Please sign in to comment.