Sign and queue nostrEvent
This commit is contained in:
@@ -9,28 +9,82 @@ import androidx.room.Dao
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Relation
|
||||
import androidx.room.Transaction
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.time.Clock
|
||||
|
||||
@Dao
|
||||
abstract class NostrDao(
|
||||
private val database: AuxDatabase
|
||||
) {
|
||||
val logger = Logger.withTag("NostrDao")
|
||||
|
||||
@Transaction
|
||||
open suspend fun insertNostrEvent(
|
||||
open suspend fun publishNostrEvent(
|
||||
nostrEvent: NostrEvent,
|
||||
relayURLs: List<String> = emptyList()
|
||||
relayURLs: List<String>
|
||||
) {
|
||||
database.profileDao().insert(
|
||||
Profile(
|
||||
publicKey = nostrEvent.pubKey,
|
||||
nostrEventId = nostrEvent.id // This is illegal... because the nostrEventId should be the one with all the profile information
|
||||
)
|
||||
)
|
||||
logger.i("Publish Nostr Event: $nostrEvent ($relayURLs)")
|
||||
// TODO: Find or create profile with the pubKey... if not found submit a sync request...
|
||||
val profile = database.profileDao().getProfileByPublicKey(nostrEvent.pubKey)
|
||||
|
||||
// TODO: Find note that is replyTo
|
||||
database.nostrEventDao().insert(nostrEvent)
|
||||
if (profile == null) {
|
||||
val placeHolderProfile = Profile(
|
||||
publicKey = nostrEvent.pubKey,
|
||||
nostrEventId = nostrEvent.pubKey // This is illegal... because the nostrEventId should be the one with all the profile information
|
||||
)
|
||||
val placeHolderProfileNostrEvent = NostrEvent(
|
||||
id = placeHolderProfile.publicKey,
|
||||
kind = 30024,
|
||||
pubKey = placeHolderProfile.publicKey,
|
||||
content = placeHolderProfile.publicKey,
|
||||
createdAt = Clock.System.now(),
|
||||
sig = placeHolderProfile.publicKey,
|
||||
tags = emptyArray(),
|
||||
)
|
||||
database.nostrEventDao().upsert(placeHolderProfileNostrEvent)
|
||||
database.profileDao().upsert(placeHolderProfile)
|
||||
|
||||
// TODO: Sync to find and update profile
|
||||
}
|
||||
|
||||
|
||||
database.nostrEventDao().upsert(nostrEvent)
|
||||
|
||||
nostrEvent.toPost()?.let { post ->
|
||||
if (post.replyToId != null) {
|
||||
// Find or create placeHolder note that is replyTo
|
||||
val replyingToPost = database.postDao().getPostById(post.replyToId)
|
||||
|
||||
if (replyingToPost == null) {
|
||||
// PlaceHolder and sync
|
||||
database.postDao().insert(
|
||||
Post(
|
||||
id = post.replyToId,
|
||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
||||
content = post.replyToId,
|
||||
profilePublicKey = nostrEvent.pubKey // Will get overwriting by sync,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (post.repostId != null) {
|
||||
val repostPost = database.postDao().getPostById(post.repostId)
|
||||
|
||||
if (repostPost == null) {
|
||||
// Placeholder Post...
|
||||
database.postDao().insert(
|
||||
Post(
|
||||
id = post.repostId,
|
||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
||||
content = post.repostId,
|
||||
profilePublicKey = nostrEvent.pubKey // Will get overwriting by sync,
|
||||
)
|
||||
)
|
||||
// Sync repostId
|
||||
}
|
||||
}
|
||||
database.postDao().insert(post)
|
||||
}
|
||||
|
||||
@@ -50,6 +104,10 @@ abstract class NostrDao(
|
||||
database.zapDao().upsert(zap)
|
||||
}
|
||||
// Submit a broadcastRequest...
|
||||
if (nostrEvent.kind == 0) {
|
||||
// Look busy
|
||||
delay(2_500)
|
||||
}
|
||||
relayURLs.forEach { relayURL ->
|
||||
database.broadcastNostrEventRequestDao().upsert(
|
||||
BroadcastNostrEventRequest(
|
||||
@@ -59,13 +117,4 @@ abstract class NostrDao(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class NostrEventAndPost(
|
||||
@Embedded val nostrEvent: NostrEvent,
|
||||
@Relation(
|
||||
parentColumn = "id",
|
||||
entityColumn = "nostrEventId"
|
||||
)
|
||||
val post: Post
|
||||
)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import ac.aux.compose.database.model.NostrEvent
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Upsert
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
@@ -14,6 +15,6 @@ interface NostrEventDao {
|
||||
@Query("SELECT * FROM NostrEvent WHERE id = :id")
|
||||
fun getNostrEventById(id: String): Flow<NostrEvent?>
|
||||
|
||||
@Insert
|
||||
suspend fun insert(nostrEvent: NostrEvent)
|
||||
@Upsert
|
||||
suspend fun upsert(nostrEvent: NostrEvent)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package ac.aux.compose.database.dao
|
||||
|
||||
import ac.aux.compose.database.model.Post
|
||||
import ac.aux.compose.database.model.Profile
|
||||
import ac.aux.compose.database.model.intermdiate.LocalPost
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Insert
|
||||
@@ -12,20 +13,11 @@ import kotlinx.coroutines.flow.Flow
|
||||
@Dao
|
||||
interface PostDao {
|
||||
@Query("SELECT * FROM Post")
|
||||
fun getAllPosts(): List<PostAndProfile>
|
||||
fun getAllPosts(): List<LocalPost>
|
||||
|
||||
@Query("SELECT * FROM Post WHERE id = :id")
|
||||
fun getPostById(id: String): Flow<Post?>
|
||||
fun getPostById(id: String): Post?
|
||||
|
||||
@Insert
|
||||
suspend fun insert(post: Post)
|
||||
|
||||
data class PostAndProfile(
|
||||
@Embedded val post: Post,
|
||||
@Relation(
|
||||
parentColumn = "profilePublicKey",
|
||||
entityColumn = "publicKey"
|
||||
)
|
||||
val profile: Profile
|
||||
)
|
||||
}
|
||||
@@ -1,18 +1,11 @@
|
||||
package ac.aux.compose.database.dao
|
||||
|
||||
import ac.aux.compose.database.model.NostrEvent
|
||||
import ac.aux.compose.database.model.Post
|
||||
import ac.aux.compose.database.model.Profile
|
||||
import ac.aux.compose.database.model.UnsignedNostrEvent
|
||||
import ac.aux.compose.database.model.intermdiate.LocalProfile
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Relation
|
||||
import androidx.room.Upsert
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface ProfileDao {
|
||||
@@ -22,15 +15,10 @@ interface ProfileDao {
|
||||
@Query("SELECT * FROM Profile WHERE publicKey = :publicKey")
|
||||
fun getProfileByPublicKey(publicKey: String): Profile?
|
||||
|
||||
@Insert(
|
||||
onConflict = OnConflictStrategy.IGNORE
|
||||
)
|
||||
suspend fun insert(profile: Profile)
|
||||
@Insert
|
||||
suspend fun insertPlaceholderProfile(profile: Profile)
|
||||
|
||||
@Upsert
|
||||
suspend fun upsert(profile: Profile)
|
||||
|
||||
@Query("SELECT * FROM UnsignedNostrEvent WHERE kind = 0 AND pubKey = :publicKey")
|
||||
fun observeProfile(publicKey: String): Flow<LocalProfile?>
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package ac.aux.compose.database.dao
|
||||
|
||||
import ac.aux.compose.database.model.NostrEvent
|
||||
import ac.aux.compose.database.model.UnsignedNostrEvent
|
||||
import ac.aux.compose.database.model.intermdiate.LocalProfile
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
@@ -21,4 +22,10 @@ interface UnsignedNostrEventDao {
|
||||
|
||||
@Upsert
|
||||
suspend fun upsert(unsignedNostrEvent: UnsignedNostrEvent): Long
|
||||
|
||||
@Query("SELECT * FROM UnsignedNostrEvent WHERE kind = 0 AND pubKey = :publicKey")
|
||||
fun observeProfile(publicKey: String): Flow<LocalProfile?>
|
||||
|
||||
@Query("SELECT * FROM UnsignedNostrEvent WHERE pubKey = :publicKey AND signedNostrEventId IS NULL")
|
||||
fun observeUnsignedNostrEvents(publicKey: String): Flow<UnsignedNostrEvent>
|
||||
}
|
||||
@@ -38,7 +38,7 @@ data class NostrEvent(
|
||||
val sig: HexKey,
|
||||
|
||||
override val createdAt: Instant = Clock.System.now(),
|
||||
override val updatedAt: 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,
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package ac.aux.compose.database.model.intermdiate
|
||||
|
||||
import ac.aux.compose.database.model.Post
|
||||
import ac.aux.compose.database.model.Profile
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Relation
|
||||
|
||||
data class LocalPost(
|
||||
@Embedded val post: Post,
|
||||
@Relation(
|
||||
parentColumn = "profilePublicKey",
|
||||
entityColumn = "publicKey"
|
||||
)
|
||||
val profile: Profile
|
||||
)
|
||||
@@ -1,6 +1,7 @@
|
||||
package ac.aux.compose.database.repository
|
||||
|
||||
import ac.aux.compose.database.AuxDatabase
|
||||
import ac.aux.compose.database.model.NostrEvent
|
||||
import ac.aux.compose.database.model.Profile
|
||||
import ac.aux.compose.database.model.UnsignedNostrEvent
|
||||
import ac.aux.compose.database.model.intermdiate.LocalProfile
|
||||
@@ -22,7 +23,11 @@ class DatabaseNostrRepository(
|
||||
val logger = Logger.withTag(TAG)
|
||||
|
||||
override suspend fun observeProfile(publicKey: HexKey): Flow<LocalProfile?> {
|
||||
return database.profileDao().observeProfile(publicKey)
|
||||
return database.unsignedNostrEventDao().observeProfile(publicKey)
|
||||
}
|
||||
|
||||
override suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<UnsignedNostrEvent> {
|
||||
return database.unsignedNostrEventDao().observeUnsignedNostrEvents(publicKey)
|
||||
}
|
||||
|
||||
override suspend fun createNewProfile(
|
||||
@@ -66,4 +71,11 @@ class DatabaseNostrRepository(
|
||||
database.wipeData()
|
||||
}
|
||||
|
||||
override suspend fun publishNostrEvent(nostrEvent: NostrEvent, relayURLs: List<String>) {
|
||||
database.nostrDao().publishNostrEvent(
|
||||
nostrEvent,
|
||||
relayURLs
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,13 +7,5 @@ import ac.aux.compose.repository.PostRepository
|
||||
class DatabasePostRepository(
|
||||
private val database: AuxDatabase
|
||||
): PostRepository {
|
||||
override suspend fun publishPost(
|
||||
nostrEvent: NostrEvent
|
||||
) {
|
||||
val nostrDao = database.nostrDao()
|
||||
|
||||
nostrDao.insertNostrEvent(
|
||||
nostrEvent
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,8 +9,12 @@ object SeedManager {
|
||||
privKey = Random(21_012_256L).nextBytes(32)
|
||||
)
|
||||
|
||||
fun activePublicKey(): ByteArray {
|
||||
fun activeKeyPair(): KeyPair {
|
||||
// TODO: Actually set and get a pair...
|
||||
return tempDevelopmentKeyPair.pubKey
|
||||
return tempDevelopmentKeyPair
|
||||
}
|
||||
|
||||
fun activePublicKey(): ByteArray {
|
||||
return activeKeyPair().pubKey
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package ac.aux.compose.nostr
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
|
||||
object Relays {
|
||||
val nos = RelayUrlNormalizer.normalize("wss://nos.lol")
|
||||
val mom = RelayUrlNormalizer.normalize("wss://nostr.mom")
|
||||
val primal = RelayUrlNormalizer.normalize("wss://relay.primal.net")
|
||||
val damus = RelayUrlNormalizer.normalize("wss://relay.damus.io")
|
||||
val wine = RelayUrlNormalizer.normalize("wss://nostr.wine")
|
||||
|
||||
val where = RelayUrlNormalizer.normalize("wss://relay.noswhere.com")
|
||||
val elites = RelayUrlNormalizer.normalize("wss://nostrelites.org")
|
||||
|
||||
val bitcoiner = RelayUrlNormalizer.normalize("wss://nostr.bitcoiner.social")
|
||||
val oxtr = RelayUrlNormalizer.normalize("wss://nostr.oxtr.dev")
|
||||
|
||||
val nostoday = RelayUrlNormalizer.normalize("wss://search.nos.today")
|
||||
val antiprimal = RelayUrlNormalizer.normalize("wss://antiprimal.net")
|
||||
val ditto = RelayUrlNormalizer.normalize("wss://relay.ditto.pub")
|
||||
|
||||
val auth = RelayUrlNormalizer.normalize("wss://auth.nostr1.com")
|
||||
val oxchat = RelayUrlNormalizer.normalize("wss://relay.0xchat.com")
|
||||
|
||||
val news = RelayUrlNormalizer.normalize("wss://news.utxo.one")
|
||||
|
||||
val purplepages = RelayUrlNormalizer.normalize("wss://purplepag.es")
|
||||
val coracle = RelayUrlNormalizer.normalize("wss://indexer.coracle.social")
|
||||
val userkinds = RelayUrlNormalizer.normalize("wss://user.kindpag.es")
|
||||
val yabu = RelayUrlNormalizer.normalize("wss://directory.yabu.me")
|
||||
val nostr1 = RelayUrlNormalizer.normalize("wss://profiles.nostr1.com")
|
||||
|
||||
val bootstrapInboxRelaySet = setOf(damus, primal, mom, nos, bitcoiner, oxtr, yabu)
|
||||
val eventFinderRelaySet = setOf(wine, damus, primal, mom, nos, bitcoiner, oxtr)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package ac.aux.compose.repository
|
||||
|
||||
import ac.aux.compose.database.model.NostrEvent
|
||||
import ac.aux.compose.database.model.Profile
|
||||
import ac.aux.compose.database.model.UnsignedNostrEvent
|
||||
import ac.aux.compose.database.model.intermdiate.LocalProfile
|
||||
@@ -9,6 +10,8 @@ import kotlinx.coroutines.flow.Flow
|
||||
interface NostrRepository {
|
||||
suspend fun observeProfile(publicKey: HexKey): Flow<LocalProfile?>
|
||||
|
||||
suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<UnsignedNostrEvent>
|
||||
|
||||
suspend fun createNewProfile(
|
||||
publicKey: HexKey,
|
||||
name: String,
|
||||
@@ -23,10 +26,19 @@ interface NostrRepository {
|
||||
|
||||
suspend fun wipeDatabase()
|
||||
|
||||
suspend fun publishNostrEvent(
|
||||
nostrEvent: NostrEvent,
|
||||
relayURLs: List<String> = emptyList()
|
||||
)
|
||||
|
||||
companion object {
|
||||
val NO_OP_NOSTR_REPOSITORY = object : NostrRepository {
|
||||
override suspend fun observeProfile(publicKey: HexKey): Flow<LocalProfile?> {
|
||||
TODO("")
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<UnsignedNostrEvent> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun createNewProfile(
|
||||
@@ -44,6 +56,10 @@ interface NostrRepository {
|
||||
|
||||
override suspend fun wipeDatabase() {
|
||||
}
|
||||
|
||||
override suspend fun publishNostrEvent(nostrEvent: NostrEvent, relayURLs: List<String>) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,4 @@ package ac.aux.compose.repository
|
||||
import ac.aux.compose.database.model.NostrEvent
|
||||
|
||||
interface PostRepository {
|
||||
suspend fun publishPost(nostrEvent: NostrEvent)
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package ac.aux.compose.ui.view.model
|
||||
|
||||
import ac.aux.compose.database.model.NostrEvent
|
||||
import ac.aux.compose.managers.SeedManager
|
||||
import ac.aux.compose.nostr.Relays
|
||||
import ac.aux.compose.repository.NostrRepository
|
||||
import ac.aux.compose.ui.view.state.NavigationUIState
|
||||
import androidx.lifecycle.ViewModel
|
||||
@@ -9,16 +11,15 @@ import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.getAndUpdate
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.time.Instant
|
||||
|
||||
class NavigationViewModel(
|
||||
initialNavigationUIState: NavigationUIState,
|
||||
@@ -48,12 +49,46 @@ class NavigationViewModel(
|
||||
val navigationUIState = _navigationUIState.asStateFlow()
|
||||
|
||||
init {
|
||||
observeUnsignedNostrEvents()
|
||||
observeProfile()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private fun observeUnsignedNostrEvents() {
|
||||
val tempSigner = NostrSignerSync(
|
||||
SeedManager.activeKeyPair()
|
||||
)
|
||||
logger.i { "observeUnsignedNostrEvents" }
|
||||
viewModelScope.launch {
|
||||
nostrRepository.observeUnsignedNostrEvents(
|
||||
publicKey = SeedManager.activePublicKey().toHexKey()
|
||||
).collect { unsignedNostrEvent ->
|
||||
|
||||
val event = tempSigner.signNormal<Event>(
|
||||
createdAt = unsignedNostrEvent.createdAt.toEpochMilliseconds(),
|
||||
kind = unsignedNostrEvent.kind,
|
||||
tags = unsignedNostrEvent.tags,
|
||||
content = unsignedNostrEvent.content
|
||||
)
|
||||
|
||||
nostrRepository.publishNostrEvent(
|
||||
NostrEvent(
|
||||
id = event.id,
|
||||
pubKey = event.pubKey,
|
||||
kind = event.kind,
|
||||
tags = event.tags,
|
||||
content = event.content,
|
||||
createdAt = Instant.fromEpochMilliseconds(event.createdAt),
|
||||
sig = event.sig
|
||||
),
|
||||
relayURLs = Relays.eventFinderRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url }
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun observeProfile() {
|
||||
logger.i("observeProfile")
|
||||
viewModelScope.launch {
|
||||
|
||||
Reference in New Issue
Block a user