From 389727cdb53aa42527ea78c17d6b266467da2385 Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 26 Feb 2026 01:27:14 +0900 Subject: [PATCH] =?UTF-8?q?fix(series):=20=EC=98=A4=EB=A6=AC=EC=A7=80?= =?UTF-8?q?=EB=84=90=20=EC=8B=9C=EB=A6=AC=EC=A6=88=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=EC=97=90=20=EC=96=91=EB=B0=A9=ED=96=A5=20=EC=B0=A8=EB=8B=A8=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=EB=A5=BC=20=EC=A0=81=EC=9A=A9=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/20260226_오리지널시리즈차단필터적용.md | 15 +++++++ .../sodalive/api/home/HomeService.kt | 1 + .../AudioContentMainTabSeriesController.kt | 1 + .../AudioContentMainTabSeriesService.kt | 5 ++- .../content/series/ContentSeriesRepository.kt | 42 ++++++++++++++++++- .../content/series/ContentSeriesService.kt | 20 ++++++++- 6 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 docs/20260226_오리지널시리즈차단필터적용.md diff --git a/docs/20260226_오리지널시리즈차단필터적용.md b/docs/20260226_오리지널시리즈차단필터적용.md new file mode 100644 index 00000000..63f62fad --- /dev/null +++ b/docs/20260226_오리지널시리즈차단필터적용.md @@ -0,0 +1,15 @@ +# 오리지널 시리즈 차단 필터 적용 + +## 구현 체크리스트 +- [x] `HomeService.fetchData` 경로에서 오리지널 시리즈 조회 시 `memberId` 전달 +- [x] `ContentSeriesService.getOriginalAudioDramaList` 시그니처에 `memberId` 반영 +- [x] `ContentSeriesRepository.getOriginalAudioDramaList` 인터페이스/구현에 `memberId` 반영 +- [x] 오리지널 시리즈 QueryDSL 조회에 양방향 차단(`내가 차단`/`나를 차단`) 서브쿼리 필터 적용 +- [x] 오리지널 탭 API 경로(`AudioContentMainTabSeries*`)에도 `memberId` 전달 +- [x] 빌드/테스트/진단 실행 후 결과 기록 + +## 검증 기록 +- 1차 구현 + - 무엇을: 홈/시리즈탭의 오리지널 시리즈 조회 경로에 `memberId`를 전달하고, `ContentSeriesRepository.getOriginalAudioDramaList` 및 `getOriginalAudioDramaTotalCount`에 양방향 차단 서브쿼리(`blockedSubquery.exists().not()`)를 추가해 차단된 크리에이터 시리즈가 제외되도록 반영했다. + - 왜: 기존에는 오리지널 시리즈 조회 쿼리에 차단 조건이 없어, 내가 차단했거나 나를 차단한 크리에이터의 시리즈가 노출될 수 있었다. + - 어떻게: `./gradlew test` 실행 성공, `./gradlew build` 실행 성공으로 컴파일/테스트/정적검사(ktlint 포함 check 단계) 통과를 확인했다. Kotlin LSP는 환경에 서버가 없어(`.kt` 미지원) 진단 도구로는 확인할 수 없어 Gradle 빌드 기반으로 검증했다. diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/api/home/HomeService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/api/home/HomeService.kt index 6e5ceaa0..7f8d624b 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/api/home/HomeService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/api/home/HomeService.kt @@ -126,6 +126,7 @@ class HomeService( ) val originalAudioDramaList = seriesService.getOriginalAudioDramaList( + memberId = memberId, isAdult = isAdult, contentType = contentType ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesController.kt index 7dcabf5b..753e11be 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesController.kt @@ -42,6 +42,7 @@ class AudioContentMainTabSeriesController(private val service: AudioContentMainT ApiResponse.ok( service.getOriginalAudioDramaList( + memberId = member.id!!, isAdult = member.auth != null && (isAdultContentVisible ?: true), contentType = contentType ?: ContentType.ALL, offset = pageable.offset, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesService.kt index b5c85b7d..692f04de 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesService.kt @@ -41,6 +41,7 @@ class AudioContentMainTabSeriesService( ) val originalAudioDrama = seriesService.getOriginalAudioDramaList( + memberId = memberId, isAdult = isAdult, contentType = contentType, offset = 0, @@ -157,13 +158,15 @@ class AudioContentMainTabSeriesService( } fun getOriginalAudioDramaList( + memberId: Long, isAdult: Boolean, contentType: ContentType, offset: Long, limit: Long ): GetSeriesListResponse { - val totalCount = seriesService.getOriginalAudioDramaTotalCount(isAdult, contentType) + val totalCount = seriesService.getOriginalAudioDramaTotalCount(memberId, isAdult, contentType) val items = seriesService.getOriginalAudioDramaList( + memberId = memberId, isAdult = isAdult, contentType = contentType, offset = offset, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt index f351a8f6..f947580f 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt @@ -84,11 +84,12 @@ interface ContentSeriesQueryRepository { isAdult: Boolean, contentType: ContentType, locale: String, + memberId: Long? = null, offset: Long = 0, limit: Long = 20 ): List - fun getOriginalAudioDramaTotalCount(isAdult: Boolean, contentType: ContentType): Int + fun getOriginalAudioDramaTotalCount(isAdult: Boolean, contentType: ContentType, memberId: Long? = null): Int fun getGenreList(isAdult: Boolean, memberId: Long, contentType: ContentType): List fun findByCurationIdV2( imageHost: String, @@ -715,6 +716,7 @@ class ContentSeriesQueryRepositoryImpl( isAdult: Boolean, contentType: ContentType, locale: String, + memberId: Long?, offset: Long, limit: Long ): List { @@ -744,6 +746,24 @@ class ContentSeriesQueryRepositoryImpl( where = where.and(audioContent.isAdult.isFalse) } + if (memberId != null) { + val blockedSubquery = queryFactory + .select(blockMember.id) + .from(blockMember) + .where( + blockMember.isActive.isTrue + .and( + blockMember.member.id.eq(series.member.id) + .and(blockMember.blockedMember.id.eq(memberId)) + .or( + blockMember.member.id.eq(memberId) + .and(blockMember.blockedMember.id.eq(series.member.id)) + ) + ) + ) + where = where.and(blockedSubquery.exists().not()) + } + val now = LocalDateTime.now() val sevenDaysAgo = now.minusDays(7) @@ -823,7 +843,7 @@ class ContentSeriesQueryRepositoryImpl( } } - override fun getOriginalAudioDramaTotalCount(isAdult: Boolean, contentType: ContentType): Int { + override fun getOriginalAudioDramaTotalCount(isAdult: Boolean, contentType: ContentType, memberId: Long?): Int { var where = series.isOriginal.isTrue .and(series.isActive.isTrue) @@ -845,6 +865,24 @@ class ContentSeriesQueryRepositoryImpl( } } + if (memberId != null) { + val blockedSubquery = queryFactory + .select(blockMember.id) + .from(blockMember) + .where( + blockMember.isActive.isTrue + .and( + blockMember.member.id.eq(series.member.id) + .and(blockMember.blockedMember.id.eq(memberId)) + .or( + blockMember.member.id.eq(memberId) + .and(blockMember.blockedMember.id.eq(series.member.id)) + ) + ) + ) + where = where.and(blockedSubquery.exists().not()) + } + return queryFactory .select(series.id) .from(series) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt index dc2b2140..36078b84 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt @@ -46,10 +46,27 @@ class ContentSeriesService( private val coverImageHost: String ) { fun getOriginalAudioDramaTotalCount(isAdult: Boolean, contentType: ContentType): Int { - return repository.getOriginalAudioDramaTotalCount(isAdult, contentType) + return repository.getOriginalAudioDramaTotalCount( + isAdult = isAdult, + contentType = contentType, + memberId = null + ) + } + + fun getOriginalAudioDramaTotalCount( + memberId: Long?, + isAdult: Boolean, + contentType: ContentType + ): Int { + return repository.getOriginalAudioDramaTotalCount( + isAdult = isAdult, + contentType = contentType, + memberId = memberId + ) } fun getOriginalAudioDramaList( + memberId: Long?, isAdult: Boolean, contentType: ContentType, offset: Long = 0, @@ -60,6 +77,7 @@ class ContentSeriesService( isAdult = isAdult, contentType = contentType, locale = langContext.lang.code, + memberId = memberId, offset = offset, limit = limit )