Skip to content

Commit 454cb4a

Browse files
kooscoclaude
andcommitted
refactor: PoseService 책임 분리 + 관리자 업로드용 userId nullable화
- PoseService를 포즈 자체의 목록 조회/등록으로 좁히고, 스크랩은 PoseScrapService, 조회수는 PoseViewService, 무작위 추첨은 RandomPoseService로 분리 - PoseRepository/PoseQuery에 관리자 전체 목록 조회(GetAllPoses) 추가 - HeadCount에 ALL 추가 (관리자 목록에서 인원수로 거르지 않는 경우) - PoseCommand.UploadPoses.userId를 Long?로 변경 — 인증이 없는 관리자 업로드는 User 귀속 없이 null로 넘긴다. apps/api 쪽은 UploadPosesUseCase에서 requireNotNull로 여전히 인증된 사용자만 허용하도록 유지 - 분리된 서비스를 쓰도록 Pose 관련 UseCase 6개 갱신 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent b53c64d commit 454cb4a

21 files changed

Lines changed: 199 additions & 105 deletions

apps/api/src/main/kotlin/com/neki/api/pose/application/GetPoseUseCase.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,22 @@ import com.neki.core.transaction.TransactionRunner
66
import com.neki.domain.pose.client.MediaClient
77
import com.neki.domain.pose.dto.PoseQuery
88
import com.neki.domain.pose.models.MediaMetadata
9-
import com.neki.domain.pose.service.PoseService
9+
import com.neki.domain.pose.service.PoseScrapService
10+
import com.neki.domain.pose.service.PoseViewService
1011

