Merge pull request '크리에이터 커뮤니티' (#102) from test into main
Reviewed-on: #102
This commit is contained in:
		| @@ -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.CreatorCheersRepository | ||||||
| import kr.co.vividnext.sodalive.explorer.profile.PostWriteCheersRequest | import kr.co.vividnext.sodalive.explorer.profile.PostWriteCheersRequest | ||||||
| import kr.co.vividnext.sodalive.explorer.profile.PutWriteCheersRequest | 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.FcmEvent | ||||||
| import kr.co.vividnext.sodalive.fcm.FcmEventType | import kr.co.vividnext.sodalive.fcm.FcmEventType | ||||||
| import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser | import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser | ||||||
| @@ -36,6 +37,8 @@ class ExplorerService( | |||||||
|     private val queryRepository: ExplorerQueryRepository, |     private val queryRepository: ExplorerQueryRepository, | ||||||
|     private val cheersRepository: CreatorCheersRepository, |     private val cheersRepository: CreatorCheersRepository, | ||||||
|     private val noticeRepository: ChannelNoticeRepository, |     private val noticeRepository: ChannelNoticeRepository, | ||||||
|  |     private val communityService: CreatorCommunityService, | ||||||
|  |  | ||||||
|     private val applicationEventPublisher: ApplicationEventPublisher, |     private val applicationEventPublisher: ApplicationEventPublisher, | ||||||
|  |  | ||||||
|     @Value("\${cloud.aws.cloud-front.host}") |     @Value("\${cloud.aws.cloud-front.host}") | ||||||
| @@ -227,6 +230,16 @@ class ExplorerService( | |||||||
|         // 공지사항 |         // 공지사항 | ||||||
|         val notice = queryRepository.getNoticeString(creatorId) |         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) |         val cheers = queryRepository.getCheersList(creatorId, timezone = timezone, offset = 0, limit = 4) | ||||||
|  |  | ||||||
| @@ -262,6 +275,7 @@ class ExplorerService( | |||||||
|             liveRoomList = liveRoomList, |             liveRoomList = liveRoomList, | ||||||
|             contentList = contentList, |             contentList = contentList, | ||||||
|             notice = notice, |             notice = notice, | ||||||
|  |             communityPostList = communityPostList, | ||||||
|             cheers = cheers, |             cheers = cheers, | ||||||
|             activitySummary = GetCreatorActivitySummary( |             activitySummary = GetCreatorActivitySummary( | ||||||
|                 liveCount = liveCount, |                 liveCount = liveCount, | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package kr.co.vividnext.sodalive.explorer | package kr.co.vividnext.sodalive.explorer | ||||||
|  |  | ||||||
| import kr.co.vividnext.sodalive.content.GetAudioContentListItem | import kr.co.vividnext.sodalive.content.GetAudioContentListItem | ||||||
|  | import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.GetCommunityPostListResponse | ||||||
|  |  | ||||||
| data class GetCreatorProfileResponse( | data class GetCreatorProfileResponse( | ||||||
|     val creator: CreatorResponse, |     val creator: CreatorResponse, | ||||||
| @@ -9,6 +10,7 @@ data class GetCreatorProfileResponse( | |||||||
|     val liveRoomList: List<LiveRoomResponse>, |     val liveRoomList: List<LiveRoomResponse>, | ||||||
|     val contentList: List<GetAudioContentListItem>, |     val contentList: List<GetAudioContentListItem>, | ||||||
|     val notice: String, |     val notice: String, | ||||||
|  |     val communityPostList: List<GetCommunityPostListResponse>, | ||||||
|     val cheers: GetCheersResponse, |     val cheers: GetCheersResponse, | ||||||
|     val activitySummary: GetCreatorActivitySummary, |     val activitySummary: GetCreatorActivitySummary, | ||||||
|     val isBlock: Boolean |     val isBlock: Boolean | ||||||
|   | |||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity | ||||||
|  |  | ||||||
|  | data class CreateCommunityPostRequest( | ||||||
|  |     val content: String, | ||||||
|  |     val isCommentAvailable: Boolean, | ||||||
|  |     val isAdult: Boolean | ||||||
|  | ) | ||||||
| @@ -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 | ||||||
|  | } | ||||||
| @@ -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 | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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? | ||||||
|  | ) | ||||||
| @@ -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? | ||||||
|  | ) | ||||||
| @@ -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?) | ||||||
| @@ -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 | ||||||
|  | } | ||||||
| @@ -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() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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 | ||||||
|  | ) | ||||||
| @@ -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?) | ||||||
| @@ -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 | ||||||
|  | } | ||||||
| @@ -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 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like | ||||||
|  |  | ||||||
|  | data class PostCommunityPostLikeRequest(val postId: Long) | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | package kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like | ||||||
|  |  | ||||||
|  | data class PostCommunityPostLikeResponse(val like: Boolean) | ||||||
| @@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.report | |||||||
|  |  | ||||||
| import kr.co.vividnext.sodalive.common.BaseEntity | import kr.co.vividnext.sodalive.common.BaseEntity | ||||||
| import kr.co.vividnext.sodalive.explorer.profile.CreatorCheers | 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 kr.co.vividnext.sodalive.member.Member | ||||||
| import javax.persistence.Entity | import javax.persistence.Entity | ||||||
| import javax.persistence.EnumType | import javax.persistence.EnumType | ||||||
| @@ -27,8 +28,12 @@ data class Report( | |||||||
|     @ManyToOne(fetch = FetchType.LAZY) |     @ManyToOne(fetch = FetchType.LAZY) | ||||||
|     @JoinColumn(name = "cheers_id", nullable = true) |     @JoinColumn(name = "cheers_id", nullable = true) | ||||||
|     var cheers: CreatorCheers? = null |     var cheers: CreatorCheers? = null | ||||||
|  |  | ||||||
|  |     @ManyToOne(fetch = FetchType.LAZY) | ||||||
|  |     @JoinColumn(name = "community_post_id", nullable = true) | ||||||
|  |     var communityPost: CreatorCommunity? = null | ||||||
| } | } | ||||||
|  |  | ||||||
| enum class ReportType { | enum class ReportType { | ||||||
|     PROFILE, USER, CHEERS, AUDIO_CONTENT |     PROFILE, USER, CHEERS, AUDIO_CONTENT, COMMUNITY_POST | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,5 +5,6 @@ data class ReportRequest( | |||||||
|     val reason: String, |     val reason: String, | ||||||
|     val reportedMemberId: Long? = null, |     val reportedMemberId: Long? = null, | ||||||
|     val cheersId: Long? = null, |     val cheersId: Long? = null, | ||||||
|     val audioContentId: Long? = null |     val audioContentId: Long? = null, | ||||||
|  |     val communityPostId: Long? = null | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.report | |||||||
|  |  | ||||||
| import kr.co.vividnext.sodalive.common.SodaException | import kr.co.vividnext.sodalive.common.SodaException | ||||||
| import kr.co.vividnext.sodalive.explorer.profile.CreatorCheersRepository | 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.Member | ||||||
| import kr.co.vividnext.sodalive.member.MemberRepository | import kr.co.vividnext.sodalive.member.MemberRepository | ||||||
| import org.springframework.data.repository.findByIdOrNull | import org.springframework.data.repository.findByIdOrNull | ||||||
| @@ -13,7 +14,8 @@ import org.springframework.transaction.annotation.Transactional | |||||||
| class ReportService( | class ReportService( | ||||||
|     private val repository: ReportRepository, |     private val repository: ReportRepository, | ||||||
|     private val memberRepository: MemberRepository, |     private val memberRepository: MemberRepository, | ||||||
|     private val cheersRepository: CreatorCheersRepository |     private val cheersRepository: CreatorCheersRepository, | ||||||
|  |     private val creatorCommunityRepository: CreatorCommunityRepository | ||||||
| ) { | ) { | ||||||
|     @Transactional |     @Transactional | ||||||
|     fun save(member: Member, request: ReportRequest) { |     fun save(member: Member, request: ReportRequest) { | ||||||
| @@ -35,18 +37,32 @@ class ReportService( | |||||||
|             null |             null | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         val communityPost = if (request.communityPostId != null) { | ||||||
|  |             creatorCommunityRepository.findByIdOrNull(request.communityPostId) | ||||||
|  |                 ?: throw SodaException("신고가 접수되었습니다.") | ||||||
|  |         } else { | ||||||
|  |             null | ||||||
|  |         } | ||||||
|  |  | ||||||
|         val report = Report(type = request.type, reason = request.reason) |         val report = Report(type = request.type, reason = request.reason) | ||||||
|         report.member = member |         report.member = member | ||||||
|         report.reportedAccount = reportedAccount |         report.reportedAccount = reportedAccount | ||||||
|         report.cheers = cheers |         report.cheers = cheers | ||||||
|  |         report.communityPost = communityPost | ||||||
|         repository.save(report) |         repository.save(report) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun conditionAllIsNull(request: ReportRequest, isNull: Boolean): Boolean { |     private fun conditionAllIsNull(request: ReportRequest, isNull: Boolean): Boolean { | ||||||
|         return if (isNull) { |         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 { |         } else { | ||||||
|             request.reportedMemberId != null && request.cheersId != null && request.audioContentId != null |             request.reportedMemberId != null && | ||||||
|  |                 request.cheersId != null && | ||||||
|  |                 request.audioContentId != null && | ||||||
|  |                 request.communityPostId != null | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user