Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main/kotlin/chatserver/ChatServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ val chatServerModule =
module {
includes(chatServerProfilesModule, chatServerMessagesModule, chatServerPresenceModule)
factoryOf(::ChatRepositories)
factoryOf(::LoggedInUserId)
}
8 changes: 8 additions & 0 deletions src/main/kotlin/chatserver/LoggedInUserId.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package chatserver

import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.auth.auth

class LoggedInUserId(private val client: SupabaseClient) {
operator fun invoke(): String = client.auth.currentUserOrNull()?.id.orEmpty()
}
7 changes: 4 additions & 3 deletions src/main/kotlin/chatserver/ProfileResult.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package chatserver

import chatserver.ReadChatRepository.ReadResult
import chatserver.profiles.SupabaseProfilesRepository.Profile

data class ProfileResult(
override val isOk: Boolean,
override val item: Map<String, String>,
override val item: Map<String, Profile>,
override val error: Exception?,
) :
ReadResult<Map<String, String>> {
ReadResult<Map<String, Profile>> {
companion object {
fun ok(item: Map<String, String>): ProfileResult = ProfileResult(true, item, null)
fun ok(item: Map<String, Profile>): ProfileResult = ProfileResult(true, item, null)

fun fail(e: Exception? = null): ProfileResult = ProfileResult(false, emptyMap(), e)
}
Expand Down
9 changes: 9 additions & 0 deletions src/main/kotlin/chatserver/profiles/ChatServerProfiles.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,13 @@ val chatServerProfilesModule =
),
)
}
single {
SupabaseProfilesRepository(
get(),
get(),
CoroutineScope(
SupervisorJob() + get<RokyDispatchers>().io,
),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import chatserver.ReadChatRepository
import chatserver.SubscribeChatRepository
import chatserver.WriteChatRepository
import chatserver.messages.LocalChatMessages
import chatserver.profiles.SupabaseProfilesRepository.Profile
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
Expand All @@ -33,7 +34,7 @@ class LocalProfilesRepository(
scope.launch(dispatchers.default) {
while (true) {
val users = LocalChatMessages.sampleUsers.shuffled()
state.value = users.associateWith { it }.let(ProfileResult::ok)
state.value = users.associateWith { Profile(it, it) }.let(ProfileResult::ok)
delay(5.seconds)
}
}
Expand All @@ -50,7 +51,7 @@ class LocalProfilesRepository(
delay(2.seconds)
check(item.isValidUsername()) { "Could not assign current username." }
val profiles = latest().item.toMutableMap()
profiles[item] = item
profiles[item] = Profile(item, item)
state.value = ok(profiles)
} catch (e: Exception) {
state.value = ProfileResult.fail(e)
Expand Down
74 changes: 74 additions & 0 deletions src/main/kotlin/chatserver/profiles/SupabaseProfilesRepository.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package chatserver.profiles

import chatserver.*
import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.annotations.SupabaseExperimental
import io.github.jan.supabase.postgrest.from
import io.github.jan.supabase.realtime.selectAsFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

class SupabaseProfilesRepository(
private val client: SupabaseClient,
private val userId: LoggedInUserId,
private val scope: CoroutineScope,
) : ReadChatRepository<ProfileResult>, WriteChatRepository<String>, SubscribeChatRepository {
private val profiles: MutableStateFlow<ProfileResult> = MutableStateFlow(ProfileResult.ok(emptyMap()))

override fun latest(): ProfileResult = profiles.value

override fun observe(): Flow<ProfileResult> = profiles.asStateFlow()

override fun write(requestedUsername: String) {
require(requestedUsername.isBlank()) {
"Requested username must not be blank."
}
val id =
userId().ifBlank {
throw IllegalStateException("User ID must not be blank.")
}
val currentUsername = latest().item[id]?.username
check(currentUsername == requestedUsername) {
"Current username must not match requested username."
}
scope.launch {
try {
client.from("profiles")
.update({
set("username", requestedUsername)
}) {
filter {
eq("id", id)
}
}
} catch (e: Exception) {
profiles.value = ProfileResult.fail(e)
}
}
}

@OptIn(SupabaseExperimental::class)
override fun subscribe() {
client.from("profiles")
.selectAsFlow(Profile::id)
.map { it.associateBy(Profile::id) }
.map { ProfileResult.ok(it) }
.onEach { profiles.value = it }
.catch { println(it) }
.launchIn(scope)
}

override fun unsubscribe() {
scope.cancel()
}

@Serializable
data class Profile(
@SerialName("id") val id: String,
@SerialName("username") val username: String,
)
}
5 changes: 3 additions & 2 deletions src/test/kotlin/profile/ProfilePresenterTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import arch.RokyDispatchers
import chatserver.ProfileResult
import chatserver.ReadChatRepository
import chatserver.WriteChatRepository
import chatserver.profiles.SupabaseProfilesRepository.Profile
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
Expand Down Expand Up @@ -43,13 +44,13 @@ class ProfilePresenterTest {
}
val writeUsername: WriteChatRepository<String> = mockk()
every { writeUsername.write(any()) } answers {
val user = it.invocation.args [0] as String
val user = it.invocation.args[0] as String
usernames.value =
if (user == INVALID_USER) {
ProfileResult.fail(RuntimeException())
} else {
val currentUsernames = usernames.value.item.toMutableMap()
currentUsernames[user] = user
currentUsernames[user] = Profile(user, user)
ProfileResult.ok(currentUsernames)
}
}
Expand Down
Loading