diff --git a/bootstrap/api/src/main/kotlin/com/threedays/bootstrap/api/chat/adapter/rest/ChatRestController.kt b/bootstrap/api/src/main/kotlin/com/threedays/bootstrap/api/chat/adapter/rest/ChatRestController.kt index b885c62..ea92e77 100644 --- a/bootstrap/api/src/main/kotlin/com/threedays/bootstrap/api/chat/adapter/rest/ChatRestController.kt +++ b/bootstrap/api/src/main/kotlin/com/threedays/bootstrap/api/chat/adapter/rest/ChatRestController.kt @@ -1,6 +1,61 @@ package com.threedays.bootstrap.api.chat.adapter.rest +import com.threedays.domain.chat.repository.MessageQueryRepository +import com.threedays.oas.api.ChatApi +import com.threedays.oas.model.GetChannelMessagesResponse +import com.threedays.oas.model.Message +import com.threedays.oas.model.MessageContent +import com.threedays.support.common.base.domain.UUIDTypeId +import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.RestController +import java.time.ZoneOffset +import java.util.* @RestController -class ChatRestController +class ChatRestController( + private val messageQueryRepository: MessageQueryRepository +) : ChatApi { + + override fun getChannelMessages( + channelId: UUID, + next: UUID?, + limit: Int + ): ResponseEntity { + val (resultMessages, resultNext) = messageQueryRepository.scrollByChannelId( + channelId = UUIDTypeId.from(channelId), + next = next?.let { UUIDTypeId.from(it) }, + limit = limit + ) + + return ResponseEntity.ok(createMessageResponse(resultMessages, resultNext)) + } + + private fun createMessageResponse( + messages: List, + nextID: com.threedays.domain.chat.entity.Message.Id?, + ): GetChannelMessagesResponse { + val messageResponse = messages.map { + Message( + id = it.id.value, + channelId = it.channelId.value, + senderUserId = it.senderUserId.value, + + content = MessageContent( + text = when (it.content) { + is com.threedays.domain.chat.entity.Message.Content.Text -> (it.content as com.threedays.domain.chat.entity.Message.Content.Text).text + is com.threedays.domain.chat.entity.Message.Content.Card -> (it.content as com.threedays.domain.chat.entity.Message.Content.Card).text + }, + cardColor = when (it.content) { + is com.threedays.domain.chat.entity.Message.Content.Card -> (it.content as com.threedays.domain.chat.entity.Message.Content.Card).color.name + else -> null + } + ), + createdAt = it.createdAt.atOffset(ZoneOffset.UTC) + ) + } + return GetChannelMessagesResponse( + messages = messageResponse, + next = nextID?.value + ) + } +} diff --git a/domain/src/main/kotlin/com/threedays/domain/chat/repository/MessageQueryRepository.kt b/domain/src/main/kotlin/com/threedays/domain/chat/repository/MessageQueryRepository.kt new file mode 100644 index 0000000..3678d70 --- /dev/null +++ b/domain/src/main/kotlin/com/threedays/domain/chat/repository/MessageQueryRepository.kt @@ -0,0 +1,15 @@ +package com.threedays.domain.chat.repository + +import com.threedays.domain.chat.entity.Channel +import com.threedays.domain.chat.entity.Message +import com.threedays.support.common.base.domain.QueryRepository + +interface MessageQueryRepository : QueryRepository { + + fun scrollByChannelId( + channelId: Channel.Id, + next: Message.Id?, + limit: Int, + ): Pair, Message.Id?> + +} diff --git a/infrastructure/persistence/src/main/kotlin/com/threedays/persistence/chat/adapter/MessageQueryPersistenceAdapter.kt b/infrastructure/persistence/src/main/kotlin/com/threedays/persistence/chat/adapter/MessageQueryPersistenceAdapter.kt new file mode 100644 index 0000000..972704e --- /dev/null +++ b/infrastructure/persistence/src/main/kotlin/com/threedays/persistence/chat/adapter/MessageQueryPersistenceAdapter.kt @@ -0,0 +1,65 @@ +package com.threedays.persistence.chat.adapter + +import com.linecorp.kotlinjdsl.dsl.jpql.jpql +import com.linecorp.kotlinjdsl.querymodel.jpql.select.SelectQuery +import com.linecorp.kotlinjdsl.render.RenderContext +import com.linecorp.kotlinjdsl.support.spring.data.jpa.extension.createQuery +import com.threedays.domain.chat.entity.Channel +import com.threedays.domain.chat.entity.Message +import com.threedays.domain.chat.repository.MessageQueryRepository +import com.threedays.persistence.chat.entity.MessageJpaEntity +import com.threedays.persistence.chat.repository.MessageJpaRepository +import jakarta.persistence.EntityManager +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Transactional + +@Component +@Transactional(readOnly = true) +class MessageQueryPersistenceAdapter( + private val entityManager: EntityManager, + private val jdslRenderContext: RenderContext, + private val messageJpaRepository: MessageJpaRepository, +) : MessageQueryRepository { + + override fun find(id: Message.Id): Message? { + return messageJpaRepository + .findById(id.value) + .map { it.toDomain() } + .orElse(null) + } + + override fun scrollByChannelId( + channelId: Channel.Id, + next: Message.Id?, + limit: Int + ): Pair, Message.Id?> { + val query: SelectQuery = jpql { + select( + entity(MessageJpaEntity::class) + ).from( + entity(MessageJpaEntity::class) + ).whereAnd( + path(MessageJpaEntity::channelId).eq(channelId.value), + next?.let { path(MessageJpaEntity::id).greaterThanOrEqualTo(it.value) } + ).orderBy( + path(MessageJpaEntity::id).desc() + ) + } + + val result: List = entityManager + .createQuery(query, jdslRenderContext) + .apply { maxResults = limit + 1 } + .resultList + .map { it.toDomain() } + + + val hasNextPage: Boolean = result.size > limit + val nextId: Message.Id? = if (hasNextPage) { + result[limit].id + } else { + null + } + + return result.take(limit) to nextId + } +} diff --git a/infrastructure/persistence/src/main/kotlin/com/threedays/persistence/user/adapter/CompanyQueryPersistenceAdapter.kt b/infrastructure/persistence/src/main/kotlin/com/threedays/persistence/user/adapter/CompanyQueryPersistenceAdapter.kt index 114bba3..7aa5f24 100644 --- a/infrastructure/persistence/src/main/kotlin/com/threedays/persistence/user/adapter/CompanyQueryPersistenceAdapter.kt +++ b/infrastructure/persistence/src/main/kotlin/com/threedays/persistence/user/adapter/CompanyQueryPersistenceAdapter.kt @@ -38,7 +38,7 @@ class CompanyQueryPersistenceAdapter( val result: List = entityManager .createQuery(query, jdslRenderContext) - .apply { setMaxResults(limit + 1) } + .apply { maxResults = limit + 1 } .resultList .map { it.toDomainEntity() } diff --git a/openapi b/openapi index 63f48f1..0b9174c 160000 --- a/openapi +++ b/openapi @@ -1 +1 @@ -Subproject commit 63f48f14fe38c0e1774564d3c4aae0cb738ca98f +Subproject commit 0b9174c7fee8e47c36b23d897e5899b744c0c575