getOrCreateChat using publicKey

This commit is contained in:
Kgothatso Ngako
2026-06-01 00:42:48 +02:00
parent c8ba6a0bf0
commit 29ec692fc4
14 changed files with 204 additions and 14 deletions

View File

@@ -17,6 +17,7 @@ import ac.cord.auxiliary.compose.database.dao.NostrDao
import ac.cord.auxiliary.compose.database.dao.NostrEventDao
import ac.cord.auxiliary.compose.database.dao.NostrEventRelayDao
import ac.cord.auxiliary.compose.database.dao.NostrNip17Dao
import ac.cord.auxiliary.compose.database.dao.ParticipantDao
import ac.cord.auxiliary.compose.database.dao.PostDao
import ac.cord.auxiliary.compose.database.dao.ProfileDao
import ac.cord.auxiliary.compose.database.dao.QuotedRelationDao
@@ -42,6 +43,7 @@ import ac.cord.auxiliary.compose.database.model.NegentropySynchronizeRequest
import ac.cord.auxiliary.compose.database.model.NegentropySynchronizeResult
import ac.cord.auxiliary.compose.database.model.NostrEvent
import ac.cord.auxiliary.compose.database.model.NostrEventRelay
import ac.cord.auxiliary.compose.database.model.Participant
import ac.cord.auxiliary.compose.database.model.Post
import ac.cord.auxiliary.compose.database.model.Profile
import ac.cord.auxiliary.compose.database.model.QuotedRelation
@@ -79,6 +81,7 @@ val GENESIS_AT = Instant.fromEpochMilliseconds(1231006505000L)
NegentropySynchronizeResult::class,
NostrEvent::class,
NostrEventRelay::class,
Participant::class,
Post::class,
Profile::class,
QuotedRelation::class,
@@ -126,6 +129,8 @@ abstract class AuxDatabase: RoomDatabase() {
abstract fun nostrNip17Dao(): NostrNip17Dao
abstract fun participantDao(): ParticipantDao
abstract fun postDao(): PostDao
abstract fun profileDao(): ProfileDao

View File

@@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface ChatRoomDao {
@Query("SELECT * FROM ChatRoom WHERE id = :id")
fun findChatRoomById(id: String): ChatRoom?
fun findChatRoomById(id: String): LocalChatRoom?
@Query("SELECT * FROM ChatRoom WHERE userPublicKey = :userPublicKey")
fun observeChatRoomByUserPublicKey(userPublicKey: String): Flow<List<LocalChatRoom>>

View File

@@ -361,11 +361,11 @@ abstract class NostrDao(
).value.toHex()
// Process chatMessage and chatRoom stuff...
// Find or create chatRoom
val chatRoom = database.chatRoomDao().findChatRoomById(
val localChatRoom = database.chatRoomDao().findChatRoomById(
chatRoomId
)
if (chatRoom == null) {
if (localChatRoom == null) {
database.chatRoomDao().upsert(
ChatRoom(
id = chatRoomId,

View File

@@ -1,18 +1,87 @@
package ac.cord.auxiliary.compose.database.dao
import ac.cord.auxiliary.compose.database.AuxDatabase
import ac.cord.auxiliary.compose.database.model.ChatRoom
import ac.cord.auxiliary.compose.database.model.Participant
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
import ac.cord.auxiliary.compose.managers.SeedManager
import androidx.room3.Dao
import androidx.room3.Transaction
import co.touchlab.kermit.Logger
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import fr.acinq.bitcoin.PublicKey
import fr.acinq.bitcoin.crypto.musig2.Musig2
import kotlin.math.log
@Dao
abstract class NostrNip17Dao(
private val database: AuxDatabase
) {
val logger = Logger.withTag(TAG)
@Transaction
open suspend fun getOrCreateChatRoom(publicKey: String): LocalChatRoom? {
open suspend fun getOrCreateChatRoom(publicKey: String, defaultSubject: String? = null): LocalChatRoom? {
logger.d("getOrCreateChatRoom: $publicKey")
val activePublicKey = SeedManager.activePublicKey().toHexKey()
val hexKeys = listOf(
activePublicKey,
publicKey
).sorted()
logger.d("hexKeys: $hexKeys")
val publicKeys = hexKeys.map {
PublicKey(
PublicKey.compress(it.hexToByteArray())
)
}
logger.d("PublicKeys: $publicKeys")
val chatRoomXOnlyPublicKey = Musig2.aggregateKeys(
publicKeys
)
logger.d("ChatRoomXOnly: $chatRoomXOnlyPublicKey")
val chatRoomId = chatRoomXOnlyPublicKey.publicKey.value.toHex()
logger.d("chatRoomId: $chatRoomId")
val localChatRoom = database.chatRoomDao().findChatRoomById(chatRoomId)
if (localChatRoom != null) {
return localChatRoom
} else {
val profiles = database.profileDao().getAllProfiles()
if (profiles.isNotEmpty()) {
// Create new chatRoom
val chatRoom = ChatRoom(
id = chatRoomId,
userPublicKey = activePublicKey,
subject = defaultSubject,
)
database.chatRoomDao().upsert(chatRoom)
database.participantDao().upsert(
profiles.map {
Participant(
participantPublicKey = it.publicKey,
chatRoomId = chatRoomId
)
}
)
return LocalChatRoom(
chatRoom = chatRoom
)
}
}
return null
}
companion object {
const val TAG = "NostrNip17Dao"
}
}

View File

@@ -0,0 +1,22 @@
package ac.cord.auxiliary.compose.database.dao
import ac.cord.auxiliary.compose.database.model.ChatRoom
import ac.cord.auxiliary.compose.database.model.Participant
import ac.cord.auxiliary.compose.database.model.Relay
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
import androidx.room3.Dao
import androidx.room3.Query
import androidx.room3.Upsert
import fr.acinq.bitcoin.PublicKey
import kotlinx.coroutines.flow.Flow
@Dao
interface ParticipantDao {
@Query("SELECT * FROM Participant WHERE participantPublicKey = :participantPublicKey")
fun findParticipantByPublicKey(participantPublicKey: String): Participant?
@Upsert
suspend fun upsert(participants: List<Participant>)
}

View File

@@ -24,6 +24,9 @@ interface ProfileDao {
@Query("SELECT * FROM Profile WHERE publicKey = :publicKey")
fun getProfileByPublicKey(publicKey: String): Profile?
@Query("SELECT * FROM Profile WHERE publicKey IN (:publicKeys)")
fun getProfileByPublicKeys(publicKeys: List<String>): List<Profile>
@Query("SELECT * FROM NostrEvent WHERE kind = 0 AND pubKey = :publicKey")
fun observeProfileWithFollowersByPublicKey(publicKey: String): Flow<LocalProfileWithFollowers?>

View File

@@ -1,6 +1,7 @@
package ac.cord.auxiliary.compose.database.model.intermdiate
import ac.cord.auxiliary.compose.database.model.ChatRoom
import ac.cord.auxiliary.compose.database.model.Participant
import ac.cord.auxiliary.compose.database.model.Post
import ac.cord.auxiliary.compose.database.model.Profile
import androidx.room3.Embedded
@@ -8,4 +9,6 @@ import androidx.room3.Relation
data class LocalChatRoom(
@Embedded val chatRoom: ChatRoom,
// val participants: List<Participant>
)

View File

@@ -11,9 +11,10 @@ class DatabaseChatRepository(
private val scope: CoroutineScope
): ChatRepository {
override suspend fun getOrCreateChatRoom(publicKey: String): LocalChatRoom? {
override suspend fun getOrCreateChatRoom(publicKey: String, defaultSubject: String?): LocalChatRoom? {
return database.nostrNip17Dao().getOrCreateChatRoom(
publicKey
publicKey,
defaultSubject = defaultSubject
)
}

View File

@@ -4,13 +4,13 @@ import ac.cord.auxiliary.compose.database.model.ChatRoom
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
interface ChatRepository {
suspend fun getOrCreateChatRoom(publicKey: String): LocalChatRoom?
suspend fun getOrCreateChatRoom(publicKey: String, defaultSubject: String? = null): LocalChatRoom?
fun updateChatRoomSubject(publicKey: String, subject: String): ChatRoom?
companion object {
val NO_OP_CHAT_REPOSITORY = object: ChatRepository {
override suspend fun getOrCreateChatRoom(publicKey: String): LocalChatRoom? {
override suspend fun getOrCreateChatRoom(publicKey: String, defaultSubject: String?): LocalChatRoom? {
return null
}

View File

@@ -36,13 +36,14 @@ import kotlin.time.Clock
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
@Composable
fun ChatRoomDetailScreen(
publicKey: String,
initialChatRoomDetailUIState: ChatRoomDetailUIState = ChatRoomDetailUIState.Loading,
nostrRepository: NostrRepository,
chatRepository: ChatRepository
) {
val chatRoomDetailViewModel: ChatRoomDetailViewModel = viewModel(
factory = ChatRoomDetailViewModel.factory(
publicKey = "",
publicKey = publicKey,
initialChatRoomDetailUIState = initialChatRoomDetailUIState,
nostrRepository = nostrRepository,
chatRepository = chatRepository
@@ -136,6 +137,7 @@ private fun UnqueuedProfileSynchronizationScreenPreview() {
modifier = Modifier.fillMaxSize()
) {
ChatRoomDetailScreen(
publicKey = "publicKey",
initialChatRoomDetailUIState = ChatRoomDetailUIState.Loaded(
localChatRoom = LocalChatRoom(
chatRoom = ChatRoom(

View File

@@ -357,8 +357,11 @@ fun AuxNavHost(
}
}
composable<ChatRoomDetailRoute> {
composable<ChatRoomDetailRoute> { backStackEntry ->
val route = backStackEntry.toRoute<ChatRoomDetailRoute>()
ChatRoomDetailScreen(
publicKey = route.directMessageIdentifier,
nostrRepository = databaseNostrRepository,
chatRepository = databaseChatRepository
)

View File

@@ -4,6 +4,6 @@ import kotlinx.serialization.Serializable
@Serializable
data class ChatRoomDetailRoute(
val directMessageIdentifier: String,
val directMessageIdentifier: String, // TODO: have this as a publicKey
): Route() {
}

View File

@@ -227,7 +227,7 @@ fun MetadataEventDetail(
onClick = {
onNavigateToChatRoom.invoke(
ChatRoomDetailRoute(
directMessageIdentifier = localNostrEvent.nostrEvent.id
directMessageIdentifier = localNostrEvent.nostrEvent.pubKey
)
)
}