크리에이터 커뮤니티 #102

Merged
klaus merged 18 commits from test into main 2023-12-21 15:10:56 +00:00
21 changed files with 1116 additions and 5 deletions

View File

@ -11,6 +11,7 @@ import kr.co.vividnext.sodalive.explorer.profile.CreatorCheers
import kr.co.vividnext.sodalive.explorer.profile.CreatorCheersRepository
import kr.co.vividnext.sodalive.explorer.profile.PostWriteCheersRequest
import kr.co.vividnext.sodalive.explorer.profile.PutWriteCheersRequest
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunityService
import kr.co.vividnext.sodalive.fcm.FcmEvent
import kr.co.vividnext.sodalive.fcm.FcmEventType
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
@ -36,6 +37,8 @@ class ExplorerService(
private val queryRepository: ExplorerQueryRepository,
private val cheersRepository: CreatorCheersRepository,
private val noticeRepository: ChannelNoticeRepository,
private val communityService: CreatorCommunityService,
private val applicationEventPublisher: ApplicationEventPublisher,
@Value("\${cloud.aws.cloud-front.host}")
@ -227,6 +230,16 @@ class ExplorerService(
// 공지사항
val notice = queryRepository.getNoticeString(creatorId)
// 커뮤니티
val communityPostList = communityService.getCommunityPostList(
creatorId = creatorId,
memberId = member.id!!,
timezone = timezone,
offset = 0,
limit = 3,
isAdult = member.auth != null
)
// 응원
val cheers = queryRepository.getCheersList(creatorId, timezone = timezone, offset = 0, limit = 4)
@ -262,6 +275,7 @@ class ExplorerService(
liveRoomList = liveRoomList,
contentList = contentList,
notice = notice,
communityPostList = communityPostList,
cheers = cheers,
activitySummary = GetCreatorActivitySummary(
liveCount = liveCount,

View File

@ -1,6 +1,7 @@
package kr.co.vividnext.sodalive.explorer
import kr.co.vividnext.sodalive.content.GetAudioContentListItem
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.GetCommunityPostListResponse
data class GetCreatorProfileResponse(
val creator: CreatorResponse,
@ -9,6 +10,7 @@ data class GetCreatorProfileResponse(
val liveRoomList: List<LiveRoomResponse>,
val contentList: List<GetAudioContentListItem>,
val notice: String,
val communityPostList: List<GetCommunityPostListResponse>,
val cheers: GetCheersResponse,
val activitySummary: GetCreatorActivitySummary,
val isBlock: Boolean

View File

@ -0,0 +1,7 @@
package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity
data class CreateCommunityPostRequest(
val content: String,
val isCommentAvailable: Boolean,
val isAdult: Boolean
)

View File

@ -0,0 +1,24 @@
package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity
import kr.co.vividnext.sodalive.common.BaseEntity
import kr.co.vividnext.sodalive.member.Member
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.FetchType
import javax.persistence.JoinColumn
import javax.persistence.ManyToOne
@Entity
data class CreatorCommunity(
@Column(columnDefinition = "TEXT", nullable = false)
var content: String,
var isCommentAvailable: Boolean,
var isAdult: Boolean,
@Column(nullable = true)
var imagePath: String? = null,
var isActive: Boolean = true
) : BaseEntity() {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
var member: Member? = null
}

View File

@ -0,0 +1,198 @@
package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.CreateCommunityPostCommentRequest
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.ModifyCommunityPostCommentRequest
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.PostCommunityPostLikeRequest
import kr.co.vividnext.sodalive.member.Member
import org.springframework.data.domain.Pageable
import org.springframework.lang.Nullable
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RequestPart
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile
@RestController
@RequestMapping("/creator-community")
class CreatorCommunityController(private val service: CreatorCommunityService) {
@PostMapping
@PreAuthorize("hasRole('CREATOR')")
fun createCommunityPost(
@Nullable
@RequestPart("postImage")
postImage: MultipartFile?,
@RequestPart("request") requestString: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.createCommunityPost(
postImage = postImage,
requestString = requestString,
member = member
)
)
}
@PutMapping
@PreAuthorize("hasRole('CREATOR')")
fun modifyCommunityPost(
@Nullable
@RequestPart("postImage")
postImage: MultipartFile?,
@RequestPart("request") requestString: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.modifyCommunityPost(
postImage = postImage,
requestString = requestString,
member = member
)
)
}
@GetMapping
fun getCommunityPostList(
@RequestParam creatorId: Long,
@RequestParam timezone: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getCommunityPostList(
creatorId = creatorId,
memberId = member.id!!,
timezone = timezone,
offset = pageable.offset,
limit = pageable.pageSize.toLong(),
isAdult = member.auth != null
)
)
}
@GetMapping("/{id}")
fun getCommunityPostDetail(
@PathVariable("id") postId: Long,
@RequestParam timezone: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getCommunityPostDetail(
postId = postId,
memberId = member.id!!,
timezone = timezone,
isAdult = member.auth != null
)
)
}
@PostMapping("/like")
fun communityPostLike(
@RequestBody request: PostCommunityPostLikeRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(service.communityPostLike(request, member))
}
@PostMapping("/comment")
fun createCommunityPostComment(
@RequestBody request: CreateCommunityPostCommentRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.createCommunityPostComment(
comment = request.comment,
postId = request.postId,
parentId = request.parentId,
member = member
)
)
}
@PutMapping("/comment")
fun modifyCommunityPostComment(
@RequestBody request: ModifyCommunityPostCommentRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.modifyCommunityPostComment(request = request, member = member)
)
}
@GetMapping("/{id}/comment")
fun getCommunityPostCommentList(
@PathVariable("id") postId: Long,
@RequestParam timezone: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getCommunityPostCommentList(
postId = postId,
timezone = timezone,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
@GetMapping("/comment/{id}")
fun getCommentReplyList(
@PathVariable("id") commentId: Long,
@RequestParam timezone: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getCommentReplyList(
commentId = commentId,
timezone = timezone,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
@GetMapping("/latest")
fun getLatestPostListFromCreatorsYouFollow(
@RequestParam timezone: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getLatestPostListFromCreatorsYouFollow(
timezone = timezone,
memberId = member.id!!,
isAdult = member.auth != null
)
)
}
}

View File

@ -0,0 +1,115 @@
package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity
import com.querydsl.core.types.dsl.Expressions
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.QCreatorCommunity.creatorCommunity
import kr.co.vividnext.sodalive.member.QMember.member
import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollowing
import org.springframework.data.jpa.repository.JpaRepository
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
interface CreatorCommunityRepository : JpaRepository<CreatorCommunity, Long>, CreatorCommunityQueryRepository
interface CreatorCommunityQueryRepository {
fun findByIdAndMemberId(id: Long, memberId: Long): CreatorCommunity?
fun getCommunityPostList(
creatorId: Long,
offset: Long,
limit: Long,
isAdult: Boolean
): List<CreatorCommunity>
fun findByIdAndActive(postId: Long, isAdult: Boolean): CreatorCommunity?
fun getLatestPostListFromCreatorsYouFollow(memberId: Long, isAdult: Boolean): List<CreatorCommunity>
}
class CreatorCommunityQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CreatorCommunityQueryRepository {
override fun findByIdAndMemberId(id: Long, memberId: Long): CreatorCommunity? {
return queryFactory
.selectFrom(creatorCommunity)
.where(
creatorCommunity.id.eq(id)
.and(creatorCommunity.member.id.eq(memberId))
)
.fetchFirst()
}
override fun getCommunityPostList(
creatorId: Long,
offset: Long,
limit: Long,
isAdult: Boolean
): List<CreatorCommunity> {
var where = creatorCommunity.member.id.eq(creatorId)
.and(creatorCommunity.isActive.isTrue)
if (!isAdult) {
where = where.and(creatorCommunity.isAdult.isFalse)
}
return queryFactory
.selectFrom(creatorCommunity)
.innerJoin(creatorCommunity.member, member)
.where(where)
.offset(offset)
.limit(limit)
.orderBy(creatorCommunity.createdAt.desc())
.fetch()
}
override fun findByIdAndActive(postId: Long, isAdult: Boolean): CreatorCommunity? {
var where = creatorCommunity.id.eq(postId)
.and(creatorCommunity.isActive.isTrue)
if (!isAdult) {
where = where.and(creatorCommunity.isAdult.isFalse)
}
return queryFactory
.selectFrom(creatorCommunity)
.where(where)
.fetchFirst()
}
override fun getLatestPostListFromCreatorsYouFollow(memberId: Long, isAdult: Boolean): List<CreatorCommunity> {
val creatorCommunity = QCreatorCommunity.creatorCommunity
val latest = QCreatorCommunity.creatorCommunity
val localDate = LocalDate.now().minusDays(7)
val localTime = LocalTime.of(0, 0)
var where = creatorCommunity.isActive.isTrue
.and(creatorCommunity.createdAt.after(LocalDateTime.of(localDate, localTime)))
if (!isAdult) {
where = where.and(creatorCommunity.isAdult.isFalse)
}
val subQuery = queryFactory
.select(latest.member.id, latest.createdAt.max().`as`("maxCreatedAt"))
.from(latest)
.groupBy(latest.member.id)
where = where.and(
Expressions.list(creatorCommunity.member.id, creatorCommunity.createdAt).`in`(subQuery)
)
val memberSubQuery = queryFactory
.select(creatorFollowing.creator.id)
.from(creatorFollowing)
.where(creatorFollowing.member.id.eq(memberId))
where = where.and(
creatorCommunity.member.id.`in`(memberSubQuery)
)
return queryFactory
.selectFrom(creatorCommunity)
.where(where)
.orderBy(creatorCommunity.createdAt.desc())
.limit(10)
.fetch()
}
}

View File

@ -0,0 +1,429 @@
package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity
import com.amazonaws.services.s3.model.ObjectMetadata
import com.fasterxml.jackson.databind.ObjectMapper
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.CreatorCommunityComment
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.CreatorCommunityCommentRepository
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.GetCommunityPostCommentListResponse
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.ModifyCommunityPostCommentRequest
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.CreatorCommunityLike
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.CreatorCommunityLikeRepository
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.PostCommunityPostLikeRequest
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.PostCommunityPostLikeResponse
import kr.co.vividnext.sodalive.fcm.FcmEvent
import kr.co.vividnext.sodalive.fcm.FcmEventType
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.utils.generateFileName
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.ApplicationEventPublisher
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.multipart.MultipartFile
import java.time.Duration
import java.time.LocalDateTime
@Service
class CreatorCommunityService(
private val repository: CreatorCommunityRepository,
private val likeRepository: CreatorCommunityLikeRepository,
private val commentRepository: CreatorCommunityCommentRepository,
private val s3Uploader: S3Uploader,
private val objectMapper: ObjectMapper,
private val applicationEventPublisher: ApplicationEventPublisher,
@Value("\${cloud.aws.s3.bucket}")
private val imageBucket: String,
@Value("\${cloud.aws.cloud-front.host}")
private val imageHost: String
) {
@Transactional
fun createCommunityPost(postImage: MultipartFile?, requestString: String, member: Member) {
val request = objectMapper.readValue(requestString, CreateCommunityPostRequest::class.java)
val post = CreatorCommunity(
content = request.content,
isCommentAvailable = request.isCommentAvailable,
isAdult = request.isAdult
)
post.member = member
repository.save(post)
if (postImage != null) {
val metadata = ObjectMetadata()
metadata.contentLength = postImage.size
// 커버 이미지 파일명 생성
val imageFileName = generateFileName(prefix = "${post.id}-image")
// 커버 이미지 업로드
val imagePath = s3Uploader.upload(
inputStream = postImage.inputStream,
bucket = imageBucket,
filePath = "creator_community/${post.id}/$imageFileName",
metadata = metadata
)
post.imagePath = imagePath
}
applicationEventPublisher.publishEvent(
FcmEvent(
type = FcmEventType.CHANGE_NOTICE,
title = member.nickname,
message = "공지를 등록했습니다.",
creatorId = member.id!!
)
)
}
@Transactional
fun modifyCommunityPost(postImage: MultipartFile?, requestString: String, member: Member) {
val request = objectMapper.readValue(requestString, ModifyCommunityPostRequest::class.java)
val post = repository.findByIdAndMemberId(id = request.creatorCommunityId, memberId = member.id!!)
?: throw SodaException("잘못된 요청입니다.")
if (request.content != null) {
post.content = request.content
}
if (request.isCommentAvailable != null) {
post.isCommentAvailable = request.isCommentAvailable
}
if (request.isAdult != null) {
post.isAdult = request.isAdult
}
if (request.isActive != null) {
post.isActive = request.isActive
}
if (postImage != null) {
val metadata = ObjectMetadata()
metadata.contentLength = postImage.size
// 커버 이미지 파일명 생성
val imageFileName = generateFileName(prefix = "${post.id}-image")
// 커버 이미지 업로드
val imagePath = s3Uploader.upload(
inputStream = postImage.inputStream,
bucket = imageBucket,
filePath = "creator_community/${post.id}/$imageFileName",
metadata = metadata
)
post.imagePath = imagePath
}
}
fun getCommunityPostList(
creatorId: Long,
memberId: Long,
timezone: String,
offset: Long,
limit: Long,
isAdult: Boolean
): List<GetCommunityPostListResponse> {
val postList = repository.getCommunityPostList(
creatorId = creatorId,
offset = offset,
limit = limit,
isAdult = isAdult
)
return postList
.asSequence()
.map {
val isLike =
likeRepository.findByPostIdAndMemberId(postId = it.id!!, memberId = memberId)?.isActive ?: false
val likeCount = likeRepository.totalCountCommunityPostLikeByPostId(it.id!!)
val commentCount = if (it.isCommentAvailable) {
commentRepository.totalCountCommentByPostId(postId = it.id!!)
} else {
0
}
val commentList = if (it.isCommentAvailable) {
commentRepository.findByPostId(
cloudFrontHost = imageHost,
timezone = timezone,
id = it.id!!,
offset = 0,
limit = 1
)
} else {
listOf()
}
val firstComment = if (it.isCommentAvailable && commentList.isNotEmpty()) {
commentList[0]
} else {
null
}
GetCommunityPostListResponse(
postId = it.id!!,
creatorId = it.member!!.id!!,
creatorNickname = it.member!!.nickname,
creatorProfileUrl = if (it.member!!.profileImage != null) {
"$imageHost/${it.member!!.profileImage}"
} else {
"$imageHost/profile/default-profile.png"
},
imageUrl = if (it.imagePath != null) {
"$imageHost/${it.imagePath!!}"
} else {
null
},
content = it.content,
date = getTimeAgoString(it.createdAt!!),
isCommentAvailable = it.isCommentAvailable,
isAdult = it.isAdult,
isLike = isLike,
likeCount = likeCount,
commentCount = commentCount,
firstComment = firstComment
)
}
.toList()
}
fun getCommunityPostDetail(
postId: Long,
memberId: Long,
timezone: String,
isAdult: Boolean
): GetCommunityPostListResponse {
val post = repository.findByIdAndActive(postId, isAdult = isAdult)
?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.")
val isLike = likeRepository.findByPostIdAndMemberId(postId = post.id!!, memberId = memberId)?.isActive ?: false
val likeCount = likeRepository.totalCountCommunityPostLikeByPostId(post.id!!)
val commentCount = if (post.isCommentAvailable) {
commentRepository.totalCountCommentByPostId(postId = post.id!!)
} else {
0
}
val commentList = if (post.isCommentAvailable) {
commentRepository.findByPostId(
cloudFrontHost = imageHost,
timezone = timezone,
id = post.id!!,
offset = 0,
limit = 1
)
} else {
listOf()
}
val firstComment = if (post.isCommentAvailable && commentList.isNotEmpty()) {
commentList[0]
} else {
null
}
return GetCommunityPostListResponse(
postId = post.id!!,
creatorId = post.member!!.id!!,
creatorNickname = post.member!!.nickname,
creatorProfileUrl = if (post.member!!.profileImage != null) {
"$imageHost/${post.member!!.profileImage}"
} else {
"$imageHost/profile/default-profile.png"
},
imageUrl = if (post.imagePath != null) {
"$imageHost/${post.imagePath!!}"
} else {
null
},
content = post.content,
date = getTimeAgoString(post.createdAt!!),
isCommentAvailable = post.isCommentAvailable,
isAdult = post.isAdult,
isLike = isLike,
likeCount = likeCount,
commentCount = commentCount,
firstComment = firstComment
)
}
private fun getTimeAgoString(dateTime: LocalDateTime): String {
val now = LocalDateTime.now()
val duration = Duration.between(dateTime, now)
return when {
duration.toMinutes() < 60 -> "${duration.toMinutes()}분전"
duration.toHours() < 24 -> "${duration.toHours()}시간전"
duration.toDays() < 30 -> "${duration.toDays()}일전"
duration.toDays() < 365 -> "${duration.toDays() / 30}분전"
else -> "${duration.toDays() / 365}년전"
}
}
@Transactional
fun communityPostLike(request: PostCommunityPostLikeRequest, member: Member): PostCommunityPostLikeResponse {
var postLike = likeRepository.findByPostIdAndMemberId(postId = request.postId, memberId = member.id!!)
if (postLike == null) {
postLike = CreatorCommunityLike()
postLike.member = member
val post = repository.findByIdAndActive(request.postId, isAdult = member.auth != null)
?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.")
postLike.creatorCommunity = post
likeRepository.save(postLike)
} else {
postLike.isActive = !postLike.isActive
}
return PostCommunityPostLikeResponse(like = postLike.isActive)
}
@Transactional
fun createCommunityPostComment(comment: String, postId: Long, parentId: Long?, member: Member) {
val post = repository.findByIdOrNull(id = postId)
?: throw SodaException("잘못된 게시물 입니다.\n다시 시도해 주세요.")
val postComment = CreatorCommunityComment(comment = comment)
postComment.creatorCommunity = post
postComment.member = member
val parent = if (parentId != null) {
commentRepository.findByIdOrNull(id = parentId)
} else {
null
}
if (parent != null) {
postComment.parent = parent
}
commentRepository.save(postComment)
}
@Transactional
fun modifyCommunityPostComment(request: ModifyCommunityPostCommentRequest, member: Member) {
val postComment = commentRepository.findByIdOrNull(id = request.commentId)
?: throw SodaException("잘못된 접근 입니다.\n확인 후 다시 시도해 주세요.")
if (request.comment != null && postComment.member!!.id!! == member.id!!) {
postComment.comment = request.comment
}
if (
request.isActive != null &&
(
postComment.member!!.id!! == member.id!! ||
postComment.creatorCommunity!!.member!!.id!! == member.id!!
)
) {
postComment.isActive = request.isActive
}
}
fun getCommunityPostCommentList(
postId: Long,
timezone: String,
offset: Long,
limit: Long
): GetCommunityPostCommentListResponse {
val commentList = commentRepository.findByPostId(
cloudFrontHost = imageHost,
timezone = timezone,
id = postId,
offset = offset,
limit = limit
)
val totalCount = commentRepository.totalCountCommentByPostId(postId = postId)
return GetCommunityPostCommentListResponse(totalCount = totalCount, items = commentList)
}
fun getCommentReplyList(
commentId: Long,
timezone: String,
offset: Long,
limit: Long
): GetCommunityPostCommentListResponse {
val commentList = commentRepository.getCommunityCommentReplyList(
cloudFrontHost = imageHost,
commentId = commentId,
timezone = timezone,
offset = offset,
limit = limit
)
val totalCount = commentRepository.commentReplyCountByCommentId(commentId)
return GetCommunityPostCommentListResponse(totalCount = totalCount, items = commentList)
}
fun getLatestPostListFromCreatorsYouFollow(
timezone: String,
memberId: Long,
isAdult: Boolean
): List<GetCommunityPostListResponse> {
val postList = repository.getLatestPostListFromCreatorsYouFollow(memberId = memberId, isAdult = isAdult)
return postList
.asSequence()
.map {
val isLike =
likeRepository.findByPostIdAndMemberId(postId = it.id!!, memberId = memberId)?.isActive ?: false
val likeCount = likeRepository.totalCountCommunityPostLikeByPostId(it.id!!)
val commentCount = if (it.isCommentAvailable) {
commentRepository.totalCountCommentByPostId(postId = it.id!!)
} else {
0
}
val commentList = if (it.isCommentAvailable) {
commentRepository.findByPostId(
cloudFrontHost = imageHost,
timezone = timezone,
id = it.id!!,
offset = 0,
limit = 1
)
} else {
listOf()
}
val firstComment = if (it.isCommentAvailable && commentList.isNotEmpty()) {
commentList[0]
} else {
null
}
GetCommunityPostListResponse(
postId = it.id!!,
creatorId = it.member!!.id!!,
creatorNickname = it.member!!.nickname,
creatorProfileUrl = if (it.member!!.profileImage != null) {
"$imageHost/${it.member!!.profileImage}"
} else {
"$imageHost/profile/default-profile.png"
},
imageUrl = if (it.imagePath != null) {
"$imageHost/${it.imagePath!!}"
} else {
null
},
content = it.content,
date = getTimeAgoString(it.createdAt!!),
isCommentAvailable = it.isCommentAvailable,
isAdult = it.isAdult,
isLike = isLike,
likeCount = likeCount,
commentCount = commentCount,
firstComment = firstComment
)
}
.toList()
}
}

View File

@ -0,0 +1,20 @@
package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity
import com.querydsl.core.annotations.QueryProjection
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.GetCommunityPostCommentListItem
data class GetCommunityPostListResponse @QueryProjection constructor(
val postId: Long,
val creatorId: Long,
val creatorNickname: String,
val creatorProfileUrl: String,
val imageUrl: String?,
val content: String,
val date: String,
val isCommentAvailable: Boolean,
val isAdult: Boolean,
val isLike: Boolean,
val likeCount: Int,
val commentCount: Int,
val firstComment: GetCommunityPostCommentListItem?
)

View File

@ -0,0 +1,9 @@
package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity
data class ModifyCommunityPostRequest(
val creatorCommunityId: Long,
val content: String?,
val isCommentAvailable: Boolean?,
val isAdult: Boolean?,
val isActive: Boolean?
)

View File

@ -0,0 +1,3 @@
package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment
data class CreateCommunityPostCommentRequest(val comment: String, val postId: Long, val parentId: Long?)

View File

@ -0,0 +1,37 @@
package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment
import kr.co.vividnext.sodalive.common.BaseEntity
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunity
import kr.co.vividnext.sodalive.member.Member
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.FetchType
import javax.persistence.JoinColumn
import javax.persistence.ManyToOne
import javax.persistence.OneToMany
@Entity
data class CreatorCommunityComment(
@Column(columnDefinition = "TEXT", nullable = false)
var comment: String,
var isActive: Boolean = true
) : BaseEntity() {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id", nullable = true)
var parent: CreatorCommunityComment? = null
set(value) {
value?.children?.add(this)
field = value
}
@OneToMany(mappedBy = "parent")
var children: MutableList<CreatorCommunityComment> = mutableListOf()
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
var member: Member? = null
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "creator_community_id", nullable = false)
var creatorCommunity: CreatorCommunity? = null
}

View File

@ -0,0 +1,142 @@
package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.QCreatorCommunityComment.creatorCommunityComment
import org.springframework.data.jpa.repository.JpaRepository
import java.time.ZoneId
import java.time.format.DateTimeFormatter
interface CreatorCommunityCommentRepository :
JpaRepository<CreatorCommunityComment, Long>, CreatorCommunityCommentQueryRepository
interface CreatorCommunityCommentQueryRepository {
fun findByPostId(
cloudFrontHost: String,
timezone: String,
id: Long,
offset: Long,
limit: Long
): List<GetCommunityPostCommentListItem>
fun commentReplyCountByCommentId(commentId: Long): Int
fun totalCountCommentByPostId(postId: Long): Int
fun getCommunityCommentReplyList(
cloudFrontHost: String,
commentId: Long,
timezone: String,
offset: Long,
limit: Long
): List<GetCommunityPostCommentListItem>
}
class CreatorCommunityCommentQueryRepositoryImpl(
private val queryFactory: JPAQueryFactory
) : CreatorCommunityCommentQueryRepository {
override fun findByPostId(
cloudFrontHost: String,
timezone: String,
id: Long,
offset: Long,
limit: Long
): List<GetCommunityPostCommentListItem> {
return queryFactory
.selectFrom(creatorCommunityComment)
.where(
creatorCommunityComment.isActive.isTrue
.and(creatorCommunityComment.creatorCommunity.id.eq(id))
.and(creatorCommunityComment.parent.isNull)
)
.offset(offset)
.limit(limit)
.orderBy(creatorCommunityComment.createdAt.desc())
.fetch()
.asSequence()
.map {
val date = it.createdAt!!
.atZone(ZoneId.of("UTC"))
.withZoneSameInstant(ZoneId.of(timezone))
GetCommunityPostCommentListItem(
id = it.id!!,
writerId = it.member!!.id!!,
nickname = it.member!!.nickname,
profileUrl = if (it.member!!.profileImage != null) {
"$cloudFrontHost/${it.member!!.profileImage}"
} else {
"$cloudFrontHost/profile/default-profile.png"
},
comment = it.comment,
date = date.format(DateTimeFormatter.ofPattern("yyyy.MM.dd E hh:mm a")),
replyCount = commentReplyCountByCommentId(it.id!!)
)
}
.toList()
}
override fun commentReplyCountByCommentId(commentId: Long): Int {
return queryFactory.select(creatorCommunityComment.id)
.from(creatorCommunityComment)
.where(
creatorCommunityComment.isActive.isTrue
.and(creatorCommunityComment.parent.isNotNull)
.and(creatorCommunityComment.parent.id.eq(commentId))
)
.fetch()
.size
}
override fun totalCountCommentByPostId(postId: Long): Int {
return queryFactory.select(creatorCommunityComment.id)
.from(creatorCommunityComment)
.where(
creatorCommunityComment.creatorCommunity.id.eq(postId)
.and(creatorCommunityComment.isActive.isTrue)
.and(creatorCommunityComment.parent.isNull)
)
.fetch()
.size
}
override fun getCommunityCommentReplyList(
cloudFrontHost: String,
commentId: Long,
timezone: String,
offset: Long,
limit: Long
): List<GetCommunityPostCommentListItem> {
return queryFactory
.selectFrom(creatorCommunityComment)
.where(
creatorCommunityComment.isActive.isTrue
.and(creatorCommunityComment.parent.isNotNull)
.and(creatorCommunityComment.parent.id.eq(commentId))
)
.offset(offset)
.limit(limit)
.orderBy(creatorCommunityComment.createdAt.desc())
.fetch()
.asSequence()
.map {
val date = it.createdAt!!
.atZone(ZoneId.of("UTC"))
.withZoneSameInstant(ZoneId.of(timezone))
GetCommunityPostCommentListItem(
id = it.id!!,
writerId = it.member!!.id!!,
nickname = it.member!!.nickname,
profileUrl = if (it.member!!.profileImage != null) {
"$cloudFrontHost/${it.member!!.profileImage}"
} else {
"$cloudFrontHost/profile/default-profile.png"
},
comment = it.comment,
date = date.format(DateTimeFormatter.ofPattern("yyyy.MM.dd E hh:mm a")),
replyCount = commentReplyCountByCommentId(it.id!!)
)
}
.toList()
}
}

View File

@ -0,0 +1,16 @@
package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment
data class GetCommunityPostCommentListResponse(
val totalCount: Int,
val items: List<GetCommunityPostCommentListItem>
)
data class GetCommunityPostCommentListItem(
val id: Long,
val writerId: Long,
val nickname: String,
val profileUrl: String,
val comment: String,
val date: String,
val replyCount: Int
)

View File

@ -0,0 +1,3 @@
package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment
data class ModifyCommunityPostCommentRequest(val commentId: Long, val comment: String?, val isActive: Boolean?)

View File

@ -0,0 +1,22 @@
package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like
import kr.co.vividnext.sodalive.common.BaseEntity
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunity
import kr.co.vividnext.sodalive.member.Member
import javax.persistence.Entity
import javax.persistence.FetchType
import javax.persistence.JoinColumn
import javax.persistence.ManyToOne
@Entity
data class CreatorCommunityLike(
var isActive: Boolean = true
) : BaseEntity() {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
var member: Member? = null
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "creator_community_id", nullable = false)
var creatorCommunity: CreatorCommunity? = null
}

View File

@ -0,0 +1,42 @@
package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.QCreatorCommunity.creatorCommunity
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.QCreatorCommunityLike.creatorCommunityLike
import org.springframework.data.jpa.repository.JpaRepository
interface CreatorCommunityLikeRepository :
JpaRepository<CreatorCommunityLike, Long>, CreatorCommunityLikeQueryRepository
interface CreatorCommunityLikeQueryRepository {
fun findByPostIdAndMemberId(postId: Long, memberId: Long): CreatorCommunityLike?
fun totalCountCommunityPostLikeByPostId(postId: Long): Int
}
class CreatorCommunityLikeQueryRepositoryImpl(
private val queryFactory: JPAQueryFactory
) : CreatorCommunityLikeQueryRepository {
override fun findByPostIdAndMemberId(postId: Long, memberId: Long): CreatorCommunityLike? {
return queryFactory
.selectFrom(creatorCommunityLike)
.innerJoin(creatorCommunityLike.creatorCommunity, creatorCommunity)
.where(
creatorCommunityLike.creatorCommunity.id.eq(postId)
.and(creatorCommunityLike.member.id.eq(memberId))
)
.fetchFirst()
}
override fun totalCountCommunityPostLikeByPostId(postId: Long): Int {
return queryFactory
.select(creatorCommunityLike.id)
.from(creatorCommunityLike)
.where(
creatorCommunityLike.isActive.isTrue
.and(creatorCommunityLike.creatorCommunity.id.eq(postId))
)
.fetch()
.size
}
}

View File

@ -0,0 +1,3 @@
package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like
data class PostCommunityPostLikeRequest(val postId: Long)

View File

@ -0,0 +1,3 @@
package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like
data class PostCommunityPostLikeResponse(val like: Boolean)

View File

@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.report
import kr.co.vividnext.sodalive.common.BaseEntity
import kr.co.vividnext.sodalive.explorer.profile.CreatorCheers
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunity
import kr.co.vividnext.sodalive.member.Member
import javax.persistence.Entity
import javax.persistence.EnumType
@ -27,8 +28,12 @@ data class Report(
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "cheers_id", nullable = true)
var cheers: CreatorCheers? = null
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "community_post_id", nullable = true)
var communityPost: CreatorCommunity? = null
}
enum class ReportType {
PROFILE, USER, CHEERS, AUDIO_CONTENT
PROFILE, USER, CHEERS, AUDIO_CONTENT, COMMUNITY_POST
}

