Add connection/following/followers db model.

This commit is contained in:
Kgothatso Ngako
2026-04-22 02:55:22 +02:00
parent 74e9004474
commit 2b693b832a
7 changed files with 259 additions and 2 deletions

View File

@@ -3,6 +3,7 @@ package ac.cord.auxiliary.compose.database
import ac.cord.auxiliary.compose.database.converters.AuxConverters
import ac.cord.auxiliary.compose.database.dao.BroadcastNostrEventReceiptDao
import ac.cord.auxiliary.compose.database.dao.BroadcastNostrEventRequestDao
import ac.cord.auxiliary.compose.database.dao.ConnectionDao
import ac.cord.auxiliary.compose.database.dao.NostrDao
import ac.cord.auxiliary.compose.database.dao.NostrEventDao
import ac.cord.auxiliary.compose.database.dao.PostDao
@@ -17,6 +18,7 @@ import ac.cord.auxiliary.compose.database.dao.UnsignedNostrEventDao
import ac.cord.auxiliary.compose.database.dao.ZapDao
import ac.cord.auxiliary.compose.database.model.BroadcastNostrEventReceipt
import ac.cord.auxiliary.compose.database.model.BroadcastNostrEventRequest
import ac.cord.auxiliary.compose.database.model.Connection
import ac.cord.auxiliary.compose.database.model.NostrEvent
import ac.cord.auxiliary.compose.database.model.Post
import ac.cord.auxiliary.compose.database.model.Profile
@@ -41,6 +43,7 @@ val GENESIS_AT = Instant.fromEpochMilliseconds(1231006505000L)
entities = [
BroadcastNostrEventReceipt::class,
BroadcastNostrEventRequest::class,
Connection::class,
NostrEvent::class,
Post::class,
Profile::class,
@@ -59,6 +62,8 @@ val GENESIS_AT = Instant.fromEpochMilliseconds(1231006505000L)
abstract class AuxDatabase: RoomDatabase() {
abstract fun broadcastNostrEventReceiptDao(): BroadcastNostrEventReceiptDao
abstract fun broadcastNostrEventRequestDao(): BroadcastNostrEventRequestDao
abstract fun connectionDao(): ConnectionDao
abstract fun nostrDao(): NostrDao
abstract fun nostrEventDao(): NostrEventDao

View File

@@ -0,0 +1,31 @@
package ac.cord.auxiliary.compose.database.dao
import ac.cord.auxiliary.compose.database.model.Connection
import ac.cord.auxiliary.compose.database.model.Profile
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalFollowers
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalFollowing
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfile
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Upsert
import fr.acinq.bitcoin.PublicKey
@Dao
interface ConnectionDao {
@Query("SELECT * FROM Connection LIMIT :limit")
fun getAllConnections(limit: Int = 21): List<Connection>
@Query("SELECT * FROM Connection WHERE sourcePublicKey = :sourcePublicKey")
fun getPublicKeyFollowing(sourcePublicKey: String): LocalFollowing?
@Query("SELECT * FROM Connection WHERE destinationPublicKey = :destinationPublicKey")
fun getPublicKeyFollowers(destinationPublicKey: String): LocalFollowers?
@Insert
suspend fun insertPlaceholderProfile(connection: Connection)
@Upsert
suspend fun upsert(connection: Connection)
}

View File

@@ -3,6 +3,7 @@ package ac.cord.auxiliary.compose.database.dao
import ac.cord.auxiliary.compose.database.AuxDatabase
import ac.cord.auxiliary.compose.database.GENESIS_AT
import ac.cord.auxiliary.compose.database.model.BroadcastNostrEventRequest
import ac.cord.auxiliary.compose.database.model.Connection
import ac.cord.auxiliary.compose.database.model.NostrEvent
import ac.cord.auxiliary.compose.database.model.Post
import ac.cord.auxiliary.compose.database.model.Profile
@@ -359,5 +360,66 @@ abstract class NostrDao(
database.zapDao().upsert(zap)
}
if (nostrEvent.kind == ContactListEvent.KIND) {
val contactListEvent = ContactListEvent(
id = nostrEvent.id,
tags = nostrEvent.tags,
pubKey = nostrEvent.pubKey,
createdAt = nostrEvent.createdAt.epochSeconds,
content = nostrEvent.content,
sig = nostrEvent.sig
)
logger.i("Store contact list: $contactListEvent")
contactListEvent.verifiedFollowKeySet().forEach { followedHexKEys ->
// Find or create profile with the pubKey... if not found submit a sync request...
val profile = database.profileDao().getProfileByPublicKey(followedHexKEys)
if (profile == null) {
val placeHolderProfile = Profile(
publicKey = nostrEvent.pubKey,
createdAt = GENESIS_AT,
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
)
database.profileDao().insertPlaceholderProfile(placeHolderProfile)
database.synchronizeNostrEventRequestDao().insert(
synchronizationRelayURLs.map { synchronizationRelayURL ->
SynchronizeNostrEventRequest(
purpose = "synchronization",
synchronizationFilters = arrayOf(
SynchronizationFilter(
authors = arrayOf(nostrEvent.pubKey),
kinds = arrayOf(
MetadataEvent.KIND,
ContactListEvent.KIND,
AdvertisedRelayListEvent.KIND,
ChatMessageRelayListEvent.KIND,
SearchRelayListEvent.KIND,
IndexerRelayListEvent.KIND,
ChannelListEvent.KIND,
RelayFeedsListEvent.KIND
)
)
),
relayURL = synchronizationRelayURL
)
}
)
}
// Save profile connections...
database.connectionDao().upsert(
Connection(
sourcePublicKey = nostrEvent.pubKey,
destinationPublicKey = followedHexKEys,
nostrEventId = nostrEvent.id
)
)
}
// TODO: Delete connections where sourcePublicKey == nostr.pubKey but destinationPublickKey !in verifiedFollowKeySet
}
}
}

