메시지 API
This commit is contained in:
parent
c25b105d4d
commit
b3d72ead1f
|
@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.live.room.cancel.CancelLiveRequest
|
import kr.co.vividnext.sodalive.live.room.cancel.CancelLiveRequest
|
||||||
import kr.co.vividnext.sodalive.live.room.donation.DeleteLiveRoomDonationMessage
|
import kr.co.vividnext.sodalive.live.room.donation.DeleteLiveRoomDonationMessage
|
||||||
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationRequest
|
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationRequest
|
||||||
|
import kr.co.vividnext.sodalive.live.room.visit.LiveRoomVisitService
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
|
@ -22,7 +23,10 @@ import org.springframework.web.multipart.MultipartFile
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/live/room")
|
@RequestMapping("/live/room")
|
||||||
class LiveRoomController(private val service: LiveRoomService) {
|
class LiveRoomController(
|
||||||
|
private val service: LiveRoomService,
|
||||||
|
private val visitService: LiveRoomVisitService
|
||||||
|
) {
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun getRoomList(
|
fun getRoomList(
|
||||||
|
@ -229,4 +233,13 @@ class LiveRoomController(private val service: LiveRoomService) {
|
||||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
ApiResponse.ok(service.quitRoom(roomId, member))
|
ApiResponse.ok(service.quitRoom(roomId, member))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/recent_visit_room/users")
|
||||||
|
fun recentVisitRoomUsers(
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
|
ApiResponse.ok(visitService.getRecentVisitRoomUsers(member.id!!))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package kr.co.vividnext.sodalive.live.room.visit
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||||
import kr.co.vividnext.sodalive.live.room.visit.QLiveRoomVisit.liveRoomVisit
|
import kr.co.vividnext.sodalive.live.room.visit.QLiveRoomVisit.liveRoomVisit
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
@ -13,6 +15,7 @@ interface LiveRoomVisitRepository : JpaRepository<LiveRoomVisit, Long>, LiveRoom
|
||||||
interface LiveRoomVisitQueryRepository {
|
interface LiveRoomVisitQueryRepository {
|
||||||
fun findByRoomIdAndMemberId(roomId: Long, memberId: Long): LiveRoomVisit?
|
fun findByRoomIdAndMemberId(roomId: Long, memberId: Long): LiveRoomVisit?
|
||||||
fun findFirstByMemberIdOrderByUpdatedAtDesc(memberId: Long): LiveRoomVisit?
|
fun findFirstByMemberIdOrderByUpdatedAtDesc(memberId: Long): LiveRoomVisit?
|
||||||
|
fun getRecentVisitRoomUsers(roomId: Long, memberId: Long): List<Member>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
|
@ -38,4 +41,17 @@ class LiveRoomVisitQueryRepositoryImpl(private val queryFactory: JPAQueryFactory
|
||||||
.orderBy(liveRoomVisit.updatedAt.desc())
|
.orderBy(liveRoomVisit.updatedAt.desc())
|
||||||
.fetchFirst()
|
.fetchFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getRecentVisitRoomUsers(roomId: Long, memberId: Long): List<Member> {
|
||||||
|
return queryFactory
|
||||||
|
.selectFrom(member)
|
||||||
|
.where(
|
||||||
|
liveRoomVisit.room.id.eq(roomId)
|
||||||
|
.and(liveRoomVisit.member.isActive.isTrue)
|
||||||
|
.and(liveRoomVisit.member.id.ne(memberId))
|
||||||
|
.and(liveRoomVisit.member.role.ne(MemberRole.ADMIN))
|
||||||
|
.and(liveRoomVisit.member.role.ne(MemberRole.AGENT))
|
||||||
|
)
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
package kr.co.vividnext.sodalive.live.room.visit
|
package kr.co.vividnext.sodalive.live.room.visit
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||||
|
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
class LiveRoomVisitService(private val repository: LiveRoomVisitRepository) {
|
class LiveRoomVisitService(
|
||||||
|
private val repository: LiveRoomVisitRepository,
|
||||||
|
|
||||||
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
|
private val cloudFrontHost: String
|
||||||
|
) {
|
||||||
@Transactional
|
@Transactional
|
||||||
fun roomVisit(room: LiveRoom, member: Member) {
|
fun roomVisit(room: LiveRoom, member: Member) {
|
||||||
var roomVisit = repository.findByRoomIdAndMemberId(room.id!!, member.id!!)
|
var roomVisit = repository.findByRoomIdAndMemberId(room.id!!, member.id!!)
|
||||||
|
@ -22,4 +29,14 @@ class LiveRoomVisitService(private val repository: LiveRoomVisitRepository) {
|
||||||
|
|
||||||
repository.save(roomVisit)
|
repository.save(roomVisit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getRecentVisitRoomUsers(memberId: Long): List<GetRoomDetailUser> {
|
||||||
|
val roomVisit = repository.findFirstByMemberIdOrderByUpdatedAtDesc(memberId)
|
||||||
|
?: return emptyList()
|
||||||
|
|
||||||
|
return repository.getRecentVisitRoomUsers(roomVisit.room!!.id!!, memberId)
|
||||||
|
.asSequence()
|
||||||
|
.map { GetRoomDetailUser(it, cloudFrontHost) }
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,4 +114,14 @@ class MemberController(private val service: MemberService) {
|
||||||
|
|
||||||
ApiResponse.ok(service.memberUnBlock(request = request, memberId = member.id!!))
|
ApiResponse.ok(service.memberUnBlock(request = request, memberId = member.id!!))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/search")
|
||||||
|
fun searchMember(
|
||||||
|
@RequestParam nickname: String,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
|
ApiResponse.ok(service.searchMember(nickname = nickname, memberId = member.id!!))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ interface MemberRepository : JpaRepository<Member, Long>, MemberQueryRepository
|
||||||
|
|
||||||
interface MemberQueryRepository {
|
interface MemberQueryRepository {
|
||||||
fun findByPushToken(pushToken: String): List<Member>
|
fun findByPushToken(pushToken: String): List<Member>
|
||||||
|
fun findByNicknameAndOtherCondition(nickname: String, memberId: Long): List<Member>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
|
@ -23,4 +24,16 @@ class MemberQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Mem
|
||||||
.where(member.pushToken.eq(pushToken))
|
.where(member.pushToken.eq(pushToken))
|
||||||
.fetch()
|
.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun findByNicknameAndOtherCondition(nickname: String, memberId: Long): List<Member> {
|
||||||
|
return queryFactory
|
||||||
|
.selectFrom(member)
|
||||||
|
.where(
|
||||||
|
member.nickname.containsIgnoreCase(nickname)
|
||||||
|
.and(member.id.ne(memberId))
|
||||||
|
.and(member.role.ne(MemberRole.ADMIN))
|
||||||
|
.and(member.role.ne(MemberRole.AGENT))
|
||||||
|
)
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.jwt.TokenProvider
|
import kr.co.vividnext.sodalive.jwt.TokenProvider
|
||||||
|
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||||
import kr.co.vividnext.sodalive.member.block.BlockMember
|
import kr.co.vividnext.sodalive.member.block.BlockMember
|
||||||
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
||||||
import kr.co.vividnext.sodalive.member.block.MemberBlockRequest
|
import kr.co.vividnext.sodalive.member.block.MemberBlockRequest
|
||||||
|
@ -334,4 +335,18 @@ class MemberService(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isBlocked(blockedMemberId: Long, memberId: Long) = blockMemberRepository.isBlocked(blockedMemberId, memberId)
|
fun isBlocked(blockedMemberId: Long, memberId: Long) = blockMemberRepository.isBlocked(blockedMemberId, memberId)
|
||||||
|
|
||||||
|
fun searchMember(nickname: String, memberId: Long): List<GetRoomDetailUser> {
|
||||||
|
if (nickname.length < 2) {
|
||||||
|
throw SodaException("두 글자 이상 입력 하셔야 합니다.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return repository.findByNicknameAndOtherCondition(nickname, memberId)
|
||||||
|
.asSequence()
|
||||||
|
.filter { blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = it.id!!) }
|
||||||
|
.map {
|
||||||
|
GetRoomDetailUser(it, cloudFrontHost)
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
package kr.co.vividnext.sodalive.message
|
||||||
|
|
||||||
|
data class GetVoiceMessageResponse(
|
||||||
|
val totalCount: Int,
|
||||||
|
val items: List<VoiceMessageItem>
|
||||||
|
) {
|
||||||
|
data class VoiceMessageItem(
|
||||||
|
val messageId: Long,
|
||||||
|
val senderId: Long,
|
||||||
|
val senderNickname: String,
|
||||||
|
val senderProfileImageUrl: String,
|
||||||
|
val recipientNickname: String,
|
||||||
|
val recipientProfileImageUrl: String,
|
||||||
|
val voiceMessageUrl: String,
|
||||||
|
val date: String,
|
||||||
|
val isKept: Boolean
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class GetTextMessageResponse(
|
||||||
|
val totalCount: Int,
|
||||||
|
val items: List<TextMessageItem>
|
||||||
|
) {
|
||||||
|
data class TextMessageItem(
|
||||||
|
val messageId: Long,
|
||||||
|
val senderId: Long,
|
||||||
|
val senderNickname: String,
|
||||||
|
val senderProfileImageUrl: String,
|
||||||
|
val recipientNickname: String,
|
||||||
|
val recipientProfileImageUrl: String,
|
||||||
|
val textMessage: String,
|
||||||
|
val date: String,
|
||||||
|
val isKept: Boolean
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package kr.co.vividnext.sodalive.message
|
||||||
|
|
||||||
|
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.EnumType
|
||||||
|
import javax.persistence.Enumerated
|
||||||
|
import javax.persistence.FetchType
|
||||||
|
import javax.persistence.JoinColumn
|
||||||
|
import javax.persistence.ManyToOne
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class Message(
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
|
val textMessage: String? = null,
|
||||||
|
var voiceMessage: String? = null,
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
val messageType: MessageType = MessageType.TEXT,
|
||||||
|
|
||||||
|
var isRecipientKeep: Boolean = false,
|
||||||
|
var isSenderDelete: Boolean = false,
|
||||||
|
var isRecipientDelete: Boolean = false
|
||||||
|
) : BaseEntity() {
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "sender_id", nullable = false)
|
||||||
|
var sender: Member? = null
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "recipient_id", nullable = false)
|
||||||
|
var recipient: Member? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class MessageType {
|
||||||
|
TEXT, VOICE
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
package kr.co.vividnext.sodalive.message
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import org.springframework.data.domain.Pageable
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping
|
||||||
|
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("/message")
|
||||||
|
class MessageController(private val service: MessageService) {
|
||||||
|
@PostMapping("/send/text")
|
||||||
|
fun sendTextMessage(
|
||||||
|
@RequestBody request: SendTextMessageRequest,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
|
ApiResponse.ok(service.sendTextMessage(request, member))
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/sent/text")
|
||||||
|
fun getSentTextMessages(
|
||||||
|
@RequestParam("timezone") timezone: String,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||||
|
pageable: Pageable
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
|
ApiResponse.ok(service.getSentTextMessages(member, pageable, timezone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/received/text")
|
||||||
|
fun getReceivedTextMessages(
|
||||||
|
@RequestParam("timezone") timezone: String,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||||
|
pageable: Pageable
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
|
ApiResponse.ok(service.getReceivedTextMessages(member, pageable, timezone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/keep/text")
|
||||||
|
fun getKeepTextMessages(
|
||||||
|
@RequestParam("timezone") timezone: String,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||||
|
pageable: Pageable
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
|
ApiResponse.ok(service.getKeepTextMessages(member, pageable, timezone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/keep/text/{id}")
|
||||||
|
fun keepTextMessage(
|
||||||
|
@PathVariable id: Long,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
ApiResponse.ok(service.keepTextMessage(id, member))
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/send/voice")
|
||||||
|
fun sendVoiceMessage(
|
||||||
|
@RequestPart("voiceMessageFile") voiceMessageFile: MultipartFile,
|
||||||
|
@RequestPart("request") requestString: String,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
|
ApiResponse.ok(service.sendVoiceMessage(voiceMessageFile, requestString, member))
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/sent/voice")
|
||||||
|
fun getSentVoiceMessages(
|
||||||
|
@RequestParam("timezone") timezone: String,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||||
|
pageable: Pageable
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
ApiResponse.ok(service.getSentVoiceMessages(member, pageable, timezone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/received/voice")
|
||||||
|
fun getReceivedVoiceMessages(
|
||||||
|
@RequestParam("timezone") timezone: String,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||||
|
pageable: Pageable
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
|
ApiResponse.ok(service.getReceivedVoiceMessages(member, pageable, timezone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/keep/voice")
|
||||||
|
fun getKeepVoiceMessages(
|
||||||
|
@RequestParam("timezone") timezone: String,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||||
|
pageable: Pageable
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
|
ApiResponse.ok(service.getKeepVoiceMessages(member, pageable, timezone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/keep/voice/{id}")
|
||||||
|
fun keepVoiceMessage(
|
||||||
|
@PathVariable id: Long,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
ApiResponse.ok(service.keepVoiceMessage(id, member))
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{messageId}")
|
||||||
|
fun deleteMessage(
|
||||||
|
@PathVariable messageId: Long,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
|
ApiResponse.ok(service.deleteMessage(messageId, member))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
package kr.co.vividnext.sodalive.message
|
||||||
|
|
||||||
|
import com.querydsl.core.types.dsl.BooleanExpression
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.message.QMessage.message
|
||||||
|
import org.springframework.data.domain.Pageable
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
interface MessageRepository : JpaRepository<Message, Long>, MessageQueryRepository
|
||||||
|
|
||||||
|
interface MessageQueryRepository {
|
||||||
|
fun getSentTextMessageCount(memberId: Long): Int
|
||||||
|
fun getSentTextMessageList(pageable: Pageable, memberId: Long): List<Message>
|
||||||
|
fun getReceivedTextMessageCount(memberId: Long): Int
|
||||||
|
fun getReceivedTextMessageList(pageable: Pageable, memberId: Long): List<Message>
|
||||||
|
fun getKeepTextMessageCount(memberId: Long): Int
|
||||||
|
fun getKeepTextMessageList(pageable: Pageable, memberId: Long): List<Message>
|
||||||
|
fun getSentVoiceMessageCount(memberId: Long): Int
|
||||||
|
fun getSentVoiceMessageList(pageable: Pageable, memberId: Long): List<Message>
|
||||||
|
fun getReceivedVoiceMessageCount(memberId: Long): Int
|
||||||
|
fun getReceivedVoiceMessageList(pageable: Pageable, memberId: Long): List<Message>
|
||||||
|
fun getKeepVoiceMessageCount(memberId: Long): Int
|
||||||
|
fun getKeepVoiceMessageList(pageable: Pageable, memberId: Long): List<Message>
|
||||||
|
}
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class MessageQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : MessageQueryRepository {
|
||||||
|
override fun getSentTextMessageCount(memberId: Long): Int {
|
||||||
|
val where = message.messageType.eq(MessageType.TEXT)
|
||||||
|
.and(message.isSenderDelete.isFalse)
|
||||||
|
.and(message.sender.id.eq(memberId))
|
||||||
|
|
||||||
|
return totalCount(where)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSentTextMessageList(pageable: Pageable, memberId: Long): List<Message> {
|
||||||
|
val where = message.messageType.eq(MessageType.TEXT)
|
||||||
|
.and(message.isSenderDelete.isFalse)
|
||||||
|
.and(message.sender.id.eq(memberId))
|
||||||
|
|
||||||
|
return messageList(pageable, where)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getReceivedTextMessageCount(memberId: Long): Int {
|
||||||
|
val where = message.messageType.eq(MessageType.TEXT)
|
||||||
|
.and(message.isRecipientDelete.isFalse)
|
||||||
|
.and(message.recipient.id.eq(memberId))
|
||||||
|
|
||||||
|
return totalCount(where)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getReceivedTextMessageList(pageable: Pageable, memberId: Long): List<Message> {
|
||||||
|
val where = message.messageType.eq(MessageType.TEXT)
|
||||||
|
.and(message.isRecipientDelete.isFalse)
|
||||||
|
.and(message.recipient.id.eq(memberId))
|
||||||
|
|
||||||
|
return messageList(pageable, where)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getKeepTextMessageCount(memberId: Long): Int {
|
||||||
|
val where = message.messageType.eq(MessageType.TEXT)
|
||||||
|
.and(message.isRecipientDelete.isFalse)
|
||||||
|
.and(message.isRecipientKeep.isTrue)
|
||||||
|
.and(message.recipient.id.eq(memberId))
|
||||||
|
|
||||||
|
return totalCount(where)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getKeepTextMessageList(pageable: Pageable, memberId: Long): List<Message> {
|
||||||
|
val where = message.messageType.eq(MessageType.TEXT)
|
||||||
|
.and(message.isRecipientDelete.isFalse)
|
||||||
|
.and(message.isRecipientKeep.isTrue)
|
||||||
|
.and(message.recipient.id.eq(memberId))
|
||||||
|
|
||||||
|
return messageList(pageable, where)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSentVoiceMessageCount(memberId: Long): Int {
|
||||||
|
val where = message.messageType.eq(MessageType.VOICE)
|
||||||
|
.and(message.isSenderDelete.isFalse)
|
||||||
|
.and(message.sender.id.eq(memberId))
|
||||||
|
|
||||||
|
return totalCount(where)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSentVoiceMessageList(pageable: Pageable, memberId: Long): List<Message> {
|
||||||
|
val where = message.messageType.eq(MessageType.VOICE)
|
||||||
|
.and(message.isSenderDelete.isFalse)
|
||||||
|
.and(message.sender.id.eq(memberId))
|
||||||
|
|
||||||
|
return messageList(pageable, where)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getReceivedVoiceMessageCount(memberId: Long): Int {
|
||||||
|
val where = message.messageType.eq(MessageType.VOICE)
|
||||||
|
.and(message.isRecipientDelete.isFalse)
|
||||||
|
.and(message.recipient.id.eq(memberId))
|
||||||
|
|
||||||
|
return totalCount(where)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getReceivedVoiceMessageList(pageable: Pageable, memberId: Long): List<Message> {
|
||||||
|
val where = message.messageType.eq(MessageType.VOICE)
|
||||||
|
.and(message.isRecipientDelete.isFalse)
|
||||||
|
.and(message.recipient.id.eq(memberId))
|
||||||
|
|
||||||
|
return messageList(pageable, where)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getKeepVoiceMessageCount(memberId: Long): Int {
|
||||||
|
val where = message.messageType.eq(MessageType.VOICE)
|
||||||
|
.and(message.isRecipientDelete.isFalse)
|
||||||
|
.and(message.isRecipientKeep.isTrue)
|
||||||
|
.and(message.recipient.id.eq(memberId))
|
||||||
|
|
||||||
|
return totalCount(where)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getKeepVoiceMessageList(pageable: Pageable, memberId: Long): List<Message> {
|
||||||
|
val where = message.messageType.eq(MessageType.VOICE)
|
||||||
|
.and(message.isRecipientDelete.isFalse)
|
||||||
|
.and(message.isRecipientKeep.isTrue)
|
||||||
|
.and(message.recipient.id.eq(memberId))
|
||||||
|
|
||||||
|
return messageList(pageable, where)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun messageList(pageable: Pageable, where: BooleanExpression): List<Message> {
|
||||||
|
return queryFactory.selectFrom(message)
|
||||||
|
.offset(pageable.offset)
|
||||||
|
.limit(pageable.pageSize.toLong())
|
||||||
|
.where(where)
|
||||||
|
.orderBy(message.id.desc())
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun totalCount(where: BooleanExpression): Int {
|
||||||
|
return queryFactory.select(message.id)
|
||||||
|
.from(message)
|
||||||
|
.where(where)
|
||||||
|
.fetch()
|
||||||
|
.size
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,240 @@
|
||||||
|
package kr.co.vividnext.sodalive.message
|
||||||
|
|
||||||
|
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.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
|
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
||||||
|
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.data.domain.Pageable
|
||||||
|
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.ZoneId
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
class MessageService(
|
||||||
|
private val repository: MessageRepository,
|
||||||
|
private val memberRepository: MemberRepository,
|
||||||
|
private val blockMemberRepository: BlockMemberRepository,
|
||||||
|
|
||||||
|
private val objectMapper: ObjectMapper,
|
||||||
|
private val s3Uploader: S3Uploader,
|
||||||
|
|
||||||
|
@Value("\${cloud.aws.s3.bucket}")
|
||||||
|
private val bucket: String,
|
||||||
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
|
private val cloudFrontHost: String
|
||||||
|
) {
|
||||||
|
@Transactional
|
||||||
|
fun sendTextMessage(request: SendTextMessageRequest, member: Member) {
|
||||||
|
val recipient = memberRepository.findByIdOrNull(request.recipientId)
|
||||||
|
?: throw SodaException("받는 사람이 없습니다.")
|
||||||
|
|
||||||
|
if (!recipient.isActive) {
|
||||||
|
throw SodaException("탈퇴한 유저에게는 메시지를 보내실 수 없습니다.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = request.recipientId)
|
||||||
|
if (isBlocked) throw SodaException("${recipient.nickname}님의 요청으로 메시지를 보낼 수 없습니다.")
|
||||||
|
|
||||||
|
val sender = memberRepository.findByIdOrNull(member.id!!)
|
||||||
|
?: throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
|
val message = Message(
|
||||||
|
textMessage = request.textMessage,
|
||||||
|
messageType = MessageType.TEXT
|
||||||
|
)
|
||||||
|
|
||||||
|
message.sender = sender
|
||||||
|
message.recipient = recipient
|
||||||
|
|
||||||
|
repository.save(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSentTextMessages(member: Member, pageable: Pageable, timezone: String): GetTextMessageResponse {
|
||||||
|
val totalCount = repository.getSentTextMessageCount(memberId = member.id!!)
|
||||||
|
val messageList = repository.getSentTextMessageList(pageable, memberId = member.id!!)
|
||||||
|
return getTextMessageResponse(totalCount, messageList, timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getReceivedTextMessages(member: Member, pageable: Pageable, timezone: String): GetTextMessageResponse {
|
||||||
|
val totalCount = repository.getReceivedTextMessageCount(memberId = member.id!!)
|
||||||
|
val messageList = repository.getReceivedTextMessageList(pageable, memberId = member.id!!)
|
||||||
|
return getTextMessageResponse(totalCount, messageList, timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getKeepTextMessages(member: Member, pageable: Pageable, timezone: String): GetTextMessageResponse {
|
||||||
|
val totalCount = repository.getKeepTextMessageCount(memberId = member.id!!)
|
||||||
|
val messageList = repository.getKeepTextMessageList(pageable, memberId = member.id!!)
|
||||||
|
return getTextMessageResponse(totalCount, messageList, timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun keepTextMessage(messageId: Long, member: Member) {
|
||||||
|
keepMessage(messageId, member)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun sendVoiceMessage(voiceMessageFile: MultipartFile, requestString: String, member: Member) {
|
||||||
|
val request = objectMapper.readValue(requestString, SendVoiceMessageRequest::class.java)
|
||||||
|
|
||||||
|
val recipient = memberRepository.findByIdOrNull(request.recipientId)
|
||||||
|
?: throw SodaException("받는 사람이 없습니다.")
|
||||||
|
|
||||||
|
if (!recipient.isActive) {
|
||||||
|
throw SodaException("탈퇴한 유저에게는 메시지를 보내실 수 없습니다.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = request.recipientId)
|
||||||
|
if (isBlocked) throw SodaException("${recipient.nickname}님의 요청으로 메시지를 보낼 수 없습니다.")
|
||||||
|
|
||||||
|
val sender = memberRepository.findByIdOrNull(member.id!!)
|
||||||
|
?: throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
|
val message = Message(messageType = MessageType.VOICE)
|
||||||
|
message.sender = sender
|
||||||
|
message.recipient = recipient
|
||||||
|
|
||||||
|
repository.save(message)
|
||||||
|
|
||||||
|
val metadata = ObjectMetadata()
|
||||||
|
metadata.contentLength = voiceMessageFile.size
|
||||||
|
|
||||||
|
val messagePath = s3Uploader.upload(
|
||||||
|
inputStream = voiceMessageFile.inputStream,
|
||||||
|
bucket = bucket,
|
||||||
|
filePath = "voice_message/${message.id}/${generateFileName(prefix = "${message.id}-message-")}",
|
||||||
|
metadata = metadata
|
||||||
|
)
|
||||||
|
|
||||||
|
message.voiceMessage = messagePath
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSentVoiceMessages(member: Member, pageable: Pageable, timezone: String): GetVoiceMessageResponse {
|
||||||
|
val totalCount = repository.getSentVoiceMessageCount(memberId = member.id!!)
|
||||||
|
val messageList = repository.getSentVoiceMessageList(pageable, memberId = member.id!!)
|
||||||
|
|
||||||
|
return getVoiceMessageResponse(totalCount, messageList, timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getReceivedVoiceMessages(member: Member, pageable: Pageable, timezone: String): GetTextMessageResponse {
|
||||||
|
val totalCount = repository.getReceivedVoiceMessageCount(memberId = member.id!!)
|
||||||
|
val messageList = repository.getReceivedVoiceMessageList(pageable, memberId = member.id!!)
|
||||||
|
return getTextMessageResponse(totalCount, messageList, timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getKeepVoiceMessages(member: Member, pageable: Pageable, timezone: String): GetTextMessageResponse {
|
||||||
|
val totalCount = repository.getKeepVoiceMessageCount(memberId = member.id!!)
|
||||||
|
val messageList = repository.getKeepVoiceMessageList(pageable, memberId = member.id!!)
|
||||||
|
return getTextMessageResponse(totalCount, messageList, timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun keepVoiceMessage(messageId: Long, member: Member) {
|
||||||
|
keepMessage(messageId, member)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun deleteMessage(messageId: Long, member: Member) {
|
||||||
|
val message = repository.findByIdOrNull(messageId)
|
||||||
|
?: throw SodaException("해당하는 메시지가 없습니다.\n다시 확인해 주시기 바랍니다.")
|
||||||
|
|
||||||
|
if (message.sender!!.id!! == member.id!!) {
|
||||||
|
message.isSenderDelete = true
|
||||||
|
} else if (message.recipient!!.id!! == member.id!!) {
|
||||||
|
message.isRecipientDelete = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getTextMessageResponse(
|
||||||
|
totalCount: Int,
|
||||||
|
messageList: List<Message>,
|
||||||
|
timezone: String
|
||||||
|
) = GetTextMessageResponse(
|
||||||
|
totalCount = totalCount,
|
||||||
|
items = messageList.asSequence()
|
||||||
|
.map {
|
||||||
|
val createdAt = it.createdAt!!
|
||||||
|
.atZone(ZoneId.of("UTC"))
|
||||||
|
.withZoneSameInstant(ZoneId.of(timezone))
|
||||||
|
|
||||||
|
GetTextMessageResponse.TextMessageItem(
|
||||||
|
messageId = it.id!!,
|
||||||
|
senderId = it.sender!!.id!!,
|
||||||
|
senderNickname = it.sender!!.nickname,
|
||||||
|
senderProfileImageUrl = if (it.sender!!.profileImage != null) {
|
||||||
|
"$cloudFrontHost/${it.sender!!.profileImage}"
|
||||||
|
} else {
|
||||||
|
"$cloudFrontHost/profile/default-profile.png"
|
||||||
|
},
|
||||||
|
recipientNickname = it.recipient!!.nickname,
|
||||||
|
recipientProfileImageUrl = if (it.recipient!!.profileImage != null) {
|
||||||
|
"$cloudFrontHost/${it.recipient!!.profileImage}"
|
||||||
|
} else {
|
||||||
|
"$cloudFrontHost/profile/default-profile.png"
|
||||||
|
},
|
||||||
|
textMessage = it.textMessage!!,
|
||||||
|
date = createdAt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
|
||||||
|
isKept = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun getVoiceMessageResponse(
|
||||||
|
totalCount: Int,
|
||||||
|
messageList: List<Message>,
|
||||||
|
timezone: String
|
||||||
|
) = GetVoiceMessageResponse(
|
||||||
|
totalCount = totalCount,
|
||||||
|
items = messageList.asSequence()
|
||||||
|
.map {
|
||||||
|
val createdAt = it.createdAt!!
|
||||||
|
.atZone(ZoneId.of("UTC"))
|
||||||
|
.withZoneSameInstant(ZoneId.of(timezone))
|
||||||
|
|
||||||
|
GetVoiceMessageResponse.VoiceMessageItem(
|
||||||
|
messageId = it.id!!,
|
||||||
|
senderId = it.sender!!.id!!,
|
||||||
|
senderNickname = it.sender!!.nickname,
|
||||||
|
senderProfileImageUrl = if (it.sender!!.profileImage != null) {
|
||||||
|
"$cloudFrontHost/${it.sender!!.profileImage}"
|
||||||
|
} else {
|
||||||
|
"$cloudFrontHost/profile/default-profile.png"
|
||||||
|
},
|
||||||
|
recipientNickname = it.recipient!!.nickname,
|
||||||
|
recipientProfileImageUrl = if (it.recipient!!.profileImage != null) {
|
||||||
|
"$cloudFrontHost/${it.recipient!!.profileImage}"
|
||||||
|
} else {
|
||||||
|
"$cloudFrontHost/profile/default-profile.png"
|
||||||
|
},
|
||||||
|
voiceMessageUrl = "$cloudFrontHost/${it.voiceMessage!!}",
|
||||||
|
date = createdAt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
|
||||||
|
isKept = it.isRecipientKeep
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun keepMessage(messageId: Long, member: Member) {
|
||||||
|
val message = repository.findByIdOrNull(messageId)
|
||||||
|
?: throw SodaException("잘못된 요청입니다.")
|
||||||
|
|
||||||
|
if (message.recipient != member) {
|
||||||
|
throw SodaException("잘못된 요청입니다.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.isRecipientKeep) {
|
||||||
|
throw SodaException("이미 보관된 메시지 입니다.")
|
||||||
|
}
|
||||||
|
|
||||||
|
message.isRecipientKeep = true
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package kr.co.vividnext.sodalive.message
|
||||||
|
|
||||||
|
data class SendVoiceMessageRequest(val recipientId: Long, val container: String)
|
||||||
|
|
||||||
|
data class SendTextMessageRequest(val recipientId: Long, val textMessage: String, val container: String)
|
Loading…
Reference in New Issue