This commit is contained in:
2026-07-29 16:21:59 +03:00
parent b83a40f4bd
commit 6485f4ec26
41 changed files with 797 additions and 224 deletions
@@ -2,33 +2,27 @@ package ru.shadowsparky.vbox.backend.data.chat
import org.koin.core.annotation.Factory
import ru.shadowsparky.chat.backend.domain.MessageStorage
import ru.shadowsparky.chat.backend.domain.ProcessUserMessageUseCase
import ru.shadowsparky.chat.domain.ChatRepository
import ru.shadowsparky.chat.domain.Message
@Factory
class BackendChatRepositoryFactory(
private val storage: MessageStorage,
private val processUserMessageUseCase: ProcessUserMessageUseCase
) {
class BackendChatRepositoryFactory(private val storage: MessageStorage) {
fun create(userId: Long): BackendChatRepository {
return BackendChatRepository(userId, storage, processUserMessageUseCase)
return BackendChatRepository(userId, storage)
}
}
class BackendChatRepository(
private val userId: Long,
private val storage: MessageStorage,
private val processUserMessageUseCase: ProcessUserMessageUseCase
private val storage: MessageStorage
) : ChatRepository {
override suspend fun query(): List<Message> {
val history = storage.query(userId, limit = 10)
override suspend fun query(afterId: Long?): List<Message> {
val history = storage.query(userId, afterId, limit = 10)
return history.sortedBy { it.timestamp }
}
override suspend fun send(message: Message) {
storage.insert(userId, message)
processUserMessageUseCase.execute(userId)
}
}
@@ -14,9 +14,13 @@ class SqlMessageStorage(
) : MessageStorage {
private val queries = appDatabase.chatQueries
override suspend fun query(userId: Long, limit: Int): List<Message> = withContext(dispatcherProvider.io) {
override suspend fun query(userId: Long, afterId: Long?, limit: Int): List<Message> = withContext(dispatcherProvider.io) {
val chatId = queries.getChatIdByUserId(userId).executeAsOneOrNull() ?: return@withContext emptyList()
val dbMessages = queries.getChatContext(chatId = chatId, limit = limit.toLong()).executeAsList()
val dbMessages = queries.getChatContext(
chatId = chatId,
afterId = afterId,
limit = limit.toLong()
).executeAsList()
dbMessages.map { dbMsg ->
Message(
id = dbMsg.id,
@@ -5,10 +5,10 @@ import org.koin.core.annotation.Single
import ru.shadowsparky.backend.data.TokenVerifier
import ru.shadowsparky.backend.domain.JwtInfo
import ru.shadowsparky.backend.domain.LoginVerifier
import ru.shadowsparky.chat.backend.domain.ProcessUserMessageUseCase
import ru.shadowsparky.vbox.backend.data.BackendRemoteEventHandler
import ru.shadowsparky.vbox.backend.data.BackendUpdateFetcherFactory
import ru.shadowsparky.vbox.backend.data.auth.AuthTokenRepositoryFactory
import ru.shadowsparky.vbox.backend.data.chat.BackendChatRepository
import ru.shadowsparky.vbox.backend.data.chat.BackendChatRepositoryFactory
import ru.shadowsparky.vbox.backend.data.tags.MovieTagRepositoryFactory
import ru.shadowsparky.vbox.backend.data.tags.UserTagRepositoryFactory
@@ -27,7 +27,8 @@ class RoutingEntryPoint(
val movieTagFactory: MovieTagRepositoryFactory,
val userTagFactory: UserTagRepositoryFactory,
val updateFetcherFactory: BackendUpdateFetcherFactory,
val chatRepositoryFactory: BackendChatRepositoryFactory
val chatRepositoryFactory: BackendChatRepositoryFactory,
val processUserMessageUseCase: ProcessUserMessageUseCase
)
@Single
@@ -45,6 +45,6 @@ fun Routing.setupAuthMethods(
setupSavedMovie(savedMovieFactory)
setupTagsRouting(userTagFactory, movieTagFactory)
setupUpdates(updateFetcherFactory)
setupChat(chatRepositoryFactory)
setupChat(chatRepositoryFactory, processUserMessageUseCase)
}
}
@@ -1,23 +1,35 @@
package ru.shadowsparky.vbox.backend.presentation.routing
import io.ktor.http.HttpStatusCode
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import ru.shadowsparky.chat.backend.domain.ProcessUserMessageUseCase
import ru.shadowsparky.chat.domain.ChatRepository
import ru.shadowsparky.chat.domain.ChatRoles
import ru.shadowsparky.chat.domain.Message
import ru.shadowsparky.chat.domain.MessagesResponse
import ru.shadowsparky.http.domain.BadRequestException
import ru.shadowsparky.vbox.backend.data.chat.BackendChatRepositoryFactory
import ru.shadowsparky.vbox.backend.presentation.obtainUserId
import ru.shadowsparky.vbox.shared.domain.DYNAMIC_PREFIX
fun Route.setupChat(chatRepositoryFactory: BackendChatRepositoryFactory) {
fun Route.setupChat(
chatRepositoryFactory: BackendChatRepositoryFactory,
processUserMessageUseCase: ProcessUserMessageUseCase
) {
get("$DYNAMIC_PREFIX/chat/query") {
val repository = chatRepositoryFactory.create(call.obtainUserId())
call.respond(repository.query())
val afterId = call.queryParameters[ChatRepository.ARG_AFTER_ID]?.toLong()
call.respond(MessagesResponse(repository.query(afterId)))
}
post("$DYNAMIC_PREFIX/chat/send") {
val repository = chatRepositoryFactory.create(call.obtainUserId())
repository.send(call.receive())
call.respond(HttpStatusCode.OK)
val userId = call.obtainUserId()
val info = call.receive<Message>()
if (info.role != ChatRoles.USER) throw BadRequestException("invalid role")
val repository = chatRepositoryFactory.create(userId)
repository.send(info)
call.respond(processUserMessageUseCase.execute(userId))
}
}
@@ -18,5 +18,6 @@ getChatContext:
SELECT id, role, content, created_at
FROM messages
WHERE chat_id = :chatId
ORDER BY created_at DESC
AND (:afterId IS NULL OR id > :afterId)
ORDER BY id ASC
LIMIT :limit;