diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 2ebb10e..b531795 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -5,7 +5,7 @@ dependencies { implementation(project(":client")) implementation(project(":in-message")) implementation(project(":out-message")) - implementation(project(":client")) + implementation(project(":storage")) implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-websocket") } diff --git a/api/src/main/kotlin/gloddy/GloddyChatApplication.kt b/api/src/main/kotlin/gloddy/GloddyChatApplication.kt index 44e53cd..1c1f33d 100644 --- a/api/src/main/kotlin/gloddy/GloddyChatApplication.kt +++ b/api/src/main/kotlin/gloddy/GloddyChatApplication.kt @@ -1,4 +1,4 @@ -package gloddy.api +package gloddy import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication diff --git a/api/src/main/kotlin/gloddy/controller/GroupChatQueryController.kt b/api/src/main/kotlin/gloddy/controller/GroupChatQueryController.kt new file mode 100644 index 0000000..ad6c127 --- /dev/null +++ b/api/src/main/kotlin/gloddy/controller/GroupChatQueryController.kt @@ -0,0 +1,25 @@ +package gloddy.controller + +import gloddy.groupChat.GroupChatMessage +import gloddy.groupChat.service.GroupChatCommander +import gloddy.groupChat.service.GroupChatReader +import gloddy.util.CursorRequest +import gloddy.util.PageCursor +import org.springframework.web.bind.annotation.* + +@RestController +class GroupChatQueryController( + private val groupChatReader: GroupChatReader, + private val groupChatCommander: GroupChatCommander +) { + + @GetMapping("/group-chat-messages/{groupId}") + fun getGroupChatMessagesByCursor( + @PathVariable("groupId") groupId: Long, + userId: Long, + @RequestBody(required = false) cursorRequest: CursorRequest + ): PageCursor { + + return groupChatReader.getMessagesByCursor(groupId, userId, cursorRequest) + } +} \ No newline at end of file diff --git a/api/src/main/resources/application.yml b/api/src/main/resources/application.yml index 7526746..f561f9f 100644 --- a/api/src/main/resources/application.yml +++ b/api/src/main/resources/application.yml @@ -8,4 +8,4 @@ spring: - application-client.yml - application-aws-credentials.yml - application-in-message.yml - - application-out-message + - application-out-message.yml diff --git a/domain/src/main/kotlin/gloddy/core/ErrorCode.kt b/domain/src/main/kotlin/gloddy/core/ErrorCode.kt index c4aebe4..850b765 100644 --- a/domain/src/main/kotlin/gloddy/core/ErrorCode.kt +++ b/domain/src/main/kotlin/gloddy/core/ErrorCode.kt @@ -11,4 +11,8 @@ enum class ErrorCode( //GroupChatMessage GROUP_CHAT_MESSAGE_NOT_FOUND(404, "GROUP_CHAT_003", "그룹 채팅 메시지를 찾을 수 없습니다."), + + //GroupChatUser + GROUP_CHAT_USER_NOT_FOUND(404, "GROUP_CHAT_USER_005", "유저를 찾을 수 없습니다.") + } \ No newline at end of file diff --git a/domain/src/main/kotlin/gloddy/groupChat/GroupChatException.kt b/domain/src/main/kotlin/gloddy/groupChat/GroupChatException.kt index f59d433..f2242f3 100644 --- a/domain/src/main/kotlin/gloddy/groupChat/GroupChatException.kt +++ b/domain/src/main/kotlin/gloddy/groupChat/GroupChatException.kt @@ -19,4 +19,10 @@ class GroupChatMessageNotFoundException : GloddyChatException( statusCode = ErrorCode.GROUP_CHAT_MESSAGE_NOT_FOUND.statusCode, errorCode = ErrorCode.GROUP_CHAT_MESSAGE_NOT_FOUND.errorCode, message = ErrorCode.GROUP_CHAT_MESSAGE_NOT_FOUND.message +) + +class GroupChatUserNotFoundException : GloddyChatException( + statusCode = ErrorCode.GROUP_CHAT_USER_NOT_FOUND.statusCode, + errorCode = ErrorCode.GROUP_CHAT_USER_NOT_FOUND.errorCode, + message = ErrorCode.GROUP_CHAT_USER_NOT_FOUND.message ) \ No newline at end of file diff --git a/domain/src/main/kotlin/gloddy/groupChat/dto/command/GroupChatGetMessageCommand.kt b/domain/src/main/kotlin/gloddy/groupChat/dto/command/GroupChatGetMessageCommand.kt new file mode 100644 index 0000000..ba3cd5b --- /dev/null +++ b/domain/src/main/kotlin/gloddy/groupChat/dto/command/GroupChatGetMessageCommand.kt @@ -0,0 +1,9 @@ +package gloddy.groupChat.dto.command + +import gloddy.util.CursorRequest +import java.util.* + +data class GroupChatGetMessageCommand( + val groupId: Long, + val cursorRequest: CursorRequest +) diff --git a/domain/src/main/kotlin/gloddy/groupChat/repository/GroupChatQueryRepository.kt b/domain/src/main/kotlin/gloddy/groupChat/repository/GroupChatQueryRepository.kt new file mode 100644 index 0000000..f39256f --- /dev/null +++ b/domain/src/main/kotlin/gloddy/groupChat/repository/GroupChatQueryRepository.kt @@ -0,0 +1,12 @@ +package gloddy.groupChat.repository + +import gloddy.groupChat.GroupChatMessage +import gloddy.util.CursorRequest +import java.time.LocalDateTime + +interface GroupChatQueryRepository { + fun findWithoutKey(groupId: Long, createdAt: LocalDateTime): List + + fun findWithKey(groupId: Long, cursorRequest: CursorRequest): List + fun findUserCreatedAtById(userId: Long): LocalDateTime +} \ No newline at end of file diff --git a/domain/src/main/kotlin/gloddy/groupChat/service/GroupChatReader.kt b/domain/src/main/kotlin/gloddy/groupChat/service/GroupChatReader.kt new file mode 100644 index 0000000..4be7171 --- /dev/null +++ b/domain/src/main/kotlin/gloddy/groupChat/service/GroupChatReader.kt @@ -0,0 +1,29 @@ +package gloddy.groupChat.service + +import gloddy.groupChat.GroupChatMessage +import gloddy.groupChat.repository.GroupChatQueryRepository +import gloddy.util.CursorRequest +import gloddy.util.PageCursor +import org.springframework.stereotype.Service + +@Service +class GroupChatReader(private val groupChatQueryRepository: GroupChatQueryRepository) { + + fun getMessagesByCursor(groupId: Long, userId: Long, cursorRequest: CursorRequest): PageCursor { + var messages = findAll(groupId, userId, cursorRequest) + var nextKey = messages.stream() + .mapToLong(GroupChatMessage::sequenceId) + .min() + .orElse(CursorRequest.NONE_KEY) + return PageCursor(cursorRequest.next(nextKey), messages.size, messages) + } + + private fun findAll(groupId: Long, userId: Long, cursorRequest: CursorRequest): List { + return if (cursorRequest.hasKey()) { + groupChatQueryRepository.findWithKey(groupId, cursorRequest) + } else { + val createdAt = groupChatQueryRepository.findUserCreatedAtById(userId) + groupChatQueryRepository.findWithoutKey(groupId, createdAt) + } + } +} \ No newline at end of file diff --git a/domain/src/main/kotlin/gloddy/util/CursorRequest.kt b/domain/src/main/kotlin/gloddy/util/CursorRequest.kt new file mode 100644 index 0000000..f8f6450 --- /dev/null +++ b/domain/src/main/kotlin/gloddy/util/CursorRequest.kt @@ -0,0 +1,18 @@ +package gloddy.util + + +data class CursorRequest( + val key: Long? +) { + companion object { + const val NONE_KEY: Long = -1L + } + + fun hasKey(): Boolean { + return key != null + } + + fun next(key: Long?): CursorRequest { + return CursorRequest(key) + } +} \ No newline at end of file diff --git a/domain/src/main/kotlin/gloddy/util/PageCursor.kt b/domain/src/main/kotlin/gloddy/util/PageCursor.kt new file mode 100644 index 0000000..7a93024 --- /dev/null +++ b/domain/src/main/kotlin/gloddy/util/PageCursor.kt @@ -0,0 +1,7 @@ +package gloddy.util + +data class PageCursor( + val nextCursorRequest: CursorRequest, + val size: Int, + val body: List +) \ No newline at end of file diff --git a/storage/src/main/kotlin/gloddy/adapter/GroupChatQueryAdapterRepository.kt b/storage/src/main/kotlin/gloddy/adapter/GroupChatQueryAdapterRepository.kt new file mode 100644 index 0000000..d3ad60c --- /dev/null +++ b/storage/src/main/kotlin/gloddy/adapter/GroupChatQueryAdapterRepository.kt @@ -0,0 +1,42 @@ +package gloddy.adapter + +import gloddy.groupChat.GroupChatMessage +import gloddy.groupChat.GroupChatUserNotFoundException +import gloddy.groupChat.repository.GroupChatQueryRepository +import gloddy.persistence.repository.GroupChatMessageJpaRepository +import gloddy.persistence.repository.GroupChatUserJpaRepository +import gloddy.persistence.util.toDomain +import gloddy.util.CursorRequest +import org.springframework.stereotype.Component +import java.time.LocalDateTime + +@Component +class GroupChatQueryAdapterRepository( + private val groupChatMessageJpaRepository: GroupChatMessageJpaRepository, + private val groupChatUserJpaRepository: GroupChatUserJpaRepository, + private val groupChatCommandAdapterRepository: GroupChatCommandAdapterRepository +) : GroupChatQueryRepository { + + override fun findWithoutKey(groupId: Long, createdAt: LocalDateTime): List { + val groupChat = groupChatCommandAdapterRepository.findByGroupId(groupId) + return groupChatMessageJpaRepository.findAllByChatIdAndOrderByIdDesc( + createdAt, + groupChat.id + ).map { it.toDomain() } + } + + override fun findWithKey(groupId: Long, cursorRequest: CursorRequest): List { + val groupChat = groupChatCommandAdapterRepository.findByGroupId(groupId) + return groupChatMessageJpaRepository.findAllByLessThanIdAndChatIdAndOrderByIdDesc( + cursorRequest.key, + groupChat.id + ).map { it.toDomain() } + } + + override fun findUserCreatedAtById(userId: Long): LocalDateTime { + val found = groupChatUserJpaRepository.findByUserId(userId) + return found?.createdAt ?: throw GroupChatUserNotFoundException() + } + + +} \ No newline at end of file diff --git a/storage/src/main/kotlin/gloddy/persistence/repository/GroupChatMessageJpaRepository.kt b/storage/src/main/kotlin/gloddy/persistence/repository/GroupChatMessageJpaRepository.kt index 415279d..1b28c89 100644 --- a/storage/src/main/kotlin/gloddy/persistence/repository/GroupChatMessageJpaRepository.kt +++ b/storage/src/main/kotlin/gloddy/persistence/repository/GroupChatMessageJpaRepository.kt @@ -2,9 +2,31 @@ package gloddy.persistence.repository import gloddy.persistence.GroupChatMessageEntity import org.springframework.data.jpa.repository.JpaRepository -import java.util.UUID +import org.springframework.data.jpa.repository.Query +import java.time.LocalDateTime +import java.util.* interface GroupChatMessageJpaRepository : JpaRepository { fun findById(id: UUID): GroupChatMessageEntity? fun findFirstByOrderBySequenceIdDesc(): GroupChatMessageEntity + + @Query( + value = "SELECT m.* " + + "FROM group_chat_message AS m " + + "inner JOIN group_chat_user u " + + " ON u.user_id = m.user_id " + + "WHERE m.chat_id = :chatId and :createdAt < m.created_at and not m.type in ('SYSTEM_JOIN') " + + "ORDER BY m.sequence_id desc", + nativeQuery = true + ) + fun findAllByChatIdAndOrderByIdDesc(createdAt: LocalDateTime, chatId: UUID): List + + @Query( + value = "SELECT * " + + "FROM group_chat_message m " + + "WHERE chat_id = :chatId and sequence_id < :sequenceId and not m.type in ('SYSTEM_JOIN') " + + "ORDER BY sequence_id desc", + nativeQuery = true + ) + fun findAllByLessThanIdAndChatIdAndOrderByIdDesc(sequenceId: Long?, chatId: UUID): List } \ No newline at end of file diff --git a/storage/src/main/kotlin/gloddy/persistence/repository/GroupChatUserJpaRepository.kt b/storage/src/main/kotlin/gloddy/persistence/repository/GroupChatUserJpaRepository.kt index 5ce56d4..b3a529c 100644 --- a/storage/src/main/kotlin/gloddy/persistence/repository/GroupChatUserJpaRepository.kt +++ b/storage/src/main/kotlin/gloddy/persistence/repository/GroupChatUserJpaRepository.kt @@ -2,9 +2,10 @@ package gloddy.persistence.repository import gloddy.persistence.GroupChatUserEntity import org.springframework.data.jpa.repository.JpaRepository -import java.util.UUID +import java.util.* interface GroupChatUserJpaRepository : JpaRepository { fun findById(id: UUID): GroupChatUserEntity? fun findFirstByOrderBySequenceIdDesc(): GroupChatUserEntity + fun findByUserId(userId: Long): GroupChatUserEntity? } \ No newline at end of file