View File

@ -5,5 +5,6 @@ data class ReportRequest(
val reason: String,
val reportedMemberId: Long? = null,
val cheersId: Long? = null,
val audioContentId: Long? = null
val audioContentId: Long? = null,
val communityPostId: Long? = null
)

View File

@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.report
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.explorer.profile.CreatorCheersRepository
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunityRepository
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRepository
import org.springframework.data.repository.findByIdOrNull
@ -13,7 +14,8 @@ import org.springframework.transaction.annotation.Transactional
class ReportService(
private val repository: ReportRepository,
private val memberRepository: MemberRepository,
private val cheersRepository: CreatorCheersRepository
private val cheersRepository: CreatorCheersRepository,
private val creatorCommunityRepository: CreatorCommunityRepository
) {
@Transactional
fun save(member: Member, request: ReportRequest) {
@ -35,18 +37,32 @@ class ReportService(
null
}
val communityPost = if (request.communityPostId != null) {
creatorCommunityRepository.findByIdOrNull(request.communityPostId)
?: throw SodaException("신고가 접수되었습니다.")
} else {
null
}
val report = Report(type = request.type, reason = request.reason)
report.member = member
report.reportedAccount = reportedAccount
report.cheers = cheers
report.communityPost = communityPost
repository.save(report)
}
private fun conditionAllIsNull(request: ReportRequest, isNull: Boolean): Boolean {
return if (isNull) {
request.reportedMemberId == null && request.cheersId == null && request.audioContentId == null
request.reportedMemberId == null &&
request.cheersId == null &&
request.audioContentId == null &&
request.communityPostId == null
} else {
request.reportedMemberId != null && request.cheersId != null && request.audioContentId != null
request.reportedMemberId != null &&
request.cheersId != null &&
request.audioContentId != null &&
request.communityPostId != null
}
}
}