From 2c19e4b76c00145aded6cae0401929d2402d5544 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 13 Apr 2026 18:51:33 +0900 Subject: [PATCH] =?UTF-8?q?feat(agent-calculate):=20=EC=86=8C=EC=86=8D=20?= =?UTF-8?q?=ED=81=AC=EB=A6=AC=EC=97=90=EC=9D=B4=ED=84=B0=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=EC=97=90=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ..._에이전트소속크리에이터프로필이미지추가.md | 20 ++++++++++++ .../AgentCalculateQueryRepository.kt | 12 +++++-- .../GetAgentAssignedCreatorResponse.kt | 3 +- ...minAgentReadCurrentMonthListSummaryTest.kt | 4 ++- .../agent/read/AdminAgentReadParityTest.kt | 4 ++- .../read/AdminAgentReadQueryRepositoryTest.kt | 4 ++- .../calculate/AgentCalculateControllerTest.kt | 4 ++- .../AgentCalculateQueryRepositoryTest.kt | 31 +++++++++++++++++-- .../calculate/AgentCalculateServiceTest.kt | 4 ++- 9 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 docs/20260413_에이전트소속크리에이터프로필이미지추가.md diff --git a/docs/20260413_에이전트소속크리에이터프로필이미지추가.md b/docs/20260413_에이전트소속크리에이터프로필이미지추가.md new file mode 100644 index 00000000..8dc3a07a --- /dev/null +++ b/docs/20260413_에이전트소속크리에이터프로필이미지추가.md @@ -0,0 +1,20 @@ +# 에이전트 소속 크리에이터 프로필 이미지 추가 + +- [x] `getAssignedCreators` 흐름과 기존 프로필 이미지 응답 패턴을 확인한다. +- [x] `GetAgentAssignedCreatorItem`에 프로필 이미지 URL 필드를 추가한다. +- [x] `AgentCalculateQueryRepository.getAssignedCreators` projection에 프로필 이미지 URL을 포함한다. +- [x] `AgentCalculateQueryRepositoryTest`에 프로필 이미지 URL 및 기본 이미지 fallback 검증을 추가한다. +- [x] `AgentCalculateServiceTest`, `AgentCalculateControllerTest` fixture와 검증을 갱신한다. +- [x] 정적 진단과 관련 테스트를 실행해 변경을 검증한다. + +## 검증 기록 + +### 1차 구현 +- 무엇을: 에이전트 소속 크리에이터 조회 응답에 프로필 이미지 URL 필드 추가 구현 및 검증 +- 왜: 크리에이터 목록 응답에서 프로필 이미지를 함께 내려주기 위해 +- 어떻게: + - `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateServiceTest"` 실행 시 `profileImageUrl` 및 `cloudFrontHost` 관련 컴파일 실패를 확인해 red 단계 검증 + - `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest" --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateServiceTest" --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateControllerTest"` 실행 결과 `BUILD SUCCESSFUL` + - `./gradlew ktlintCheck` 실행 결과 `BUILD SUCCESSFUL` + - `./gradlew test --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest.shouldGetAssignedCreatorsWithProfileImageUrl" --tests "kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateControllerTest.shouldForwardAssignedCreatorsRequestToService"` 실행 결과 `BUILD SUCCESSFUL` + - Kotlin LSP 서버가 현재 환경에 없어 `lsp_diagnostics`는 수행 불가였고, 대신 compile/test/ktlint 결과로 정적 검증 대체 diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateQueryRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateQueryRepository.kt index 060a658e..ba9d4316 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateQueryRepository.kt @@ -15,6 +15,7 @@ import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom import kr.co.vividnext.sodalive.member.QMember.member import kr.co.vividnext.sodalive.partner.agent.assignment.QAgentCreatorRelation.agentCreatorRelation import kr.co.vividnext.sodalive.partner.agent.ratio.QAgentSettlementRatio.agentSettlementRatio +import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Repository import java.time.LocalDateTime import javax.persistence.EntityManager @@ -22,7 +23,10 @@ import javax.persistence.EntityManager @Repository class AgentCalculateQueryRepository( private val queryFactory: JPAQueryFactory, - private val entityManager: EntityManager + private val entityManager: EntityManager, + + @Value("\${cloud.aws.cloud-front.host}") + private val cloudFrontHost: String ) { fun getAssignedCreatorTotalCount(agentId: Long, currentTime: LocalDateTime): Int { return queryFactory @@ -46,7 +50,11 @@ class AgentCalculateQueryRepository( .select( QGetAgentAssignedCreatorItem( agentCreatorRelation.creator.id, - agentCreatorRelation.creator.nickname + agentCreatorRelation.creator.nickname, + agentCreatorRelation.creator.profileImage + .coalesce("profile/default-profile.png") + .prepend("/") + .prepend(cloudFrontHost) ) ) .from(agentCreatorRelation) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/GetAgentAssignedCreatorResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/GetAgentAssignedCreatorResponse.kt index 7d76e099..ce6ae730 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/GetAgentAssignedCreatorResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/GetAgentAssignedCreatorResponse.kt @@ -9,5 +9,6 @@ data class GetAgentAssignedCreatorResponse( data class GetAgentAssignedCreatorItem @QueryProjection constructor( val creatorId: Long, - val creatorNickname: String + val creatorNickname: String, + val profileImageUrl: String ) diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadCurrentMonthListSummaryTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadCurrentMonthListSummaryTest.kt index 3615c656..680fb90f 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadCurrentMonthListSummaryTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadCurrentMonthListSummaryTest.kt @@ -58,12 +58,14 @@ class AdminAgentReadCurrentMonthListSummaryTest @Autowired constructor( private val snapshotRepository: AgentSettlementSnapshotRepository, private val entityManager: EntityManager ) { + private val cloudFrontHost = "https://cdn.test" + private lateinit var repository: AdminAgentReadQueryRepository private lateinit var calculateService: AgentCalculateService @BeforeEach fun setup() { - val calculateQueryRepository = AgentCalculateQueryRepository(queryFactory, entityManager) + val calculateQueryRepository = AgentCalculateQueryRepository(queryFactory, entityManager, cloudFrontHost) repository = AdminAgentReadQueryRepository(queryFactory, calculateQueryRepository) calculateService = AgentCalculateService(calculateQueryRepository, snapshotRepository) registerMysqlDateFunctions() diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadParityTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadParityTest.kt index 9c2d5ad7..cb00e507 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadParityTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadParityTest.kt @@ -55,6 +55,8 @@ class AdminAgentReadParityTest @Autowired constructor( private val snapshotRepository: AgentSettlementSnapshotRepository, private val entityManager: EntityManager ) { + private val cloudFrontHost = "https://cdn.test" + private var seededAgentId: Long = 0L private lateinit var agentService: AgentCalculateService private lateinit var adminService: AdminAgentReadService @@ -62,7 +64,7 @@ class AdminAgentReadParityTest @Autowired constructor( @BeforeEach fun setup() { registerMysqlDateFunctions() - val queryRepository = AgentCalculateQueryRepository(queryFactory, entityManager) + val queryRepository = AgentCalculateQueryRepository(queryFactory, entityManager, cloudFrontHost) val adminQueryRepository = AdminAgentReadQueryRepository(queryFactory, queryRepository) agentService = AgentCalculateService(queryRepository, snapshotRepository) adminService = AdminAgentReadService(adminQueryRepository, memberRepository, agentService) diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadQueryRepositoryTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadQueryRepositoryTest.kt index 6fb04fd8..e05de78d 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadQueryRepositoryTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/read/AdminAgentReadQueryRepositoryTest.kt @@ -28,13 +28,15 @@ class AdminAgentReadQueryRepositoryTest @Autowired constructor( private val relationRepository: AgentCreatorRelationRepository, private val entityManager: EntityManager ) { + private val cloudFrontHost = "https://cdn.test" + private lateinit var repository: AdminAgentReadQueryRepository @BeforeEach fun setup() { repository = AdminAgentReadQueryRepository( queryFactory, - AgentCalculateQueryRepository(queryFactory, entityManager) + AgentCalculateQueryRepository(queryFactory, entityManager, cloudFrontHost) ) } diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateControllerTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateControllerTest.kt index 3b7a9046..87909515 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateControllerTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateControllerTest.kt @@ -43,7 +43,8 @@ class AgentCalculateControllerTest { items = listOf( GetAgentAssignedCreatorItem( creatorId = 21L, - creatorNickname = "creator-a" + creatorNickname = "creator-a", + profileImageUrl = "https://cdn.test/profile/creator-a.png" ) ) ) @@ -58,6 +59,7 @@ class AgentCalculateControllerTest { assertEquals(true, apiResponse.success) assertEquals(1, apiResponse.data!!.totalCount) assertEquals(21L, apiResponse.data!!.items[0].creatorId) + assertEquals("https://cdn.test/profile/creator-a.png", apiResponse.data!!.items[0].profileImageUrl) Mockito.verify(service).getAssignedCreators(agentId = 7L, offset = 10L, limit = 5L) } diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateQueryRepositoryTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateQueryRepositoryTest.kt index ce66edd9..1637ff6a 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateQueryRepositoryTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateQueryRepositoryTest.kt @@ -56,13 +56,15 @@ class AgentCalculateQueryRepositoryTest @Autowired constructor( private val useCanCalculateRepository: UseCanCalculateRepository, private val entityManager: EntityManager ) { + private val cloudFrontHost = "https://cdn.test" + private lateinit var repository: AgentCalculateQueryRepository private lateinit var service: AgentCalculateService @BeforeEach fun setup() { registerMysqlDateFunctions() - repository = AgentCalculateQueryRepository(queryFactory, entityManager) + repository = AgentCalculateQueryRepository(queryFactory, entityManager, cloudFrontHost) service = AgentCalculateService(repository, snapshotRepository) } @@ -110,6 +112,28 @@ class AgentCalculateQueryRepositoryTest @Autowired constructor( assertEquals(listOf(endingFutureCreator.id, currentCreator.id), items.map { it.creatorId }) } + @Test + @DisplayName("소속 크리에이터 목록 조회는 프로필 이미지 URL과 기본 이미지를 함께 반환한다") + fun shouldGetAssignedCreatorsWithProfileImageUrl() { + val agent = saveMember("agent-image", MemberRole.AGENT) + val creatorWithImage = saveMember("creator-image", MemberRole.CREATOR, profileImage = "profile/creator-image.png") + val creatorWithoutImage = saveMember("creator-default", MemberRole.CREATOR) + val currentTime = LocalDateTime.of(2026, 2, 20, 12, 0, 0) + + saveRelation(agent, creatorWithImage) + saveRelation(agent, creatorWithoutImage) + + val items = repository.getAssignedCreators(agent.id!!, offset = 0, limit = 10, currentTime = currentTime) + + assertEquals( + listOf( + "$cloudFrontHost/profile/default-profile.png", + "$cloudFrontHost/profile/creator-image.png" + ), + items.map { it.profileImageUrl } + ) + } + @Test @DisplayName("라이브 크리에이터별 조회는 소속된 크리에이터만 집계한다") fun shouldGetLiveSummaryRowsOnlyForAssignedCreators() { @@ -683,13 +707,14 @@ class AgentCalculateQueryRepositoryTest @Autowired constructor( assertEquals(zeroTotal, repository.getCalculateContentDonationByCreatorTotal(startDate, endDate, agent.id!!)) } - private fun saveMember(nickname: String, role: MemberRole): Member { + private fun saveMember(nickname: String, role: MemberRole, profileImage: String? = null): Member { return memberRepository.saveAndFlush( Member( email = "$nickname@test.com", password = "password", nickname = nickname, - role = role + role = role, + profileImage = profileImage ) ) } diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateServiceTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateServiceTest.kt index 4bdafdfa..2bd0bdba 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateServiceTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/partner/agent/calculate/AgentCalculateServiceTest.kt @@ -41,7 +41,8 @@ class AgentCalculateServiceTest { val items = listOf( GetAgentAssignedCreatorItem( creatorId = 21L, - creatorNickname = "creator-a" + creatorNickname = "creator-a", + profileImageUrl = "https://cdn.test/profile/creator-a.png" ) ) @@ -64,6 +65,7 @@ class AgentCalculateServiceTest { assertEquals(1, response.totalCount) assertEquals(21L, response.items[0].creatorId) + assertEquals("https://cdn.test/profile/creator-a.png", response.items[0].profileImageUrl) Mockito.verify(repository).getAssignedCreatorTotalCount( Mockito.eq(7L), Mockito.any(LocalDateTime::class.java) ?: LocalDateTime.now()