getOrCreateChat using publicKey
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "10c21ff08b8acfda26aa17864d1e940e",
|
||||
"identityHash": "3594831027f19047682084075fd88ace",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "BroadcastNostrEventReceipt",
|
||||
@@ -1362,6 +1362,88 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "Participant",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `participantPublicKey` TEXT NOT NULL, `chatRoomId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER, FOREIGN KEY(`participantPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`chatRoomId`) REFERENCES `ChatRoom`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "participantPublicKey",
|
||||
"columnName": "participantPublicKey",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "chatRoomId",
|
||||
"columnName": "chatRoomId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "createdAt",
|
||||
"columnName": "createdAt",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "updatedAt",
|
||||
"columnName": "updatedAt",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "savedAt",
|
||||
"columnName": "savedAt",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "deletedAt",
|
||||
"columnName": "deletedAt",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "broadcastedAt",
|
||||
"columnName": "broadcastedAt",
|
||||
"affinity": "INTEGER"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "Profile",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"participantPublicKey"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"publicKey"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "ChatRoom",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"chatRoomId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "Post",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `repostId` TEXT, `quote` TEXT, `content` TEXT NOT NULL, `nostrEventId` TEXT NOT NULL, `replyToId` TEXT, `profilePublicKey` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`profilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replyToId`) REFERENCES `Post`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`repostId`) REFERENCES `Post`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
@@ -2543,7 +2625,7 @@
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '10c21ff08b8acfda26aa17864d1e940e')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3594831027f19047682084075fd88ace')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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>)
|
||||
|
||||
}
|
||||
@@ -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?>
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -357,8 +357,11 @@ fun AuxNavHost(
|
||||
|
||||
}
|
||||
}
|
||||
composable<ChatRoomDetailRoute> {
|
||||
composable<ChatRoomDetailRoute> { backStackEntry ->
|
||||
val route = backStackEntry.toRoute<ChatRoomDetailRoute>()
|
||||
|
||||
ChatRoomDetailScreen(
|
||||
publicKey = route.directMessageIdentifier,
|
||||
nostrRepository = databaseNostrRepository,
|
||||
chatRepository = databaseChatRepository
|
||||
)
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
@@ -227,7 +227,7 @@ fun MetadataEventDetail(
|
||||
onClick = {
|
||||
onNavigateToChatRoom.invoke(
|
||||
ChatRoomDetailRoute(
|
||||
directMessageIdentifier = localNostrEvent.nostrEvent.id
|
||||
directMessageIdentifier = localNostrEvent.nostrEvent.pubKey
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user