From c39b26c2e1921c59d76b696df7da5eb6ac04139a Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Wed, 8 Jul 2026 21:19:02 +0200 Subject: [PATCH] Bug fix for resyncing --- .../1.json | 14 +++------- .../dao/BroadcastNostrEventRequestDao.kt | 12 +++++--- ...geBroadcastNostrEventRequestRelationDao.kt | 5 ++-- .../compose/database/dao/ChatMessageDao.kt | 3 ++ .../compose/database/dao/MarmotOutboundDao.kt | 1 - .../at/torch/compose/database/dao/NostrDao.kt | 28 ++++++++++++++++++- .../compose/database/dao/NostrEventDao.kt | 2 +- ...ssageBroadcastNostrEventRequestRelation.kt | 1 + .../database/model/MarmotGroupEvent.kt | 9 ++---- .../repository/DatabaseNostrRepository.kt | 7 +++-- .../compose/repository/NostrRepository.kt | 4 +-- .../ui/view/model/SynchronizationViewModel.kt | 9 +++--- 12 files changed, 60 insertions(+), 35 deletions(-) diff --git a/composeApp/schemas/at.torch.compose.database.TorchDatabase/1.json b/composeApp/schemas/at.torch.compose.database.TorchDatabase/1.json index 437c7fc8..da2b9037 100644 --- a/composeApp/schemas/at.torch.compose.database.TorchDatabase/1.json +++ b/composeApp/schemas/at.torch.compose.database.TorchDatabase/1.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "ec367eb293e2be375d50f8d1b8133e00", + "identityHash": "f0a498de098c8c2c2007d4309022282e", "entities": [ { "tableName": "BroadcastNostrEventReceipt", @@ -1252,7 +1252,7 @@ }, { "tableName": "MarmotGroupEvent", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `userPublicKey` TEXT NOT NULL, `publicKey` TEXT NOT NULL, `chatRoomId` TEXT NOT NULL, `signature` TEXT NOT NULL, `encryptedContent` TEXT NOT NULL, `expiresAt` INTEGER, `nostrEventId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `userPublicKey` TEXT NOT NULL, `publicKey` TEXT NOT NULL, `chatRoomId` TEXT NOT NULL, `signature` TEXT NOT NULL, `encryptedContent` TEXT NOT NULL, `expiresAt` INTEGER, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`id`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "fields": [ { "fieldPath": "id", @@ -1295,12 +1295,6 @@ "columnName": "expiresAt", "affinity": "INTEGER" }, - { - "fieldPath": "nostrEventId", - "columnName": "nostrEventId", - "affinity": "TEXT", - "notNull": true - }, { "fieldPath": "createdAt", "columnName": "createdAt", @@ -1342,7 +1336,7 @@ "onDelete": "CASCADE", "onUpdate": "NO ACTION", "columns": [ - "nostrEventId" + "id" ], "referencedColumns": [ "id" @@ -3458,7 +3452,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, 'ec367eb293e2be375d50f8d1b8133e00')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f0a498de098c8c2c2007d4309022282e')" ] } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/BroadcastNostrEventRequestDao.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/BroadcastNostrEventRequestDao.kt index ef4c0db0..44e5e78a 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/BroadcastNostrEventRequestDao.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/BroadcastNostrEventRequestDao.kt @@ -4,6 +4,7 @@ import androidx.room3.Dao import androidx.room3.Insert import androidx.room3.Query import androidx.room3.Upsert +import at.torch.compose.database.model.BroadcastNostrEventRequest import at.torch.compose.database.model.intermdiate.LocalBroadcastNostrEventRequest import kotlinx.coroutines.flow.Flow import kotlin.time.Clock @@ -12,17 +13,20 @@ import kotlin.time.Instant @Dao interface BroadcastNostrEventRequestDao { @Query("SELECT * FROM BroadcastNostrEventRequest") - fun getAllBroadcastNostrEventRequests(): List + fun getAllBroadcastNostrEventRequests(): List + + @Query("SELECT * FROM BroadcastNostrEventRequest WHERE nostrEventId = :nostrEventId ORDER BY createdAt ASC") + fun getFirstBroadcastNostrEventRequestByNostrEventId(nostrEventId: String): BroadcastNostrEventRequest? @Query("SELECT * FROM BroadcastNostrEventRequest WHERE status = :status AND createdAt > :createdAt") fun observeBroadcastNostrEventRequestsByStatus(status: String, createdAt: Instant = Clock.System.now()): Flow @Query("SELECT * FROM BroadcastNostrEventRequest WHERE nostrEventId = :nostrEventId") - fun observeBroadcastNostrEventRequestByNostrEventId(nostrEventId: String): Flow + fun observeBroadcastNostrEventRequestByNostrEventId(nostrEventId: String): Flow @Upsert - suspend fun upsert(broadcastNostrEventRequest: at.torch.compose.database.model.BroadcastNostrEventRequest) + suspend fun upsert(broadcastNostrEventRequest: BroadcastNostrEventRequest): Long @Insert - suspend fun insert(broadcastNostrEventRequests: List): LongArray + suspend fun insert(broadcastNostrEventRequests: List): LongArray } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/ChatMessageBroadcastNostrEventRequestRelationDao.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/ChatMessageBroadcastNostrEventRequestRelationDao.kt index 79061e4a..a7e084a4 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/ChatMessageBroadcastNostrEventRequestRelationDao.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/ChatMessageBroadcastNostrEventRequestRelationDao.kt @@ -3,12 +3,13 @@ package at.torch.compose.database.dao import androidx.room3.Dao import androidx.room3.Query import androidx.room3.Upsert +import at.torch.compose.database.model.ChatMessageBroadcastNostrEventRequestRelation @Dao interface ChatMessageBroadcastNostrEventRequestRelationDao { @Query("SELECT * FROM ChatMessageBroadcastNostrEventRequestRelation WHERE broadcastNostrEventRequestId = :broadcastNostrEventRequestId") - suspend fun getChatMessageBroadcastNostrEventRequestRelationByBroadcastRequest(broadcastNostrEventRequestId: Long): at.torch.compose.database.model.ChatMessageBroadcastNostrEventRequestRelation? + suspend fun getChatMessageBroadcastNostrEventRequestRelationByBroadcastRequest(broadcastNostrEventRequestId: Long): ChatMessageBroadcastNostrEventRequestRelation? @Upsert - suspend fun upsert(chatMessageBroadcastNostrEventRequestRelation: at.torch.compose.database.model.ChatMessageBroadcastNostrEventRequestRelation) + suspend fun upsert(chatMessageBroadcastNostrEventRequestRelation: ChatMessageBroadcastNostrEventRequestRelation) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/ChatMessageDao.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/ChatMessageDao.kt index 3e06eb61..42b70d90 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/ChatMessageDao.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/ChatMessageDao.kt @@ -24,6 +24,9 @@ interface ChatMessageDao { @Query("SELECT * FROM ChatMessage WHERE marmotInnerEventId = :marmotInnerEventId ORDER BY createdAt DESC") fun getChatMessagesByMarmotInnerEventId(marmotInnerEventId: HexKey): ChatMessage? + @Query("SELECT * FROM ChatMessage WHERE marmotGroupEventId = :marmotGroupEventId ORDER BY createdAt DESC") + fun getChatMessagesByMarmotGroupEventId(marmotGroupEventId: HexKey): ChatMessage? + @Query("SELECT COUNT(*) FROM ChatMessage WHERE chatRoomId = :chatRoomId AND senderPublicKey = :senderPublicKey") fun countChatMessagesBySenderPublicKey(chatRoomId: HexKey, senderPublicKey: HexKey): Int diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/MarmotOutboundDao.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/MarmotOutboundDao.kt index e3a9f13f..8e901401 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/MarmotOutboundDao.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/MarmotOutboundDao.kt @@ -394,7 +394,6 @@ abstract class MarmotOutboundDao( expiresAt = groupEvent.expiration()?.let { Instant.fromEpochSeconds(it) }, encryptedContent = groupEvent.encryptedContent(), chatRoomId = localChatRoom.chatRoom.id, - nostrEventId = groupEvent.id, userPublicKey = groupEvent.pubKey, publicKey = groupEvent.pubKey, signature = groupEvent.sig, diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/NostrDao.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/NostrDao.kt index 73c4341a..ae54e8b3 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/NostrDao.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/NostrDao.kt @@ -6,6 +6,7 @@ import androidx.room3.Transaction import at.torch.compose.database.GENESIS_AT import at.torch.compose.database.model.BroadcastNostrEventRequest import at.torch.compose.database.model.ChatMessage +import at.torch.compose.database.model.ChatMessageBroadcastNostrEventRequestRelation import at.torch.compose.database.model.ChatRoom import at.torch.compose.database.model.Connection import at.torch.compose.database.model.MarmotGroupEvent @@ -384,7 +385,6 @@ abstract class NostrDao( publicKey = groupEvent.pubKey, encryptedContent = groupEvent.encryptedContent(), chatRoomId = chatRoomId, - nostrEventId = nostrEvent.id, createdAt = Instant.fromEpochSeconds(groupEvent.createdAt), expiresAt = groupEvent.expiration()?.let { Instant.fromEpochSeconds(it) }, signature = groupEvent.sig @@ -1170,4 +1170,30 @@ abstract class NostrDao( } } } + + + /** + * Persist the rescheduling using a transaction so that we are assured of the existence of the + * [ChatMessageBroadcastNostrEventRequestRelation] when we produce the [at.torch.compose.database.model.BroadcastNostrEventReceipt] + */ + @Transaction + open suspend fun rescheduleBroadcastNostrEventRequests(broadcastNostrEventRequests: List) { + // Check if any previous chatMessageBroadcastNostrEventRequestRelationDao exists for this nostrEventId... + broadcastNostrEventRequests.forEach { broadcastNostrEventRequest -> + val broadcastNostrEventRequestId = database.broadcastNostrEventRequestDao().upsert( + broadcastNostrEventRequest + ) + + database.chatMessageDao().getChatMessagesByMarmotGroupEventId( + marmotGroupEventId = broadcastNostrEventRequest.nostrEventId + )?.let { chatMessage -> + database.chatMessageBroadcastNostrEventRequestRelationDao().upsert( + ChatMessageBroadcastNostrEventRequestRelation( + broadcastNostrEventRequestId = broadcastNostrEventRequestId, + chatMessageId = chatMessage.id + ) + ) + } + } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/NostrEventDao.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/NostrEventDao.kt index 719e492c..46e80726 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/NostrEventDao.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/NostrEventDao.kt @@ -109,7 +109,7 @@ interface NostrEventDao { ): List @Transaction - @Query("SELECT * FROM MarmotGroupEvent WHERE nostrEventId IS NOT NULL AND chatRoomId in (:chatRoomIds) AND createdAt > :since AND (expiresAt IS NULL OR expiresAt < :expiresAt) ORDER BY createdAt ASC LIMIT :limit") + @Query("SELECT * FROM MarmotGroupEvent WHERE chatRoomId in (:chatRoomIds) AND createdAt > :since AND (expiresAt IS NULL OR expiresAt < :expiresAt) ORDER BY createdAt ASC LIMIT :limit") fun getMarmotGroupEvents( chatRoomIds: Array, since: Instant, diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/database/model/ChatMessageBroadcastNostrEventRequestRelation.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/database/model/ChatMessageBroadcastNostrEventRequestRelation.kt index c40c1bee..e3783c3b 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/database/model/ChatMessageBroadcastNostrEventRequestRelation.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/database/model/ChatMessageBroadcastNostrEventRequestRelation.kt @@ -28,6 +28,7 @@ data class ChatMessageBroadcastNostrEventRequestRelation( val chatMessageId: Long, val broadcastNostrEventRequestId: Long, + // Add nostrEventId here... override val createdAt: Instant = Clock.System.now(), override val updatedAt: Instant = Clock.System.now(), diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/database/model/MarmotGroupEvent.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/database/model/MarmotGroupEvent.kt index 91748356..99ce1024 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/database/model/MarmotGroupEvent.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/database/model/MarmotGroupEvent.kt @@ -25,7 +25,7 @@ import kotlin.time.Instant ForeignKey( entity = NostrEvent::class, parentColumns = ["id"], - childColumns = ["nostrEventId"], + childColumns = ["id"], onDelete = ForeignKey.CASCADE, ), ], @@ -48,17 +48,12 @@ data class MarmotGroupEvent( val encryptedContent: String, val expiresAt: Instant?, - /** - * What gets broadcasts? - */ - override val nostrEventId: String, - override val createdAt: Instant = Clock.System.now(), override val updatedAt: Instant = createdAt, override val savedAt: Instant = Clock.System.now(), override val deletedAt: Instant? = null, override val broadcastedAt: Instant? = null, -) : NostrEventEntity, TimestampedEntity, LocalStoreEntity, BroadcastableEntity, SoftDeletableEntity { +) : TimestampedEntity, LocalStoreEntity, BroadcastableEntity, SoftDeletableEntity { companion object { const val KIND = GroupEvent.KIND diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/database/repository/DatabaseNostrRepository.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/database/repository/DatabaseNostrRepository.kt index 942eec76..6bbc7a1b 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/database/repository/DatabaseNostrRepository.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/database/repository/DatabaseNostrRepository.kt @@ -2,6 +2,7 @@ package at.torch.compose.database.repository import at.torch.compose.database.GENESIS_AT import at.torch.compose.database.model.BroadcastNostrEventRequest +import at.torch.compose.database.model.ChatMessageBroadcastNostrEventRequestRelation import at.torch.compose.database.model.NostrEvent import at.torch.compose.database.model.UnsignedNostrEvent import at.torch.compose.database.model.intermdiate.LocalAccount @@ -698,7 +699,7 @@ class DatabaseNostrRepository( } ).map { marmotGroupEvent -> NostrEvent( - id = marmotGroupEvent.nostrEventId, + id = marmotGroupEvent.id, content = marmotGroupEvent.encryptedContent, kind = GroupEvent.KIND, tags = emptyArray(), @@ -774,8 +775,8 @@ class DatabaseNostrRepository( return database.relayDao().observePublicKeyRelays(publicKey) } - override suspend fun scheduleBroadcastNostrEventRequests(broadcastNostrEventRequests: List) { - database.broadcastNostrEventRequestDao().insert( + override suspend fun rescheduleBroadcastNostrEventRequests(broadcastNostrEventRequests: List) { + database.nostrDao().rescheduleBroadcastNostrEventRequests( broadcastNostrEventRequests ) } diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/repository/NostrRepository.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/repository/NostrRepository.kt index af4b14a4..464e842e 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/repository/NostrRepository.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/repository/NostrRepository.kt @@ -134,7 +134,7 @@ interface NostrRepository { suspend fun saveSearchQuery(query: String, synchronizationFilterArray: at.torch.compose.database.model.typealiases.SynchronizationFilterArray? = null) - suspend fun scheduleBroadcastNostrEventRequests(broadcastNostrEventRequests: List) + suspend fun rescheduleBroadcastNostrEventRequests(broadcastNostrEventRequests: List) companion object { val NO_OP_NOSTR_REPOSITORY = object : NostrRepository { @@ -323,7 +323,7 @@ interface NostrRepository { TODO("Not yet implemented") } - override suspend fun scheduleBroadcastNostrEventRequests(broadcastNostrEventRequests: List) { + override suspend fun rescheduleBroadcastNostrEventRequests(broadcastNostrEventRequests: List) { TODO("Not yet implemented") } } diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/ui/view/model/SynchronizationViewModel.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/ui/view/model/SynchronizationViewModel.kt index 3173ca1d..587f9bb5 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/ui/view/model/SynchronizationViewModel.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/ui/view/model/SynchronizationViewModel.kt @@ -338,10 +338,11 @@ class SynchronizationViewModel( ) } logger.d("broadcastNostrEventRequests: $broadcastNostrEventRequests") - // TODO: Schedule broadcastNostrEventRequests -// nostrRepository.scheduleBroadcastNostrEventRequests( -// broadcastNostrEventRequests -// ) + // Schedule broadcastNostrEventRequests + + nostrRepository.rescheduleBroadcastNostrEventRequests( + broadcastNostrEventRequests + ) relaysSocketManager.closeNegentropySync( negCloseCmd,