Skip to content
This repository has been archived by the owner on Jul 6, 2024. It is now read-only.

Commit

Permalink
Use polymorphic serialization to make possible send different events …
Browse files Browse the repository at this point in the history
…through websocket
  • Loading branch information
mklkj committed Feb 10, 2024
1 parent e1c9659 commit 5748f89
Show file tree
Hide file tree
Showing 25 changed files with 199 additions and 197 deletions.
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,28 @@ odbierać wiadomości niż potrzeba. Uświadomiłem to sobie dopiero jak trafił
https://youtrack.jetbrains.com/issue/KTOR-4452. Ostatecznie zrobiłem swoją metodę pomocniczą,
działającą podobnie do `receiveDeserialized()` z pominięciem wywołania `receive()` w środku.

## Aktualizacja bibliotek, więcej websocketów (2024-02-08/10)

Na początek aktualizacja bibliotek, żeby mieć najnowsze compose multiplatform. Jeszcze wszystko nie
ma stabilnej wersji, ale to nic. Wziąłem wersję dev, żeby była jak najbliższa kolejnemu wydaniu:
https://github.com/JetBrains/compose-multiplatform/releases/tag/v1.6.0-dev1409. Do tego wersja
beta material3, bo coś zepsuli i z compose BOM nie idzie odpowiednia
https://github.com/JetBrains/compose-multiplatform/issues/4157 i nawet działa.

Idąc dalej — do realizacji ostatniego punktu wymagań potrzebuję jeszcze statusów o pisaniu
i aktywności. Żeby przesyłać statusy o tym, że ktoś coś pisze najlepiej wykorzystać websockety.
Nie chcemy jednak tworzyć miliona osobnych połączeń, a maksymalnie jedno per czat (tak jest teraz).
Po tym jednym połączeniu chcemy przesyłać wiadomości. Żeby sobie _ułatwić_ pracę, zastosujemy
polimiroficzną serializację z kotlinx.serialization
https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#sealed-classes.
I tutaj dałem się złapać na jedną niby oczywistą rzecz, ale w natłoku całej reszty kodu jej nie
zauważyłem. Ciągle nie chciał mi się dodawać oryginalny typ do zserializowanych jsonów, przez co
server nie mógł rozpoznać typu ramki. Okazało się, że chodzi o brak jasno zadeklarowanego typu
przy wywołaniu serializera, tj. np. `Json.encodeToString()` zserializuje obiekt tak, jak go widzi.
Żeby wziąć pod uwagę rodzica i dodać pole z `class discriminatorem` trzeba powiedzieć, o jaki typ
nadrzędny nam chodzi, więc powinniśmy zapisać `Json.encodeToString<Typ>()` i tego mi właśnie
na początku zabrakło.

## Materiały

- biblioteki KMM 1 - https://github.com/terrakok/kmm-awesome
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import io.github.mklkj.kommunicator.data.models.Chat
import io.github.mklkj.kommunicator.data.models.ChatCreateRequest
import io.github.mklkj.kommunicator.data.models.ChatCreateResponse
import io.github.mklkj.kommunicator.data.models.Message
import io.github.mklkj.kommunicator.data.models.MessageRequest
import io.github.mklkj.kommunicator.data.models.MessageEvent
import kotlinx.uuid.UUID

