feat(channel-home): 숨긴 후원을 제외하고 소유자에게 후원 ID를 제공한다
This commit is contained in:
+4
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.creator.channel.home.dto
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.home.dto
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.common.dto.CreatorChannelAudioContentResponse
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.common.dto.CreatorChannelAudioContentResponse
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
||||||
@@ -109,6 +110,8 @@ data class CreatorChannelLiveResponse(
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class CreatorChannelDonationResponse(
|
data class CreatorChannelDonationResponse(
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
val donationId: Long?,
|
||||||
val nickname: String,
|
val nickname: String,
|
||||||
val profileImageUrl: String,
|
val profileImageUrl: String,
|
||||||
val can: Int,
|
val can: Int,
|
||||||
@@ -120,6 +123,7 @@ data class CreatorChannelDonationResponse(
|
|||||||
companion object {
|
companion object {
|
||||||
fun from(donation: CreatorChannelDonation): CreatorChannelDonationResponse {
|
fun from(donation: CreatorChannelDonation): CreatorChannelDonationResponse {
|
||||||
return CreatorChannelDonationResponse(
|
return CreatorChannelDonationResponse(
|
||||||
|
donationId = donation.donationId,
|
||||||
nickname = donation.nickname,
|
nickname = donation.nickname,
|
||||||
profileImageUrl = donation.profileImageUrl,
|
profileImageUrl = donation.profileImageUrl,
|
||||||
can = donation.can,
|
can = donation.can,
|
||||||
|
|||||||
+2
@@ -195,6 +195,7 @@ class DefaultCreatorChannelHomeQueryRepository(
|
|||||||
.select(
|
.select(
|
||||||
Projections.constructor(
|
Projections.constructor(
|
||||||
CreatorChannelDonationRecord::class.java,
|
CreatorChannelDonationRecord::class.java,
|
||||||
|
channelDonationMessage.id,
|
||||||
channelDonationMessage.member.id,
|
channelDonationMessage.member.id,
|
||||||
donorName.displayName,
|
donorName.displayName,
|
||||||
channelDonationMessage.member.profileImage,
|
channelDonationMessage.member.profileImage,
|
||||||
@@ -210,6 +211,7 @@ class DefaultCreatorChannelHomeQueryRepository(
|
|||||||
.leftJoin(donorName.name).on(donorName.condition)
|
.leftJoin(donorName.name).on(donorName.condition)
|
||||||
.where(
|
.where(
|
||||||
channelDonationMessage.creator.id.eq(creatorId),
|
channelDonationMessage.creator.id.eq(creatorId),
|
||||||
|
channelDonationMessage.isHidden.isFalse,
|
||||||
channelDonationMessage.createdAt.goe(start),
|
channelDonationMessage.createdAt.goe(start),
|
||||||
channelDonationMessage.createdAt.lt(end)
|
channelDonationMessage.createdAt.lt(end)
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-2
@@ -103,7 +103,7 @@ class CreatorChannelHomeQueryService(
|
|||||||
)?.toDomain(),
|
)?.toDomain(),
|
||||||
latestAudioContent = latestAudioContent,
|
latestAudioContent = latestAudioContent,
|
||||||
channelDonations = queryPort.findChannelDonations(creatorId, viewerId, now, lang = lang)
|
channelDonations = queryPort.findChannelDonations(creatorId, viewerId, now, lang = lang)
|
||||||
.map { it.toDomain(creatorId, viewerId, lang) },
|
.map { it.toDomain(creatorId, viewer, lang) },
|
||||||
notices = communityQueryService.findHomeCommunityPosts(
|
notices = communityQueryService.findHomeCommunityPosts(
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
viewerId = viewerId,
|
viewerId = viewerId,
|
||||||
@@ -197,9 +197,11 @@ class CreatorChannelHomeQueryService(
|
|||||||
isRented = isRented
|
isRented = isRented
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun CreatorChannelDonationRecord.toDomain(creatorId: Long, viewerId: Long, lang: Lang): CreatorChannelDonation {
|
private fun CreatorChannelDonationRecord.toDomain(creatorId: Long, viewer: Member, lang: Lang): CreatorChannelDonation {
|
||||||
|
val viewerId = viewer.id!!
|
||||||
val isMasked = isSecret && viewerId != creatorId && viewerId != memberId
|
val isMasked = isSecret && viewerId != creatorId && viewerId != memberId
|
||||||
return CreatorChannelDonation(
|
return CreatorChannelDonation(
|
||||||
|
donationId = donationId.takeIf { viewer.role == MemberRole.CREATOR && viewerId == creatorId },
|
||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
nickname = when {
|
nickname = when {
|
||||||
isMasked -> messageSource.getMessage("explorer.channel_donation.anonymous.nickname", lang).orEmpty()
|
isMasked -> messageSource.getMessage("explorer.channel_donation.anonymous.nickname", lang).orEmpty()
|
||||||
|
|||||||
+1
@@ -43,6 +43,7 @@ data class CreatorChannelLive(
|
|||||||
)
|
)
|
||||||
|
|
||||||
data class CreatorChannelDonation(
|
data class CreatorChannelDonation(
|
||||||
|
val donationId: Long?,
|
||||||
val memberId: Long,
|
val memberId: Long,
|
||||||
val nickname: String,
|
val nickname: String,
|
||||||
val profileImageUrl: String,
|
val profileImageUrl: String,
|
||||||
|
|||||||
+1
@@ -114,6 +114,7 @@ data class CreatorChannelAudioContentRecord(
|
|||||||
)
|
)
|
||||||
|
|
||||||
data class CreatorChannelDonationRecord(
|
data class CreatorChannelDonationRecord(
|
||||||
|
val donationId: Long,
|
||||||
val memberId: Long,
|
val memberId: Long,
|
||||||
val nickname: String,
|
val nickname: String,
|
||||||
val profileImagePath: String?,
|
val profileImagePath: String?,
|
||||||
|
|||||||
+6
-1
@@ -35,6 +35,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
|
|||||||
import org.springframework.boot.test.context.SpringBootTest
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean
|
import org.springframework.boot.test.mock.mockito.MockBean
|
||||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
|
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
|
||||||
|
import org.springframework.test.annotation.DirtiesContext
|
||||||
import org.springframework.test.context.ContextConfiguration
|
import org.springframework.test.context.ContextConfiguration
|
||||||
import org.springframework.test.web.servlet.MockMvc
|
import org.springframework.test.web.servlet.MockMvc
|
||||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
||||||
@@ -54,6 +55,7 @@ import javax.persistence.EntityManager
|
|||||||
)
|
)
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
@ContextConfiguration(initializers = [EmbeddedRedisInitializer::class])
|
@ContextConfiguration(initializers = [EmbeddedRedisInitializer::class])
|
||||||
|
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
|
||||||
class CreatorChannelHomeEndToEndTest @Autowired constructor(
|
class CreatorChannelHomeEndToEndTest @Autowired constructor(
|
||||||
private val mockMvc: MockMvc,
|
private val mockMvc: MockMvc,
|
||||||
private val entityManager: EntityManager,
|
private val entityManager: EntityManager,
|
||||||
@@ -178,9 +180,12 @@ class CreatorChannelHomeEndToEndTest @Autowired constructor(
|
|||||||
item["createdAtUtc"].asText()
|
item["createdAtUtc"].asText()
|
||||||
)
|
)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
setOf("nickname", "profileImageUrl", "can", "message", "createdAtUtc", "isSecret"),
|
setOf("nickname", "profileImageUrl", "can", "message", "createdAtUtc", "isSecret") +
|
||||||
|
if (viewerRole == "creator") setOf("donationId") else emptySet(),
|
||||||
item.fieldNames().asSequence().toSet()
|
item.fieldNames().asSequence().toSet()
|
||||||
)
|
)
|
||||||
|
if (viewerRole == "creator") assertEquals(stored.id, item["donationId"]?.asLong())
|
||||||
|
assertFalse(item.has("memberId"))
|
||||||
val masked = stored.isSecret && fixture.viewer.id != fixture.creatorId && fixture.viewer.id != stored.member?.id
|
val masked = stored.isSecret && fixture.viewer.id != fixture.creatorId && fixture.viewer.id != stored.member?.id
|
||||||
if (masked) {
|
if (masked) {
|
||||||
assertEquals(anonymousNickname, item["nickname"].asText())
|
assertEquals(anonymousNickname, item["nickname"].asText())
|
||||||
|
|||||||
+1
@@ -254,6 +254,7 @@ class CreatorChannelHomeControllerTest @Autowired constructor(
|
|||||||
),
|
),
|
||||||
channelDonations = listOf(
|
channelDonations = listOf(
|
||||||
CreatorChannelDonation(
|
CreatorChannelDonation(
|
||||||
|
donationId = null,
|
||||||
memberId = 20L,
|
memberId = 20L,
|
||||||
nickname = "fan",
|
nickname = "fan",
|
||||||
profileImageUrl = "fan.png",
|
profileImageUrl = "fan.png",
|
||||||
|
|||||||
+1
@@ -186,6 +186,7 @@ class CreatorChannelHomeFacadeTest {
|
|||||||
),
|
),
|
||||||
channelDonations = listOf(
|
channelDonations = listOf(
|
||||||
CreatorChannelDonation(
|
CreatorChannelDonation(
|
||||||
|
donationId = null,
|
||||||
memberId = 20L,
|
memberId = 20L,
|
||||||
nickname = "fan",
|
nickname = "fan",
|
||||||
profileImageUrl = "fan.png",
|
profileImageUrl = "fan.png",
|
||||||
|
|||||||
+52
-1
@@ -821,6 +821,55 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
|
|||||||
assertEquals(listOf(true, true, false, true, false, true, false, true), records.map { it.isSecret })
|
assertEquals(listOf(true, true, false, true, false, true, false, true), records.map { it.isSecret })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("홈은 최신 숨김 공개·비밀 후보를 제외한 뒤 동시각 ID순으로 visible 8건을 보충한다")
|
||||||
|
fun shouldRefillHomeWithLatestEightVisibleDonationsAfterHiddenRowsAreExcluded() {
|
||||||
|
val now = LocalDateTime.of(2026, 6, 12, 12, 0)
|
||||||
|
val creator = saveMember("hidden-home-creator", MemberRole.CREATOR)
|
||||||
|
val donor = saveMember("hidden-home-donor", MemberRole.USER)
|
||||||
|
val viewers = listOf(
|
||||||
|
creator,
|
||||||
|
donor,
|
||||||
|
saveMember("hidden-home-outsider", MemberRole.USER),
|
||||||
|
saveMember("hidden-home-other-creator", MemberRole.CREATOR),
|
||||||
|
saveMember("hidden-home-admin", MemberRole.ADMIN)
|
||||||
|
)
|
||||||
|
val rows = (1..12).map { index ->
|
||||||
|
saveDonation(
|
||||||
|
creator,
|
||||||
|
donor,
|
||||||
|
index,
|
||||||
|
now.plusMinutes((index / 2).toLong()),
|
||||||
|
isSecret = index % 2 == 1,
|
||||||
|
isHidden = index >= 10
|
||||||
|
)
|
||||||
|
}
|
||||||
|
flushAndClear()
|
||||||
|
|
||||||
|
viewers.forEach { viewer ->
|
||||||
|
val records = repository.findChannelDonations(creator.id!!, viewer.id!!, now, limit = 8, lang = Lang.KO)
|
||||||
|
assertEquals((9 downTo 2).toList(), records.map { it.can })
|
||||||
|
assertEquals(rows.take(9).drop(1).reversed().map { it.id }, records.map { it.donationId })
|
||||||
|
assertEquals(listOf(true, false, true, false, true, false, true, false), records.map { it.isSecret })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("모든 후원이 숨김이면 홈 후원은 빈 목록이다")
|
||||||
|
fun shouldReturnEmptyHomeDonationsWhenAllRowsAreHidden() {
|
||||||
|
val now = LocalDateTime.of(2026, 6, 12, 12, 0)
|
||||||
|
val creator = saveMember("all-hidden-home-creator", MemberRole.CREATOR)
|
||||||
|
val donor = saveMember("all-hidden-home-donor", MemberRole.USER)
|
||||||
|
listOf(false, true).forEach { secret ->
|
||||||
|
saveDonation(creator, donor, 100, now, isSecret = secret, isHidden = true)
|
||||||
|
}
|
||||||
|
flushAndClear()
|
||||||
|
|
||||||
|
listOf(creator, donor).forEach { viewer ->
|
||||||
|
assertTrue(repository.findChannelDonations(creator.id!!, viewer.id!!, now, limit = 8, lang = Lang.KO).isEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("채널 후원 메시지는 추가 메시지만 반환하고 없으면 빈 문자열을 반환한다")
|
@DisplayName("채널 후원 메시지는 추가 메시지만 반환하고 없으면 빈 문자열을 반환한다")
|
||||||
fun shouldReturnOnlyAdditionalChannelDonationMessage() {
|
fun shouldReturnOnlyAdditionalChannelDonationMessage() {
|
||||||
@@ -1376,9 +1425,11 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
|
|||||||
can: Int,
|
can: Int,
|
||||||
createdAt: LocalDateTime,
|
createdAt: LocalDateTime,
|
||||||
isSecret: Boolean = false,
|
isSecret: Boolean = false,
|
||||||
additionalMessage: String? = "thanks"
|
additionalMessage: String? = "thanks",
|
||||||
|
isHidden: Boolean = false
|
||||||
): ChannelDonationMessage {
|
): ChannelDonationMessage {
|
||||||
val donation = ChannelDonationMessage(can = can, isSecret = isSecret, additionalMessage = additionalMessage)
|
val donation = ChannelDonationMessage(can = can, isSecret = isSecret, additionalMessage = additionalMessage)
|
||||||
|
donation.isHidden = isHidden
|
||||||
donation.creator = creator
|
donation.creator = creator
|
||||||
donation.member = donor
|
donation.member = donor
|
||||||
entityManager.persist(donation)
|
entityManager.persist(donation)
|
||||||
|
|||||||
+6
@@ -204,6 +204,9 @@ class CreatorChannelHomeQueryServiceTest {
|
|||||||
"20, USER, true",
|
"20, USER, true",
|
||||||
"10, USER, true",
|
"10, USER, true",
|
||||||
"30, CREATOR, true",
|
"30, CREATOR, true",
|
||||||
|
"1, USER, true",
|
||||||
|
"1, ADMIN, true",
|
||||||
|
"40, ADMIN, true",
|
||||||
"1, CREATOR, false",
|
"1, CREATOR, false",
|
||||||
"20, USER, false",
|
"20, USER, false",
|
||||||
"10, USER, false",
|
"10, USER, false",
|
||||||
@@ -237,6 +240,7 @@ class CreatorChannelHomeQueryServiceTest {
|
|||||||
assertEquals(isSecret, donation.isSecret)
|
assertEquals(isSecret, donation.isSecret)
|
||||||
assertEquals(30, donation.can)
|
assertEquals(30, donation.can)
|
||||||
assertEquals(LocalDateTime.of(2026, 6, 13, 8, 0), donation.createdAt)
|
assertEquals(LocalDateTime.of(2026, 6, 13, 8, 0), donation.createdAt)
|
||||||
|
assertEquals(if (role == MemberRole.CREATOR && viewerId == 1L) 101L else null, donation.donationId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -455,6 +459,7 @@ class CreatorChannelHomeQueryServiceTest {
|
|||||||
),
|
),
|
||||||
channelDonations = listOf(
|
channelDonations = listOf(
|
||||||
CreatorChannelDonation(
|
CreatorChannelDonation(
|
||||||
|
donationId = null,
|
||||||
memberId = 20L,
|
memberId = 20L,
|
||||||
nickname = "fan",
|
nickname = "fan",
|
||||||
profileImageUrl = "fan.png",
|
profileImageUrl = "fan.png",
|
||||||
@@ -690,6 +695,7 @@ private class FakeCreatorChannelHomeQueryPort(private val donorId: Long = 20L) :
|
|||||||
lang: Lang
|
lang: Lang
|
||||||
): List<CreatorChannelDonationRecord> = listOf(
|
): List<CreatorChannelDonationRecord> = listOf(
|
||||||
CreatorChannelDonationRecord(
|
CreatorChannelDonationRecord(
|
||||||
|
donationId = 101L,
|
||||||
memberId = donorId,
|
memberId = donorId,
|
||||||
nickname = channelDonationNickname,
|
nickname = channelDonationNickname,
|
||||||
profileImagePath = channelDonationProfilePath,
|
profileImagePath = channelDonationProfilePath,
|
||||||
|
|||||||
Reference in New Issue
Block a user