Storing nostr events and handling their different kinds
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package ac.aux.compose.database
|
||||
|
||||
import ac.aux.compose.database.converters.Converters
|
||||
import ac.aux.compose.database.dao.BroadcastNostrEventReceiptDao
|
||||
import ac.aux.compose.database.dao.BroadcastNostrEventRequestDao
|
||||
import ac.aux.compose.database.dao.NostrDao
|
||||
import ac.aux.compose.database.dao.NostrEventDao
|
||||
import ac.aux.compose.database.dao.PostDao
|
||||
@@ -31,6 +33,8 @@ import androidx.room.TypeConverters
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class AuxDatabase: RoomDatabase() {
|
||||
abstract fun broadcastNostrEventReceiptDao(): BroadcastNostrEventReceiptDao
|
||||
abstract fun broadcastNostrEventRequestDao(): BroadcastNostrEventRequestDao
|
||||
abstract fun nostrDao(): NostrDao
|
||||
|
||||
abstract fun nostrEventDao(): NostrEventDao
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package ac.aux.compose.database.dao
|
||||
|
||||
import ac.aux.compose.database.model.BroadcastNostrEventReceipt
|
||||
import ac.aux.compose.database.model.BroadcastNostrEventRequest
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import androidx.room.Upsert
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface BroadcastNostrEventReceiptDao {
|
||||
@Query("SELECT * FROM BroadcastNostrEventReceipt")
|
||||
fun getAllBroadcastNostrEventReceipts(): List<BroadcastNostrEventReceipt>
|
||||
|
||||
@Query("SELECT * FROM BroadcastNostrEventReceipt WHERE id = :id")
|
||||
fun getBroadcastNostrEventReceiptById(id: Long): Flow<BroadcastNostrEventReceipt?>
|
||||
|
||||
@Query("SELECT * FROM BroadcastNostrEventReceipt WHERE nostrEventId = :nostrEventId")
|
||||
fun getBroadcastNostrEventReceiptByNostrEventId(nostrEventId: String): Flow<BroadcastNostrEventReceipt?>
|
||||
|
||||
@Upsert
|
||||
suspend fun upsert(broadcastNostrEventReceipt: BroadcastNostrEventReceipt)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package ac.aux.compose.database.dao
|
||||
|
||||
import ac.aux.compose.database.model.BroadcastNostrEventRequest
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import androidx.room.Upsert
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface BroadcastNostrEventRequestDao {
|
||||
@Query("SELECT * FROM BroadcastNostrEventRequest")
|
||||
fun getAllBroadcastNostrEventRequests(): List<BroadcastNostrEventRequest>
|
||||
|
||||
@Query("SELECT * FROM BroadcastNostrEventRequest WHERE id = :id")
|
||||
fun getBroadcastNostrEventRequestById(id: Long): Flow<BroadcastNostrEventRequest?>
|
||||
|
||||
@Query("SELECT * FROM BroadcastNostrEventRequest WHERE nostrEventId = :nostrEventId")
|
||||
fun getBroadcastNostrEventRequestByNostrEventId(nostrEventId: String): Flow<BroadcastNostrEventRequest?>
|
||||
|
||||
@Upsert
|
||||
suspend fun upsert(broadcastNostrEventRequest: BroadcastNostrEventRequest)
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package ac.aux.compose.database.dao
|
||||
|
||||
import ac.aux.compose.database.AuxDatabase
|
||||
import ac.aux.compose.database.model.BroadcastNostrEventRequest
|
||||
import ac.aux.compose.database.model.NostrEvent
|
||||
import ac.aux.compose.database.model.Post
|
||||
import ac.aux.compose.database.model.Profile
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Relation
|
||||
@@ -13,13 +15,48 @@ abstract class NostrDao(
|
||||
private val database: AuxDatabase
|
||||
) {
|
||||
@Transaction
|
||||
open suspend fun insertNostrEvent(nostrEvent: NostrEvent) {
|
||||
// TODO: Find or create profile with nostrEvent.pubkey
|
||||
open suspend fun insertNostrEvent(
|
||||
nostrEvent: NostrEvent,
|
||||
relayURLs: List<String> = emptyList()
|
||||
) {
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
// TODO: Find note that is replyTo
|
||||
database.nostrEventDao().insert(nostrEvent)
|
||||
|
||||
// TODO: Get post from nostrEvent
|
||||
// database.postDao().insert(nostrEvent.post) // TODO: Make use of the nostrEventId...
|
||||
nostrEvent.toPost()?.let { post ->
|
||||
database.postDao().insert(post)
|
||||
}
|
||||
|
||||
nostrEvent.toProfile()?.let { profile ->
|
||||
database.profileDao().upsert(profile)
|
||||
}
|
||||
|
||||
nostrEvent.toReaction()?.let { reaction ->
|
||||
database.reactionDao().upsert(reaction)
|
||||
}
|
||||
|
||||
nostrEvent.toRepost()?.let { repost ->
|
||||
database.repostDao().upsert(repost)
|
||||
}
|
||||
|
||||
nostrEvent.toZap()?.let { zap ->
|
||||
database.zapDao().upsert(zap)
|
||||
}
|
||||
// Submit a broadcastRequest...
|
||||
relayURLs.forEach { relayURL ->
|
||||
database.broadcastNostrEventRequestDao().upsert(
|
||||
BroadcastNostrEventRequest(
|
||||
nostrEventId = nostrEvent.id,
|
||||
relayURL = relayURL
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class NostrEventAndPost(
|
||||
|
||||
@@ -5,7 +5,9 @@ import ac.aux.compose.database.model.Post
|
||||
import ac.aux.compose.database.model.Profile
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Upsert
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
@@ -16,6 +18,11 @@ interface ProfileDao {
|
||||
@Query("SELECT * FROM Profile WHERE publicKey = :publicKey")
|
||||
fun getProfileByPublicKey(publicKey: String): Flow<Profile?>
|
||||
|
||||
@Insert
|
||||
@Insert(
|
||||
onConflict = OnConflictStrategy.IGNORE
|
||||
)
|
||||
suspend fun insert(profile: Profile)
|
||||
|
||||
@Upsert
|
||||
suspend fun upsert(profile: Profile)
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
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.Reaction
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Upsert
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
@@ -17,6 +15,6 @@ interface ReactionDao {
|
||||
@Query("SELECT * FROM Reaction WHERE id = :id")
|
||||
fun getReactionById(id: String): Flow<Reaction?>
|
||||
|
||||
@Insert
|
||||
suspend fun insert(reaction: Reaction)
|
||||
@Upsert
|
||||
suspend fun upsert(reaction: Reaction)
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
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.Repost
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Upsert
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
@@ -16,6 +15,6 @@ interface RepostDao {
|
||||
@Query("SELECT * FROM Repost WHERE id = :id")
|
||||
fun getRepostById(id: String): Flow<Repost?>
|
||||
|
||||
@Insert
|
||||
suspend fun insert(repost: Repost)
|
||||
@Upsert
|
||||
suspend fun upsert(repost: Repost)
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
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.Zap
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Upsert
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
@@ -13,6 +12,6 @@ interface ZapDao {
|
||||
@Query("SELECT * FROM Zap WHERE id = :id")
|
||||
fun getZapById(id: String): Flow<Zap?>
|
||||
|
||||
@Insert
|
||||
suspend fun insert(zap: Zap)
|
||||
@Upsert
|
||||
suspend fun upsert(zap: Zap)
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
)
|
||||
data class BroadcastNostrEventReceipt(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
val id: Long,
|
||||
val id: Long = 0,
|
||||
override val nostrEventId: HexKey,
|
||||
val isAccepted: Boolean,
|
||||
val relayURL: String,
|
||||
|
||||
@@ -21,9 +21,9 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
Index("status"),
|
||||
],
|
||||
)
|
||||
data class BroadcastRequest(
|
||||
data class BroadcastNostrEventRequest(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
val id: Long,
|
||||
val id: Long = 0,
|
||||
override val nostrEventId: HexKey,
|
||||
val status: String = "pending",
|
||||
val relayURL: String,
|
||||
|
||||
@@ -4,15 +4,19 @@ import ac.aux.compose.database.model.traits.BroadcastableEntity
|
||||
import ac.aux.compose.database.model.traits.LocalStoreEntity
|
||||
import ac.aux.compose.database.model.traits.SoftDeletableEntity
|
||||
import ac.aux.compose.database.model.traits.TimestampedEntity
|
||||
import ac.aux.compose.database.model.traits.UserViewableEntity
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Fts4
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.utils.EventFactory
|
||||
import kotlin.String
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
@@ -32,6 +36,8 @@ data class NostrEvent(
|
||||
override val deletedAt: Instant? = null,
|
||||
override val broadcastedAt: Instant? = null,
|
||||
): TimestampedEntity, LocalStoreEntity, BroadcastableEntity, SoftDeletableEntity {
|
||||
val logger = Logger.withTag("NostrEvent")
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || this::class != other::class) return false
|
||||
@@ -59,4 +65,163 @@ data class NostrEvent(
|
||||
result = 31 * result + sig.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
fun toPost(): Post? = try {
|
||||
if (kind == TextNoteEvent.KIND) {
|
||||
val textNote = EventFactory.create<TextNoteEvent>(
|
||||
id = id,
|
||||
pubKey = pubKey,
|
||||
createdAt = createdAt.toEpochMilliseconds(),
|
||||
kind = kind,
|
||||
tags = tags,
|
||||
content = content,
|
||||
sig = sig
|
||||
)
|
||||
|
||||
return Post(
|
||||
id = textNote.id,
|
||||
repostId = null,
|
||||
quote = null,
|
||||
content = textNote.content,
|
||||
nostrEventId = id,
|
||||
replyToId = textNote.replyingTo(),
|
||||
profilePublicKey = textNote.pubKey,
|
||||
createdAt = Instant.fromEpochMilliseconds(textNote.createdAt),
|
||||
updatedAt = Instant.fromEpochMilliseconds(textNote.createdAt),
|
||||
)
|
||||
}
|
||||
return null
|
||||
} catch (e: Throwable) {
|
||||
logger.e("Failed to return Post: ", e)
|
||||
return null
|
||||
}
|
||||
|
||||
fun toProfile(): Profile? = try {
|
||||
if (kind == MetadataEvent.KIND) {
|
||||
val metadataEvent = EventFactory.create<MetadataEvent>(
|
||||
id = id,
|
||||
pubKey = pubKey,
|
||||
createdAt = createdAt.toEpochMilliseconds(),
|
||||
kind = kind,
|
||||
tags = tags,
|
||||
content = content,
|
||||
sig = sig
|
||||
)
|
||||
|
||||
return metadataEvent.contactMetaData()?.let { userMetadata ->
|
||||
return Profile(
|
||||
publicKey = pubKey,
|
||||
nostrEventId = id,
|
||||
|
||||
createdAt = Instant.fromEpochMilliseconds(metadataEvent.createdAt),
|
||||
updatedAt = Instant.fromEpochMilliseconds(metadataEvent.createdAt),
|
||||
|
||||
userName = userMetadata.name,
|
||||
displayName = userMetadata.displayName,
|
||||
picture = userMetadata.picture,
|
||||
banner = userMetadata.banner,
|
||||
website = userMetadata.website,
|
||||
about = userMetadata.about,
|
||||
bot = userMetadata.bot,
|
||||
pronouns = userMetadata.pronouns,
|
||||
nip05 = userMetadata.nip05,
|
||||
domain = userMetadata.domain,
|
||||
lud06 = userMetadata.lud06,
|
||||
lud16 = userMetadata.lud16,
|
||||
twitter = userMetadata.twitter
|
||||
)
|
||||
}
|
||||
}
|
||||
return null
|
||||
} catch (e: Throwable) {
|
||||
logger.e("Failed to return Profile: ", e)
|
||||
return null
|
||||
}
|
||||
|
||||
fun toReaction(): Reaction? = try {
|
||||
if (kind == ReactionEvent.KIND) {
|
||||
val reactionEvent = EventFactory.create<ReactionEvent>(
|
||||
id = id,
|
||||
pubKey = pubKey,
|
||||
createdAt = createdAt.toEpochMilliseconds(),
|
||||
kind = kind,
|
||||
tags = tags,
|
||||
content = content,
|
||||
sig = sig
|
||||
)
|
||||
|
||||
return reactionEvent.originalPost().firstOrNull()?.let { reactedTo ->
|
||||
Reaction(
|
||||
id = reactionEvent.id,
|
||||
content = reactionEvent.content,
|
||||
|
||||
nostrEventId = id,
|
||||
replyToId = reactedTo,
|
||||
profilePublicKey = reactionEvent.pubKey,
|
||||
|
||||
createdAt = Instant.fromEpochMilliseconds(reactionEvent.createdAt),
|
||||
updatedAt = Instant.fromEpochMilliseconds(reactionEvent.createdAt),
|
||||
)
|
||||
}
|
||||
}
|
||||
return null
|
||||
} catch (e: Throwable) {
|
||||
logger.e("Failed to return Reaction: ", e)
|
||||
return null
|
||||
}
|
||||
|
||||
fun toRepost(): Repost? = try {
|
||||
if (kind == RepostEvent.KIND) {
|
||||
val repostEvent = EventFactory.create<RepostEvent>(
|
||||
id = id,
|
||||
pubKey = pubKey,
|
||||
createdAt = createdAt.toEpochMilliseconds(),
|
||||
kind = kind,
|
||||
tags = tags,
|
||||
content = content,
|
||||
sig = sig
|
||||
)
|
||||
|
||||
return repostEvent.boostedEventId()?.let { repostedPostId ->
|
||||
Repost(
|
||||
id = repostEvent.id,
|
||||
repostedPostId = repostedPostId,
|
||||
content = repostEvent.content,
|
||||
|
||||
nostrEventId = id,
|
||||
profilePublicKey = repostEvent.pubKey,
|
||||
|
||||
createdAt = Instant.fromEpochMilliseconds(repostEvent.createdAt),
|
||||
updatedAt = Instant.fromEpochMilliseconds(repostEvent.createdAt),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
} catch (e: Throwable) {
|
||||
logger.e("Failed to return Repost: ", e)
|
||||
return null
|
||||
}
|
||||
|
||||
fun toZap(): Zap? = try {
|
||||
if (kind == LnZapEvent.KIND) {
|
||||
val zapEvent = EventFactory.create<LnZapEvent>(
|
||||
id = id,
|
||||
pubKey = pubKey,
|
||||
createdAt = createdAt.toEpochMilliseconds(),
|
||||
kind = kind,
|
||||
tags = tags,
|
||||
content = content,
|
||||
sig = sig
|
||||
)
|
||||
|
||||
// TODO: Return Zap...
|
||||
return null
|
||||
|
||||
}
|
||||
return null
|
||||
} catch (e: Throwable) {
|
||||
logger.e("Failed to return Zap: ", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,6 @@ import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
@@ -36,20 +34,20 @@ import kotlin.time.Instant
|
||||
ForeignKey(
|
||||
entity = Post::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["postId"],
|
||||
childColumns = ["repostedPostId"],
|
||||
onDelete = ForeignKey.CASCADE,
|
||||
),
|
||||
],
|
||||
indices = [
|
||||
Index("profilePublicKey"),
|
||||
Index("nostrEventId"),
|
||||
Index("postId")
|
||||
Index("repostedPostId")
|
||||
],
|
||||
)
|
||||
data class Repost(
|
||||
@PrimaryKey
|
||||
val id: HexKey,
|
||||
val postId: HexKey? = null,
|
||||
val repostedPostId: HexKey? = null,
|
||||
val content: String,
|
||||
|
||||
override val nostrEventId: HexKey, // Might be a replaceable nostr event?
|
||||
|
||||
Reference in New Issue
Block a user