interface MessagesService {
Expand All @@ -28,6 +28,6 @@ interface MessagesService {
@POST("/api/chats/{chatId}/messages")
suspend fun sendMessage(
@Path("chatId") chatId: UUID,
@Body message: MessageRequest,
@Body message: MessageEvent,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import io.github.mklkj.kommunicator.Participants
import io.github.mklkj.kommunicator.SelectAllChats
import io.github.mklkj.kommunicator.Users
import io.github.mklkj.kommunicator.data.db.adapters.InstantStringAdapter
import io.github.mklkj.kommunicator.data.db.entity.LocalContact
import io.github.mklkj.kommunicator.data.db.entity.LocalUser
import io.github.mklkj.kommunicator.data.db.entity.LocalChat
import io.github.mklkj.kommunicator.data.db.entity.LocalMessage
import io.github.mklkj.kommunicator.data.models.Chat
import io.github.mklkj.kommunicator.data.models.Contact
import io.github.mklkj.kommunicator.data.models.Message
import io.github.mklkj.kommunicator.data.models.MessageBroadcast
import io.github.mklkj.kommunicator.data.models.MessageRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
Expand Down Expand Up @@ -53,12 +54,12 @@ class Database(sqlDriver: SqlDriver) {
)
private val dbQuery = database.appDatabaseQueries

fun getAllUsers(): Flow<List<LocalUser>> {
return dbQuery.selectAllUsers(::mapUserSelecting).asFlow().mapToList(Dispatchers.IO)
fun getAllUsers(): Flow<List<Users>> {
return dbQuery.selectAllUsers().asFlow().mapToList(Dispatchers.IO)
}

suspend fun getCurrentUser(): LocalUser? = withContext(Dispatchers.IO) {
dbQuery.selectAllUsers(::mapUserSelecting).executeAsOneOrNull()
suspend fun getCurrentUser(): Users? = withContext(Dispatchers.IO) {
dbQuery.selectAllUsers().executeAsOneOrNull()
}

suspend fun deleteCurrentUser() = withContext(Dispatchers.IO) {
Expand All @@ -76,7 +77,7 @@ class Database(sqlDriver: SqlDriver) {
)
}

suspend fun insertUser(user: LocalUser) {
suspend fun insertUser(user: Users) {
withContext(Dispatchers.IO) {
dbQuery.insertUser(
id = user.id,
Expand All @@ -91,13 +92,12 @@ class Database(sqlDriver: SqlDriver) {
}
}

fun observeContacts(userId: UUID): Flow<List<LocalContact>> {
return dbQuery.selectAllContacts(userId, ::mapContactSelecting)
.asFlow().mapToList(Dispatchers.IO)
fun observeContacts(userId: UUID): Flow<List<Contacts>> {
return dbQuery.selectAllContacts(userId).asFlow().mapToList(Dispatchers.IO)
}

fun getContacts(userId: UUID): List<LocalContact> {
return dbQuery.selectAllContacts(userId, ::mapContactSelecting).executeAsList()
fun getContacts(userId: UUID): List<Contacts> {
return dbQuery.selectAllContacts(userId).executeAsList()
}

suspend fun insertContacts(userId: UUID, contacts: List<Contact>) =
Expand All @@ -119,12 +119,12 @@ class Database(sqlDriver: SqlDriver) {
}
}

fun observeChats(userId: UUID): Flow<List<Chat>> {
fun observeChats(userId: UUID): Flow<List<LocalChat>> {
return dbQuery.selectAllChats(userId).asFlow()
.mapToList(Dispatchers.IO)
.map { chats ->
chats.map {
Chat(
LocalChat(
id = it.chatId,
customName = it.chatCustomName,
avatarUrl = it.avatarUrl,
Expand All @@ -133,15 +133,15 @@ class Database(sqlDriver: SqlDriver) {
isActive = Random.nextBoolean(),
participants = listOf(),

lastMessage = Message(
lastMessage = LocalMessage(
id = it.lastMessageId,
isUserMessage = false,
participantId = it.lastMessageAuthorId,
participantFirstName = it.firstname,
participantLastName = it.lastName,
participantCustomName = it.lastMessageAuthorCustomName,
isUserMessage = userId == it.lastMessageAuthorId, // todo
authorId = it.lastMessageAuthorId,
participantName = it.lastMessageAuthorCustomName ?: it.firstname,
createdAt = it.createdAt,
content = it.content,
chatId = it.chatId,
userId = userId,
),
)
}
Expand Down Expand Up @@ -209,20 +209,20 @@ class Database(sqlDriver: SqlDriver) {
}
}

fun observeMessages(chatId: UUID, userId: UUID): Flow<List<Message>> {
fun observeMessages(chatId: UUID, userId: UUID): Flow<List<LocalMessage>> {
return dbQuery.selectMessages(chatId).asFlow()
.mapToList(Dispatchers.IO)
.map { messages ->
messages.map {
Message(
LocalMessage(
id = it.id,
isUserMessage = it.userId == userId,
participantId = it.authorId,
participantFirstName = it.firstname.orEmpty(),
participantLastName = it.lastName.orEmpty(),
participantCustomName = it.customName,
authorId = userId,
participantName = it.customName ?: it.firstname.orEmpty(),
createdAt = it.createdAt,
content = it.content
content = it.content,
chatId = chatId,
userId = userId,
)
}
}
Expand All @@ -244,7 +244,7 @@ class Database(sqlDriver: SqlDriver) {
}
}

suspend fun insertIncomingMessage(chatId: UUID, message: Message) {
suspend fun insertIncomingMessage(chatId: UUID, message: MessageBroadcast) {
withContext(Dispatchers.IO) {
dbQuery.insertMessage(
id = message.id,
Expand All @@ -269,43 +269,3 @@ class Database(sqlDriver: SqlDriver) {
}
}
}

private fun mapUserSelecting(
id: UUID,
email: String,
username: String,
token: String,
refreshToken: String,
firstName: String,
lastName: String,
avatarUrl: String,
): LocalUser = LocalUser(
id = id,
email = email,
username = username,
token = token,
refreshToken = refreshToken,
firstName = firstName,
lastName = lastName,
avatarUrl = avatarUrl,
)

private fun mapContactSelecting(
id: UUID,
userId: UUID,
contactUserId: UUID,
avatarUrl: String,
firstName: String,
lastName: String,
username: String,
isActive: Boolean,
): LocalContact = LocalContact(
id = id,
userId = userId,
contactUserId = contactUserId,
avatarUrl = avatarUrl,
firstName = firstName,
lastName = lastName,
username = username,
isActive = isActive
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.github.mklkj.kommunicator.data.db.entity

import kotlinx.uuid.UUID

data class LocalChat(
val id: UUID,
val avatarUrl: String,
val isUnread: Boolean,
val isActive: Boolean,
val participants: List<LocalParticipant>,
val lastMessage: LocalMessage?,
val customName: String?,
)

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ data class LocalMessage(
val userId: UUID,
val authorId: UUID,
val isUserMessage: Boolean,
val timestamp: Instant,
val participantName: String,
val createdAt: Instant,
val content: String,
)

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package io.github.mklkj.kommunicator.data.repository

import io.github.mklkj.kommunicator.Contacts
import io.github.mklkj.kommunicator.data.api.service.ContactService
import io.github.mklkj.kommunicator.data.db.Database
import io.github.mklkj.kommunicator.data.db.entity.LocalContact
import io.github.mklkj.kommunicator.data.models.ContactAddRequest
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
Expand All @@ -20,7 +20,7 @@ class ContactRepository(
refreshContacts()
}

fun observeContacts(): Flow<List<LocalContact>> {
fun observeContacts(): Flow<List<Contacts>> {
return flow {
val userId = database.getCurrentUser()?.id ?: error("There is no current user!")
if (database.getContacts(userId).isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package io.github.mklkj.kommunicator.data.repository

import io.github.mklkj.kommunicator.BuildKonfig
import io.github.mklkj.kommunicator.Chats
import io.github.mklkj.kommunicator.Contacts
import io.github.mklkj.kommunicator.data.api.service.MessagesService
import io.github.mklkj.kommunicator.data.db.Database
import io.github.mklkj.kommunicator.data.db.entity.LocalContact
import io.github.mklkj.kommunicator.data.models.Chat
import io.github.mklkj.kommunicator.data.db.entity.LocalChat
import io.github.mklkj.kommunicator.data.db.entity.LocalMessage
import io.github.mklkj.kommunicator.data.models.ChatCreateRequest
import io.github.mklkj.kommunicator.data.models.Message
import io.github.mklkj.kommunicator.data.models.MessageBroadcast
import io.github.mklkj.kommunicator.data.models.MessagePush
import io.github.mklkj.kommunicator.data.models.MessageRequest
import io.ktor.client.HttpClient
import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession
Expand All @@ -29,7 +31,7 @@ class MessagesRepository(
private val database: Database,
) {

suspend fun createChat(contacts: List<LocalContact>): UUID {
suspend fun createChat(contacts: List<Contacts>): UUID {
return messagesService.createChat(
ChatCreateRequest(
customName = null,
Expand All @@ -40,7 +42,7 @@ class MessagesRepository(
).chatId
}

fun observeChats(): Flow<List<Chat>> {
fun observeChats(): Flow<List<LocalChat>> {
return flow {
val userId = database.getCurrentUser()?.id ?: error("There is no current user!")
if (database.getChats(userId).isEmpty()) {
Expand Down Expand Up @@ -75,7 +77,7 @@ class MessagesRepository(
database.insertMessages(chatId, messages)
}

fun observeMessages(chatId: UUID): Flow<List<Message>> {
fun observeMessages(chatId: UUID): Flow<List<LocalMessage>> {
return flow {
val userId = database.getCurrentUser()?.id ?: error("There is no current user!")
emitAll(database.observeMessages(chatId, userId))
Expand All @@ -84,7 +86,13 @@ class MessagesRepository(

suspend fun sendMessage(chatId: UUID, message: MessageRequest) {
saveMessageToSend(chatId, message)
messagesService.sendMessage(chatId, message)
messagesService.sendMessage(
chatId = chatId,
message = MessagePush(
id = message.id,
content = message.content,
)
)
}

suspend fun saveMessageToSend(chatId: UUID, message: MessageRequest) {
Expand All @@ -108,7 +116,7 @@ class MessagesRepository(
return session
}

suspend fun handleReceivedMessage(chatId: UUID, message: Message) {
suspend fun handleReceivedMessage(chatId: UUID, message: MessageBroadcast) {
database.insertIncomingMessage(chatId, message)
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package io.github.mklkj.kommunicator.data.repository

import com.mmk.kmpnotifier.notification.NotifierManager
import io.github.mklkj.kommunicator.Users
import io.github.mklkj.kommunicator.data.api.service.UserService
import io.github.mklkj.kommunicator.data.db.Database
import io.github.mklkj.kommunicator.data.db.entity.LocalUser
import io.github.mklkj.kommunicator.data.models.LoginRequest
import io.github.mklkj.kommunicator.data.models.PushTokenRequest
import io.github.mklkj.kommunicator.data.models.UserRequest
Expand Down Expand Up @@ -69,7 +69,7 @@ class UserRepository(
}
}

suspend fun getCurrentUser(): LocalUser {
suspend fun getCurrentUser(): Users {
return database.getCurrentUser() ?: error("There is no currently logged in user!")
}

Expand All @@ -94,7 +94,7 @@ class UserRepository(
id = response.id,
)
database.insertUser(
LocalUser(
Users(
id = response.id,
email = user.email,
username = user.username,
Expand Down
Loading

0 comments on commit 5748f89

Please sign in to comment.