feat(creator): 채널 홈 조회 어댑터를 추가한다
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
package kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence
|
||||
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelHomeQueryPort
|
||||
|
||||
interface CreatorChannelHomeQueryRepository : CreatorChannelHomeQueryPort
|
||||
@@ -0,0 +1,916 @@
|
||||
package kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence
|
||||
|
||||
import com.querydsl.core.types.Projections
|
||||
import com.querydsl.core.types.dsl.BooleanExpression
|
||||
import com.querydsl.core.types.dsl.Expressions
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||
import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
|
||||
import kr.co.vividnext.sodalive.chat.character.QChatCharacter.chatCharacter
|
||||
import kr.co.vividnext.sodalive.content.ContentType
|
||||
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
||||
import kr.co.vividnext.sodalive.explorer.profile.QCreatorCheers.creatorCheers
|
||||
import kr.co.vividnext.sodalive.explorer.profile.channelDonation.QChannelDonationMessage.channelDonationMessage
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.QCreatorCommunity.creatorCommunity
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.QCreatorCommunityComment.creatorCommunityComment
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.QCreatorCommunityLike.creatorCommunityLike
|
||||
import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix
|
||||
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||
import kr.co.vividnext.sodalive.live.room.visit.QLiveRoomVisit.liveRoomVisit
|
||||
import kr.co.vividnext.sodalive.member.Gender
|
||||
import kr.co.vividnext.sodalive.member.MemberKind
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
import kr.co.vividnext.sodalive.member.block.QBlockMember
|
||||
import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollowing
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelActivityRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelAudioContentRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelCommunityPostRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelCreatorRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelDonationRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelFanTalkRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelFanTalkSummaryRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelLiveRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelScheduleRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelSeriesRecord
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.port.out.CreatorChannelSnsRecord
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.time.Duration
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
@Repository
|
||||
class DefaultCreatorChannelHomeQueryRepository(
|
||||
private val queryFactory: JPAQueryFactory
|
||||
) : CreatorChannelHomeQueryRepository {
|
||||
override fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelCreatorRecord? {
|
||||
val creator = queryFactory
|
||||
.select(member.id, member.nickname, member.profileImage, member.introduce, member.memberKind)
|
||||
.from(member)
|
||||
.where(
|
||||
member.id.eq(creatorId),
|
||||
member.role.eq(MemberRole.CREATOR),
|
||||
member.isActive.isTrue
|
||||
)
|
||||
.fetchFirst() ?: return null
|
||||
|
||||
val following = viewerId?.let {
|
||||
queryFactory
|
||||
.select(creatorFollowing.isActive, creatorFollowing.isNotify)
|
||||
.from(creatorFollowing)
|
||||
.where(
|
||||
creatorFollowing.member.id.eq(it),
|
||||
creatorFollowing.creator.id.eq(creatorId),
|
||||
creatorFollowing.isActive.isTrue
|
||||
)
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
val characterId = queryFactory
|
||||
.select(chatCharacter.id)
|
||||
.from(chatCharacter)
|
||||
.where(
|
||||
chatCharacter.creatorMember.id.eq(creatorId),
|
||||
chatCharacter.isActive.isTrue
|
||||
)
|
||||
.fetchFirst()
|
||||
|
||||
return CreatorChannelCreatorRecord(
|
||||
creatorId = creator.get(member.id)!!,
|
||||
characterId = characterId,
|
||||
nickname = creator.get(member.nickname)!!,
|
||||
profileImagePath = creator.get(member.profileImage),
|
||||
introduce = creator.get(member.introduce)!!,
|
||||
followerCount = queryFactory
|
||||
.select(creatorFollowing.id.count())
|
||||
.from(creatorFollowing)
|
||||
.where(
|
||||
creatorFollowing.creator.id.eq(creatorId),
|
||||
creatorFollowing.isActive.isTrue,
|
||||
creatorFollowing.member.isActive.isTrue
|
||||
)
|
||||
.fetchOne()
|
||||
?.toInt()
|
||||
?: 0,
|
||||
isAiChatAvailable = characterId != null,
|
||||
isDmAvailable = creator.get(member.memberKind) != MemberKind.AI_CHARACTER,
|
||||
isFollow = following?.get(creatorFollowing.isActive) ?: false,
|
||||
isNotify = following?.get(creatorFollowing.isNotify) ?: false
|
||||
)
|
||||
}
|
||||
|
||||
override fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean {
|
||||
val blockMember = QBlockMember("creatorChannelBlockMember")
|
||||
return queryFactory
|
||||
.select(blockMember.id)
|
||||
.from(blockMember)
|
||||
.where(
|
||||
blockMember.isActive.isTrue,
|
||||
blockMember.member.id.eq(viewerId).and(blockMember.blockedMember.id.eq(creatorId))
|
||||
.or(blockMember.member.id.eq(creatorId).and(blockMember.blockedMember.id.eq(viewerId)))
|
||||
)
|
||||
.fetchFirst() != null
|
||||
}
|
||||
|
||||
override fun findCurrentLive(
|
||||
creatorId: Long,
|
||||
now: LocalDateTime,
|
||||
canViewAdultContent: Boolean,
|
||||
viewerId: Long?,
|
||||
isViewerCreator: Boolean,
|
||||
effectiveViewerGender: Gender?
|
||||
): CreatorChannelLiveRecord? {
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
CreatorChannelLiveRecord::class.java,
|
||||
liveRoom.id,
|
||||
liveRoom.title,
|
||||
liveRoom.coverImage,
|
||||
liveRoom.beginDateTime,
|
||||
liveRoom.price,
|
||||
liveRoom.isAdult
|
||||
)
|
||||
)
|
||||
.from(liveRoom)
|
||||
.where(
|
||||
liveRoom.member.id.eq(creatorId),
|
||||
liveRoom.member.isActive.isTrue,
|
||||
liveRoom.isActive.isTrue,
|
||||
liveRoom.channelName.isNotNull,
|
||||
liveRoom.channelName.isNotEmpty,
|
||||
liveRoom.beginDateTime.loe(now),
|
||||
adultLiveCondition(canViewAdultContent),
|
||||
genderLiveCondition(viewerId, effectiveViewerGender),
|
||||
creatorJoinLiveCondition(viewerId, isViewerCreator)
|
||||
)
|
||||
.orderBy(liveRoom.beginDateTime.desc(), liveRoom.id.desc())
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
override fun findLatestAudioContent(
|
||||
creatorId: Long,
|
||||
now: LocalDateTime,
|
||||
canViewAdultContent: Boolean
|
||||
): CreatorChannelAudioContentRecord? {
|
||||
val row = findAudioContentRows(creatorId, now, null, canViewAdultContent, 1).firstOrNull() ?: return null
|
||||
return row.toAudioRecord(
|
||||
firstContentId = firstAudioContentId(creatorId, now, canViewAdultContent),
|
||||
seriesByContentId = audioSeriesByContentIds(listOf(itAudioId(row)))
|
||||
)
|
||||
}
|
||||
|
||||
override fun findChannelDonations(
|
||||
creatorId: Long,
|
||||
viewerId: Long?,
|
||||
now: LocalDateTime,
|
||||
limit: Int
|
||||
): List<CreatorChannelDonationRecord> {
|
||||
val kstZoneId = ZoneId.of("Asia/Seoul")
|
||||
val utcZoneId = ZoneId.of("UTC")
|
||||
val nowKst = now.atZone(utcZoneId).withZoneSameInstant(kstZoneId)
|
||||
val start = nowKst.toLocalDate().withDayOfMonth(1).atStartOfDay(kstZoneId)
|
||||
.withZoneSameInstant(utcZoneId)
|
||||
.toLocalDateTime()
|
||||
val end = nowKst.toLocalDate().withDayOfMonth(1).atStartOfDay(kstZoneId).plusMonths(1)
|
||||
.withZoneSameInstant(utcZoneId)
|
||||
.toLocalDateTime()
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
CreatorChannelDonationRecord::class.java,
|
||||
channelDonationMessage.member.nickname,
|
||||
channelDonationMessage.member.profileImage,
|
||||
channelDonationMessage.can,
|
||||
channelDonationMessage.additionalMessage.coalesce(""),
|
||||
channelDonationMessage.createdAt
|
||||
)
|
||||
)
|
||||
.from(channelDonationMessage)
|
||||
.where(
|
||||
channelDonationMessage.creator.id.eq(creatorId),
|
||||
channelDonationMessage.createdAt.goe(start),
|
||||
channelDonationMessage.createdAt.lt(end),
|
||||
donationVisibilityCondition(creatorId, viewerId)
|
||||
)
|
||||
.orderBy(channelDonationMessage.createdAt.desc(), channelDonationMessage.id.desc())
|
||||
.limit(limit.toLong())
|
||||
.fetch()
|
||||
}
|
||||
|
||||
override fun findCommunityPosts(
|
||||
creatorId: Long,
|
||||
viewerId: Long?,
|
||||
isFixed: Boolean,
|
||||
canViewAdultContent: Boolean,
|
||||
limit: Int
|
||||
): List<CreatorChannelCommunityPostRecord> {
|
||||
val posts = queryFactory
|
||||
.select(
|
||||
creatorCommunity.id,
|
||||
creatorCommunity.member.id,
|
||||
creatorCommunity.member.nickname,
|
||||
creatorCommunity.member.profileImage,
|
||||
creatorCommunity.imagePath,
|
||||
creatorCommunity.audioPath,
|
||||
creatorCommunity.content,
|
||||
creatorCommunity.price,
|
||||
creatorCommunity.createdAt,
|
||||
creatorCommunity.fixedAt,
|
||||
creatorCommunity.isFixed,
|
||||
creatorCommunity.isCommentAvailable
|
||||
)
|
||||
.from(creatorCommunity)
|
||||
.where(
|
||||
creatorCommunity.member.id.eq(creatorId),
|
||||
creatorCommunity.member.isActive.isTrue,
|
||||
visibleCommunityPostCondition(viewerId),
|
||||
creatorCommunity.isFixed.eq(isFixed),
|
||||
fixedNoticeCondition(isFixed),
|
||||
adultCommunityCondition(canViewAdultContent)
|
||||
)
|
||||
.orderBy(
|
||||
if (isFixed) creatorCommunity.fixedAt.desc() else creatorCommunity.createdAt.desc(),
|
||||
creatorCommunity.id.desc()
|
||||
)
|
||||
.limit(limit.toLong())
|
||||
.fetch()
|
||||
|
||||
val postIds = posts.map { it.get(creatorCommunity.id)!! }
|
||||
val orderedPostIds = orderedCommunityPostIds(creatorId, viewerId, postIds)
|
||||
val likeCounts = communityLikeCounts(postIds)
|
||||
val commentCounts = communityCommentCounts(
|
||||
postIds = posts.filter { it.get(creatorCommunity.isCommentAvailable)!! }.map { it.get(creatorCommunity.id)!! },
|
||||
viewerId = viewerId,
|
||||
isContentCreator = viewerId == creatorId
|
||||
)
|
||||
|
||||
return posts
|
||||
.map {
|
||||
val postId = it.get(creatorCommunity.id)!!
|
||||
val postCreatorId = it.get(creatorCommunity.member.id)!!
|
||||
val isFixedPost = it.get(creatorCommunity.isFixed)!!
|
||||
val price = it.get(creatorCommunity.price)!!
|
||||
val existOrdered = postId in orderedPostIds
|
||||
val canAccessPaidContent = canAccessPaidCommunityContent(
|
||||
price = price,
|
||||
viewerId = viewerId,
|
||||
creatorId = postCreatorId,
|
||||
existOrdered = existOrdered
|
||||
)
|
||||
CreatorChannelCommunityPostRecord(
|
||||
postId = postId,
|
||||
creatorId = postCreatorId,
|
||||
creatorNickname = it.get(creatorCommunity.member.nickname)!!,
|
||||
creatorProfilePath = it.get(creatorCommunity.member.profileImage),
|
||||
imagePath = it.get(creatorCommunity.imagePath),
|
||||
audioPath = if (canAccessPaidContent) it.get(creatorCommunity.audioPath) else null,
|
||||
content = maskPaidCommunityContent(
|
||||
content = it.get(creatorCommunity.content)!!,
|
||||
canAccessPaidContent = canAccessPaidContent
|
||||
),
|
||||
price = price,
|
||||
date = if (isFixedPost) {
|
||||
it.get(creatorCommunity.fixedAt) ?: it.get(creatorCommunity.createdAt)!!
|
||||
} else {
|
||||
it.get(creatorCommunity.createdAt)!!
|
||||
},
|
||||
existOrdered = existOrdered,
|
||||
likeCount = likeCounts[postId] ?: 0,
|
||||
commentCount = commentCounts[postId] ?: 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun findSchedules(
|
||||
creatorId: Long,
|
||||
now: LocalDateTime,
|
||||
canViewAdultContent: Boolean,
|
||||
viewerId: Long?,
|
||||
isViewerCreator: Boolean,
|
||||
effectiveViewerGender: Gender?,
|
||||
limit: Int
|
||||
): List<CreatorChannelScheduleRecord> {
|
||||
val liveSchedules = queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
CreatorChannelScheduleRecord::class.java,
|
||||
liveRoom.beginDateTime,
|
||||
liveRoom.title,
|
||||
Expressions.constant(CreatorActivityType.LIVE),
|
||||
liveRoom.id,
|
||||
liveRoom.isAdult
|
||||
)
|
||||
)
|
||||
.from(liveRoom)
|
||||
.where(
|
||||
liveRoom.member.id.eq(creatorId),
|
||||
liveRoom.member.isActive.isTrue,
|
||||
liveRoom.isActive.isTrue,
|
||||
liveRoom.channelName.isNull.or(liveRoom.channelName.isEmpty),
|
||||
liveRoom.beginDateTime.gt(now),
|
||||
adultLiveCondition(canViewAdultContent),
|
||||
genderLiveCondition(viewerId, effectiveViewerGender),
|
||||
creatorJoinLiveCondition(viewerId, isViewerCreator)
|
||||
)
|
||||
.fetch()
|
||||
|
||||
val audioSchedules = queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
CreatorChannelScheduleRecord::class.java,
|
||||
audioContent.releaseDate,
|
||||
audioContent.title,
|
||||
Expressions.constant(CreatorActivityType.AUDIO),
|
||||
audioContent.id,
|
||||
audioContent.isAdult
|
||||
)
|
||||
)
|
||||
.from(audioContent)
|
||||
.where(
|
||||
audioContent.member.id.eq(creatorId),
|
||||
audioContent.member.isActive.isTrue,
|
||||
audioContent.duration.isNotNull,
|
||||
audioContent.releaseDate.isNotNull,
|
||||
audioContent.releaseDate.gt(now),
|
||||
adultAudioCondition(canViewAdultContent)
|
||||
)
|
||||
.fetch()
|
||||
|
||||
return (liveSchedules + audioSchedules)
|
||||
.sortedWith(compareBy<CreatorChannelScheduleRecord> { it.scheduledAt }.thenBy { it.type.sortOrder })
|
||||
.take(limit)
|
||||
}
|
||||
|
||||
override fun findAudioContents(
|
||||
creatorId: Long,
|
||||
now: LocalDateTime,
|
||||
latestAudioContentId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
limit: Int
|
||||
): List<CreatorChannelAudioContentRecord> {
|
||||
val rows = findAudioContentRows(creatorId, now, latestAudioContentId, canViewAdultContent, limit)
|
||||
val firstContentId = firstAudioContentId(creatorId, now, canViewAdultContent)
|
||||
val seriesByContentId = audioSeriesByContentIds(rows.map { itAudioId(it) })
|
||||
return rows.map { it.toAudioRecord(firstContentId, seriesByContentId) }
|
||||
}
|
||||
|
||||
override fun findSeries(
|
||||
creatorId: Long,
|
||||
viewerId: Long?,
|
||||
now: LocalDateTime,
|
||||
canViewAdultContent: Boolean,
|
||||
contentType: ContentType,
|
||||
limit: Int
|
||||
): List<CreatorChannelSeriesRecord> {
|
||||
val seriesRows = queryFactory
|
||||
.select(
|
||||
series.id,
|
||||
series.title,
|
||||
series.coverImage,
|
||||
series.isOriginal
|
||||
)
|
||||
.from(series)
|
||||
.where(
|
||||
series.member.id.eq(creatorId),
|
||||
series.member.isActive.isTrue,
|
||||
series.isActive.isTrue,
|
||||
adultSeriesCondition(canViewAdultContent),
|
||||
contentTypeSeriesCondition(canViewAdultContent, contentType),
|
||||
notBlockedSeriesCreatorCondition(viewerId)
|
||||
)
|
||||
.fetch()
|
||||
|
||||
val seriesIds = seriesRows.map { it.get(series.id)!! }
|
||||
val contentStats = seriesContentStats(seriesIds, now, canViewAdultContent)
|
||||
val newSeriesIds = newSeriesIds(seriesIds, now, canViewAdultContent)
|
||||
return seriesRows
|
||||
.mapNotNull { seriesRow ->
|
||||
contentStats[seriesRow.get(series.id)!!]?.let { seriesRow to it }
|
||||
}
|
||||
.sortedByDescending { it.second.latestPublishedAt }
|
||||
.take(limit)
|
||||
.map { (seriesRow, stats) ->
|
||||
val seriesId = seriesRow.get(series.id)!!
|
||||
CreatorChannelSeriesRecord(
|
||||
seriesId = seriesId,
|
||||
title = seriesRow.get(series.title)!!,
|
||||
coverImagePath = seriesRow.get(series.coverImage),
|
||||
numberOfContent = stats.contentCount,
|
||||
isNew = seriesId in newSeriesIds,
|
||||
isOriginal = seriesRow.get(series.isOriginal)!!
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun findFanTalkSummary(creatorId: Long, viewerId: Long?): CreatorChannelFanTalkSummaryRecord {
|
||||
val totalCount = queryFactory
|
||||
.select(creatorCheers.id.count())
|
||||
.from(creatorCheers)
|
||||
.where(fanTalkSummaryCondition(creatorId, viewerId))
|
||||
.fetchOne()
|
||||
?.toInt()
|
||||
?: 0
|
||||
|
||||
val latestTalk = queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
CreatorChannelFanTalkRecord::class.java,
|
||||
creatorCheers.id,
|
||||
creatorCheers.member.id,
|
||||
creatorCheers.member.nickname,
|
||||
creatorCheers.member.profileImage,
|
||||
creatorCheers.cheers,
|
||||
creatorCheers.languageCode,
|
||||
creatorCheers.createdAt
|
||||
)
|
||||
)
|
||||
.from(creatorCheers)
|
||||
.where(fanTalkSummaryCondition(creatorId, viewerId))
|
||||
.orderBy(creatorCheers.createdAt.desc(), creatorCheers.id.desc())
|
||||
.limit(1)
|
||||
.fetchFirst()
|
||||
?.let { it.copy(nickname = it.nickname.removeDeletedNicknamePrefix()) }
|
||||
|
||||
return CreatorChannelFanTalkSummaryRecord(
|
||||
totalCount = totalCount,
|
||||
latestFanTalk = latestTalk
|
||||
)
|
||||
}
|
||||
|
||||
override fun findActivity(creatorId: Long, now: LocalDateTime): CreatorChannelActivityRecord {
|
||||
val firstLiveAt = queryFactory
|
||||
.select(liveRoom.beginDateTime.min())
|
||||
.from(liveRoom)
|
||||
.where(liveRoom.member.id.eq(creatorId), liveRoom.channelName.isNotNull, liveRoom.beginDateTime.loe(now))
|
||||
.fetchFirst()
|
||||
val firstAudioAt = firstAudioDebutAt(creatorId, now)
|
||||
val debutDate = listOfNotNull(firstLiveAt, firstAudioAt).minOrNull()
|
||||
|
||||
return CreatorChannelActivityRecord(
|
||||
debutDate = debutDate,
|
||||
dDay = debutDate?.let { "D+${ChronoUnit.DAYS.between(it.toLocalDate(), now.toLocalDate())}" }.orEmpty(),
|
||||
liveCount = queryFactory
|
||||
.select(liveRoom.id.count())
|
||||
.from(liveRoom)
|
||||
.where(liveRoom.member.id.eq(creatorId), liveRoom.channelName.isNotNull)
|
||||
.fetchOne()
|
||||
?: 0L,
|
||||
liveDurationHours = liveDurationHours(creatorId),
|
||||
liveContributorCount = queryFactory
|
||||
.select(liveRoomVisit.member.id.count())
|
||||
.from(liveRoomVisit)
|
||||
.innerJoin(liveRoomVisit.room, liveRoom)
|
||||
.where(liveRoom.member.id.eq(creatorId), liveRoom.channelName.isNotNull)
|
||||
.fetchOne()
|
||||
?: 0L,
|
||||
audioContentCount = queryFactory
|
||||
.select(audioContent.id.count())
|
||||
.from(audioContent)
|
||||
.where(
|
||||
audioContent.member.id.eq(creatorId),
|
||||
audioContent.isActive.isTrue,
|
||||
audioContent.releaseDate.isNotNull,
|
||||
audioContent.releaseDate.loe(now)
|
||||
)
|
||||
.fetchOne()
|
||||
?: 0L,
|
||||
seriesCount = queryFactory
|
||||
.select(series.id.count())
|
||||
.from(series)
|
||||
.where(series.member.id.eq(creatorId), series.isActive.isTrue)
|
||||
.fetchOne()
|
||||
?: 0L
|
||||
)
|
||||
}
|
||||
|
||||
override fun findSns(creatorId: Long): CreatorChannelSnsRecord {
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
CreatorChannelSnsRecord::class.java,
|
||||
member.instagramUrl.coalesce(""),
|
||||
member.fancimmUrl.coalesce(""),
|
||||
member.xUrl.coalesce(""),
|
||||
member.youtubeUrl.coalesce(""),
|
||||
member.websiteUrl.coalesce("")
|
||||
)
|
||||
)
|
||||
.from(member)
|
||||
.where(member.id.eq(creatorId))
|
||||
.fetchFirst()
|
||||
?: CreatorChannelSnsRecord(
|
||||
instagramUrl = "",
|
||||
fancimmUrl = "",
|
||||
xUrl = "",
|
||||
youtubeUrl = "",
|
||||
kakaoOpenChatUrl = ""
|
||||
)
|
||||
}
|
||||
|
||||
private fun findAudioContentRows(
|
||||
creatorId: Long,
|
||||
now: LocalDateTime,
|
||||
excludedContentId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
limit: Int
|
||||
) = queryFactory
|
||||
.select(
|
||||
audioContent.id,
|
||||
audioContent.title,
|
||||
audioContent.duration,
|
||||
audioContent.coverImage,
|
||||
audioContent.price,
|
||||
audioContent.isAdult,
|
||||
audioContent.isPointAvailable,
|
||||
audioContent.releaseDate,
|
||||
audioContent.createdAt
|
||||
)
|
||||
.from(audioContent)
|
||||
.where(
|
||||
audioContent.member.id.eq(creatorId),
|
||||
audioContent.member.isActive.isTrue,
|
||||
audioContent.isActive.isTrue,
|
||||
audioContent.duration.isNotNull,
|
||||
audioContent.releaseDate.isNotNull,
|
||||
audioContent.releaseDate.loe(now),
|
||||
excludedContentId?.let { audioContent.id.ne(it) },
|
||||
adultAudioCondition(canViewAdultContent)
|
||||
)
|
||||
.orderBy(audioContent.releaseDate.desc(), audioContent.id.desc())
|
||||
.limit(limit.toLong())
|
||||
.fetch()
|
||||
|
||||
private fun itAudioId(row: com.querydsl.core.Tuple): Long = row.get(audioContent.id)!!
|
||||
|
||||
private fun com.querydsl.core.Tuple.toAudioRecord(
|
||||
firstContentId: Long?,
|
||||
seriesByContentId: Map<Long, AudioSeriesSummary>
|
||||
): CreatorChannelAudioContentRecord {
|
||||
val audioContentId = get(audioContent.id)!!
|
||||
val seriesSummary = seriesByContentId[audioContentId]
|
||||
return CreatorChannelAudioContentRecord(
|
||||
audioContentId = audioContentId,
|
||||
title = get(audioContent.title)!!,
|
||||
duration = get(audioContent.duration),
|
||||
imagePath = get(audioContent.coverImage),
|
||||
price = get(audioContent.price)!!,
|
||||
isAdult = get(audioContent.isAdult)!!,
|
||||
isPointAvailable = get(audioContent.isPointAvailable)!!,
|
||||
isFirstContent = firstContentId == audioContentId,
|
||||
publishedAt = get(audioContent.releaseDate)!!,
|
||||
seriesName = seriesSummary?.title,
|
||||
isOriginalSeries = seriesSummary?.isOriginal
|
||||
)
|
||||
}
|
||||
|
||||
private fun audioSeriesByContentIds(contentIds: List<Long>): Map<Long, AudioSeriesSummary> {
|
||||
if (contentIds.isEmpty()) return emptyMap()
|
||||
return queryFactory
|
||||
.select(seriesContent.content.id, series.title, series.isOriginal)
|
||||
.from(seriesContent)
|
||||
.innerJoin(seriesContent.series, series)
|
||||
.where(seriesContent.content.id.`in`(contentIds))
|
||||
.fetch()
|
||||
.associate {
|
||||
it.get(seriesContent.content.id)!! to AudioSeriesSummary(
|
||||
title = it.get(series.title)!!,
|
||||
isOriginal = it.get(series.isOriginal)!!
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun firstAudioContentId(creatorId: Long, now: LocalDateTime, canViewAdultContent: Boolean): Long? {
|
||||
return queryFactory
|
||||
.select(audioContent.id)
|
||||
.from(audioContent)
|
||||
.where(
|
||||
audioContent.member.id.eq(creatorId),
|
||||
audioContent.member.isActive.isTrue,
|
||||
audioContent.isActive.isTrue,
|
||||
audioContent.duration.isNotNull,
|
||||
audioContent.releaseDate.isNotNull,
|
||||
audioContent.releaseDate.loe(now),
|
||||
adultAudioCondition(canViewAdultContent)
|
||||
)
|
||||
.orderBy(audioContent.releaseDate.asc(), audioContent.id.asc())
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
private fun orderedCommunityPostIds(creatorId: Long, viewerId: Long?, postIds: List<Long>): Set<Long> {
|
||||
if (viewerId == null || postIds.isEmpty()) return emptySet()
|
||||
if (viewerId == creatorId) return postIds.toSet()
|
||||
return queryFactory
|
||||
.select(useCan.communityPost.id)
|
||||
.from(useCan)
|
||||
.where(
|
||||
useCan.member.id.eq(viewerId),
|
||||
useCan.communityPost.id.`in`(postIds),
|
||||
useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST),
|
||||
useCan.isRefund.isFalse
|
||||
)
|
||||
.fetch()
|
||||
.toSet()
|
||||
}
|
||||
|
||||
private fun communityLikeCounts(postIds: List<Long>): Map<Long, Int> {
|
||||
if (postIds.isEmpty()) return emptyMap()
|
||||
return queryFactory
|
||||
.select(creatorCommunityLike.creatorCommunity.id, creatorCommunityLike.id.count())
|
||||
.from(creatorCommunityLike)
|
||||
.where(creatorCommunityLike.creatorCommunity.id.`in`(postIds), creatorCommunityLike.isActive.isTrue)
|
||||
.groupBy(creatorCommunityLike.creatorCommunity.id)
|
||||
.fetch()
|
||||
.associate {
|
||||
it.get(creatorCommunityLike.creatorCommunity.id)!! to
|
||||
(it.get(creatorCommunityLike.id.count())?.toInt() ?: 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun communityCommentCounts(postIds: List<Long>, viewerId: Long?, isContentCreator: Boolean): Map<Long, Int> {
|
||||
if (postIds.isEmpty()) return emptyMap()
|
||||
var where = creatorCommunityComment.creatorCommunity.id.`in`(postIds)
|
||||
.and(creatorCommunityComment.isActive.isTrue)
|
||||
.and(creatorCommunityComment.parent.isNull)
|
||||
|
||||
if (viewerId != null) {
|
||||
where = where
|
||||
.and(creatorCommunityComment.member.id.notIn(blockedMemberIdSubQuery(viewerId)))
|
||||
.and(creatorCommunityComment.member.id.notIn(blockingMemberIdSubQuery(viewerId)))
|
||||
}
|
||||
|
||||
if (!isContentCreator) {
|
||||
where = where.and(
|
||||
creatorCommunityComment.isSecret.isFalse.or(
|
||||
viewerId?.let { creatorCommunityComment.member.id.eq(it) }
|
||||
?: creatorCommunityComment.isSecret.isFalse
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.select(creatorCommunityComment.creatorCommunity.id, creatorCommunityComment.id.count())
|
||||
.from(creatorCommunityComment)
|
||||
.where(where)
|
||||
.groupBy(creatorCommunityComment.creatorCommunity.id)
|
||||
.fetch()
|
||||
.associate {
|
||||
it.get(creatorCommunityComment.creatorCommunity.id)!! to
|
||||
(it.get(creatorCommunityComment.id.count())?.toInt() ?: 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun blockedMemberIdSubQuery(viewerId: Long) = QBlockMember("communityCommentViewerBlock").let { viewerBlock ->
|
||||
queryFactory
|
||||
.select(viewerBlock.blockedMember.id)
|
||||
.from(viewerBlock)
|
||||
.where(viewerBlock.member.id.eq(viewerId), viewerBlock.isActive.isTrue)
|
||||
}
|
||||
|
||||
private fun blockingMemberIdSubQuery(viewerId: Long) = QBlockMember("communityCommentWriterBlock").let { writerBlock ->
|
||||
queryFactory
|
||||
.select(writerBlock.member.id)
|
||||
.from(writerBlock)
|
||||
.where(writerBlock.blockedMember.id.eq(viewerId), writerBlock.isActive.isTrue)
|
||||
}
|
||||
|
||||
private fun canAccessPaidCommunityContent(
|
||||
price: Int,
|
||||
viewerId: Long?,
|
||||
creatorId: Long,
|
||||
existOrdered: Boolean
|
||||
): Boolean {
|
||||
return price <= 0 || viewerId == creatorId || existOrdered
|
||||
}
|
||||
|
||||
private fun maskPaidCommunityContent(content: String, canAccessPaidContent: Boolean): String {
|
||||
if (canAccessPaidContent) return content
|
||||
val length = content.codePointCount(0, content.length)
|
||||
val endIndex = if (length > 15) {
|
||||
content.offsetByCodePoints(0, 15)
|
||||
} else {
|
||||
content.offsetByCodePoints(0, length / 2)
|
||||
}
|
||||
return content.substring(0, endIndex).plus("...")
|
||||
}
|
||||
|
||||
private fun firstAudioDebutAt(creatorId: Long, now: LocalDateTime): LocalDateTime? {
|
||||
val firstThreeUploads = queryFactory
|
||||
.select(audioContent.releaseDate, audioContent.createdAt)
|
||||
.from(audioContent)
|
||||
.where(
|
||||
audioContent.member.id.eq(creatorId),
|
||||
audioContent.duration.isNotNull
|
||||
)
|
||||
.orderBy(audioContent.createdAt.asc(), audioContent.id.asc())
|
||||
.limit(3)
|
||||
.fetch()
|
||||
|
||||
val firstPublishedAt = firstThreeUploads
|
||||
.mapNotNull { it.get(audioContent.releaseDate) }
|
||||
.firstOrNull { !it.isAfter(now) }
|
||||
if (firstPublishedAt != null) return firstPublishedAt
|
||||
|
||||
val thirdUpload = firstThreeUploads.getOrNull(2) ?: return null
|
||||
return if (thirdUpload.get(audioContent.releaseDate) == null) {
|
||||
thirdUpload.get(audioContent.createdAt)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun liveDurationHours(creatorId: Long): Long {
|
||||
return queryFactory
|
||||
.select(liveRoom.beginDateTime, liveRoom.updatedAt)
|
||||
.from(liveRoom)
|
||||
.where(liveRoom.member.id.eq(creatorId), liveRoom.channelName.isNotNull)
|
||||
.fetch()
|
||||
.sumOf { Duration.between(it.get(liveRoom.beginDateTime), it.get(liveRoom.updatedAt)).toSeconds() } / 3600
|
||||
}
|
||||
|
||||
private fun adultLiveCondition(canViewAdultContent: Boolean): BooleanExpression? {
|
||||
return if (canViewAdultContent) null else liveRoom.isAdult.isFalse
|
||||
}
|
||||
|
||||
private fun adultAudioCondition(canViewAdultContent: Boolean): BooleanExpression? {
|
||||
return if (canViewAdultContent) null else audioContent.isAdult.isFalse
|
||||
}
|
||||
|
||||
private fun genderLiveCondition(viewerId: Long?, effectiveViewerGender: Gender?): BooleanExpression? {
|
||||
if (effectiveViewerGender == null || effectiveViewerGender == Gender.NONE) return null
|
||||
val genderCondition = when (effectiveViewerGender) {
|
||||
Gender.MALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.MALE_ONLY)
|
||||
Gender.FEMALE -> liveRoom.genderRestriction.`in`(GenderRestriction.ALL, GenderRestriction.FEMALE_ONLY)
|
||||
Gender.NONE -> return null
|
||||
}
|
||||
return viewerId?.let { genderCondition.or(liveRoom.member.id.eq(it)) } ?: genderCondition
|
||||
}
|
||||
|
||||
private fun creatorJoinLiveCondition(viewerId: Long?, isViewerCreator: Boolean): BooleanExpression? {
|
||||
if (!isViewerCreator || viewerId == null) return null
|
||||
return liveRoom.isAvailableJoinCreator.isTrue.or(liveRoom.member.id.eq(viewerId))
|
||||
}
|
||||
|
||||
private fun adultCommunityCondition(canViewAdultContent: Boolean): BooleanExpression? {
|
||||
return if (canViewAdultContent) null else creatorCommunity.isAdult.isFalse
|
||||
}
|
||||
|
||||
private fun fixedNoticeCondition(isFixed: Boolean): BooleanExpression? {
|
||||
return if (isFixed) creatorCommunity.fixedAt.isNotNull else null
|
||||
}
|
||||
|
||||
private fun visibleCommunityPostCondition(viewerId: Long?): BooleanExpression {
|
||||
val activePost = creatorCommunity.isActive.isTrue
|
||||
if (viewerId == null) return activePost
|
||||
return activePost.or(
|
||||
queryFactory
|
||||
.select(useCan.id)
|
||||
.from(useCan)
|
||||
.where(
|
||||
useCan.member.id.eq(viewerId),
|
||||
useCan.communityPost.id.eq(creatorCommunity.id),
|
||||
useCan.canUsage.eq(CanUsage.PAID_COMMUNITY_POST),
|
||||
useCan.isRefund.isFalse
|
||||
)
|
||||
.exists()
|
||||
)
|
||||
}
|
||||
|
||||
private fun adultSeriesCondition(canViewAdultContent: Boolean): BooleanExpression? {
|
||||
return if (canViewAdultContent) null else series.isAdult.isFalse
|
||||
}
|
||||
|
||||
private fun contentTypeSeriesCondition(
|
||||
canViewAdultContent: Boolean,
|
||||
contentType: ContentType
|
||||
): BooleanExpression? {
|
||||
if (!canViewAdultContent || contentType == ContentType.ALL) return null
|
||||
return series.member.isNull.or(
|
||||
series.member.auth.gender.eq(
|
||||
if (contentType == ContentType.MALE) 0 else 1
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun notBlockedSeriesCreatorCondition(viewerId: Long?): BooleanExpression? {
|
||||
if (viewerId == null) return null
|
||||
val seriesCreatorBlock = QBlockMember("seriesCreatorBlockViewer")
|
||||
return queryFactory
|
||||
.select(seriesCreatorBlock.id)
|
||||
.from(seriesCreatorBlock)
|
||||
.where(
|
||||
seriesCreatorBlock.isActive.isTrue,
|
||||
seriesCreatorBlock.member.id.eq(series.member.id).and(seriesCreatorBlock.blockedMember.id.eq(viewerId))
|
||||
.or(seriesCreatorBlock.member.id.eq(viewerId).and(seriesCreatorBlock.blockedMember.id.eq(series.member.id)))
|
||||
)
|
||||
.exists()
|
||||
.not()
|
||||
}
|
||||
|
||||
private fun donationVisibilityCondition(creatorId: Long, viewerId: Long?): BooleanExpression? {
|
||||
return if (viewerId == null) {
|
||||
channelDonationMessage.isSecret.isFalse
|
||||
} else if (viewerId == creatorId) {
|
||||
null
|
||||
} else {
|
||||
channelDonationMessage.isSecret.isFalse.or(channelDonationMessage.member.id.eq(viewerId))
|
||||
}
|
||||
}
|
||||
|
||||
private fun seriesContentStats(
|
||||
seriesIds: List<Long>,
|
||||
now: LocalDateTime,
|
||||
canViewAdultContent: Boolean
|
||||
): Map<Long, SeriesContentStats> {
|
||||
if (seriesIds.isEmpty()) return emptyMap()
|
||||
val publishedAt = audioContent.releaseDate.coalesce(audioContent.createdAt)
|
||||
return queryFactory
|
||||
.select(seriesContent.series.id, seriesContent.id.count(), publishedAt.max())
|
||||
.from(seriesContent)
|
||||
.innerJoin(seriesContent.content, audioContent)
|
||||
.where(
|
||||
seriesContent.series.id.`in`(seriesIds),
|
||||
audioContent.isActive.isTrue,
|
||||
audioContent.duration.isNotNull,
|
||||
audioContent.releaseDate.isNotNull,
|
||||
audioContent.releaseDate.loe(now),
|
||||
adultAudioCondition(canViewAdultContent)
|
||||
)
|
||||
.groupBy(seriesContent.series.id)
|
||||
.fetch()
|
||||
.associate {
|
||||
it.get(seriesContent.series.id)!! to SeriesContentStats(
|
||||
contentCount = it.get(seriesContent.id.count())?.toInt() ?: 0,
|
||||
latestPublishedAt = it.get(publishedAt.max())!!
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun newSeriesIds(
|
||||
seriesIds: List<Long>,
|
||||
now: LocalDateTime,
|
||||
canViewAdultContent: Boolean
|
||||
): Set<Long> {
|
||||
if (seriesIds.isEmpty()) return emptySet()
|
||||
return queryFactory
|
||||
.select(seriesContent.series.id)
|
||||
.from(seriesContent)
|
||||
.innerJoin(seriesContent.content, audioContent)
|
||||
.where(
|
||||
seriesContent.series.id.`in`(seriesIds),
|
||||
audioContent.isActive.isTrue,
|
||||
audioContent.duration.isNotNull,
|
||||
audioContent.releaseDate.between(now.minusDays(7), now),
|
||||
adultAudioCondition(canViewAdultContent)
|
||||
)
|
||||
.fetch()
|
||||
.toSet()
|
||||
}
|
||||
|
||||
private fun notBlockedFanTalkWriterCondition(viewerId: Long?): BooleanExpression? {
|
||||
if (viewerId == null) return null
|
||||
val viewerBlock = QBlockMember("viewerBlockFanTalkWriter")
|
||||
val writerBlock = QBlockMember("writerBlockViewerFanTalk")
|
||||
return creatorCheers.member.id.notIn(
|
||||
queryFactory
|
||||
.select(viewerBlock.blockedMember.id)
|
||||
.from(viewerBlock)
|
||||
.where(viewerBlock.member.id.eq(viewerId), viewerBlock.isActive.isTrue)
|
||||
).and(
|
||||
creatorCheers.member.id.notIn(
|
||||
queryFactory
|
||||
.select(writerBlock.member.id)
|
||||
.from(writerBlock)
|
||||
.where(writerBlock.blockedMember.id.eq(viewerId), writerBlock.isActive.isTrue)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun fanTalkSummaryCondition(creatorId: Long, viewerId: Long?): BooleanExpression {
|
||||
return creatorCheers.creator.id.eq(creatorId)
|
||||
.and(creatorCheers.isActive.isTrue)
|
||||
.and(creatorCheers.parent.isNull)
|
||||
.and(notBlockedFanTalkWriterCondition(viewerId))
|
||||
}
|
||||
|
||||
private val CreatorActivityType.sortOrder: Int
|
||||
get() = when (this) {
|
||||
CreatorActivityType.LIVE -> 0
|
||||
else -> 1
|
||||
}
|
||||
|
||||
private data class AudioSeriesSummary(
|
||||
val title: String,
|
||||
val isOriginal: Boolean
|
||||
)
|
||||
|
||||
private data class SeriesContentStats(
|
||||
val contentCount: Int,
|
||||
val latestPublishedAt: LocalDateTime
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package kr.co.vividnext.sodalive.v2.creator.channel.port.out
|
||||
|
||||
import kr.co.vividnext.sodalive.content.ContentType
|
||||
import kr.co.vividnext.sodalive.member.Gender
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
||||
import java.time.LocalDateTime
|
||||
|
||||
interface CreatorChannelHomeQueryPort {
|
||||
fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelCreatorRecord?
|
||||
|
||||
fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean
|
||||
|
||||
fun findCurrentLive(
|
||||
creatorId: Long,
|
||||
now: LocalDateTime,
|
||||
canViewAdultContent: Boolean,
|
||||
viewerId: Long?,
|
||||
isViewerCreator: Boolean,
|
||||
effectiveViewerGender: Gender?
|
||||
): CreatorChannelLiveRecord?
|
||||
|
||||
fun findLatestAudioContent(
|
||||
creatorId: Long,
|
||||
now: LocalDateTime,
|
||||
canViewAdultContent: Boolean
|
||||
): CreatorChannelAudioContentRecord?
|
||||
|
||||
fun findChannelDonations(
|
||||
creatorId: Long,
|
||||
viewerId: Long?,
|
||||
now: LocalDateTime,
|
||||
limit: Int = 8
|
||||
): List<CreatorChannelDonationRecord>
|
||||
|
||||
fun findCommunityPosts(
|
||||
creatorId: Long,
|
||||
viewerId: Long?,
|
||||
isFixed: Boolean,
|
||||
canViewAdultContent: Boolean,
|
||||
limit: Int = 3
|
||||
): List<CreatorChannelCommunityPostRecord>
|
||||
|
||||
fun findSchedules(
|
||||
creatorId: Long,
|
||||
now: LocalDateTime,
|
||||
canViewAdultContent: Boolean,
|
||||
viewerId: Long?,
|
||||
isViewerCreator: Boolean,
|
||||
effectiveViewerGender: Gender?,
|
||||
limit: Int = 3
|
||||
): List<CreatorChannelScheduleRecord>
|
||||
|
||||
fun findAudioContents(
|
||||
creatorId: Long,
|
||||
now: LocalDateTime,
|
||||
latestAudioContentId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
limit: Int = 9
|
||||
): List<CreatorChannelAudioContentRecord>
|
||||
|
||||
fun findSeries(
|
||||
creatorId: Long,
|
||||
viewerId: Long?,
|
||||
now: LocalDateTime,
|
||||
canViewAdultContent: Boolean,
|
||||
contentType: ContentType,
|
||||
limit: Int = 8
|
||||
): List<CreatorChannelSeriesRecord>
|
||||
|
||||
fun findFanTalkSummary(creatorId: Long, viewerId: Long?): CreatorChannelFanTalkSummaryRecord
|
||||
|
||||
fun findActivity(creatorId: Long, now: LocalDateTime): CreatorChannelActivityRecord
|
||||
|
||||
fun findSns(creatorId: Long): CreatorChannelSnsRecord
|
||||
}
|
||||
|
||||
data class CreatorChannelCreatorRecord(
|
||||
val creatorId: Long,
|
||||
val characterId: Long?,
|
||||
val nickname: String,
|
||||
val profileImagePath: String?,
|
||||
val introduce: String,
|
||||
val followerCount: Int,
|
||||
val isAiChatAvailable: Boolean,
|
||||
val isDmAvailable: Boolean,
|
||||
val isFollow: Boolean,
|
||||
val isNotify: Boolean
|
||||
)
|
||||
|
||||
data class CreatorChannelLiveRecord(
|
||||
val liveId: Long,
|
||||
val title: String,
|
||||
val coverImagePath: String?,
|
||||
val beginDateTime: LocalDateTime,
|
||||
val price: Int,
|
||||
val isAdult: Boolean
|
||||
)
|
||||
|
||||
data class CreatorChannelAudioContentRecord(
|
||||
val audioContentId: Long,
|
||||
val title: String,
|
||||
val duration: String?,
|
||||
val imagePath: String?,
|
||||
val price: Int,
|
||||
val isAdult: Boolean,
|
||||
val isPointAvailable: Boolean,
|
||||
val isFirstContent: Boolean,
|
||||
val publishedAt: LocalDateTime,
|
||||
val seriesName: String?,
|
||||
val isOriginalSeries: Boolean?
|
||||
)
|
||||
|
||||
data class CreatorChannelDonationRecord(
|
||||
val nickname: String,
|
||||
val profileImagePath: String?,
|
||||
val can: Int,
|
||||
val message: String,
|
||||
val createdAt: LocalDateTime
|
||||
)
|
||||
|
||||
data class CreatorChannelScheduleRecord(
|
||||
val scheduledAt: LocalDateTime,
|
||||
val title: String,
|
||||
val type: CreatorActivityType,
|
||||
val targetId: Long,
|
||||
val isAdult: Boolean
|
||||
)
|
||||
|
||||
data class CreatorChannelSeriesRecord(
|
||||
val seriesId: Long,
|
||||
val title: String,
|
||||
val coverImagePath: String?,
|
||||
val numberOfContent: Int,
|
||||
val isNew: Boolean,
|
||||
val isOriginal: Boolean
|
||||
)
|
||||
|
||||
data class CreatorChannelCommunityPostRecord(
|
||||
val postId: Long,
|
||||
val creatorId: Long,
|
||||
val creatorNickname: String,
|
||||
val creatorProfilePath: String?,
|
||||
val imagePath: String?,
|
||||
val audioPath: String?,
|
||||
val content: String,
|
||||
val price: Int,
|
||||
val date: LocalDateTime,
|
||||
val existOrdered: Boolean,
|
||||
val likeCount: Int,
|
||||
val commentCount: Int
|
||||
)
|
||||
|
||||
data class CreatorChannelFanTalkSummaryRecord(
|
||||
val totalCount: Int,
|
||||
val latestFanTalk: CreatorChannelFanTalkRecord?
|
||||
)
|
||||
|
||||
data class CreatorChannelFanTalkRecord(
|
||||
val fanTalkId: Long,
|
||||
val memberId: Long,
|
||||
val nickname: String,
|
||||
val profileImagePath: String?,
|
||||
val content: String,
|
||||
val languageCode: String?,
|
||||
val createdAt: LocalDateTime
|
||||
)
|
||||
|
||||
data class CreatorChannelActivityRecord(
|
||||
val debutDate: LocalDateTime?,
|
||||
val dDay: String,
|
||||
val liveCount: Long,
|
||||
val liveDurationHours: Long,
|
||||
val liveContributorCount: Long,
|
||||
val audioContentCount: Long,
|
||||
val seriesCount: Long
|
||||
)
|
||||
|
||||
data class CreatorChannelSnsRecord(
|
||||
val instagramUrl: String,
|
||||
val fancimmUrl: String,
|
||||
val xUrl: String,
|
||||
val youtubeUrl: String,
|
||||
val kakaoOpenChatUrl: String
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user