1112
@UseCase
1213
class GetPoseUseCase(
13-
private val poseService: PoseService,
14+
private val poseScrapService: PoseScrapService,
15+
private val poseViewService: PoseViewService,
1416
private val mediaClient: MediaClient,
1517
private val transactionRunner: TransactionRunner,
1618
) {
1719

1820
fun execute(query: PoseQuery.GetPose): PoseResult.GetPose {
19-
val (pose, isScraped) = poseService.getOwnedPoseWithScrap(query)
21+
val (pose, isScraped) = poseScrapService.getOwnedPoseWithScrap(query)
2022

21-
if (poseService.isFirstViewOf(query)) {
22-
transactionRunner.run { poseService.incrementViewCount(query) }
23+
if (poseViewService.isFirstViewOf(query)) {
24+
transactionRunner.run { poseViewService.incrementViewCount(query) }
2325
}
2426

2527
val mediaInfo: MediaMetadata = mediaClient.getMediaMetadata(pose.mediaId)

apps/api/src/main/kotlin/com/neki/api/pose/application/GetPosesUseCase.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import com.neki.domain.pose.client.MediaClient
99
import com.neki.domain.pose.dto.PoseQuery
1010
import com.neki.domain.pose.models.MediaMetadata
1111
import com.neki.domain.pose.models.PoseWithScrap
12-
import com.neki.domain.pose.service.PoseService
12+
import com.neki.domain.pose.service.PoseScrapService
1313

1414
/**
1515
* fileName : GetPosesUseCase
@@ -19,14 +19,14 @@ import com.neki.domain.pose.service.PoseService
1919
*/
2020
@UseCase
2121
class GetPosesUseCase(
22-
private val poseService: PoseService,
22+
private val poseScrapService: PoseScrapService,
2323
private val mediaClient: MediaClient,
2424
private val transactionRunner: TransactionRunner,
2525
) {
2626

2727
fun execute(query: PoseQuery.GetPoses): PoseResult.GetPoses {
2828
val page: Page<PoseWithScrap> = transactionRunner.readOnly {
29-
poseService.listPosesWithScrap(query)
29+
poseScrapService.listPosesWithScrap(query)
3030
}
3131

3232
if (page.items.isEmpty()) {

apps/api/src/main/kotlin/com/neki/api/pose/application/GetScrapPosesUseCase.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import com.neki.domain.pose.client.MediaClient
99
import com.neki.domain.pose.dto.PoseQuery
1010
import com.neki.domain.pose.models.MediaMetadata
1111
import com.neki.domain.pose.models.Pose
12-
import com.neki.domain.pose.service.PoseService
12+
import com.neki.domain.pose.service.PoseScrapService
1313

1414
/**
1515
* fileName : GetScrapPosesUseCase
@@ -19,14 +19,14 @@ import com.neki.domain.pose.service.PoseService
1919
*/
2020
@UseCase
2121
class GetScrapPosesUseCase(
22-
private val poseService: PoseService,
22+
private val poseScrapService: PoseScrapService,
2323
private val mediaClient: MediaClient,
2424
private val transactionRunner: TransactionRunner,
2525
) {
2626

2727
fun execute(query: PoseQuery.GetScrapPoses): PoseResult.GetPoses {
2828
val page: Page<Pose> = transactionRunner.readOnly {
29-
poseService.listOwnedScrapPoses(query)
29+
poseScrapService.listOwnedScrapPoses(query)
3030
}
3131

3232
if (page.items.isEmpty()) {

apps/api/src/main/kotlin/com/neki/api/pose/application/RandomPoseUseCase.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,20 @@ import com.neki.domain.pose.client.MediaClient
66
import com.neki.domain.pose.dto.PoseQuery
77
import com.neki.domain.pose.models.MediaMetadata
88
import com.neki.domain.pose.models.Pose
9-
import com.neki.domain.pose.service.PoseService
9+
import com.neki.domain.pose.service.PoseScrapService
10+
import com.neki.domain.pose.service.RandomPoseService
1011

1112
@UseCase
12-
class RandomPoseUseCase(private val poseService: PoseService, private val mediaClient: MediaClient) {
13+
class RandomPoseUseCase(
14+
private val randomPoseService: RandomPoseService,
15+
private val poseScrapService: PoseScrapService,
16+
private val mediaClient: MediaClient,
17+
) {
1318

1419
fun execute(query: PoseQuery.GetRandomPose): PoseResult.GetPose {
15-
val pose: Pose = poseService.pickRandomPose(query)
20+
val pose: Pose = randomPoseService.pickRandomPose(query)
1621

17-
val isScraped: Boolean = poseService.isScraped(query, pose)
22+
val isScraped: Boolean = poseScrapService.isScraped(query, pose)
1823

1924
val mediaInfo: MediaMetadata = mediaClient.getMediaMetadata(pose.mediaId)
2025

apps/api/src/main/kotlin/com/neki/api/pose/application/UpdatePoseScrapUseCase.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ package com.neki.api.pose.application
22

33
import com.neki.core.annotation.UseCase
44
import com.neki.domain.pose.dto.PoseCommand
5-
import com.neki.domain.pose.service.PoseService
5+
import com.neki.domain.pose.service.PoseScrapService
66
import org.springframework.transaction.annotation.Transactional
77

88
@UseCase
9-
class UpdatePoseScrapUseCase(private val poseService: PoseService) {
9+
class UpdatePoseScrapUseCase(private val poseScrapService: PoseScrapService) {
1010

1111
@Transactional
12-
fun execute(command: PoseCommand.UpdatePoseScrap) = poseService.updateScrap(command)
12+
fun execute(command: PoseCommand.UpdatePoseScrap) = poseScrapService.updateScrap(command)
1313
}

apps/api/src/main/kotlin/com/neki/api/pose/application/UploadPosesUseCase.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,18 @@ class UploadPosesUseCase(
2424
) {
2525

2626
fun execute(command: PoseCommand.UploadPoses) {
27+
// apps:api 업로드는 인증된 사용자만 호출한다. userId가 없는 업로드는 apps:admin 전용이다.
28+
val userId: Long = requireNotNull(command.userId)
29+
2730
val poses: List<Pose> = poseService.createPoses(command)
2831

2932
val mediaIds: List<Long> = command.uploads.map { it.mediaId }
30-
verifyUploadedOrRollback(command.userId, mediaIds)
33+
verifyUploadedOrRollback(userId, mediaIds)
3134

3235
try {
3336
transactionRunner.run { poseService.saveAll(poses) }
3437
} catch (e: Exception) {
35-
mediaClient.rollbackMediasUploaded(command.userId, mediaIds)
38+
mediaClient.rollbackMediasUploaded(userId, mediaIds)
3639
throw e
3740
}
3841
}

apps/api/src/test/kotlin/com/neki/api/pose/application/usecase/GetPoseUseCaseTest.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import com.neki.domain.pose.external.PoseViewCache
1111
import com.neki.domain.pose.models.MediaMetadata
1212
import com.neki.domain.pose.models.PoseWithScrap
1313
import com.neki.domain.pose.repository.PoseRepository
14-
import com.neki.domain.pose.service.PoseService
14+
import com.neki.domain.pose.service.PoseScrapService
15+
import com.neki.domain.pose.service.PoseViewService
1516
import io.kotest.assertions.throwables.shouldThrow
1617
import io.kotest.matchers.shouldBe
1718
import io.mockk.every
@@ -37,7 +38,8 @@ class GetPoseUseCaseTest {
3738
poseViewCache = mockk()
3839
transactionRunner = FakeTransactionRunner()
3940
useCase = GetPoseUseCase(
40-
PoseService(poseRepository, mockk(), poseViewCache, mockk()),
41+
PoseScrapService(poseRepository, mockk()),
42+
PoseViewService(poseRepository, poseViewCache),
4143
mediaClient,
4244
transactionRunner,
4345
)

apps/api/src/test/kotlin/com/neki/api/pose/application/usecase/GetPosesUseCaseTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import com.neki.domain.pose.dto.PoseQuery
1010
import com.neki.domain.pose.models.MediaMetadata
1111
import com.neki.domain.pose.models.PoseWithScrap
1212
import com.neki.domain.pose.repository.PoseRepository
13-
import com.neki.domain.pose.service.PoseService
13+
import com.neki.domain.pose.service.PoseScrapService
1414
import io.kotest.matchers.shouldBe
1515
import io.mockk.every
1616
import io.mockk.mockk
@@ -32,7 +32,7 @@ class GetPosesUseCaseTest {
3232
mediaClient = mockk()
3333
transactionRunner = FakeTransactionRunner()
3434
useCase = GetPosesUseCase(
35-
PoseService(poseRepository, mockk(), mockk(), mockk()),
35+
PoseScrapService(poseRepository, mockk()),
3636
mediaClient,
3737
transactionRunner,
3838
)

apps/api/src/test/kotlin/com/neki/api/pose/application/usecase/GetScrapPosesUseCaseTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import com.neki.domain.pose.dto.PoseQuery
1010
import com.neki.domain.pose.models.MediaMetadata
1111
import com.neki.domain.pose.models.Pose
1212
import com.neki.domain.pose.repository.PoseRepository
13-
import com.neki.domain.pose.service.PoseService
13+
import com.neki.domain.pose.service.PoseScrapService
1414
import io.kotest.matchers.shouldBe
1515
import io.mockk.every
1616
import io.mockk.mockk
@@ -32,7 +32,7 @@ class GetScrapPosesUseCaseTest {
3232
mediaClient = mockk()
3333
transactionRunner = FakeTransactionRunner()
3434
useCase = GetScrapPosesUseCase(
35-
PoseService(poseRepository, mockk(), mockk(), mockk()),
35+
PoseScrapService(poseRepository, mockk()),
3636
mediaClient,
3737
transactionRunner,
3838
)

apps/api/src/test/kotlin/com/neki/api/pose/application/usecase/RandomPoseUseCaseTest.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import com.neki.domain.pose.models.MediaMetadata
1212
import com.neki.domain.pose.models.ScrapPoseId
1313
import com.neki.domain.pose.repository.PoseRepository
1414
import com.neki.domain.pose.repository.ScrapPoseRepository
15-
import com.neki.domain.pose.service.PoseService
15+
import com.neki.domain.pose.service.PoseScrapService
16+
import com.neki.domain.pose.service.RandomPoseService
1617
import io.kotest.assertions.throwables.shouldThrow
1718
import io.kotest.matchers.shouldBe
1819
import io.mockk.every
@@ -38,7 +39,8 @@ class RandomPoseUseCaseTest {
3839
randomGenerator = mockk()
3940
useCase =
4041
RandomPoseUseCase(
41-
PoseService(poseRepository, scrapPoseRepository, mockk(), randomGenerator),
42+
RandomPoseService(poseRepository, randomGenerator),
43+
PoseScrapService(poseRepository, scrapPoseRepository),
4244
mediaClient,
4345
)
4446
}

0 commit comments

Comments
 (0)