diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/AdminCalculateQueryRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/AdminCalculateQueryRepository.kt index 6a45e4a..026e9ec 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/AdminCalculateQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/AdminCalculateQueryRepository.kt @@ -166,7 +166,7 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) { .and(useCan.createdAt.loe(endDate)) ) .groupBy(donationFormattedDate, audioContent.id) - .orderBy(member.id.asc(), donationFormattedDate.desc()) + .orderBy(member.id.asc(), donationFormattedDate.desc(), audioContent.id.desc()) .fetch() } @@ -219,7 +219,7 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) { .and(useCan.createdAt.loe(endDate)) ) .groupBy(formattedDate, creatorCommunity.id, creatorSettlementRatio.communitySettlementRatio) - .orderBy(member.id.asc(), formattedDate.desc()) + .orderBy(member.id.asc(), formattedDate.desc(), creatorCommunity.id.desc()) .offset(offset) .limit(limit) .fetch() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentRepository.kt index c78cc03..eecd945 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentRepository.kt @@ -1,13 +1,13 @@ package kr.co.vividnext.sodalive.content.comment +import com.querydsl.core.types.dsl.Expressions import com.querydsl.jpa.impl.JPAQueryFactory import kr.co.vividnext.sodalive.content.QAudioContent.audioContent import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment import kr.co.vividnext.sodalive.member.QMember.member import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository -import java.time.ZoneId -import java.time.format.DateTimeFormatter +import java.time.LocalDateTime @Repository interface AudioContentCommentRepository : JpaRepository, AudioContentCommentQueryRepository @@ -64,8 +64,33 @@ class AudioContentCommentQueryRepositoryImpl( ) } + val formattedDate = Expressions.stringTemplate( + "DATE_FORMAT({0}, {1})", + Expressions.dateTimeTemplate( + LocalDateTime::class.java, + "CONVERT_TZ({0},{1},{2})", + audioContentComment.createdAt, + "UTC", + "Asia/Seoul" + ), + "%Y.%m.%d %W %h:%i %p" + ) + return queryFactory - .selectFrom(audioContentComment) + .select( + QGetAudioContentCommentListItem( + audioContentComment.id, + audioContentComment.member.id, + audioContentComment.member.nickname, + audioContentComment.member.profileImage.prepend("/").prepend(cloudFrontHost), + audioContentComment.comment, + audioContentComment.isSecret, + audioContentComment.donationCan.coalesce(0), + formattedDate, + Expressions.constant(0) + ) + ) + .from(audioContentComment) .innerJoin(audioContentComment.audioContent, audioContent) .innerJoin(audioContentComment.member, member) .where(where) @@ -74,25 +99,8 @@ class AudioContentCommentQueryRepositoryImpl( .orderBy(audioContentComment.createdAt.desc()) .fetch() .map { - val date = it.createdAt!! - .atZone(ZoneId.of("UTC")) - .withZoneSameInstant(ZoneId.of(timezone)) - - GetAudioContentCommentListItem( - 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, - isSecret = it.isSecret, - donationCan = it.donationCan ?: 0, - date = date.format(DateTimeFormatter.ofPattern("yyyy.MM.dd E hh:mm a")), - replyCount = commentReplyCountByAudioContentCommentId(it.id!!) - ) + it.replyCount = commentReplyCountByAudioContentCommentId(it.id) + it } } @@ -137,7 +145,34 @@ class AudioContentCommentQueryRepositoryImpl( offset: Long, limit: Int ): List { - return queryFactory.selectFrom(audioContentComment) + val formattedDate = Expressions.stringTemplate( + "DATE_FORMAT({0}, {1})", + Expressions.dateTimeTemplate( + LocalDateTime::class.java, + "CONVERT_TZ({0},{1},{2})", + audioContentComment.createdAt, + "UTC", + "Asia/Seoul" + ), + "%Y.%m.%d %W %h:%i %p" + ) + + return queryFactory + .select( + QGetAudioContentCommentListItem( + audioContentComment.id, + audioContentComment.member.id, + audioContentComment.member.nickname, + audioContentComment.member.profileImage.prepend("/").prepend(cloudFrontHost), + audioContentComment.comment, + audioContentComment.isSecret, + audioContentComment.donationCan.coalesce(0), + formattedDate, + Expressions.constant(0) + ) + ) + .from(audioContentComment) + .innerJoin(audioContentComment.member, member) .where( audioContentComment.parent.isNotNull .and(audioContentComment.parent.id.eq(commentId)) @@ -147,29 +182,6 @@ class AudioContentCommentQueryRepositoryImpl( .limit(limit.toLong()) .orderBy(audioContentComment.createdAt.desc()) .fetch() - .asSequence() - .map { - val date = it.createdAt!! - .atZone(ZoneId.of("UTC")) - .withZoneSameInstant(ZoneId.of(timezone)) - - GetAudioContentCommentListItem( - 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, - isSecret = it.isSecret, - donationCan = it.donationCan ?: 0, - date = date.format(DateTimeFormatter.ofPattern("yyyy.MM.dd E hh:mm a")), - replyCount = 0 - ) - } - .toList() } override fun findPushTokenByContentIdAndCommentParentIdMyMemberId( diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentService.kt index 632ad9f..9d885c1 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/AudioContentCommentService.kt @@ -132,7 +132,11 @@ class AudioContentCommentService( return GetAudioContentCommentListResponse(totalCount, commentList) } - fun getCommentReplyList(commentId: Long, timezone: String, pageable: Pageable): GetAudioContentCommentListResponse { + fun getCommentReplyList( + commentId: Long, + timezone: String, + pageable: Pageable + ): GetAudioContentCommentListResponse { val commentList = repository.getAudioContentCommentReplyList( cloudFrontHost = cloudFrontHost, commentId = commentId, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/GetAudioContentCommentListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/GetAudioContentCommentListResponse.kt index 2313d35..37bd46a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/GetAudioContentCommentListResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/comment/GetAudioContentCommentListResponse.kt @@ -16,5 +16,5 @@ data class GetAudioContentCommentListItem @QueryProjection constructor( val isSecret: Boolean, val donationCan: Int, val date: String, - val replyCount: Int + var replyCount: Int = 0 ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/GetMemberProfileResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/GetMemberProfileResponse.kt new file mode 100644 index 0000000..196e6f7 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/GetMemberProfileResponse.kt @@ -0,0 +1,10 @@ +package kr.co.vividnext.sodalive.member + +import com.querydsl.core.annotations.QueryProjection + +data class GetMemberProfileResponse @QueryProjection constructor( + val memberId: Long, + val nickname: String, + val profileImageUrl: String, + val isBlocked: Boolean +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt index 91da556..e3312d7 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt @@ -10,6 +10,7 @@ import org.springframework.data.domain.Pageable import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.core.userdetails.User 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 @@ -240,4 +241,13 @@ class MemberController(private val service: MemberService) { fun forgotPassword( @RequestBody request: ForgotPasswordRequest ) = ApiResponse.ok(service.forgotPassword(request)) + + @GetMapping("/profile/{id}") + fun getMemberProfile( + @PathVariable id: Long, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + ApiResponse.ok(service.getMemberProfile(memberId = id, myMemberId = member.id!!)) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt index 177b2d2..57c9eca 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt @@ -8,10 +8,12 @@ import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom import kr.co.vividnext.sodalive.member.QMember.member import kr.co.vividnext.sodalive.member.auth.QAuth.auth import kr.co.vividnext.sodalive.member.block.BlockMemberRepository +import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollowing import kr.co.vividnext.sodalive.member.nickname.QNicknameChangeLog.nicknameChangeLog import kr.co.vividnext.sodalive.member.notification.QMemberNotification.memberNotification import kr.co.vividnext.sodalive.message.QMessage.message +import org.springframework.beans.factory.annotation.Value import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @@ -54,12 +56,17 @@ interface MemberQueryRepository { fun getChangeNoticeRecipientPushTokens(creatorId: Long): Map>> fun getPushTokenFromReservationList(roomId: Long): Map>> + + fun getMemberProfile(memberId: Long, myMemberId: Long): GetMemberProfileResponse } @Repository class MemberQueryRepositoryImpl( private val queryFactory: JPAQueryFactory, - private val blockMemberRepository: BlockMemberRepository + private val blockMemberRepository: BlockMemberRepository, + + @Value("\${cloud.aws.cloud-front.host}") + private val cloudFrontHost: String ) : MemberQueryRepository { override fun findByPushToken(pushToken: String): List { return queryFactory @@ -415,4 +422,25 @@ class MemberQueryRepositoryImpl( return mapOf("aos" to aosPushTokens, "ios" to iosPushTokens) } + + override fun getMemberProfile(memberId: Long, myMemberId: Long): GetMemberProfileResponse { + return queryFactory + .select( + QGetMemberProfileResponse( + member.id, + member.nickname, + member.profileImage.prepend("/").prepend(cloudFrontHost), + blockMember.id.isNotNull + ) + ) + .from(member) + .leftJoin(blockMember) + .on( + member.id.eq(blockMember.blockedMember.id) + .and(blockMember.isActive.isTrue) + .and(blockMember.member.id.eq(myMemberId)) + ) + .where(member.id.eq(memberId)) + .fetchFirst() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt index e658ba2..e1a96fc 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt @@ -622,6 +622,10 @@ class MemberService( ) } + fun getMemberProfile(memberId: Long, myMemberId: Long): GetMemberProfileResponse { + return repository.getMemberProfile(memberId, myMemberId) + } + private fun getOrCreateLock(memberId: Long): ReentrantReadWriteLock { return tokenLocks.computeIfAbsent(memberId) { ReentrantReadWriteLock() } }