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 8feb6d4e..f54bbe9e 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 @@ -50,6 +50,7 @@ import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent import com.vitorpamplona.quartz.nip40Expiration.expiration import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent import kotlin.io.encoding.Base64 +import kotlin.math.log import kotlin.time.Clock import kotlin.time.Instant @@ -393,12 +394,14 @@ abstract class NostrDao( mlsGroup = mlsGroup, groupEvent = groupEvent )?.let { groupEventResult -> + logger.d("groupEventResult: $groupEventResult") ChatMessage.fromGroupEventResult( database = database, activeKeyPair = activeKeyPair, groupEvent = groupEvent, groupEventResult = groupEventResult, )?.let { chatMessage -> + logger.d("chatMessage: $chatMessage") database.chatMessageDao().upsert( chatMessage ) 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 c0311bc4..caedee62 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 @@ -8,6 +8,8 @@ import androidx.room3.RawQuery import androidx.room3.RoomRawQuery import androidx.room3.Transaction import androidx.room3.Upsert +import at.torch.compose.database.model.NostrEvent +import com.vitorpamplona.quartz.marmot.mip03GroupMessages.GroupEvent import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.Kind import kotlinx.coroutines.flow.Flow @@ -67,7 +69,7 @@ interface NostrEventDao { kinds: Array, since: Instant, limit: Int - ): List + ): List @Transaction @Query("SELECT * FROM NostrEvent WHERE content LIKE '%' || :search || '%' AND kind in (:kinds) AND createdAt > :since ORDER BY createdAt ASC LIMIT :limit") @@ -76,7 +78,7 @@ interface NostrEventDao { search: String, since: Instant, limit: Int - ): List + ): List @Transaction @Query("SELECT * FROM NostrEvent WHERE kind in (:kinds) AND id in (:ids) AND createdAt > :since ORDER BY createdAt ASC LIMIT :limit") @@ -85,7 +87,7 @@ interface NostrEventDao { ids: Array, since: Instant, limit: Int - ): List + ): List @Transaction @Query("SELECT * FROM NostrEvent WHERE id in (:ids) AND createdAt > :since ORDER BY createdAt ASC LIMIT :limit") @@ -93,7 +95,15 @@ interface NostrEventDao { ids: Array, since: Instant, limit: Int - ): List + ): List + + + @Transaction + @Query("SELECT * FROM NostrEvent WHERE kind = ${GroupEvent.KIND} AND createdAt > :since ORDER BY createdAt ASC LIMIT :limit") + fun getMLSGroupEvents( + since: Instant, + limit: Int + ): List @Transaction @Query("SELECT * FROM NostrEvent WHERE kind in (:kinds) AND pubKey in (:authors) AND createdAt > :since ORDER BY createdAt ASC LIMIT :limit") @@ -102,7 +112,7 @@ interface NostrEventDao { authors: Array, since: Instant, limit: Int - ): List + ): List @Transaction @Query("SELECT * FROM NostrEvent WHERE tags LIKE '%' || :publicKey || '%' AND kind in (:kinds) AND createdAt > :since ORDER BY createdAt ASC LIMIT :limit") @@ -111,7 +121,7 @@ interface NostrEventDao { publicKey: HexKey, since: Instant, limit: Int - ): List + ): List @Transaction @Query("SELECT * FROM NostrEvent WHERE tags LIKE '%' || :eventId || '%reply%' AND kind in (:kinds) AND createdAt > :since ORDER BY createdAt ASC LIMIT :limit") @@ -120,26 +130,26 @@ interface NostrEventDao { eventId: HexKey, since: Instant, limit: Int - ): List + ): List @Query("SELECT * FROM NostrEvent WHERE kind in (:kinds) ORDER BY savedAt DESC") - fun getAllNostrEvents(kinds: Array): List + fun getAllNostrEvents(kinds: Array): List @Transaction @Query("SELECT * FROM NostrEvent WHERE id = :id") fun observeNostrEventById(id: String): Flow @Query("SELECT * FROM NostrEvent WHERE id = :id") - fun getNostrEventById(id: String): at.torch.compose.database.model.NostrEvent? + fun getNostrEventById(id: String): NostrEvent? @Query("SELECT * FROM NostrEvent WHERE pubKey = :publicKey AND kind = :kind ORDER BY createdAt DESC") - fun getNostrEventByPublicKeyAndKind(publicKey: HexKey, kind: Kind): at.torch.compose.database.model.NostrEvent? + fun getNostrEventByPublicKeyAndKind(publicKey: HexKey, kind: Kind): NostrEvent? @Upsert - suspend fun upsert(nostrEvent: at.torch.compose.database.model.NostrEvent) + suspend fun upsert(nostrEvent: NostrEvent) @Insert( onConflict = IGNORE ) - suspend fun insert(nostrEvent: at.torch.compose.database.model.NostrEvent) + suspend fun insert(nostrEvent: NostrEvent) } \ No newline at end of file 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 d0e6890f..4d94f038 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 @@ -1,29 +1,16 @@ 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.NostrEvent import at.torch.compose.database.model.UnsignedNostrEvent import at.torch.compose.database.model.intermdiate.LocalAccount +import at.torch.compose.database.model.types.SynchronizationFilter import at.torch.compose.nostr.Relays import co.touchlab.kermit.Logger -import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageEvent import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent -import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRotationManager.Companion.KEY_PACKAGE_LIFETIME_SECONDS -import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageUtils -import com.vitorpamplona.quartz.marmot.mls.crypto.Ed25519 -import com.vitorpamplona.quartz.marmot.mls.crypto.MlsCryptoProvider -import com.vitorpamplona.quartz.marmot.mls.crypto.X25519 -import com.vitorpamplona.quartz.marmot.mls.messages.KeyPackageBundle -import com.vitorpamplona.quartz.marmot.mls.messages.MlsKeyPackage -import com.vitorpamplona.quartz.marmot.mls.tree.Capabilities -import com.vitorpamplona.quartz.marmot.mls.tree.Credential -import com.vitorpamplona.quartz.marmot.mls.tree.Extension -import com.vitorpamplona.quartz.marmot.mls.tree.LeafNode -import com.vitorpamplona.quartz.marmot.mls.tree.LeafNodeSource -import com.vitorpamplona.quartz.marmot.mls.tree.Lifetime import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.Kind -import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray -import com.vitorpamplona.quartz.nip01Core.core.toHexKey import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent @@ -51,13 +38,11 @@ import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent import com.vitorpamplona.quartz.nip51Lists.relayLists.RelayFeedsListEvent import com.vitorpamplona.quartz.nip51Lists.tags.RelayTag import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent -import com.vitorpamplona.quartz.utils.TimeUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import kotlin.io.encoding.Base64 import kotlin.time.Clock import kotlin.time.Instant @@ -219,7 +204,7 @@ class DatabaseNostrRepository( createdAt = Instant.fromEpochSeconds(profileEventTemplate.createdAt), tags = profileEventTemplate.tags, content = profileEventTemplate.content, - signedAt = _root_ide_package_.at.torch.compose.database.GENESIS_AT // This will keep us from trying to sign this event... but we should automatically sync the npub profile + signedAt = GENESIS_AT // This will keep us from trying to sign this event... but we should automatically sync the npub profile ) ) } @@ -372,7 +357,7 @@ class DatabaseNostrRepository( override suspend fun publishNostrEvent( unsignedNostrEvent: UnsignedNostrEvent, - nostrEvent: at.torch.compose.database.model.NostrEvent, + nostrEvent: NostrEvent, relayURLs: List, activeKeyPair: KeyPair ) { @@ -462,7 +447,7 @@ class DatabaseNostrRepository( } override suspend fun saveNostrEvent( - nostrEvent: at.torch.compose.database.model.NostrEvent, + nostrEvent: NostrEvent, synchronizeNostrEventRequest: at.torch.compose.database.model.SynchronizeNostrEventRequest, synchronizationRelayURLs: List, activeKeyPair: KeyPair, @@ -494,7 +479,7 @@ class DatabaseNostrRepository( } override suspend fun saveNostrEvent( - nostrEvent: at.torch.compose.database.model.NostrEvent, + nostrEvent: NostrEvent, negentropySynchronizeRequest: at.torch.compose.database.model.NegentropySynchronizeRequest, synchronizationRelayURLs: List, activeKeyPair: KeyPair @@ -544,7 +529,7 @@ class DatabaseNostrRepository( ) } - override suspend fun getNostrEvent(publicKey: HexKey, kind: Kind): at.torch.compose.database.model.NostrEvent? { + override suspend fun getNostrEvent(publicKey: HexKey, kind: Kind): NostrEvent? { return database.nostrEventDao().getNostrEventByPublicKeyAndKind( publicKey = publicKey, kind = kind @@ -562,7 +547,7 @@ class DatabaseNostrRepository( ) } - override suspend fun observeNostrFeed(synchronizationFilter: at.torch.compose.database.model.types.SynchronizationFilter): Flow> { + override suspend fun observeNostrFeed(synchronizationFilter: SynchronizationFilter): Flow> { return when { synchronizationFilter.kinds != null && synchronizationFilter.search != null -> { logger.d("observeFilteredNostrEvents: $synchronizationFilter") @@ -611,10 +596,11 @@ class DatabaseNostrRepository( } } - override suspend fun getNostrFeedIds( - synchronizationFilters: Array, + override suspend fun getNegentropicNostrFeedIds( + synchronizationFilters: Array, applyLimits: Boolean - ): List { + ): List { + logger.d("getNegentropicNostrFeedIds: ${synchronizationFilters.contentToString()}") if (synchronizationFilters.size == 1) { val synchronizationFilter = synchronizationFilters.first() @@ -624,7 +610,7 @@ class DatabaseNostrRepository( database.nostrEventDao().getFilteredNostrEvents( kinds = synchronizationFilter.kinds, search = synchronizationFilter.search, - since = synchronizationFilter.since ?: _root_ide_package_.at.torch.compose.database.GENESIS_AT, + since = synchronizationFilter.since ?: GENESIS_AT, limit = if (applyLimits) { synchronizationFilter.limit ?: 50 } else { @@ -637,7 +623,7 @@ class DatabaseNostrRepository( database.nostrEventDao().getFilteredNostrEvents( kinds = synchronizationFilter.kinds, ids = synchronizationFilter.ids, - since = synchronizationFilter.since ?: _root_ide_package_.at.torch.compose.database.GENESIS_AT, + since = synchronizationFilter.since ?: GENESIS_AT, limit = if (applyLimits) { synchronizationFilter.limit ?: 50 } else { @@ -649,7 +635,7 @@ class DatabaseNostrRepository( logger.d("getNostrEventsByIds($applyLimits): $synchronizationFilter") database.nostrEventDao().getNostrEventsByIds( ids = synchronizationFilter.ids, - since = synchronizationFilter.since ?: _root_ide_package_.at.torch.compose.database.GENESIS_AT, + since = synchronizationFilter.since ?: GENESIS_AT, limit = if (applyLimits) { synchronizationFilter.limit ?: 50 } else { @@ -662,7 +648,7 @@ class DatabaseNostrRepository( database.nostrEventDao().getAuthoredNostrEvents( kinds = synchronizationFilter.kinds, authors = synchronizationFilter.authors, - since = synchronizationFilter.since ?: _root_ide_package_.at.torch.compose.database.GENESIS_AT, + since = synchronizationFilter.since ?: GENESIS_AT, limit = if (applyLimits) { synchronizationFilter.limit ?: 50 } else { @@ -688,7 +674,20 @@ class DatabaseNostrRepository( database.nostrEventDao().getPublicKeyMentionedNostrEvents( kinds = synchronizationFilter.kinds, publicKey = synchronizationFilter.tags["p"]?.first()!!, - since = synchronizationFilter.since ?: _root_ide_package_.at.torch.compose.database.GENESIS_AT, + since = synchronizationFilter.since ?: GENESIS_AT, + limit = if (applyLimits) { + synchronizationFilter.limit ?: 50 + } else { + -1 + } + ) + } + synchronizationFilter.kinds?.isNotEmpty() == true && synchronizationFilter.tags?.contains("h") == true -> { + val chatRoomIds = synchronizationFilter.tags["h"]?.toTypedArray() ?: emptyArray() + logger.d("ChatRoomIds: ${chatRoomIds.contentToString()}") + logger.d("getMLSGroupEvents($applyLimits): $synchronizationFilter") + database.nostrEventDao().getMLSGroupEvents( + since = synchronizationFilter.since ?: GENESIS_AT, limit = if (applyLimits) { synchronizationFilter.limit ?: 50 } else { @@ -702,7 +701,7 @@ class DatabaseNostrRepository( kinds = arrayOf( TextNoteEvent.KIND, ), - since = synchronizationFilter.since ?: _root_ide_package_.at.torch.compose.database.GENESIS_AT, + since = synchronizationFilter.since ?: GENESIS_AT, limit = if (applyLimits) { synchronizationFilter.limit ?: 50 } else { diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/managers/MarmotInboundManager.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/managers/MarmotInboundManager.kt index 0d3c615f..fc681d26 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/managers/MarmotInboundManager.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/managers/MarmotInboundManager.kt @@ -24,6 +24,7 @@ import com.vitorpamplona.quartz.marmot.mls.group.MlsGroup import com.vitorpamplona.quartz.marmot.mls.group.RetainedEpochSecrets import com.vitorpamplona.quartz.marmot.mls.schedule.KeySchedule import com.vitorpamplona.quartz.marmot.mls.schedule.SecretTree +import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair @@ -136,9 +137,9 @@ object MarmotInboundManager { // could mint events claiming a different author. Non-event // payloads (raw bytes via buildGroupEventFromBytes) bypass // this check since there is no author field to verify. - val innerEvent = - com.vitorpamplona.quartz.nip01Core.core.Event - .fromJsonOrNull(innerJson) + val innerEvent = Event.fromJsonOrNull(innerJson) + logger.d("innerEvent: ${innerEvent?.toJson()}") + if (innerEvent != null) { val senderIdentity = mlsGroup.memberIdentityHex(decrypted.senderLeafIndex) if (senderIdentity == null || innerEvent.pubKey != senderIdentity) { 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 cc984c70..93b9622e 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/repository/NostrRepository.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/repository/NostrRepository.kt @@ -98,7 +98,7 @@ interface NostrRepository { suspend fun observeNostrFeed(synchronizationFilter: at.torch.compose.database.model.types.SynchronizationFilter): Flow> - suspend fun getNostrFeedIds( + suspend fun getNegentropicNostrFeedIds( synchronizationFilters: Array, applyLimits: Boolean = true ): List @@ -266,7 +266,7 @@ interface NostrRepository { TODO("Not yet implemented") } - override suspend fun getNostrFeedIds( + override suspend fun getNegentropicNostrFeedIds( synchronizationFilters: Array, applyLimits: Boolean ): List { 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 61b8c3bb..ae60f481 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 @@ -197,7 +197,7 @@ class SynchronizationViewModel( mutex.withLock { logger.i("negentropySynchronizeRequest: $negentropySynchronizeRequest") - val events = nostrRepository.getNostrFeedIds( + val events = nostrRepository.getNegentropicNostrFeedIds( arrayOf( negentropySynchronizeRequest.synchronizationFilter ),