diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/api/live/LiveApiController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/api/live/LiveApiController.kt new file mode 100644 index 0000000..263db9f --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/api/live/LiveApiController.kt @@ -0,0 +1,33 @@ +package kr.co.vividnext.sodalive.api.live + +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.content.ContentType +import kr.co.vividnext.sodalive.member.Member +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/api/live") +class LiveApiController( + private val service: LiveApiService +) { + @GetMapping + fun fetchData( + @RequestParam timezone: String, + @RequestParam("contentType", required = false) contentType: ContentType? = null, + @RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + ApiResponse.ok( + service.fetchData( + isAdultContentVisible = isAdultContentVisible ?: true, + contentType = contentType ?: ContentType.ALL, + timezone = timezone, + member = member + ) + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/api/live/LiveApiService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/api/live/LiveApiService.kt new file mode 100644 index 0000000..d7d60e2 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/api/live/LiveApiService.kt @@ -0,0 +1,106 @@ +package kr.co.vividnext.sodalive.api.live + +import kr.co.vividnext.sodalive.content.AudioContentService +import kr.co.vividnext.sodalive.content.ContentType +import kr.co.vividnext.sodalive.explorer.ExplorerQueryRepository +import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunityService +import kr.co.vividnext.sodalive.live.recommend.LiveRecommendService +import kr.co.vividnext.sodalive.live.room.LiveRoomService +import kr.co.vividnext.sodalive.live.room.LiveRoomStatus +import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.member.block.BlockMemberRepository +import org.springframework.data.domain.Pageable +import org.springframework.stereotype.Service + +@Service +class LiveApiService( + private val liveService: LiveRoomService, + private val contentService: AudioContentService, + private val recommendService: LiveRecommendService, + private val creatorCommunityService: CreatorCommunityService, + + private val blockMemberRepository: BlockMemberRepository, + private val explorerQueryRepository: ExplorerQueryRepository +) { + fun fetchData( + isAdultContentVisible: Boolean, + contentType: ContentType, + timezone: String, + member: Member? + ): LiveMainResponse { + val memberId = member?.id + val isAdult = member?.auth != null && isAdultContentVisible + + val liveOnAirRoomList = liveService.getRoomList( + dateString = null, + status = LiveRoomStatus.NOW, + isAdultContentVisible = isAdultContentVisible, + pageable = Pageable.ofSize(20), + member = member, + timezone = timezone + ) + + val communityPostList = if (memberId != null) { + creatorCommunityService.getLatestPostListFromCreatorsYouFollow( + timezone = timezone, + memberId = memberId, + isAdult = isAdult + ) + } else { + listOf() + } + + val recommendLiveList = recommendService.getRecommendLive(member) + + val latestFinishedLiveList = liveService.getLatestFinishedLive(member) + .map { + if (memberId != null) { + it.isFollowing = explorerQueryRepository.getCreatorFollowing( + creatorId = it.memberId, + memberId = memberId + )?.isFollow ?: false + } + + it + } + + val replayLive = contentService.getLatestContentByTheme( + theme = listOf("다시듣기"), + contentType = contentType, + isFree = false, + isAdult = isAdult + ) + .filter { content -> + if (memberId != null) { + !blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = content.creatorId) + } else { + true + } + } + + val followingChannelList = if (memberId != null) { + recommendService.getFollowingChannelList(member) + } else { + listOf() + } + + val liveReservationRoomList = liveService.getRoomList( + dateString = null, + status = LiveRoomStatus.RESERVATION, + isAdultContentVisible = isAdultContentVisible, + pageable = Pageable.ofSize(10), + member = member, + timezone = timezone + ) + + return LiveMainResponse( + liveOnAirRoomList = liveOnAirRoomList, + communityPostList = communityPostList, + recommendLiveList = recommendLiveList, + latestFinishedLiveList = latestFinishedLiveList, + replayLive = replayLive, + followingChannelList = followingChannelList, + liveReservationRoomList = liveReservationRoomList + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/api/live/LiveMainResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/api/live/LiveMainResponse.kt new file mode 100644 index 0000000..a1e16bd --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/api/live/LiveMainResponse.kt @@ -0,0 +1,18 @@ +package kr.co.vividnext.sodalive.api.live + +import kr.co.vividnext.sodalive.content.AudioContentMainItem +import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.GetCommunityPostListResponse +import kr.co.vividnext.sodalive.live.recommend.GetRecommendChannelResponse +import kr.co.vividnext.sodalive.live.recommend.GetRecommendLiveResponse +import kr.co.vividnext.sodalive.live.room.GetLatestFinishedLiveResponse +import kr.co.vividnext.sodalive.live.room.GetRoomListResponse + +data class LiveMainResponse( + val liveOnAirRoomList: List, + val communityPostList: List, + val recommendLiveList: List, + val latestFinishedLiveList: List, + val replayLive: List, + val followingChannelList: List, + val liveReservationRoomList: List +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt index cf2a581..04a6c2c 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt @@ -96,6 +96,33 @@ class RedisConfig( ) ) + cacheConfigMap["cache_ttl_10_minutes"] = RedisCacheConfiguration.defaultCacheConfig() + .entryTtl(Duration.ofMinutes(10)) + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer())) + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer( + GenericJackson2JsonRedisSerializer() + ) + ) + + cacheConfigMap["cache_ttl_5_minutes"] = RedisCacheConfiguration.defaultCacheConfig() + .entryTtl(Duration.ofMinutes(5)) + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer())) + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer( + GenericJackson2JsonRedisSerializer() + ) + ) + + cacheConfigMap["cache_ttl_3_minutes"] = RedisCacheConfiguration.defaultCacheConfig() + .entryTtl(Duration.ofMinutes(3)) + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer())) + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer( + GenericJackson2JsonRedisSerializer() + ) + ) + return RedisCacheManager.builder(redisConnectionFactory) .cacheDefaults(defaultCacheConfig) .withInitialCacheConfigurations(cacheConfigMap) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt index e6c2470..b317300 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt @@ -83,6 +83,7 @@ class SecurityConfig( .antMatchers("/api/home").permitAll() .antMatchers("/api/home/latest-content").permitAll() .antMatchers("/api/home/day-of-week-series").permitAll() + .antMatchers(HttpMethod.GET, "/api/live").permitAll() .antMatchers(HttpMethod.GET, "/faq").permitAll() .antMatchers(HttpMethod.GET, "/faq/category").permitAll() .antMatchers("/audition").permitAll() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt index b651166..91476f6 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentService.kt @@ -939,6 +939,11 @@ class AudioContentService( return GenerateUrlResponse(contentUrl) } + @Transactional(readOnly = true) + @Cacheable( + cacheNames = ["default"], + key = "'getLatestContentByTheme:' + #theme + ':' + #contentType + ':' + #isAdult" + ) fun getLatestContentByTheme( theme: List, contentType: ContentType, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunityService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunityService.kt index a209742..c9820bc 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunityService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/CreatorCommunityService.kt @@ -24,6 +24,7 @@ import kr.co.vividnext.sodalive.member.block.BlockMemberRepository import kr.co.vividnext.sodalive.utils.generateFileName import kr.co.vividnext.sodalive.utils.validateImage import org.springframework.beans.factory.annotation.Value +import org.springframework.cache.annotation.Cacheable import org.springframework.context.ApplicationEventPublisher import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service @@ -441,6 +442,11 @@ class CreatorCommunityService( return GetCommunityPostCommentListResponse(totalCount = totalCount, items = commentList) } + @Transactional(readOnly = true) + @Cacheable( + cacheNames = ["cache_ttl_5_minutes"], + key = "'getLatestPostListFromCreatorsYouFollow:' + #memberId + ':' + #isAdult + ':' + #timezone" + ) fun getLatestPostListFromCreatorsYouFollow( timezone: String, memberId: Long, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendService.kt index 67d86fa..066dbda 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendService.kt @@ -3,15 +3,21 @@ package kr.co.vividnext.sodalive.live.recommend import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.MemberRole import kr.co.vividnext.sodalive.member.block.BlockMemberRepository +import org.springframework.cache.annotation.Cacheable import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional @Service class LiveRecommendService( private val repository: LiveRecommendRepository, private val blockMemberRepository: BlockMemberRepository ) { - + @Transactional(readOnly = true) + @Cacheable( + cacheNames = ["cache_ttl_3_hours"], + key = "'getRecommendLive:' + (#member ?: 'guest')" + ) fun getRecommendLive(member: Member?): List { return repository.getRecommendLive( isBlocked = { diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/GetLatestFinishedLiveResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/GetLatestFinishedLiveResponse.kt index 8955da6..8f7f98f 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/GetLatestFinishedLiveResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/GetLatestFinishedLiveResponse.kt @@ -8,7 +8,6 @@ data class GetLatestFinishedLiveQueryResponse @QueryProjection constructor( val memberId: Long, val nickname: String, val profileImageUrl: String, - val title: String, val updatedAt: LocalDateTime ) @@ -16,14 +15,13 @@ data class GetLatestFinishedLiveResponse( val memberId: Long, val nickname: String, val profileImageUrl: String, - val title: String, - val timeAgo: String + val timeAgo: String, + var isFollowing: Boolean = false ) { constructor(response: GetLatestFinishedLiveQueryResponse) : this( response.memberId, response.nickname, response.profileImageUrl, - response.title, response.updatedAt.getTimeAgoString() ) } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt index 5967fb9..4006683 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt @@ -404,7 +404,6 @@ class LiveRoomQueryRepositoryImpl( member.id, member.nickname, member.profileImage.prepend("/").prepend(cloudFrontHost), - liveRoom.title, liveRoom.updatedAt ) ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomService.kt index b8a6e27..953b542 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomService.kt @@ -56,6 +56,7 @@ import kr.co.vividnext.sodalive.member.MemberRole import kr.co.vividnext.sodalive.member.block.BlockMemberRepository import kr.co.vividnext.sodalive.utils.generateFileName import org.springframework.beans.factory.annotation.Value +import org.springframework.cache.annotation.Cacheable import org.springframework.context.ApplicationEventPublisher import org.springframework.data.domain.Pageable import org.springframework.data.repository.findByIdOrNull @@ -113,6 +114,7 @@ class LiveRoomService( ) { private val tokenLocks: MutableMap = mutableMapOf() + @Transactional(readOnly = true) fun getRoomList( dateString: String?, status: LiveRoomStatus, @@ -1297,6 +1299,11 @@ class LiveRoomService( ) } + @Transactional(readOnly = true) + @Cacheable( + cacheNames = ["cache_ttl_10_minutes"], + key = "'getLatestFinishedLive:' + (#member ?: 'guest')" + ) fun getLatestFinishedLive(member: Member?): List { return repository.getLatestFinishedLive() .filter {