View File

@@ -0,0 +1,42 @@
package ac.cord.auxiliary.compose.database.model
import ac.cord.auxiliary.compose.database.model.traits.BroadcastableEntity
import ac.cord.auxiliary.compose.database.model.traits.LocalStoreEntity
import ac.cord.auxiliary.compose.database.model.traits.NostrEventEntity
import ac.cord.auxiliary.compose.database.model.traits.SoftDeletableEntity
import ac.cord.auxiliary.compose.database.model.traits.TimestampedEntity
import androidx.room.Entity
import androidx.room.ForeignKey
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import kotlin.time.Clock
import kotlin.time.Instant
@Entity(
foreignKeys = [
ForeignKey(
entity = Profile::class,
parentColumns = ["publicKey"],
childColumns = ["sourcePublicKey"],
onDelete = ForeignKey.CASCADE,
),
ForeignKey(
entity = Profile::class,
parentColumns = ["publicKey"],
childColumns = ["destinationPublicKey"],
onDelete = ForeignKey.CASCADE,
),
],
primaryKeys = ["sourcePublicKey", "destinationPublicKey"]
)
data class Connection(
val sourcePublicKey: HexKey,
val destinationPublicKey: HexKey,
override val nostrEventId: HexKey, // This is the contactListEvent with this following...
override val createdAt: Instant = Clock.System.now(),
override val updatedAt: Instant = Clock.System.now(),
override val savedAt: Instant = Clock.System.now(),
override val deletedAt: Instant? = null,
override val broadcastedAt: Instant? = null,
): NostrEventEntity, TimestampedEntity, LocalStoreEntity, BroadcastableEntity, SoftDeletableEntity

View File

@@ -0,0 +1,17 @@
package ac.cord.auxiliary.compose.database.model.intermdiate
import ac.cord.auxiliary.compose.database.model.Profile
import ac.cord.auxiliary.compose.database.model.Connection
import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation
data class LocalFollowers(
@Embedded val connection: Connection,
@Relation(
parentColumn = "destinationPublicKey",
entityColumn = "publicKey",
)
val followers: List<Profile>
)

View File

@@ -0,0 +1,17 @@
package ac.cord.auxiliary.compose.database.model.intermdiate
import ac.cord.auxiliary.compose.database.model.Profile
import ac.cord.auxiliary.compose.database.model.Connection
import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation
data class LocalFollowing(
@Embedded val connection: Connection,
@Relation(
parentColumn = "sourcePublicKey",
entityColumn = "publicKey",
)
val following: List<Profile>
)