| @@ -75,7 +75,13 @@ class HomeService( | |||||||
|             } |             } | ||||||
|             .map { |             .map { | ||||||
|                 val followerCount = explorerQueryRepository.getNotificationUserIds(it.id!!).size |                 val followerCount = explorerQueryRepository.getNotificationUserIds(it.id!!).size | ||||||
|                 it.toExplorerSectionCreator(imageHost, followerCount) |                 val follow = if (memberId != null) { | ||||||
|  |                     explorerQueryRepository.isFollow(it.id!!, memberId = memberId) | ||||||
|  |                 } else { | ||||||
|  |                     false | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 it.toExplorerSectionCreator(imageHost, follow, followerCount = followerCount) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         val latestContentThemeList = contentThemeService.getActiveThemeOfContent( |         val latestContentThemeList = contentThemeService.getActiveThemeOfContent( | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,94 @@ | |||||||
|  | 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.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 | ||||||
|  | ) { | ||||||
|  |     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) | ||||||
|  |  | ||||||
|  |         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 | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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<GetRoomListResponse>, | ||||||
|  |     val communityPostList: List<GetCommunityPostListResponse>, | ||||||
|  |     val recommendLiveList: List<GetRecommendLiveResponse>, | ||||||
|  |     val latestFinishedLiveList: List<GetLatestFinishedLiveResponse>, | ||||||
|  |     val replayLive: List<AudioContentMainItem>, | ||||||
|  |     val followingChannelList: List<GetRecommendChannelResponse>, | ||||||
|  |     val liveReservationRoomList: List<GetRoomListResponse> | ||||||
|  | ) | ||||||
| @@ -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) |         return RedisCacheManager.builder(redisConnectionFactory) | ||||||
|             .cacheDefaults(defaultCacheConfig) |             .cacheDefaults(defaultCacheConfig) | ||||||
|             .withInitialCacheConfigurations(cacheConfigMap) |             .withInitialCacheConfigurations(cacheConfigMap) | ||||||
|   | |||||||
| @@ -83,6 +83,7 @@ class SecurityConfig( | |||||||
|             .antMatchers("/api/home").permitAll() |             .antMatchers("/api/home").permitAll() | ||||||
|             .antMatchers("/api/home/latest-content").permitAll() |             .antMatchers("/api/home/latest-content").permitAll() | ||||||
|             .antMatchers("/api/home/day-of-week-series").permitAll() |             .antMatchers("/api/home/day-of-week-series").permitAll() | ||||||
|  |             .antMatchers(HttpMethod.GET, "/api/live").permitAll() | ||||||
|             .antMatchers(HttpMethod.GET, "/faq").permitAll() |             .antMatchers(HttpMethod.GET, "/faq").permitAll() | ||||||
|             .antMatchers(HttpMethod.GET, "/faq/category").permitAll() |             .antMatchers(HttpMethod.GET, "/faq/category").permitAll() | ||||||
|             .antMatchers("/audition").permitAll() |             .antMatchers("/audition").permitAll() | ||||||
|   | |||||||
| @@ -939,6 +939,7 @@ class AudioContentService( | |||||||
|         return GenerateUrlResponse(contentUrl) |         return GenerateUrlResponse(contentUrl) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Transactional(readOnly = true) | ||||||
|     fun getLatestContentByTheme( |     fun getLatestContentByTheme( | ||||||
|         theme: List<String>, |         theme: List<String>, | ||||||
|         contentType: ContentType, |         contentType: ContentType, | ||||||
|   | |||||||
| @@ -15,5 +15,6 @@ data class GetExplorerSectionCreatorResponse( | |||||||
|     val nickname: String, |     val nickname: String, | ||||||
|     val tags: String, |     val tags: String, | ||||||
|     val profileImageUrl: String, |     val profileImageUrl: String, | ||||||
|  |     val follow: Boolean, | ||||||
|     val followerCount: Int |     val followerCount: Int | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -441,6 +441,7 @@ class CreatorCommunityService( | |||||||
|         return GetCommunityPostCommentListResponse(totalCount = totalCount, items = commentList) |         return GetCommunityPostCommentListResponse(totalCount = totalCount, items = commentList) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Transactional(readOnly = true) | ||||||
|     fun getLatestPostListFromCreatorsYouFollow( |     fun getLatestPostListFromCreatorsYouFollow( | ||||||
|         timezone: String, |         timezone: String, | ||||||
|         memberId: Long, |         memberId: Long, | ||||||
|   | |||||||
| @@ -3,15 +3,21 @@ package kr.co.vividnext.sodalive.live.recommend | |||||||
| import kr.co.vividnext.sodalive.member.Member | import kr.co.vividnext.sodalive.member.Member | ||||||
| import kr.co.vividnext.sodalive.member.MemberRole | import kr.co.vividnext.sodalive.member.MemberRole | ||||||
| import kr.co.vividnext.sodalive.member.block.BlockMemberRepository | import kr.co.vividnext.sodalive.member.block.BlockMemberRepository | ||||||
|  | import org.springframework.cache.annotation.Cacheable | ||||||
| import org.springframework.data.domain.Pageable | import org.springframework.data.domain.Pageable | ||||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||||
|  | import org.springframework.transaction.annotation.Transactional | ||||||
|  |  | ||||||
| @Service | @Service | ||||||
| class LiveRecommendService( | class LiveRecommendService( | ||||||
|     private val repository: LiveRecommendRepository, |     private val repository: LiveRecommendRepository, | ||||||
|     private val blockMemberRepository: BlockMemberRepository |     private val blockMemberRepository: BlockMemberRepository | ||||||
| ) { | ) { | ||||||
|  |     @Transactional(readOnly = true) | ||||||
|  |     @Cacheable( | ||||||
|  |         cacheNames = ["cache_ttl_3_hours"], | ||||||
|  |         key = "'getRecommendLive:' + (#member ?: 'guest')" | ||||||
|  |     ) | ||||||
|     fun getRecommendLive(member: Member?): List<GetRecommendLiveResponse> { |     fun getRecommendLive(member: Member?): List<GetRecommendLiveResponse> { | ||||||
|         return repository.getRecommendLive( |         return repository.getRecommendLive( | ||||||
|             isBlocked = { |             isBlocked = { | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| package kr.co.vividnext.sodalive.live.room | package kr.co.vividnext.sodalive.live.room | ||||||
|  |  | ||||||
|  | import com.fasterxml.jackson.annotation.JsonProperty | ||||||
| import com.querydsl.core.annotations.QueryProjection | import com.querydsl.core.annotations.QueryProjection | ||||||
| import kr.co.vividnext.sodalive.extensions.getTimeAgoString | import kr.co.vividnext.sodalive.extensions.getTimeAgoString | ||||||
| import java.time.LocalDateTime | import java.time.LocalDateTime | ||||||
| @@ -8,22 +9,19 @@ data class GetLatestFinishedLiveQueryResponse @QueryProjection constructor( | |||||||
|     val memberId: Long, |     val memberId: Long, | ||||||
|     val nickname: String, |     val nickname: String, | ||||||
|     val profileImageUrl: String, |     val profileImageUrl: String, | ||||||
|     val title: String, |  | ||||||
|     val updatedAt: LocalDateTime |     val updatedAt: LocalDateTime | ||||||
| ) | ) | ||||||
|  |  | ||||||
| data class GetLatestFinishedLiveResponse( | data class GetLatestFinishedLiveResponse( | ||||||
|     val memberId: Long, |     @JsonProperty("memberId") val memberId: Long, | ||||||
|     val nickname: String, |     @JsonProperty("nickname") val nickname: String, | ||||||
|     val profileImageUrl: String, |     @JsonProperty("profileImageUrl") val profileImageUrl: String, | ||||||
|     val title: String, |     @JsonProperty("timeAgo") val timeAgo: String | ||||||
|     val timeAgo: String |  | ||||||
| ) { | ) { | ||||||
|     constructor(response: GetLatestFinishedLiveQueryResponse) : this( |     constructor(response: GetLatestFinishedLiveQueryResponse) : this( | ||||||
|         response.memberId, |         response.memberId, | ||||||
|         response.nickname, |         response.nickname, | ||||||
|         response.profileImageUrl, |         response.profileImageUrl, | ||||||
|         response.title, |  | ||||||
|         response.updatedAt.getTimeAgoString() |         response.updatedAt.getTimeAgoString() | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -404,7 +404,6 @@ class LiveRoomQueryRepositoryImpl( | |||||||
|                     member.id, |                     member.id, | ||||||
|                     member.nickname, |                     member.nickname, | ||||||
|                     member.profileImage.prepend("/").prepend(cloudFrontHost), |                     member.profileImage.prepend("/").prepend(cloudFrontHost), | ||||||
|                     liveRoom.title, |  | ||||||
|                     liveRoom.updatedAt |                     liveRoom.updatedAt | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|   | |||||||
| @@ -56,6 +56,7 @@ import kr.co.vividnext.sodalive.member.MemberRole | |||||||
| import kr.co.vividnext.sodalive.member.block.BlockMemberRepository | import kr.co.vividnext.sodalive.member.block.BlockMemberRepository | ||||||
| import kr.co.vividnext.sodalive.utils.generateFileName | import kr.co.vividnext.sodalive.utils.generateFileName | ||||||
| import org.springframework.beans.factory.annotation.Value | import org.springframework.beans.factory.annotation.Value | ||||||
|  | import org.springframework.cache.annotation.Cacheable | ||||||
| import org.springframework.context.ApplicationEventPublisher | import org.springframework.context.ApplicationEventPublisher | ||||||
| import org.springframework.data.domain.Pageable | import org.springframework.data.domain.Pageable | ||||||
| import org.springframework.data.repository.findByIdOrNull | import org.springframework.data.repository.findByIdOrNull | ||||||
| @@ -113,6 +114,7 @@ class LiveRoomService( | |||||||
| ) { | ) { | ||||||
|     private val tokenLocks: MutableMap<Long, ReentrantReadWriteLock> = mutableMapOf() |     private val tokenLocks: MutableMap<Long, ReentrantReadWriteLock> = mutableMapOf() | ||||||
|  |  | ||||||
|  |     @Transactional(readOnly = true) | ||||||
|     fun getRoomList( |     fun getRoomList( | ||||||
|         dateString: String?, |         dateString: String?, | ||||||
|         status: LiveRoomStatus, |         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<GetLatestFinishedLiveResponse> { |     fun getLatestFinishedLive(member: Member?): List<GetLatestFinishedLiveResponse> { | ||||||
|         return repository.getLatestFinishedLive() |         return repository.getLatestFinishedLive() | ||||||
|             .filter { |             .filter { | ||||||
|   | |||||||
| @@ -123,7 +123,11 @@ data class Member( | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun toExplorerSectionCreator(imageHost: String, followerCount: Int = 0): GetExplorerSectionCreatorResponse { |     fun toExplorerSectionCreator( | ||||||
|  |         imageHost: String, | ||||||
|  |         follow: Boolean = false, | ||||||
|  |         followerCount: Int = 0 | ||||||
|  |     ): GetExplorerSectionCreatorResponse { | ||||||
|         return GetExplorerSectionCreatorResponse( |         return GetExplorerSectionCreatorResponse( | ||||||
|             id = id!!, |             id = id!!, | ||||||
|             nickname = nickname, |             nickname = nickname, | ||||||
| @@ -136,7 +140,8 @@ data class Member( | |||||||
|             } else { |             } else { | ||||||
|                 "$imageHost/profile/default-profile.png" |                 "$imageHost/profile/default-profile.png" | ||||||
|             }, |             }, | ||||||
|             followerCount = followerCount |             followerCount = followerCount, | ||||||
|  |             follow = follow | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user