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.fcm.FcmEvent import kr.co.vividnext.sodalive.fcm.FcmEventType 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.context.ApplicationEventPublisher 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 applicationEventPublisher: ApplicationEventPublisher, 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) applicationEventPublisher.publishEvent( FcmEvent( type = FcmEventType.SEND_MESSAGE, title = "메시지", message = "${sender.nickname}님으로 부터 문자메시지가 도착했습니다.", messageId = message.id ) ) } 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 applicationEventPublisher.publishEvent( FcmEvent( type = FcmEventType.SEND_MESSAGE, title = "메시지", message = "${sender.nickname}님으로 부터 음성메시지가 도착했습니다.", messageId = message.id ) ) } 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): GetVoiceMessageResponse { val totalCount = repository.getReceivedVoiceMessageCount(memberId = member.id!!) val messageList = repository.getReceivedVoiceMessageList(pageable, memberId = member.id!!) return getVoiceMessageResponse(totalCount, messageList, timezone) } fun getKeepVoiceMessages(member: Member, pageable: Pageable, timezone: String): GetVoiceMessageResponse { val totalCount = repository.getKeepVoiceMessageCount(memberId = member.id!!) val messageList = repository.getKeepVoiceMessageList(pageable, memberId = member.id!!) return getVoiceMessageResponse(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, 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, 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 } }