feat(content-preference): 콘텐츠 조회 설정 서버 저장 전환을 반영한다

This commit is contained in:
2026-03-27 13:33:51 +09:00
parent 1ba3cb8a40
commit a87bd147dc
75 changed files with 3593 additions and 301 deletions

View File

@@ -4,6 +4,8 @@ import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.like.PutAudioContentLikeRequest
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.member.contentpreference.ViewerContentPreference
import org.springframework.data.domain.Pageable
import org.springframework.lang.Nullable
import org.springframework.security.access.prepost.PreAuthorize
@@ -25,7 +27,10 @@ import java.time.temporal.TemporalAdjusters
@RestController
@RequestMapping("/audio-content")
class AudioContentController(private val service: AudioContentService) {
class AudioContentController(
private val service: AudioContentService,
private val memberContentPreferenceService: MemberContentPreferenceService
) {
@PostMapping
@PreAuthorize("hasRole('CREATOR')")
fun createAudioContent(
@@ -112,14 +117,15 @@ class AudioContentController(private val service: AudioContentService) {
pageable: Pageable
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getAudioContentList(
creatorId = creatorId,
sortType = sortType ?: SortType.NEWEST,
categoryId = categoryId ?: 0,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member = member,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
@@ -135,12 +141,13 @@ class AudioContentController(private val service: AudioContentService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, null)
ApiResponse.ok(
service.getDetail(
id = id,
member = member,
isAdultContentVisible = isAdultContentVisible ?: true,
isAdultContentVisible = preference.isAdultContentVisible,
timezone = timezone
)
)
@@ -192,6 +199,7 @@ class AudioContentController(private val service: AudioContentService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
val preference = resolvePreference(member, isAdultContentVisible, contentType)
val currentDateTime = LocalDateTime.now()
val startDate = currentDateTime
.withHour(15)
@@ -204,8 +212,8 @@ class AudioContentController(private val service: AudioContentService) {
ApiResponse.ok(
service.getAudioContentRanking(
isAdult = member?.auth != null && (isAdultContentVisible ?: true),
contentType = contentType ?: ContentType.ALL,
isAdult = preference.isAdult,
contentType = preference.contentType,
startDate = startDate,
endDate = endDate,
offset = pageable.offset,
@@ -249,17 +257,18 @@ class AudioContentController(private val service: AudioContentService) {
pageable: Pageable
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getLatestContentByTheme(
memberId = member.id!!,
theme = if (theme == null) listOf() else listOf(theme),
contentType = contentType ?: ContentType.ALL,
contentType = preference.contentType,
offset = pageable.offset,
limit = pageable.pageSize.toLong(),
sortType = sortType ?: SortType.NEWEST,
isFree = isFree ?: false,
isAdult = (isAdultContentVisible ?: true) && member.auth != null,
isAdult = preference.isAdult,
isPointAvailableOnly = isPointAvailableOnly ?: false
)
)
@@ -271,18 +280,36 @@ class AudioContentController(private val service: AudioContentService) {
@RequestParam("contentType", required = false) contentType: ContentType? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getLatestContentByTheme(
memberId = member?.id,
theme = listOf("다시듣기"),
contentType = contentType ?: ContentType.ALL,
contentType = preference.contentType,
isFree = false,
isAdult = if (member != null) {
(isAdultContentVisible ?: true) && member.auth != null
} else {
false
}
isAdult = preference.isAdult
)
)
}
private fun resolvePreference(
member: Member?,
isAdultContentVisible: Boolean?,
contentType: ContentType?
): ViewerContentPreference {
if (member == null) {
return ViewerContentPreference(
countryCode = "KR",
isAdultContentVisible = isAdultContentVisible ?: false,
contentType = contentType ?: ContentType.ALL,
isAdult = false
)
}
return memberContentPreferenceService.resolveForQuery(
member = member,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType
)
}
}

View File

@@ -40,6 +40,7 @@ import kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationService
import kr.co.vividnext.sodalive.i18n.translation.TranslateRequest
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy
import kr.co.vividnext.sodalive.utils.generateFileName
import org.springframework.beans.factory.annotation.Value
import org.springframework.cache.annotation.Cacheable
@@ -527,12 +528,16 @@ class AudioContentService(
isAdultContentVisible: Boolean,
timezone: String
): GetAudioContentDetailResponse {
val isAdult = member.auth != null && isAdultContentVisible
val isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible)
// 오디오 콘텐츠 조회 (content_id, 제목, 내용, 테마, 태그, 19여부, 이미지, 콘텐츠 PATH)
val audioContent = repository.findByIdOrNull(id)
?: throw SodaException(messageKey = "content.error.invalid_content_retry")
if (audioContent.isAdult && !isAdult) {
throw SodaException(messageKey = "common.error.adult_verification_required")
}
// 크리에이터(유저) 정보
val creatorId = audioContent.member!!.id!!
val creator = explorerQueryRepository.getMember(creatorId)
@@ -670,14 +675,16 @@ class AudioContentService(
cloudfrontHost = coverImageHost,
contentId = audioContent.id!!,
creatorId = creatorId,
isAdult = member.auth != null
// 관련 콘텐츠 노출도 동일하게 저장 선호 기반 성인 정책을 따른다.
isAdult = isAdult
)
val sameThemeOtherContentList = repository.getSameThemeOtherContentList(
cloudfrontHost = coverImageHost,
contentId = audioContent.id!!,
themeId = audioContent.theme!!.id!!,
isAdult = member.auth != null
// 동일 테마 추천도 메인 상세와 동일한 성인 정책으로 정렬한다.
isAdult = isAdult
)
val likeCount = audioContentLikeRepository.totalCountAudioContentLike(contentId = id)
@@ -864,7 +871,8 @@ class AudioContentService(
orderSequence = orderSequence,
isActivePreview = audioContent.isGeneratePreview,
isAdult = audioContent.isAdult,
isMosaic = audioContent.isAdult && member.auth == null,
// 성인 콘텐츠이면서 현재 조회 정책으로 열람 불가한 경우에만 모자이크를 적용한다.
isMosaic = audioContent.isAdult && !isAdult,
isOnlyRental = isOnlyRental,
existOrdered = isExistsAudioContent,
purchaseOption = purchaseOption,
@@ -904,7 +912,7 @@ class AudioContentService(
member: Member,
isAdultContentVisible: Boolean
): GetAudioContentListItem? {
val isAdult = member.auth != null && isAdultContentVisible
val isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible)
if (isBlockedBetweenMembers(memberId = member.id!!, creatorId = creatorId)) {
return null
@@ -978,7 +986,7 @@ class AudioContentService(
offset: Long,
limit: Long
): GetAudioContentListResponse {
val isAdult = member.auth != null && isAdultContentVisible
val isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible)
val isCreator = member.id == creatorId
if (!isCreator && isBlockedBetweenMembers(memberId = member.id!!, creatorId = creatorId)) {

View File

@@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.content.order.OrderService
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import org.springframework.data.domain.Pageable
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
@@ -16,18 +17,20 @@ import org.springframework.web.bind.annotation.RestController
@RequestMapping("/audio-content/main")
class AudioContentMainController(
private val service: AudioContentMainService,
private val orderService: OrderService
private val orderService: OrderService,
private val memberContentPreferenceService: MemberContentPreferenceService
) {
@GetMapping("/new-content-upload-creator")
fun newContentUploadCreatorList(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, null, null)
ApiResponse.ok(
service.getNewContentUploadCreatorList(
memberId = member.id!!,
isAdult = member.auth != null
isAdult = preference.isAdult
)
)
}
@@ -37,11 +40,12 @@ class AudioContentMainController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, null, null)
ApiResponse.ok(
service.getAudioContentMainBannerList(
memberId = member.id!!,
isAdult = member.auth != null
isAdult = preference.isAdult
)
)
}
@@ -69,12 +73,13 @@ class AudioContentMainController(
pageable: Pageable
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getNewContentByTheme(
theme,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member,
pageable
)
@@ -88,11 +93,12 @@ class AudioContentMainController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getThemeList(
isAdult = member.auth != null && (isAdultContentVisible ?: true),
contentType = contentType ?: ContentType.ALL
isAdult = preference.isAdult,
contentType = preference.contentType
)
)
}
@@ -106,12 +112,13 @@ class AudioContentMainController(
pageable: Pageable
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getNewContentFor2WeeksByTheme(
theme = theme,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member = member,
pageable = pageable
)
@@ -126,15 +133,26 @@ class AudioContentMainController(
pageable: Pageable
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getAudioContentCurationListWithPaging(
memberId = member.id!!,
isAdult = member.auth != null && (isAdultContentVisible ?: true),
contentType = contentType ?: ContentType.ALL,
isAdult = preference.isAdult,
contentType = preference.contentType,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
private fun resolvePreference(
member: Member,
isAdultContentVisible: Boolean?,
contentType: ContentType?
) = memberContentPreferenceService.resolveForQuery(
member = member,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType
)
}

View File

@@ -13,6 +13,7 @@ import kr.co.vividnext.sodalive.event.EventItem
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy
import org.springframework.beans.factory.annotation.Value
import org.springframework.cache.annotation.Cacheable
import org.springframework.data.domain.Pageable
@@ -68,7 +69,7 @@ class AudioContentMainService(
} else {
emptyList()
},
isAdult = member.auth != null && isAdultContentVisible,
isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible),
contentType = contentType,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
@@ -87,7 +88,7 @@ class AudioContentMainService(
* - AS-IS theme은 한글만 처리하도록 되어 있음
* - TO-BE 번역된 theme이 들어와도 동일한 동작을 하도록 처리
*/
val isAdult = member.auth != null && isAdultContentVisible
val isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible)
val themeListRaw = if (theme.isBlank()) {
audioContentThemeRepository.getActiveThemeOfContent(
isAdult = isAdult,

View File

@@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.content.SortType
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import org.springframework.data.domain.Pageable
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
@@ -15,7 +16,10 @@ import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/audio-content/curation")
class AudioContentCurationController(private val service: AudioContentCurationService) {
class AudioContentCurationController(
private val service: AudioContentCurationService,
private val memberContentPreferenceService: MemberContentPreferenceService
) {
@GetMapping("/{id}")
fun getCurationContent(
@PathVariable id: Long,
@@ -26,16 +30,27 @@ class AudioContentCurationController(private val service: AudioContentCurationSe
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getCurationContent(
curationId = id,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
sortType = sortType ?: SortType.NEWEST,
member = member,
pageable = pageable
)
)
}
private fun resolvePreference(
member: Member,
isAdultContentVisible: Boolean?,
contentType: ContentType?
) = memberContentPreferenceService.resolveForQuery(
member = member,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType
)
}

View File

@@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.content.SortType
import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
@@ -30,20 +31,19 @@ class AudioContentCurationService(
): GetCurationContentResponse {
val totalCount = repository.findTotalCountByCurationId(
curationId = curationId,
isAdult = member.auth != null && isAdultContentVisible,
isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible),
contentType = contentType
)
val audioContentList = repository.findByCurationId(
curationId = curationId,
cloudfrontHost = cloudFrontHost,
isAdult = member.auth != null && isAdultContentVisible,
isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible),
contentType = contentType,
sortType = sortType,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
.filter { !isBlockedBetweenMembers(memberId = member.id!!, creatorId = it.creatorId) }
).filter { !isBlockedBetweenMembers(memberId = member.id!!, creatorId = it.creatorId) }
return GetCurationContentResponse(
totalCount = totalCount,

View File

@@ -4,6 +4,7 @@ import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import org.springframework.data.domain.Pageable
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
@@ -13,7 +14,10 @@ import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/v2/audio-content/main/alarm")
class AudioContentMainTabAlarmController(private val service: AudioContentMainTabAlarmService) {
class AudioContentMainTabAlarmController(
private val service: AudioContentMainTabAlarmService,
private val memberContentPreferenceService: MemberContentPreferenceService
) {
@GetMapping
fun fetchContentMainTabAlarm(
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@@ -21,11 +25,12 @@ class AudioContentMainTabAlarmController(private val service: AudioContentMainTa
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.fetchData(
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member
)
)
@@ -40,16 +45,27 @@ class AudioContentMainTabAlarmController(private val service: AudioContentMainTa
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.fetchAlarmContentByTheme(
theme,
member,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
private fun resolvePreference(
member: Member,
isAdultContentVisible: Boolean?,
contentType: ContentType?
) = memberContentPreferenceService.resolveForQuery(
member = member,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType
)
}

View File

@@ -8,6 +8,7 @@ import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryR
import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse
import kr.co.vividnext.sodalive.event.EventService
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy
import kr.co.vividnext.sodalive.rank.RankingService
import org.springframework.stereotype.Service
import java.time.DayOfWeek
@@ -27,7 +28,7 @@ class AudioContentMainTabAlarmService(
contentType: ContentType,
member: Member
): GetContentMainTabAlarmResponse {
val isAdult = member.auth != null && isAdultContentVisible
val isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible)
val memberId = member.id!!
val contentBannerList = bannerService.getBannerList(
@@ -105,7 +106,7 @@ class AudioContentMainTabAlarmService(
}
val memberId = member.id!!
val isAdult = member.auth != null && isAdultContentVisible
val isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible)
val totalCount = contentRepository.totalAlarmCountByTheme(
memberId = memberId,

View File

@@ -4,6 +4,7 @@ import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
@@ -12,7 +13,10 @@ import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/v2/audio-content/main/asmr")
class AudioContentMainTabAsmrController(private val service: AudioContentMainTabAsmrService) {
class AudioContentMainTabAsmrController(
private val service: AudioContentMainTabAsmrService,
private val memberContentPreferenceService: MemberContentPreferenceService
) {
@GetMapping
fun fetchContentMainTabAsmr(
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@@ -20,11 +24,12 @@ class AudioContentMainTabAsmrController(private val service: AudioContentMainTab
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.fetchData(
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member
)
)
@@ -38,13 +43,24 @@ class AudioContentMainTabAsmrController(private val service: AudioContentMainTab
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getPopularContentByCreator(
creatorId = creatorId,
isAdult = member.auth != null && (isAdultContentVisible ?: true),
contentType = contentType ?: ContentType.ALL
isAdult = preference.isAdult,
contentType = preference.contentType
)
)
}
private fun resolvePreference(
member: Member,
isAdultContentVisible: Boolean?,
contentType: ContentType?
) = memberContentPreferenceService.resolveForQuery(
member = member,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType
)
}

View File

@@ -9,6 +9,7 @@ import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTabRepository
import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse
import kr.co.vividnext.sodalive.event.EventService
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy
import kr.co.vividnext.sodalive.rank.RankingService
import org.springframework.stereotype.Service
@@ -26,7 +27,7 @@ class AudioContentMainTabAsmrService(
contentType: ContentType,
member: Member
): GetContentMainTabAsmrResponse {
val isAdult = member.auth != null && isAdultContentVisible
val isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible)
val memberId = member.id!!
val theme = "ASMR"
val tabId = 5L

View File

@@ -4,6 +4,7 @@ import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import org.springframework.data.domain.Pageable
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
@@ -13,7 +14,10 @@ import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/v2/audio-content/main/content")
class AudioContentMainTabContentController(private val service: AudioContentMainTabContentService) {
class AudioContentMainTabContentController(
private val service: AudioContentMainTabContentService,
private val memberContentPreferenceService: MemberContentPreferenceService
) {
@GetMapping
fun fetchContentMainTabContent(
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@@ -21,11 +25,12 @@ class AudioContentMainTabContentController(private val service: AudioContentMain
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.fetchData(
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member
)
)
@@ -40,12 +45,13 @@ class AudioContentMainTabContentController(private val service: AudioContentMain
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getAudioContentRanking(
memberId = member.id!!,
isAdult = member.auth != null && (isAdultContentVisible ?: true),
contentType = contentType ?: ContentType.ALL,
isAdult = preference.isAdult,
contentType = preference.contentType,
sortType = sortType ?: "매출"
)
)
@@ -60,12 +66,13 @@ class AudioContentMainTabContentController(private val service: AudioContentMain
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getNewContentByTheme(
theme,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member
)
)
@@ -79,12 +86,13 @@ class AudioContentMainTabContentController(private val service: AudioContentMain
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getPopularContentByCreator(
creatorId = creatorId,
isAdult = member.auth != null && (isAdultContentVisible ?: true),
contentType = contentType ?: ContentType.ALL
isAdult = preference.isAdult,
contentType = preference.contentType
)
)
}
@@ -92,16 +100,29 @@ class AudioContentMainTabContentController(private val service: AudioContentMain
@GetMapping("/recommend-content-by-tag")
fun getRecommendedContentByTag(
@RequestParam tag: String,
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@RequestParam("contentType", required = false) contentType: ContentType? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getRecommendedContentByTag(
memberId = member.id!!,
tag = tag,
contentType = contentType ?: ContentType.ALL
isAdult = preference.isAdult,
contentType = preference.contentType
)
)
}
private fun resolvePreference(
member: Member,
isAdultContentVisible: Boolean?,
contentType: ContentType?
) = memberContentPreferenceService.resolveForQuery(
member = member,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType
)
}

View File

@@ -10,6 +10,7 @@ import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse
import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository
import kr.co.vividnext.sodalive.event.EventService
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy
import kr.co.vividnext.sodalive.rank.RankingService
import org.springframework.stereotype.Service
import java.time.LocalDateTime
@@ -30,7 +31,7 @@ class AudioContentMainTabContentService(
member: Member
): GetContentMainTabContentResponse {
val memberId = member.id!!
val isAdult = member.auth != null && isAdultContentVisible
val isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible)
val tabId = 3L
// 단편 배너
@@ -114,6 +115,7 @@ class AudioContentMainTabContentService(
tagCurationService.getTagCurationContentList(
memberId = memberId,
tag = tagList[0],
isAdult = isAdult,
contentType = contentType
)
} else {
@@ -189,7 +191,7 @@ class AudioContentMainTabContentService(
contentType: ContentType,
member: Member
): List<GetAudioContentMainItem> {
val isAdult = member.auth != null && isAdultContentVisible
val isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible)
val themeList = if (theme.isBlank()) {
audioContentThemeRepository.getActiveThemeOfContent(isAdult = isAdult, contentType = contentType)
@@ -232,8 +234,14 @@ class AudioContentMainTabContentService(
fun getRecommendedContentByTag(
memberId: Long,
tag: String,
isAdult: Boolean,
contentType: ContentType
): List<GetAudioContentMainItem> {
return tagCurationService.getTagCurationContentList(memberId = memberId, tag = tag, contentType = contentType)
return tagCurationService.getTagCurationContentList(
memberId = memberId,
tag = tag,
isAdult = isAdult,
contentType = contentType
)
}
}

View File

@@ -27,7 +27,9 @@ class ContentMainTabTagCurationRepository(
.and(contentHashTagCurationItem.isActive.isTrue)
if (!isAdult) {
// 큐레이션 메타와 실제 콘텐츠 양쪽에서 성인 항목을 함께 차단한다.
where = where.and(contentHashTagCuration.isAdult.isFalse)
.and(audioContent.isAdult.isFalse)
} else {
if (contentType != ContentType.ALL) {
where = where.and(
@@ -60,6 +62,7 @@ class ContentMainTabTagCurationRepository(
fun getTagCurationContentList(
memberId: Long,
tag: String,
isAdult: Boolean,
contentType: ContentType
): List<GetAudioContentMainItem> {
val blockMemberCondition = blockMember.isActive.isTrue
@@ -79,6 +82,11 @@ class ContentMainTabTagCurationRepository(
.and(contentHashTagCurationItem.isActive.isTrue)
.and(contentHashTagCuration.tag.eq(tag))
if (!isAdult) {
// 추천 태그 콘텐츠 조회에서도 실제 오디오 콘텐츠 성인 노출을 동일 정책으로 제한한다.
where = where.and(audioContent.isAdult.isFalse)
}
if (contentType != ContentType.ALL) {
where = where.and(
audioContent.member.isNull.or(

View File

@@ -13,8 +13,14 @@ class ContentMainTabTagCurationService(private val repository: ContentMainTabTag
fun getTagCurationContentList(
memberId: Long,
tag: String,
isAdult: Boolean,
contentType: ContentType
): List<GetAudioContentMainItem> {
return repository.getTagCurationContentList(memberId = memberId, tag = tag, contentType = contentType)
return repository.getTagCurationContentList(
memberId = memberId,
tag = tag,
isAdult = isAdult,
contentType = contentType
)
}
}

View File

@@ -4,6 +4,7 @@ import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import org.springframework.data.domain.Pageable
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
@@ -13,7 +14,10 @@ import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/v2/audio-content/main/free")
class AudioContentMainTabFreeController(private val service: AudioContentMainTabFreeService) {
class AudioContentMainTabFreeController(
private val service: AudioContentMainTabFreeService,
private val memberContentPreferenceService: MemberContentPreferenceService
) {
@GetMapping
fun fetchContentMainFree(
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@@ -21,11 +25,12 @@ class AudioContentMainTabFreeController(private val service: AudioContentMainTab
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.fetchData(
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member
)
)
@@ -39,12 +44,13 @@ class AudioContentMainTabFreeController(private val service: AudioContentMainTab
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getIntroduceCreator(
member,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
@@ -60,12 +66,13 @@ class AudioContentMainTabFreeController(private val service: AudioContentMainTab
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getNewContentByTheme(
theme,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
@@ -81,13 +88,24 @@ class AudioContentMainTabFreeController(private val service: AudioContentMainTab
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getPopularContentByCreator(
creatorId = creatorId,
isAdult = member.auth != null && (isAdultContentVisible ?: true),
contentType = contentType ?: ContentType.ALL
isAdult = preference.isAdult,
contentType = preference.contentType
)
)
}
private fun resolvePreference(
member: Member,
isAdultContentVisible: Boolean?,
contentType: ContentType?
) = memberContentPreferenceService.resolveForQuery(
member = member,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType
)
}

View File

@@ -11,6 +11,7 @@ import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse
import kr.co.vividnext.sodalive.content.main.tab.RecommendSeriesRepository
import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy
import kr.co.vividnext.sodalive.rank.RankingService
import org.springframework.stereotype.Service
@@ -30,7 +31,7 @@ class AudioContentMainTabFreeService(
contentType: ContentType,
member: Member
): GetContentMainTabFreeResponse {
val isAdult = member.auth != null && isAdultContentVisible
val isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible)
val memberId = member.id!!
val tabId = 7L
@@ -134,7 +135,7 @@ class AudioContentMainTabFreeService(
offset: Long,
limit: Long
): List<GetAudioContentMainItem> {
val isAdult = member.auth != null && isAdultContentVisible
val isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible)
val memberId = member.id!!
val introduceCreatorCuration = curationRepository.findByContentMainTabIdAndTitle(
@@ -171,7 +172,7 @@ class AudioContentMainTabFreeService(
listOf(theme)
} else {
audioContentThemeRepository.getActiveThemeOfContent(
isAdult = member.auth != null && isAdultContentVisible,
isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible),
isFree = true,
contentType = contentType
).filter {
@@ -185,7 +186,7 @@ class AudioContentMainTabFreeService(
it != "자기소개"
}
},
isAdult = member.auth != null && isAdultContentVisible,
isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible),
contentType = contentType,
offset = offset,
limit = limit,

View File

@@ -3,6 +3,8 @@ package kr.co.vividnext.sodalive.content.main.tab.home
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.member.contentpreference.ViewerContentPreference
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
@@ -11,17 +13,21 @@ import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/v2/audio-content/main/home")
class AudioContentMainTabHomeController(private val service: AudioContentMainTabHomeService) {
class AudioContentMainTabHomeController(
private val service: AudioContentMainTabHomeService,
private val memberContentPreferenceService: MemberContentPreferenceService
) {
@GetMapping
fun fetchContentMainHome(
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@RequestParam("contentType", required = false) contentType: ContentType? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.fetchData(
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member
)
)
@@ -34,11 +40,12 @@ class AudioContentMainTabHomeController(private val service: AudioContentMainTab
@RequestParam("contentType", required = false) contentType: ContentType? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getPopularContentByCreator(
creatorId = creatorId,
isAdult = member?.auth != null && (isAdultContentVisible ?: true),
contentType = contentType ?: ContentType.ALL
isAdult = preference.isAdult,
contentType = preference.contentType
)
)
}
@@ -50,13 +57,35 @@ class AudioContentMainTabHomeController(private val service: AudioContentMainTab
@RequestParam("contentType", required = false) contentType: ContentType? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getContentRanking(
sortType = sortType ?: "매출",
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member
)
)
}
private fun resolvePreference(
member: Member?,
isAdultContentVisible: Boolean?,
contentType: ContentType?
): ViewerContentPreference {
if (member == null) {
return ViewerContentPreference(
countryCode = "KR",
isAdultContentVisible = isAdultContentVisible ?: false,
contentType = contentType ?: ContentType.ALL,
isAdult = false
)
}
return memberContentPreferenceService.resolveForQuery(
member = member,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType
)
}
}

View File

@@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService
import kr.co.vividnext.sodalive.event.EventService
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy
import kr.co.vividnext.sodalive.notice.ServiceNoticeService
import kr.co.vividnext.sodalive.rank.RankingService
import org.springframework.stereotype.Service
@@ -42,7 +43,7 @@ class AudioContentMainTabHomeService(
val formattedLastMonday = startDate.format(startDateFormatter)
val formattedLastSunday = endDate.format(endDateFormatter)
val isAdult = member?.auth != null && isAdultContentVisible
val isAdult = member?.let { isAdultVisibleByPolicy(it, isAdultContentVisible) } ?: false
// 최근 공지사항
val latestNotice = noticeService.getLatestNotice()
@@ -130,7 +131,7 @@ class AudioContentMainTabHomeService(
contentType: ContentType,
member: Member?
): List<GetAudioContentRankingItem> {
val isAdult = member?.auth != null && isAdultContentVisible
val isAdult = member?.let { isAdultVisibleByPolicy(it, isAdultContentVisible) } ?: false
val currentDateTime = LocalDateTime.now()
val startDate = currentDateTime

View File

@@ -4,6 +4,7 @@ import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
@@ -12,7 +13,10 @@ import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/v2/audio-content/main/replay")
class AudioContentMainTabLiveReplayController(private val service: AudioContentMainTabLiveReplayService) {
class AudioContentMainTabLiveReplayController(
private val service: AudioContentMainTabLiveReplayService,
private val memberContentPreferenceService: MemberContentPreferenceService
) {
@GetMapping
fun fetchContentMainTabLiveReplay(
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@@ -20,11 +24,12 @@ class AudioContentMainTabLiveReplayController(private val service: AudioContentM
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.fetchData(
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member
)
)
@@ -38,13 +43,24 @@ class AudioContentMainTabLiveReplayController(private val service: AudioContentM
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getPopularContentByCreator(
creatorId = creatorId,
isAdult = member.auth != null && (isAdultContentVisible ?: true),
contentType = contentType ?: ContentType.ALL
isAdult = preference.isAdult,
contentType = preference.contentType
)
)
}
private fun resolvePreference(
member: Member,
isAdultContentVisible: Boolean?,
contentType: ContentType?
) = memberContentPreferenceService.resolveForQuery(
member = member,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType
)
}

View File

@@ -9,6 +9,7 @@ import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTabRepository
import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse
import kr.co.vividnext.sodalive.event.EventService
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy
import kr.co.vividnext.sodalive.rank.RankingService
import org.springframework.stereotype.Service
@@ -26,7 +27,7 @@ class AudioContentMainTabLiveReplayService(
contentType: ContentType,
member: Member
): GetContentMainTabLiveReplayResponse {
val isAdult = member.auth != null && isAdultContentVisible
val isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible)
val memberId = member.id!!
val theme = "다시듣기"
val tabId = 6L

View File

@@ -4,6 +4,7 @@ import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import org.springframework.data.domain.Pageable
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
@@ -13,7 +14,10 @@ import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/v2/audio-content/main/series")
class AudioContentMainTabSeriesController(private val service: AudioContentMainTabSeriesService) {
class AudioContentMainTabSeriesController(
private val service: AudioContentMainTabSeriesService,
private val memberContentPreferenceService: MemberContentPreferenceService
) {
@GetMapping
fun fetchContentMainSeries(
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@@ -21,11 +25,12 @@ class AudioContentMainTabSeriesController(private val service: AudioContentMainT
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.fetchData(
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member
)
)
@@ -39,12 +44,13 @@ class AudioContentMainTabSeriesController(private val service: AudioContentMainT
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getOriginalAudioDramaList(
memberId = member.id!!,
isAdult = member.auth != null && (isAdultContentVisible ?: true),
contentType = contentType ?: ContentType.ALL,
isAdult = preference.isAdult,
contentType = preference.contentType,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
@@ -59,12 +65,13 @@ class AudioContentMainTabSeriesController(private val service: AudioContentMainT
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getRank10DaysCompletedSeriesList(
memberId = member.id!!,
isAdult = member.auth != null && (isAdultContentVisible ?: true),
contentType = contentType ?: ContentType.ALL,
isAdult = preference.isAdult,
contentType = preference.contentType,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
@@ -79,13 +86,14 @@ class AudioContentMainTabSeriesController(private val service: AudioContentMainT
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getRecommendSeriesListByGenre(
genreId,
memberId = member.id!!,
isAdult = member.auth != null && (isAdultContentVisible ?: true),
contentType = contentType ?: ContentType.ALL
isAdult = preference.isAdult,
contentType = preference.contentType
)
)
}
@@ -98,13 +106,24 @@ class AudioContentMainTabSeriesController(private val service: AudioContentMainT
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getRecommendSeriesByCreator(
creatorId = creatorId,
isAdult = member.auth != null && (isAdultContentVisible ?: true),
contentType = contentType ?: ContentType.ALL
isAdult = preference.isAdult,
contentType = preference.contentType
)
)
}
private fun resolvePreference(
member: Member,
isAdultContentVisible: Boolean?,
contentType: ContentType?
) = memberContentPreferenceService.resolveForQuery(
member = member,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType
)
}

View File

@@ -8,6 +8,7 @@ import kr.co.vividnext.sodalive.content.series.ContentSeriesService
import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse
import kr.co.vividnext.sodalive.event.EventService
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy
import kr.co.vividnext.sodalive.rank.RankingService
import org.springframework.stereotype.Service
import java.time.DayOfWeek
@@ -30,7 +31,7 @@ class AudioContentMainTabSeriesService(
contentType: ContentType,
member: Member
): GetContentMainTabSeriesResponse {
val isAdult = member.auth != null && isAdultContentVisible
val isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible)
val memberId = member.id!!
// 메인 배너 (시리즈)

View File

@@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesSortType
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import org.springframework.data.domain.Pageable
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
@@ -15,7 +16,10 @@ import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/audio-content/series")
class ContentSeriesController(private val service: ContentSeriesService) {
class ContentSeriesController(
private val service: ContentSeriesService,
private val memberContentPreferenceService: MemberContentPreferenceService
) {
@GetMapping
fun getSeriesList(
@RequestParam(required = false) creatorId: Long?,
@@ -27,14 +31,15 @@ class ContentSeriesController(private val service: ContentSeriesService) {
pageable: Pageable
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getSeriesList(
creatorId = creatorId,
isOriginal = isOriginal ?: false,
isCompleted = isCompleted ?: false,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member = member,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
@@ -50,12 +55,13 @@ class ContentSeriesController(private val service: ContentSeriesService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getSeriesDetail(
seriesId = id,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member = member
)
)
@@ -71,12 +77,13 @@ class ContentSeriesController(private val service: ContentSeriesService) {
pageable: Pageable
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getSeriesContentList(
seriesId = id,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member = member,
sortType = sortType ?: SeriesSortType.NEWEST,
offset = pageable.offset,
@@ -92,13 +99,24 @@ class ContentSeriesController(private val service: ContentSeriesService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getRecommendSeriesList(
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member = member
)
)
}
private fun resolvePreference(
member: Member,
isAdultContentVisible: Boolean?,
contentType: ContentType?
) = memberContentPreferenceService.resolveForQuery(
member = member,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType
)
}

View File

@@ -918,8 +918,10 @@ class ContentSeriesQueryRepositoryImpl(
.and(blockMember.id.isNull)
if (!isAdult) {
// 비성인 조회에서는 장르/시리즈/콘텐츠 3계층 모두에서 성인 항목을 제외한다.
where = where.and(seriesGenre.isAdult.isFalse)
.and(series.isAdult.isFalse)
.and(audioContent.isAdult.isFalse)
} else {
if (contentType != ContentType.ALL) {
where = where.and(

View File

@@ -21,6 +21,7 @@ import kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationService
import kr.co.vividnext.sodalive.i18n.translation.TranslateRequest
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@@ -168,7 +169,7 @@ class ContentSeriesService(
offset: Long = 0,
limit: Long = 20
): GetSeriesListResponse {
val isAuth = member.auth != null && isAdultContentVisible
val isAuth = isAdultVisibleByPolicy(member, isAdultContentVisible)
val totalCount = repository.getSeriesTotalCount(
creatorId = creatorId,
@@ -206,7 +207,7 @@ class ContentSeriesService(
offset: Long = 0,
limit: Long = 20
): GetSeriesListResponse {
val isAuth = member.auth != null && isAdultContentVisible
val isAuth = isAdultVisibleByPolicy(member, isAdultContentVisible)
val totalCount = repository.getSeriesByGenreTotalCount(
genreId = genreId,
@@ -240,7 +241,7 @@ class ContentSeriesService(
): GetSeriesDetailResponse {
val series = repository.getSeriesDetail(
seriesId = seriesId,
isAuth = member.auth != null && isAdultContentVisible,
isAuth = isAdultVisibleByPolicy(member, isAdultContentVisible),
contentType = contentType
) ?: throw SodaException(messageKey = "series.error.invalid_series_retry")
@@ -428,7 +429,7 @@ class ContentSeriesService(
offset: Long,
limit: Long
): GetSeriesContentListResponse {
val isAdult = member.auth != null && isAdultContentVisible
val isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible)
val totalCount = seriesContentRepository.getContentCount(seriesId, isAdult = isAdult, contentType = contentType)
val contentList = seriesContentRepository.getContentList(
@@ -491,7 +492,7 @@ class ContentSeriesService(
contentType: ContentType,
member: Member
): List<GetSeriesListResponse.SeriesListItem> {
val isAuth = member.auth != null && isAdultContentVisible
val isAuth = isAdultVisibleByPolicy(member, isAdultContentVisible)
return repository.getRecommendSeriesListV2(
imageHost = coverImageHost,
isAuth = isAuth,

View File

@@ -8,6 +8,7 @@ import kr.co.vividnext.sodalive.content.series.ContentSeriesService
import kr.co.vividnext.sodalive.content.series.main.banner.ContentSeriesBannerService
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.domain.PageRequest
import org.springframework.security.core.annotation.AuthenticationPrincipal
@@ -21,6 +22,7 @@ import org.springframework.web.bind.annotation.RestController
class SeriesMainController(
private val contentSeriesService: ContentSeriesService,
private val bannerService: ContentSeriesBannerService,
private val memberContentPreferenceService: MemberContentPreferenceService,
@Value("\${cloud.aws.cloud-front.host}")
private val imageHost: String
@@ -32,6 +34,7 @@ class SeriesMainController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
val banners = bannerService.getActiveBanners(PageRequest.of(0, 10))
.content
@@ -43,14 +46,14 @@ class SeriesMainController(
creatorId = null,
isCompleted = true,
orderByRandom = true,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member = member
).items
val recommendSeriesList = contentSeriesService.getRecommendSeriesList(
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member = member
)
@@ -71,11 +74,12 @@ class SeriesMainController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
contentSeriesService.getRecommendSeriesList(
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member = member
)
)
@@ -91,13 +95,14 @@ class SeriesMainController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
val pageable = PageRequest.of(page, size)
ApiResponse.ok(
contentSeriesService.getDayOfWeekSeriesList(
memberId = member.id,
isAdult = member.auth != null && (isAdultContentVisible ?: true),
contentType = contentType ?: ContentType.ALL,
isAdult = preference.isAdult,
contentType = preference.contentType,
dayOfWeek = dayOfWeek,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
@@ -112,15 +117,16 @@ class SeriesMainController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
val memberId = member.id!!
val isAdult = member.auth != null && (isAdultContentVisible ?: true)
val isAdult = preference.isAdult
ApiResponse.ok(
contentSeriesService.getGenreList(
memberId = memberId,
isAdult = isAdult,
contentType = contentType ?: ContentType.ALL
contentType = preference.contentType
)
)
}
@@ -135,17 +141,28 @@ class SeriesMainController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
val pageable = PageRequest.of(page, size)
ApiResponse.ok(
contentSeriesService.getSeriesListByGenre(
genreId = genreId,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member = member,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
private fun resolvePreference(
member: Member,
isAdultContentVisible: Boolean?,
contentType: ContentType?
) = memberContentPreferenceService.resolveForQuery(
member = member,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType
)
}

View File

@@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.content.SortType
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import org.springframework.data.domain.Pageable
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.core.annotation.AuthenticationPrincipal
@@ -16,7 +17,10 @@ import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/audio-content/theme")
class AudioContentThemeController(private val service: AudioContentThemeService) {
class AudioContentThemeController(
private val service: AudioContentThemeService,
private val memberContentPreferenceService: MemberContentPreferenceService
) {
@GetMapping
@PreAuthorize("hasRole('CREATOR')")
fun getThemes(
@@ -36,13 +40,14 @@ class AudioContentThemeController(private val service: AudioContentThemeService)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getActiveThemeOfContent(
isAdult = member.auth != null && (isAdultContentVisible ?: true),
isAdult = preference.isAdult,
isFree = isFree ?: false,
isPointAvailableOnly = isPointAvailableOnly ?: false,
contentType = contentType ?: ContentType.ALL
contentType = preference.contentType
)
)
}
@@ -57,17 +62,28 @@ class AudioContentThemeController(private val service: AudioContentThemeService)
pageable: Pageable
) = run {
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member, isAdultContentVisible, contentType)
ApiResponse.ok(
service.getContentByTheme(
themeId = id,
sortType = sortType ?: SortType.NEWEST,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
isAdultContentVisible = preference.isAdultContentVisible,
contentType = preference.contentType,
member = member,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
private fun resolvePreference(
member: Member,
isAdultContentVisible: Boolean?,
contentType: ContentType?
) = memberContentPreferenceService.resolveForQuery(
member = member,
isAdultContentVisible = isAdultContentVisible,
contentType = contentType
)
}

View File

@@ -12,6 +12,7 @@ import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.translation.PapagoTranslationService
import kr.co.vividnext.sodalive.i18n.translation.TranslateRequest
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@@ -129,7 +130,7 @@ class AudioContentThemeService(
val totalCount = contentRepository.totalCountByTheme(
memberId = member.id!!,
theme = listOf(theme.theme),
isAdult = member.auth != null && isAdultContentVisible,
isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible),
contentType = contentType
)
@@ -137,7 +138,7 @@ class AudioContentThemeService(
memberId = member.id!!,
theme = listOf(theme.theme),
sortType = sortType,
isAdult = member.auth != null && isAdultContentVisible,
isAdult = isAdultVisibleByPolicy(member, isAdultContentVisible),
contentType = contentType,
offset = offset,
limit = limit