feat(gift): 선물함 상대 회원 정보를 추가한다
This commit is contained in:
@@ -59,6 +59,9 @@ data class GiftListResponse(
|
|||||||
priceCan = it.priceCan,
|
priceCan = it.priceCan,
|
||||||
categoryName = it.categoryName,
|
categoryName = it.categoryName,
|
||||||
sizeName = it.sizeName,
|
sizeName = it.sizeName,
|
||||||
|
counterpartMemberId = it.counterpartMemberId,
|
||||||
|
counterpartNickname = it.counterpartNickname,
|
||||||
|
counterpartProfileImageUrl = it.counterpartProfileImageUrl,
|
||||||
createdAt = it.createdAt?.toUtcIso()
|
createdAt = it.createdAt?.toUtcIso()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -77,6 +80,9 @@ data class GiftListItemResponse(
|
|||||||
val priceCan: Int,
|
val priceCan: Int,
|
||||||
val categoryName: String,
|
val categoryName: String,
|
||||||
val sizeName: String,
|
val sizeName: String,
|
||||||
|
val counterpartMemberId: Long,
|
||||||
|
val counterpartNickname: String,
|
||||||
|
val counterpartProfileImageUrl: String,
|
||||||
val createdAt: String?
|
val createdAt: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.gift.application
|
package kr.co.vividnext.sodalive.v2.gift.application
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.Gift
|
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.Gift
|
||||||
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftCategoryRepository
|
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftCategoryRepository
|
||||||
@@ -11,6 +12,7 @@ import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftSizePriceRep
|
|||||||
import kr.co.vividnext.sodalive.v2.gift.domain.GiftDirection
|
import kr.co.vividnext.sodalive.v2.gift.domain.GiftDirection
|
||||||
import kr.co.vividnext.sodalive.v2.gift.domain.GiftListType
|
import kr.co.vividnext.sodalive.v2.gift.domain.GiftListType
|
||||||
import kr.co.vividnext.sodalive.v2.gift.domain.GiftStatus
|
import kr.co.vividnext.sodalive.v2.gift.domain.GiftStatus
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.data.domain.PageRequest
|
import org.springframework.data.domain.PageRequest
|
||||||
import org.springframework.data.domain.Sort
|
import org.springframework.data.domain.Sort
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
@@ -39,6 +41,9 @@ data class GiftListItemResult(
|
|||||||
val priceCan: Int,
|
val priceCan: Int,
|
||||||
val categoryName: String,
|
val categoryName: String,
|
||||||
val sizeName: String,
|
val sizeName: String,
|
||||||
|
val counterpartMemberId: Long,
|
||||||
|
val counterpartNickname: String,
|
||||||
|
val counterpartProfileImageUrl: String,
|
||||||
val createdAt: LocalDateTime?
|
val createdAt: LocalDateTime?
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -99,7 +104,9 @@ class GiftQueryService(
|
|||||||
private val sizePriceRepository: GiftSizePriceRepository,
|
private val sizePriceRepository: GiftSizePriceRepository,
|
||||||
private val giftRepository: GiftRepository,
|
private val giftRepository: GiftRepository,
|
||||||
private val deliveryRepository: GiftDeliveryRepository,
|
private val deliveryRepository: GiftDeliveryRepository,
|
||||||
private val memberRepository: MemberRepository
|
private val memberRepository: MemberRepository,
|
||||||
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
|
private val cloudFrontHost: String
|
||||||
) {
|
) {
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun getFormOptions() = GiftFormOptionsResult(
|
fun getFormOptions() = GiftFormOptionsResult(
|
||||||
@@ -140,9 +147,13 @@ class GiftQueryService(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val membersById = memberRepository.findAllById(
|
||||||
|
gifts.content.flatMap { listOf(it.senderMemberId, it.recipientMemberId) }.toSet()
|
||||||
|
).associateBy { it.id ?: throw invalidRequest() }
|
||||||
|
|
||||||
return GiftListResult(
|
return GiftListResult(
|
||||||
totalCount = gifts.totalElements,
|
totalCount = gifts.totalElements,
|
||||||
items = gifts.content.map { it.toGiftListItem(memberId) },
|
items = gifts.content.map { it.toGiftListItem(memberId, membersById) },
|
||||||
page = normalizedPage,
|
page = normalizedPage,
|
||||||
size = normalizedSize,
|
size = normalizedSize,
|
||||||
hasNext = gifts.hasNext()
|
hasNext = gifts.hasNext()
|
||||||
@@ -189,16 +200,31 @@ class GiftQueryService(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Gift.toGiftListItem(memberId: Long) = GiftListItemResult(
|
private fun Gift.toGiftListItem(memberId: Long, membersById: Map<Long, Member>): GiftListItemResult {
|
||||||
|
val direction = if (senderMemberId == memberId) GiftDirection.SENT else GiftDirection.RECEIVED
|
||||||
|
val counterpartId = if (direction == GiftDirection.SENT) recipientMemberId else senderMemberId
|
||||||
|
val counterpart = membersById[counterpartId] ?: throw invalidRequest()
|
||||||
|
|
||||||
|
return GiftListItemResult(
|
||||||
applicationNo = applicationNo,
|
applicationNo = applicationNo,
|
||||||
direction = if (senderMemberId == memberId) GiftDirection.SENT else GiftDirection.RECEIVED,
|
direction = direction,
|
||||||
status = status,
|
status = status,
|
||||||
statusName = status.displayName,
|
statusName = status.displayName,
|
||||||
priceCan = salePriceCan,
|
priceCan = salePriceCan,
|
||||||
categoryName = categoryNameSnapshot,
|
categoryName = categoryNameSnapshot,
|
||||||
sizeName = sizeCode.displayName,
|
sizeName = sizeCode.displayName,
|
||||||
|
counterpartMemberId = counterpartId,
|
||||||
|
counterpartNickname = counterpart.nickname,
|
||||||
|
counterpartProfileImageUrl = counterpart.profileImageUrl(),
|
||||||
createdAt = createdAt
|
createdAt = createdAt
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Member.profileImageUrl() = if (profileImage.isNullOrBlank()) {
|
||||||
|
"$cloudFrontHost/profile/default-profile.png"
|
||||||
|
} else {
|
||||||
|
"$cloudFrontHost/$profileImage"
|
||||||
|
}
|
||||||
|
|
||||||
private fun Gift.directionFor(memberId: Long) = when {
|
private fun Gift.directionFor(memberId: Long) = when {
|
||||||
senderMemberId == memberId -> GiftDirection.SENT
|
senderMemberId == memberId -> GiftDirection.SENT
|
||||||
|
|||||||
+24
-6
@@ -46,7 +46,9 @@ import java.time.LocalDateTime
|
|||||||
|
|
||||||
@SpringBootTest(
|
@SpringBootTest(
|
||||||
properties = [
|
properties = [
|
||||||
"spring.datasource.url=jdbc:h2:mem:gift-controller;MODE=MySQL;NON_KEYWORDS=VALUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"
|
"spring.datasource.url=" +
|
||||||
|
"jdbc:h2:mem:gift-controller;MODE=MySQL;NON_KEYWORDS=VALUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE",
|
||||||
|
"cloud.aws.cloud-front.host=https://cdn.test"
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
@@ -86,13 +88,26 @@ class GiftControllerTest @Autowired constructor(
|
|||||||
@Test
|
@Test
|
||||||
@DisplayName("회원은 선물함 목록에서 보낸 선물과 노출 가능한 받은 선물을 페이징 응답으로 조회한다")
|
@DisplayName("회원은 선물함 목록에서 보낸 선물과 노출 가능한 받은 선물을 페이징 응답으로 조회한다")
|
||||||
fun shouldListGifts() {
|
fun shouldListGifts() {
|
||||||
val member = Member(password = "password", nickname = "member").apply { id = 10L }
|
val member = memberRepository.save(Member(password = "password", nickname = "member"))
|
||||||
saveGift("G-CONTROLLER-SENT", senderMemberId = 10L, recipientMemberId = 20L, status = GiftStatus.RECEIVED)
|
val counterpart = memberRepository.save(
|
||||||
saveGift("G-CONTROLLER-HIDDEN", senderMemberId = 20L, recipientMemberId = 10L, status = GiftStatus.RECEIVED)
|
Member(password = "password", nickname = "counterpart", profileImage = "profile/counterpart.png")
|
||||||
|
)
|
||||||
|
saveGift(
|
||||||
|
"G-CONTROLLER-SENT",
|
||||||
|
senderMemberId = member.id!!,
|
||||||
|
recipientMemberId = counterpart.id!!,
|
||||||
|
status = GiftStatus.RECEIVED
|
||||||
|
)
|
||||||
|
saveGift(
|
||||||
|
"G-CONTROLLER-HIDDEN",
|
||||||
|
senderMemberId = counterpart.id!!,
|
||||||
|
recipientMemberId = member.id!!,
|
||||||
|
status = GiftStatus.RECEIVED
|
||||||
|
)
|
||||||
saveGift(
|
saveGift(
|
||||||
"G-CONTROLLER-VISIBLE",
|
"G-CONTROLLER-VISIBLE",
|
||||||
senderMemberId = 20L,
|
senderMemberId = counterpart.id!!,
|
||||||
recipientMemberId = 10L,
|
recipientMemberId = member.id!!,
|
||||||
status = GiftStatus.TRACKING_REGISTERED
|
status = GiftStatus.TRACKING_REGISTERED
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -117,6 +132,9 @@ class GiftControllerTest @Autowired constructor(
|
|||||||
.andExpect(jsonPath("$.data.items[0].priceCan").value(80))
|
.andExpect(jsonPath("$.data.items[0].priceCan").value(80))
|
||||||
.andExpect(jsonPath("$.data.items[0].categoryName").value("인형"))
|
.andExpect(jsonPath("$.data.items[0].categoryName").value("인형"))
|
||||||
.andExpect(jsonPath("$.data.items[0].sizeName").value("소형"))
|
.andExpect(jsonPath("$.data.items[0].sizeName").value("소형"))
|
||||||
|
.andExpect(jsonPath("$.data.items[0].counterpartMemberId").value(counterpart.id!!))
|
||||||
|
.andExpect(jsonPath("$.data.items[0].counterpartNickname").value("counterpart"))
|
||||||
|
.andExpect(jsonPath("$.data.items[0].counterpartProfileImageUrl").value("https://cdn.test/profile/counterpart.png"))
|
||||||
.andExpect(jsonPath("$.data.items[0].createdAt").value(endsWith("Z")))
|
.andExpect(jsonPath("$.data.items[0].createdAt").value(endsWith("Z")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+49
-16
@@ -61,10 +61,17 @@ class GiftQueryServiceTest @Autowired constructor(
|
|||||||
@Test
|
@Test
|
||||||
@DisplayName("보낸 선물은 접수 직후 선물함에 노출된다")
|
@DisplayName("보낸 선물은 접수 직후 선물함에 노출된다")
|
||||||
fun shouldListReceivedGiftForSender() {
|
fun shouldListReceivedGiftForSender() {
|
||||||
saveGift("G-SENT-RECEIVED", senderMemberId = MEMBER_ID, recipientMemberId = 2L, status = GiftStatus.RECEIVED)
|
val sender = saveMember("fan")
|
||||||
|
val recipient = saveMember("creator", "profile/creator.png")
|
||||||
|
saveGift(
|
||||||
|
"G-SENT-RECEIVED",
|
||||||
|
senderMemberId = sender.id!!,
|
||||||
|
recipientMemberId = recipient.id!!,
|
||||||
|
status = GiftStatus.RECEIVED
|
||||||
|
)
|
||||||
|
|
||||||
val result = queryService().getGifts(
|
val result = queryService().getGifts(
|
||||||
memberId = MEMBER_ID,
|
memberId = sender.id!!,
|
||||||
type = GiftListType.SENT,
|
type = GiftListType.SENT,
|
||||||
page = 0,
|
page = 0,
|
||||||
size = 20
|
size = 20
|
||||||
@@ -79,22 +86,33 @@ class GiftQueryServiceTest @Autowired constructor(
|
|||||||
assertEquals(80, result.items.single().priceCan)
|
assertEquals(80, result.items.single().priceCan)
|
||||||
assertEquals("인형", result.items.single().categoryName)
|
assertEquals("인형", result.items.single().categoryName)
|
||||||
assertEquals("소형", result.items.single().sizeName)
|
assertEquals("소형", result.items.single().sizeName)
|
||||||
|
assertEquals(recipient.id, result.items.single().counterpartMemberId)
|
||||||
|
assertEquals("creator", result.items.single().counterpartNickname)
|
||||||
|
assertEquals("https://cdn.test/profile/creator.png", result.items.single().counterpartProfileImageUrl)
|
||||||
assertNotNull(result.items.single().createdAt)
|
assertNotNull(result.items.single().createdAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("받은 선물은 운송장 등록 이후 상태만 선물함에 노출된다")
|
@DisplayName("받은 선물은 운송장 등록 이후 상태만 선물함에 노출된다")
|
||||||
fun shouldExcludeReceivedOnlyGiftAndListVisibleGiftForRecipient() {
|
fun shouldExcludeReceivedOnlyGiftAndListVisibleGiftForRecipient() {
|
||||||
saveGift("G-RECEIVED-HIDDEN", senderMemberId = 2L, recipientMemberId = MEMBER_ID, status = GiftStatus.RECEIVED)
|
val recipient = saveMember("creator")
|
||||||
|
val hiddenSender = saveMember("hidden-fan")
|
||||||
|
val visibleSender = saveMember("visible-fan")
|
||||||
|
saveGift(
|
||||||
|
"G-RECEIVED-HIDDEN",
|
||||||
|
senderMemberId = hiddenSender.id!!,
|
||||||
|
recipientMemberId = recipient.id!!,
|
||||||
|
status = GiftStatus.RECEIVED
|
||||||
|
)
|
||||||
saveGift(
|
saveGift(
|
||||||
"G-RECEIVED-VISIBLE",
|
"G-RECEIVED-VISIBLE",
|
||||||
senderMemberId = 3L,
|
senderMemberId = visibleSender.id!!,
|
||||||
recipientMemberId = MEMBER_ID,
|
recipientMemberId = recipient.id!!,
|
||||||
status = GiftStatus.TRACKING_REGISTERED
|
status = GiftStatus.TRACKING_REGISTERED
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = queryService().getGifts(
|
val result = queryService().getGifts(
|
||||||
memberId = MEMBER_ID,
|
memberId = recipient.id!!,
|
||||||
type = GiftListType.RECEIVED,
|
type = GiftListType.RECEIVED,
|
||||||
page = 0,
|
page = 0,
|
||||||
size = 20
|
size = 20
|
||||||
@@ -103,22 +121,29 @@ class GiftQueryServiceTest @Autowired constructor(
|
|||||||
assertEquals(1L, result.totalCount)
|
assertEquals(1L, result.totalCount)
|
||||||
assertEquals(listOf("G-RECEIVED-VISIBLE"), result.items.map { it.applicationNo })
|
assertEquals(listOf("G-RECEIVED-VISIBLE"), result.items.map { it.applicationNo })
|
||||||
assertEquals(GiftDirection.RECEIVED, result.items.single().direction)
|
assertEquals(GiftDirection.RECEIVED, result.items.single().direction)
|
||||||
|
assertEquals(visibleSender.id, result.items.single().counterpartMemberId)
|
||||||
|
assertEquals("visible-fan", result.items.single().counterpartNickname)
|
||||||
|
assertEquals("https://cdn.test/profile/default-profile.png", result.items.single().counterpartProfileImageUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("전체 선물함은 보낸 선물과 노출 가능한 받은 선물을 방향과 함께 반환한다")
|
@DisplayName("전체 선물함은 보낸 선물과 노출 가능한 받은 선물을 방향과 함께 반환한다")
|
||||||
fun shouldListSentAndVisibleReceivedGiftsWithDirectionsForAll() {
|
fun shouldListSentAndVisibleReceivedGiftsWithDirectionsForAll() {
|
||||||
saveGift("G-ALL-SENT", senderMemberId = MEMBER_ID, recipientMemberId = 2L, status = GiftStatus.CANCELED)
|
val member = saveMember("member")
|
||||||
|
val creator = saveMember("creator")
|
||||||
|
val fan = saveMember("fan")
|
||||||
|
val hiddenFan = saveMember("hidden-fan")
|
||||||
|
saveGift("G-ALL-SENT", senderMemberId = member.id!!, recipientMemberId = creator.id!!, status = GiftStatus.CANCELED)
|
||||||
saveGift(
|
saveGift(
|
||||||
"G-ALL-RECEIVED",
|
"G-ALL-RECEIVED",
|
||||||
senderMemberId = 3L,
|
senderMemberId = fan.id!!,
|
||||||
recipientMemberId = MEMBER_ID,
|
recipientMemberId = member.id!!,
|
||||||
status = GiftStatus.ARRIVED_AT_MAILBOX
|
status = GiftStatus.ARRIVED_AT_MAILBOX
|
||||||
)
|
)
|
||||||
saveGift("G-ALL-HIDDEN", senderMemberId = 4L, recipientMemberId = MEMBER_ID, status = GiftStatus.RECEIVED)
|
saveGift("G-ALL-HIDDEN", senderMemberId = hiddenFan.id!!, recipientMemberId = member.id!!, status = GiftStatus.RECEIVED)
|
||||||
|
|
||||||
val result = queryService().getGifts(
|
val result = queryService().getGifts(
|
||||||
memberId = MEMBER_ID,
|
memberId = member.id!!,
|
||||||
type = GiftListType.ALL,
|
type = GiftListType.ALL,
|
||||||
page = 0,
|
page = 0,
|
||||||
size = 20
|
size = 20
|
||||||
@@ -129,15 +154,21 @@ class GiftQueryServiceTest @Autowired constructor(
|
|||||||
mapOf("G-ALL-SENT" to GiftDirection.SENT, "G-ALL-RECEIVED" to GiftDirection.RECEIVED),
|
mapOf("G-ALL-SENT" to GiftDirection.SENT, "G-ALL-RECEIVED" to GiftDirection.RECEIVED),
|
||||||
result.items.associate { it.applicationNo to it.direction }
|
result.items.associate { it.applicationNo to it.direction }
|
||||||
)
|
)
|
||||||
|
assertEquals(
|
||||||
|
mapOf("G-ALL-SENT" to creator.id, "G-ALL-RECEIVED" to fan.id),
|
||||||
|
result.items.associate { it.applicationNo to it.counterpartMemberId }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("선물함 page와 size는 음수 또는 0이면 기본값으로 보정된다")
|
@DisplayName("선물함 page와 size는 음수 또는 0이면 기본값으로 보정된다")
|
||||||
fun shouldNormalizeGiftListPageAndSize() {
|
fun shouldNormalizeGiftListPageAndSize() {
|
||||||
saveGift("G-PAGING", senderMemberId = MEMBER_ID, recipientMemberId = 2L, status = GiftStatus.RECEIVED)
|
val sender = saveMember("fan")
|
||||||
|
val recipient = saveMember("creator")
|
||||||
|
saveGift("G-PAGING", senderMemberId = sender.id!!, recipientMemberId = recipient.id!!, status = GiftStatus.RECEIVED)
|
||||||
|
|
||||||
val result = queryService().getGifts(
|
val result = queryService().getGifts(
|
||||||
memberId = MEMBER_ID,
|
memberId = sender.id!!,
|
||||||
type = GiftListType.SENT,
|
type = GiftListType.SENT,
|
||||||
page = -1,
|
page = -1,
|
||||||
size = 0
|
size = 0
|
||||||
@@ -379,7 +410,9 @@ class GiftQueryServiceTest @Autowired constructor(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun saveMember(nickname: String) = memberRepository.save(Member(password = "password", nickname = nickname))
|
private fun saveMember(nickname: String, profileImage: String? = null) = memberRepository.save(
|
||||||
|
Member(password = "password", nickname = nickname, profileImage = profileImage)
|
||||||
|
)
|
||||||
|
|
||||||
private fun giftCategory(
|
private fun giftCategory(
|
||||||
classificationNumber: String,
|
classificationNumber: String,
|
||||||
@@ -416,11 +449,11 @@ class GiftQueryServiceTest @Autowired constructor(
|
|||||||
sizePriceRepository,
|
sizePriceRepository,
|
||||||
giftRepository,
|
giftRepository,
|
||||||
deliveryRepository,
|
deliveryRepository,
|
||||||
memberRepository
|
memberRepository,
|
||||||
|
"https://cdn.test"
|
||||||
)
|
)
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
const val MEMBER_ID = 1L
|
|
||||||
val NOW: LocalDateTime = LocalDateTime.of(2026, 9, 29, 12, 0)
|
val NOW: LocalDateTime = LocalDateTime.of(2026, 9, 29, 12, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user