Use unique constraint when saving to the database.

This commit is contained in:
Kgothatso Ngako committed 2026-07-12 21:15:27 +02:00
1 parent 9331669aea
commit 7e1be3c3f6
4 files changed
+69 -43

No files matched your search

@@ -1,18 +1,23 @@
package at.torch.compose.database.dao package at.torch.compose.database.dao
import androidx.room3.Dao import androidx.room3.Dao
import androidx.room3.Insert
import androidx.room3.Query import androidx.room3.Query
import androidx.room3.Upsert import androidx.room3.Upsert
import at.torch.compose.database.model.GiftWrapPayload
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface GiftWrapPayloadDao { interface GiftWrapPayloadDao {
@Query("SELECT * FROM GiftWrapPayload") @Query("SELECT * FROM GiftWrapPayload")
fun getAllGiftWrapUnsigned(): List<at.torch.compose.database.model.GiftWrapPayload> fun getAllGiftWrapUnsigned(): List<GiftWrapPayload>
@Query("SELECT * FROM GiftWrapPayload WHERE publicKey = :publicKey AND giftWrapSealId IS NULL") @Query("SELECT * FROM GiftWrapPayload WHERE publicKey = :publicKey AND giftWrapSealId IS NULL")
fun observeUnsealedGiftWrapPayloads(publicKey: String): Flow<at.torch.compose.database.model.GiftWrapPayload?> fun observeUnsealedGiftWrapPayloads(publicKey: String): Flow<GiftWrapPayload?>
@Upsert @Upsert
suspend fun upsert(giftWrapPayload: at.torch.compose.database.model.GiftWrapPayload) suspend fun upsert(giftWrapPayload: GiftWrapPayload)
@Insert
suspend fun insert(giftWrapPayload: GiftWrapPayload)
} }
@@ -45,6 +45,7 @@ import com.vitorpamplona.quartz.nip59Giftwrap.rumors.RumorAssembler
import com.vitorpamplona.quartz.utils.RandomInstance import com.vitorpamplona.quartz.utils.RandomInstance
import com.vitorpamplona.quartz.utils.TimeUtils import com.vitorpamplona.quartz.utils.TimeUtils
import kotlin.io.encoding.Base64 import kotlin.io.encoding.Base64
import kotlin.time.Clock
import kotlin.time.Instant import kotlin.time.Instant
@Dao @Dao
@@ -121,7 +122,7 @@ abstract class MarmotOutboundDao(
} }
@Transaction @Transaction
open suspend fun inviteMember( open suspend fun inviteMemberToChatRoom(
localChatRoom: LocalChatRoom, localChatRoom: LocalChatRoom,
peerPublicKey: HexKey, peerPublicKey: HexKey,
peerKeyPackage: MarmotKeyPackage, peerKeyPackage: MarmotKeyPackage,
@@ -204,6 +205,7 @@ abstract class MarmotOutboundDao(
welcomeBytes = welcomeBytes, welcomeBytes = welcomeBytes,
peerKeyPackageEventId = peerKeyPackage.id, peerKeyPackageEventId = peerKeyPackage.id,
relays = relays, relays = relays,
createdAt = Clock.System.now()
) )
} }
} else { } else {
@@ -283,51 +285,64 @@ abstract class MarmotOutboundDao(
welcomeBytes: ByteArray, welcomeBytes: ByteArray,
peerKeyPackageEventId: HexKey, peerKeyPackageEventId: HexKey,
relays: List<String>, relays: List<String>,
createdAt: Instant
) { ) {
val welcomeBase64 = Base64.encode( try {
source = welcomeBytes val welcomeBase64 = Base64.encode(
) source = welcomeBytes
)
// GiftWrap... // GiftWrap...
val welcomeEvent = WelcomeEvent.build( val welcomeEvent = WelcomeEvent.build(
welcomeBase64 = welcomeBase64, welcomeBase64 = welcomeBase64,
keyPackageEventId = peerKeyPackageEventId, createdAt = createdAt.epochSeconds,
relays = relays.map { NormalizedRelayUrl(it) }, keyPackageEventId = peerKeyPackageEventId,
nostrGroupId = nostrGroupId, relays = relays.map { NormalizedRelayUrl(it) },
) nostrGroupId = nostrGroupId,
// Compute welcomeEventId )
val welcomeEventId = EventHasher.hashId( // Compute welcomeEventId
pubKey = userPublicKey, val welcomeEventId = EventHasher.hashId(
kind = welcomeEvent.kind, pubKey = userPublicKey,
tags = welcomeEvent.tags,
createdAt = welcomeEvent.createdAt,
content = welcomeEvent.content
)
database.giftWrapPayloadDao().upsert(
GiftWrapPayload(
id = welcomeEventId,
publicKey = userPublicKey,
chatRoomId = nostrGroupId,
kind = welcomeEvent.kind, kind = welcomeEvent.kind,
tags = welcomeEvent.tags, tags = welcomeEvent.tags,
content = welcomeEvent.content, createdAt = welcomeEvent.createdAt,
createdAt = Instant.fromEpochSeconds(welcomeEvent.createdAt) content = welcomeEvent.content
) )
) logger.d("Welcome Event: ${welcomeEvent.toJson()}")
logger.d("Welcome Event Id: $welcomeEventId")
// Save chatMessage for the invite...
database.chatMessageDao().upsert( database.giftWrapPayloadDao().insert(
ChatMessage( GiftWrapPayload(
content = "Invited participant to chat", // use profile.humanReadable... id = welcomeEventId,
chatRoomId = nostrGroupId, publicKey = userPublicKey,
senderPublicKey = userPublicKey, chatRoomId = nostrGroupId,
isUserMessage = true, // TODO: This is information message... kind = welcomeEvent.kind,
giftWrapPayloadId = welcomeEventId, tags = welcomeEvent.tags,
marmotGroupEventId = null, content = welcomeEvent.content,
marmotInnerEventId = null createdAt = Instant.fromEpochSeconds(welcomeEvent.createdAt)
)
) )
)
database.marmotKeyPackageDao().getMarmotKeyPackageById(peerKeyPackageEventId)?.let { marmotKeyPackage ->
database.profileDao().getProfileByPublicKey(marmotKeyPackage.publicKey)?.let { profile ->
// Save chatMessage for the invite...
database.chatMessageDao().upsert(
ChatMessage(
content = "Invited ${profile.humanReadableNameOrPubkey() ?: "participant"} to chat", // use profile.humanReadable...
chatRoomId = nostrGroupId,
senderPublicKey = userPublicKey,
isUserMessage = true, // TODO: This is information message...
giftWrapPayloadId = welcomeEventId,
marmotGroupEventId = null,
marmotInnerEventId = null
)
)
}
}
} catch (e: Throwable) {
logger.e("Failed to deliver welcome:", e)
}
} }
private fun encryptedCommitEvent( private fun encryptedCommitEvent(
@@ -136,7 +136,7 @@ class DatabaseChatRepository(
peerPublicKey: HexKey, peerPublicKey: HexKey,
peerKeyPackage: MarmotKeyPackage peerKeyPackage: MarmotKeyPackage
) { ) {
database.marmotOutboundDao().inviteMember( database.marmotOutboundDao().inviteMemberToChatRoom(
localChatRoom = localChatRoom, localChatRoom = localChatRoom,
peerPublicKey = peerPublicKey, peerPublicKey = peerPublicKey,
peerKeyPackage = peerKeyPackage peerKeyPackage = peerKeyPackage
@@ -7,6 +7,7 @@ import at.torch.compose.database.model.UnsignedNostrEvent
import at.torch.compose.database.model.intermdiate.LocalAccount import at.torch.compose.database.model.intermdiate.LocalAccount
import at.torch.compose.database.model.intermdiate.LocalBroadcastNostrEventRequest import at.torch.compose.database.model.intermdiate.LocalBroadcastNostrEventRequest
import at.torch.compose.database.model.types.SynchronizationFilter import at.torch.compose.database.model.types.SynchronizationFilter
import at.torch.compose.extensions.toHex
import at.torch.compose.nostr.Relays import at.torch.compose.nostr.Relays
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent
@@ -406,15 +407,20 @@ class DatabaseNostrRepository(
database.marmotCommitResultDao().getMarmotCommitRequestById( database.marmotCommitResultDao().getMarmotCommitRequestById(
nostrEventId = broadcastNostrEventRequest.nostrEventId nostrEventId = broadcastNostrEventRequest.nostrEventId
)?.let { marmotCommitResult -> )?.let { marmotCommitResult ->
logger.d("marmotCommitResult: $marmotCommitResult")
if (marmotCommitResult.isOneMemberInitialGroupCreation.not()) { if (marmotCommitResult.isOneMemberInitialGroupCreation.not()) {
logger.d("Deliver Welcome")
database.chatRoomDao().findChatRoomById(marmotCommitResult.chatRoomId)?.let { localChatRoom -> database.chatRoomDao().findChatRoomById(marmotCommitResult.chatRoomId)?.let { localChatRoom ->
// TODO: Update status of participant Invitation.PENDING -> Invitation.SENT // TODO: Update status of participant Invitation.PENDING -> Invitation.SENT
logger.d("localChatRoom: $localChatRoom")
marmotCommitResult.welcomeBytes?.let { welcomeBytes -> marmotCommitResult.welcomeBytes?.let { welcomeBytes ->
logger.d("welcomeBytes: ${welcomeBytes.toHex()}")
database.marmotOutboundDao().deliveryWelcome( database.marmotOutboundDao().deliveryWelcome(
nostrGroupId = marmotCommitResult.chatRoomId, nostrGroupId = marmotCommitResult.chatRoomId,
userPublicKey = marmotCommitResult.userPublicKey, userPublicKey = marmotCommitResult.userPublicKey,
welcomeBytes = welcomeBytes, welcomeBytes = welcomeBytes,
peerKeyPackageEventId = marmotCommitResult.peerKeyPackageEventId, peerKeyPackageEventId = marmotCommitResult.peerKeyPackageEventId,
createdAt = marmotCommitResult.createdAt,
relays = Relays.DefaultDMRelayList.map { it.url } // TODO: Get localChatRoom relays... relays = Relays.DefaultDMRelayList.map { it.url } // TODO: Get localChatRoom relays...
) )
} }