Setting up relations

This commit is contained in:
Kgothatso Ngako
2026-04-30 18:43:18 +02:00
parent c99e3a31c2
commit be3f4390ff
24 changed files with 959 additions and 924 deletions

View File

@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "98aa54dd7fe776faca364638070a3871",
"identityHash": "296c3251f1f835c144c94c7ff1598939",
"entities": [
{
"tableName": "BroadcastNostrEventReceipt",
@@ -263,62 +263,35 @@
]
},
{
"tableName": "InReplyToPost",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `inReplyToPostId` TEXT NOT NULL, `inReplyToPostTags` TEXT NOT NULL, `inReplyToPostCreatedAt` INTEGER NOT NULL, `inReplyToPostContent` TEXT NOT NULL, `inReplyToPostAuthorPublicKey` TEXT NOT NULL, `content` TEXT NOT NULL, `nostrEventId` TEXT NOT NULL, `profilePublicKey` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`profilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`inReplyToPostAuthorPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`inReplyToPostId`) REFERENCES `Post`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"tableName": "InReplyToRelation",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`replyingNostrEventId` TEXT NOT NULL, `inReplyToNostrEventId` TEXT NOT NULL, `inReplyToProfilePublicKey` TEXT, `inReplyToRootNostrEventId` TEXT, `inReplyToRootProfilePublicKey` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, PRIMARY KEY(`replyingNostrEventId`, `inReplyToNostrEventId`), FOREIGN KEY(`inReplyToProfilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`inReplyToRootProfilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replyingNostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`inReplyToNostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`inReplyToRootNostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"fieldPath": "replyingNostrEventId",
"columnName": "replyingNostrEventId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "inReplyToPostId",
"columnName": "inReplyToPostId",
"fieldPath": "inReplyToNostrEventId",
"columnName": "inReplyToNostrEventId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "inReplyToPostTags",
"columnName": "inReplyToPostTags",
"affinity": "TEXT",
"notNull": true
"fieldPath": "inReplyToProfilePublicKey",
"columnName": "inReplyToProfilePublicKey",
"affinity": "TEXT"
},
{
"fieldPath": "inReplyToPostCreatedAt",
"columnName": "inReplyToPostCreatedAt",
"affinity": "INTEGER",
"notNull": true
"fieldPath": "inReplyToRootNostrEventId",
"columnName": "inReplyToRootNostrEventId",
"affinity": "TEXT"
},
{
"fieldPath": "inReplyToPostContent",
"columnName": "inReplyToPostContent",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "inReplyToPostAuthorPublicKey",
"columnName": "inReplyToPostAuthorPublicKey",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "nostrEventId",
"columnName": "nostrEventId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "profilePublicKey",
"columnName": "profilePublicKey",
"affinity": "TEXT",
"notNull": true
"fieldPath": "inReplyToRootProfilePublicKey",
"columnName": "inReplyToRootProfilePublicKey",
"affinity": "TEXT"
},
{
"fieldPath": "createdAt",
@@ -347,55 +320,22 @@
"fieldPath": "deletedAt",
"columnName": "deletedAt",
"affinity": "INTEGER"
},
{
"fieldPath": "broadcastedAt",
"columnName": "broadcastedAt",
"affinity": "INTEGER"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
"replyingNostrEventId",
"inReplyToNostrEventId"
]
},
"indices": [
{
"name": "index_InReplyToPost_profilePublicKey",
"unique": false,
"columnNames": [
"profilePublicKey"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_InReplyToPost_profilePublicKey` ON `${TABLE_NAME}` (`profilePublicKey`)"
},
{
"name": "index_InReplyToPost_nostrEventId",
"unique": false,
"columnNames": [
"nostrEventId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_InReplyToPost_nostrEventId` ON `${TABLE_NAME}` (`nostrEventId`)"
},
{
"name": "index_InReplyToPost_inReplyToPostId",
"unique": false,
"columnNames": [
"inReplyToPostId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_InReplyToPost_inReplyToPostId` ON `${TABLE_NAME}` (`inReplyToPostId`)"
}
],
"foreignKeys": [
{
"table": "Profile",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"profilePublicKey"
"inReplyToProfilePublicKey"
],
"referencedColumns": [
"publicKey"
@@ -406,7 +346,7 @@
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"inReplyToPostAuthorPublicKey"
"inReplyToRootProfilePublicKey"
],
"referencedColumns": [
"publicKey"
@@ -417,18 +357,29 @@
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"nostrEventId"
"replyingNostrEventId"
],
"referencedColumns": [
"id"
]
},
{
"table": "Post",
"table": "NostrEvent",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"inReplyToPostId"
"inReplyToNostrEventId"
],
"referencedColumns": [
"id"
]
},
{
"table": "NostrEvent",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"inReplyToRootNostrEventId"
],
"referencedColumns": [
"id"
@@ -438,11 +389,11 @@
},
{
"tableName": "Mention",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`publicKey` TEXT NOT NULL, `relayUrl` TEXT, `nostrEventId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`nostrEventId`, `publicKey`), FOREIGN KEY(`publicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`mentionedPublicKey` TEXT NOT NULL, `relayUrl` TEXT, `nostrEventId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`nostrEventId`, `mentionedPublicKey`), FOREIGN KEY(`mentionedPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "publicKey",
"columnName": "publicKey",
"fieldPath": "mentionedPublicKey",
"columnName": "mentionedPublicKey",
"affinity": "TEXT",
"notNull": true
},
@@ -490,7 +441,7 @@
"autoGenerate": false,
"columnNames": [
"nostrEventId",
"publicKey"
"mentionedPublicKey"
]
},
"foreignKeys": [
@@ -499,7 +450,7 @@
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"publicKey"
"mentionedPublicKey"
],
"referencedColumns": [
"publicKey"
@@ -520,7 +471,7 @@
},
{
"tableName": "NostrEvent",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `pubKey` TEXT NOT NULL, `kind` INTEGER NOT NULL, `tags` TEXT NOT NULL, `content` TEXT NOT NULL, `sig` TEXT NOT NULL, `quotedNostrEventId` TEXT, `rootNostrEventId` TEXT, `replyNostrEventId` TEXT, `taggedNostrEventId` TEXT, `taggedNostrEventRelayUrl` TEXT, `unsignedNostrEventId` INTEGER, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`))",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `pubKey` TEXT NOT NULL, `kind` INTEGER NOT NULL, `tags` TEXT NOT NULL, `content` TEXT NOT NULL, `sig` TEXT NOT NULL, `quotedNostrEventId` TEXT, `quotedAuthorPublicKey` TEXT, `inReplyToNostrEventId` TEXT, `inReplyToAuthorPublicKey` TEXT, `inReplyToRootNostrEventId` TEXT, `inReplyToRootAuthorPublicKey` TEXT, `repostedNostrEventId` TEXT, `repostedAuthorPublicKey` TEXT, `unsignedNostrEventId` INTEGER, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
@@ -564,23 +515,38 @@
"affinity": "TEXT"
},
{
"fieldPath": "rootNostrEventId",
"columnName": "rootNostrEventId",
"fieldPath": "quotedAuthorPublicKey",
"columnName": "quotedAuthorPublicKey",
"affinity": "TEXT"
},
{
"fieldPath": "replyNostrEventId",
"columnName": "replyNostrEventId",
"fieldPath": "inReplyToNostrEventId",
"columnName": "inReplyToNostrEventId",
"affinity": "TEXT"
},
{
"fieldPath": "taggedNostrEventId",
"columnName": "taggedNostrEventId",
"fieldPath": "inReplyToAuthorPublicKey",
"columnName": "inReplyToAuthorPublicKey",
"affinity": "TEXT"
},
{
"fieldPath": "taggedNostrEventRelayUrl",
"columnName": "taggedNostrEventRelayUrl",
"fieldPath": "inReplyToRootNostrEventId",
"columnName": "inReplyToRootNostrEventId",
"affinity": "TEXT"
},
{
"fieldPath": "inReplyToRootAuthorPublicKey",
"columnName": "inReplyToRootAuthorPublicKey",
"affinity": "TEXT"
},
{
"fieldPath": "repostedNostrEventId",
"columnName": "repostedNostrEventId",
"affinity": "TEXT"
},
{
"fieldPath": "repostedAuthorPublicKey",
"columnName": "repostedAuthorPublicKey",
"affinity": "TEXT"
},
{
@@ -652,22 +618,22 @@
"createSql": "CREATE INDEX IF NOT EXISTS `index_NostrEvent_quotedNostrEventId` ON `${TABLE_NAME}` (`quotedNostrEventId`)"
},
{
"name": "index_NostrEvent_rootNostrEventId",
"name": "index_NostrEvent_inReplyToNostrEventId",
"unique": false,
"columnNames": [
"rootNostrEventId"
"inReplyToNostrEventId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_NostrEvent_rootNostrEventId` ON `${TABLE_NAME}` (`rootNostrEventId`)"
"createSql": "CREATE INDEX IF NOT EXISTS `index_NostrEvent_inReplyToNostrEventId` ON `${TABLE_NAME}` (`inReplyToNostrEventId`)"
},
{
"name": "index_NostrEvent_replyNostrEventId",
"name": "index_NostrEvent_inReplyToRootNostrEventId",
"unique": false,
"columnNames": [
"replyNostrEventId"
"inReplyToRootNostrEventId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_NostrEvent_replyNostrEventId` ON `${TABLE_NAME}` (`replyNostrEventId`)"
"createSql": "CREATE INDEX IF NOT EXISTS `index_NostrEvent_inReplyToRootNostrEventId` ON `${TABLE_NAME}` (`inReplyToRootNostrEventId`)"
}
]
},
@@ -986,62 +952,25 @@
]
},
{
"tableName": "QuotedPost",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `quotedPostId` TEXT NOT NULL, `quotedPostTags` TEXT NOT NULL, `quotedPostCreatedAt` INTEGER NOT NULL, `quotedPostContent` TEXT NOT NULL, `quotedPostAuthorPublicKey` TEXT NOT NULL, `content` TEXT NOT NULL, `nostrEventId` TEXT NOT NULL, `profilePublicKey` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`profilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`quotedPostAuthorPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`quotedPostId`) REFERENCES `Post`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"tableName": "QuotedRelation",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`quotingNostrEventId` TEXT NOT NULL, `quotedNostrEventId` TEXT NOT NULL, `quotedProfilePublicKey` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`quotingNostrEventId`, `quotedNostrEventId`), FOREIGN KEY(`quotedProfilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`quotingNostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`quotedNostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"fieldPath": "quotingNostrEventId",
"columnName": "quotingNostrEventId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "quotedPostId",
"columnName": "quotedPostId",
"fieldPath": "quotedNostrEventId",
"columnName": "quotedNostrEventId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "quotedPostTags",
"columnName": "quotedPostTags",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "quotedPostCreatedAt",
"columnName": "quotedPostCreatedAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "quotedPostContent",
"columnName": "quotedPostContent",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "quotedPostAuthorPublicKey",
"columnName": "quotedPostAuthorPublicKey",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "nostrEventId",
"columnName": "nostrEventId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "profilePublicKey",
"columnName": "profilePublicKey",
"affinity": "TEXT",
"notNull": true
"fieldPath": "quotedProfilePublicKey",
"columnName": "quotedProfilePublicKey",
"affinity": "TEXT"
},
{
"fieldPath": "createdAt",
@@ -1080,56 +1009,17 @@
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
"quotingNostrEventId",
"quotedNostrEventId"
]
},
"indices": [
{
"name": "index_QuotedPost_profilePublicKey",
"unique": false,
"columnNames": [
"profilePublicKey"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_QuotedPost_profilePublicKey` ON `${TABLE_NAME}` (`profilePublicKey`)"
},
{
"name": "index_QuotedPost_nostrEventId",
"unique": false,
"columnNames": [
"nostrEventId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_QuotedPost_nostrEventId` ON `${TABLE_NAME}` (`nostrEventId`)"
},
{
"name": "index_QuotedPost_quotedPostId",
"unique": false,
"columnNames": [
"quotedPostId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_QuotedPost_quotedPostId` ON `${TABLE_NAME}` (`quotedPostId`)"
}
],
"foreignKeys": [
{
"table": "Profile",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"profilePublicKey"
],
"referencedColumns": [
"publicKey"
]
},
{
"table": "Profile",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"quotedPostAuthorPublicKey"
"quotedProfilePublicKey"
],
"referencedColumns": [
"publicKey"
@@ -1140,18 +1030,18 @@
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"nostrEventId"
"quotingNostrEventId"
],
"referencedColumns": [
"id"
]
},
{
"table": "Post",
"table": "NostrEvent",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"quotedPostId"
"quotedNostrEventId"
],
"referencedColumns": [
"id"
@@ -1378,62 +1268,25 @@
}
},
{
"tableName": "Repost",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `repostedPostId` TEXT NOT NULL, `repostedPostTags` TEXT NOT NULL, `repostedPostCreatedAt` INTEGER NOT NULL, `repostedPostContent` TEXT NOT NULL, `repostedPostAuthorPublicKey` TEXT NOT NULL, `content` TEXT NOT NULL, `nostrEventId` TEXT NOT NULL, `profilePublicKey` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`profilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`repostedPostAuthorPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`repostedPostId`) REFERENCES `Post`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"tableName": "RepostedRelation",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`repostingNostrEventId` TEXT NOT NULL, `repostedNostrEventId` TEXT NOT NULL, `repostedProfilePublicKey` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`repostingNostrEventId`, `repostedNostrEventId`), FOREIGN KEY(`repostedProfilePublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`repostedNostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`repostingNostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"fieldPath": "repostingNostrEventId",
"columnName": "repostingNostrEventId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "repostedPostId",
"columnName": "repostedPostId",
"fieldPath": "repostedNostrEventId",
"columnName": "repostedNostrEventId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "repostedPostTags",
"columnName": "repostedPostTags",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "repostedPostCreatedAt",
"columnName": "repostedPostCreatedAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "repostedPostContent",
"columnName": "repostedPostContent",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "repostedPostAuthorPublicKey",
"columnName": "repostedPostAuthorPublicKey",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "nostrEventId",
"columnName": "nostrEventId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "profilePublicKey",
"columnName": "profilePublicKey",
"affinity": "TEXT",
"notNull": true
"fieldPath": "repostedProfilePublicKey",
"columnName": "repostedProfilePublicKey",
"affinity": "TEXT"
},
{
"fieldPath": "createdAt",
@@ -1472,56 +1325,17 @@
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
"repostingNostrEventId",
"repostedNostrEventId"
]
},
"indices": [
{
"name": "index_Repost_profilePublicKey",
"unique": false,
"columnNames": [
"profilePublicKey"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Repost_profilePublicKey` ON `${TABLE_NAME}` (`profilePublicKey`)"
},
{
"name": "index_Repost_nostrEventId",
"unique": false,
"columnNames": [
"nostrEventId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Repost_nostrEventId` ON `${TABLE_NAME}` (`nostrEventId`)"
},
{
"name": "index_Repost_repostedPostId",
"unique": false,
"columnNames": [
"repostedPostId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Repost_repostedPostId` ON `${TABLE_NAME}` (`repostedPostId`)"
}
],
"foreignKeys": [
{
"table": "Profile",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"profilePublicKey"
],
"referencedColumns": [
"publicKey"
]
},
{
"table": "Profile",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"repostedPostAuthorPublicKey"
"repostedProfilePublicKey"
],
"referencedColumns": [
"publicKey"
@@ -1532,18 +1346,18 @@
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"nostrEventId"
"repostedNostrEventId"
],
"referencedColumns": [
"id"
]
},
{
"table": "Post",
"table": "NostrEvent",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"repostedPostId"
"repostingNostrEventId"
],
"referencedColumns": [
"id"
@@ -2004,7 +1818,7 @@
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '98aa54dd7fe776faca364638070a3871')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '296c3251f1f835c144c94c7ff1598939')"
]
}
}

View File

@@ -4,17 +4,17 @@ 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.InReplyToPostDao
import ac.cord.auxiliary.compose.database.dao.InReplyToRelationDao
import ac.cord.auxiliary.compose.database.dao.MentionDao
import ac.cord.auxiliary.compose.database.dao.NostrDao
import ac.cord.auxiliary.compose.database.dao.NostrEventDao
import ac.cord.auxiliary.compose.database.dao.PostDao
import ac.cord.auxiliary.compose.database.dao.ProfileDao
import ac.cord.auxiliary.compose.database.dao.QuotedPostDao
import ac.cord.auxiliary.compose.database.dao.QuotedRelationDao
import ac.cord.auxiliary.compose.database.dao.ReactionDao
import ac.cord.auxiliary.compose.database.dao.RecentSearchDao
import ac.cord.auxiliary.compose.database.dao.RelayDao
import ac.cord.auxiliary.compose.database.dao.RepostDao
import ac.cord.auxiliary.compose.database.dao.RepostedRelationDao
import ac.cord.auxiliary.compose.database.dao.SynchronizeNostrEventRequestDao
import ac.cord.auxiliary.compose.database.dao.SynchronizeNostrEventResultDao
import ac.cord.auxiliary.compose.database.dao.UnsignedNostrEventDao
@@ -22,16 +22,16 @@ 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.InReplyToPost
import ac.cord.auxiliary.compose.database.model.InReplyToRelation
import ac.cord.auxiliary.compose.database.model.Mention
import ac.cord.auxiliary.compose.database.model.NostrEvent
import ac.cord.auxiliary.compose.database.model.Post
import ac.cord.auxiliary.compose.database.model.Profile
import ac.cord.auxiliary.compose.database.model.QuotedPost
import ac.cord.auxiliary.compose.database.model.QuotedRelation
import ac.cord.auxiliary.compose.database.model.Reaction
import ac.cord.auxiliary.compose.database.model.RecentSearch
import ac.cord.auxiliary.compose.database.model.Relay
import ac.cord.auxiliary.compose.database.model.Repost
import ac.cord.auxiliary.compose.database.model.RepostedRelation
import ac.cord.auxiliary.compose.database.model.SynchronizeNostrEventRequest
import ac.cord.auxiliary.compose.database.model.SynchronizeNostrEventResult
import ac.cord.auxiliary.compose.database.model.UnsignedNostrEvent
@@ -50,16 +50,16 @@ val GENESIS_AT = Instant.fromEpochMilliseconds(1231006505000L)
BroadcastNostrEventReceipt::class,
BroadcastNostrEventRequest::class,
Connection::class,
InReplyToPost::class,
InReplyToRelation::class,
Mention::class,
NostrEvent::class,
Post::class,
Profile::class,
QuotedPost::class,
QuotedRelation::class,
Reaction::class,
RecentSearch::class,
Relay::class,
Repost::class,
RepostedRelation::class,
SynchronizeNostrEventRequest::class,
SynchronizeNostrEventResult::class,
UnsignedNostrEvent::class,
@@ -73,7 +73,7 @@ abstract class AuxDatabase: RoomDatabase() {
abstract fun broadcastNostrEventRequestDao(): BroadcastNostrEventRequestDao
abstract fun connectionDao(): ConnectionDao
abstract fun inReplyToPostDao(): InReplyToPostDao
abstract fun inReplyToRelationDao(): InReplyToRelationDao
abstract fun mentionDao(): MentionDao
@@ -83,7 +83,7 @@ abstract class AuxDatabase: RoomDatabase() {
abstract fun postDao(): PostDao
abstract fun profileDao(): ProfileDao
abstract fun quotedPostDao(): QuotedPostDao
abstract fun quotedRelationDao(): QuotedRelationDao
abstract fun reactionDao(): ReactionDao
@@ -91,7 +91,7 @@ abstract class AuxDatabase: RoomDatabase() {
abstract fun relayDao(): RelayDao
abstract fun repostDao(): RepostDao
abstract fun repostedRelationDao(): RepostedRelationDao
abstract fun synchronizeNostrEventRequestDao(): SynchronizeNostrEventRequestDao

View File

@@ -1,18 +0,0 @@
package ac.cord.auxiliary.compose.database.dao
import ac.cord.auxiliary.compose.database.model.InReplyToPost
import ac.cord.auxiliary.compose.database.model.Post
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalPost
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Upsert
@Dao
interface InReplyToPostDao {
@Insert
suspend fun insert(inReplyToPost: InReplyToPost)
@Upsert
suspend fun upsert(inReplyToPost: InReplyToPost)
}

View File

@@ -0,0 +1,22 @@
package ac.cord.auxiliary.compose.database.dao
import ac.cord.auxiliary.compose.database.model.InReplyToRelation
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Upsert
@Dao
interface InReplyToRelationDao {
@Query("SELECT * FROM InReplyToRelation WHERE replyingNostrEventId = :replyingNostrEventId")
fun getRepostByReplyingNostrEventId(replyingNostrEventId: String): InReplyToRelation?
@Query("SELECT * FROM InReplyToRelation WHERE inReplyToNostrEventId = :inReplyToNostrEventId")
fun getRepostByInReplyToNostrEventId(inReplyToNostrEventId: String): InReplyToRelation?
@Insert
suspend fun insert(inReplyToRelation: InReplyToRelation)
@Upsert
suspend fun upsert(inReplyToRelation: InReplyToRelation)
}

View File

@@ -18,12 +18,9 @@ import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEvents
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
import com.vitorpamplona.quartz.nip28PublicChat.list.ChannelListEvent
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.RelayFeedsListEvent
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nip18Reposts.quotes.QAddressableTag
import com.vitorpamplona.quartz.nip18Reposts.quotes.QEventTag
import com.vitorpamplona.quartz.nip18Reposts.quotes.firstTaggedQuote
import kotlin.time.Clock
@Dao
@@ -187,41 +184,141 @@ abstract class NostrDao(
}
// Sync tagged events and authors...
// nostrEvent.tags.firstTaggedQuote()?.let { taggedQuote ->
// when (taggedQuote) {
// is QEventTag -> {
// Triple(
// taggedQuote.eventId,
// taggedQuote.author,
// taggedQuote.relay,
// )
// }
// is QAddressableTag -> {
// Triple(
// taggedQuote.address.dTag,
// taggedQuote.address.pubKeyHex,
// taggedQuote.relay,
// )
// }
// else -> null
// }?.let { quotedEventData ->
// val quotedNostrEvent = database.nostrEventDao().getNostrEventById(quotedEventData.first)
//
// if (quotedNostrEvent == null) {
//// val quotedPost = database.postDao().getPostById(quotedEventData.first)
//
//// if (quotedPost == null) {
//// // Persist PlaceHolder and sync
//// database.postDao().insert(
//// Post(
//// id = quotedEventData.first,
//// createdAt = GENESIS_AT,
//// nostrEventId = nostrEvent.id, // Will get overwriting by sync handled by tagged events...
//// content = "sync required",
//// profilePublicKey = nostrEvent.pubKey // Will get overwriting by sync handled by tagged events...
//// )
//// )
//// }
////
//// quotedEventData.second?.let { authorPubkey ->
//// val repostedPostProfile = database.profileDao().getProfileByPublicKey(authorPubkey)
////
//// if (repostedPostProfile == null) {
//// // Persist PlaceHolder and sync
//// database.profileDao().insertPlaceholderProfile(
//// Profile(
//// publicKey = authorPubkey,
//// createdAt = GENESIS_AT,
//// nostrEventId = nostrEvent.id, // Will get overwriting by sync handled by tagged events...
//// )
//// )
//// profilePublicKeysToSync.add(
//// authorPubkey
//// )
//// } else if (repostedPostProfile.createdAt == GENESIS_AT && level == 0) {
//// logger.i("${level} This is a placeholder profile... that might need to get synced...: $repostedPostProfile")
//// profilePublicKeysToSync.add(authorPubkey)
//// }
//// }
////
//// database.quotedPostDao()
//
// val recommendRelayUrl = quotedEventData.third?.url
//
// val synchronizeNostrEventRequests = if (recommendRelayUrl != null) {
// eventIdsToSync.add(quotedEventData.first)
//
// listOf(
// SynchronizeNostrEventRequest(
// purpose = "synchronization",
// synchronizationFilters = arrayOf(
// SynchronizationFilter(
// ids = arrayOf(quotedEventData.first)
// )
// ),
// relayURL = recommendRelayUrl,
// isRecommendedRelay = true,
// level = level + 1
// )
// )
// } else {
// eventIdsToSync.add(quotedEventData.first)
// synchronizationRelayURLs.map { synchronizationRelayURL ->
// SynchronizeNostrEventRequest(
// purpose = "synchronization",
// synchronizationFilters = arrayOf(
// SynchronizationFilter(
// ids = arrayOf(quotedEventData.first)
// )
// ),
// relayURL = synchronizationRelayURL,
// level = level + 1
// )
// }
// }
//
// database.synchronizeNostrEventRequestDao().insert(
// synchronizeNostrEventRequests
// )
// }
// }
// }
nostrEvent.tags.taggedEvents().take(4).forEach { taggedEvent -> // We only look at the first 3 tagged events...
val taggedNostrEvent = database.nostrEventDao().getNostrEventById(taggedEvent.eventId)
if (taggedNostrEvent == null) {
val recommendRelayUrl = taggedEvent.relay?.url
val synchronizeNostrEventRequests = if (recommendRelayUrl != null) {
eventIdsToSync.add(taggedEvent.eventId)
listOf(
SynchronizeNostrEventRequest(
purpose = "synchronization",
synchronizationFilters = arrayOf(
SynchronizationFilter(
ids = arrayOf(taggedEvent.eventId)
)
),
relayURL = recommendRelayUrl,
isRecommendedRelay = true,
level = level + 1
)
eventIdsToSync.add(taggedEvent.eventId)
listOf(
SynchronizeNostrEventRequest(
purpose = "synchronization",
synchronizationFilters = arrayOf(
SynchronizationFilter(
ids = arrayOf(taggedEvent.eventId)
)
),
relayURL = recommendRelayUrl,
isRecommendedRelay = true,
level = level + 1
)
)
} else {
eventIdsToSync.add(taggedEvent.eventId)
synchronizationRelayURLs.map { synchronizationRelayURL ->
SynchronizeNostrEventRequest(
purpose = "synchronization",
synchronizationFilters = arrayOf(
SynchronizationFilter(
ids = arrayOf(taggedEvent.eventId)
)
),
relayURL = synchronizationRelayURL,
level = level + 1
)
} else {
eventIdsToSync.add(taggedEvent.eventId)
synchronizationRelayURLs.map { synchronizationRelayURL ->
SynchronizeNostrEventRequest(
purpose = "synchronization",
synchronizationFilters = arrayOf(
SynchronizationFilter(
ids = arrayOf(taggedEvent.eventId)
)
),
relayURL = synchronizationRelayURL,
level = level + 1
)
}
}
}
database.synchronizeNostrEventRequestDao().insert(
synchronizeNostrEventRequests
@@ -232,6 +329,19 @@ abstract class NostrDao(
nostrEvent.tags.taggedUsers().take(5).forEach { taggedUser -> // 5 tagged users...
val recommendedRelay = taggedUser.relayHint?.url
val taggedProfile = database.profileDao().getProfileByPublicKey(taggedUser.pubKey)
if (taggedProfile == null) {
// Save a placeholder
database.profileDao().insertPlaceholderProfile(
Profile(
publicKey = taggedUser.pubKey,
createdAt = GENESIS_AT,
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
)
)
}
val synchronizeNostrEventRequests = if (recommendedRelay != null) {
// listOf(
// SynchronizeNostrEventRequest(
@@ -327,60 +437,144 @@ abstract class NostrDao(
database.reactionDao().upsert(reaction)
}
nostrEvent.toRepost()?.let { repost ->
// Find or create placeHolder note that is being reposted
val repostedPost = database.postDao().getPostById(repost.repostedPostId)
nostrEvent.toQuotedRelation()?.let { quotedRelation ->
val quotedNostrEvent = database.nostrEventDao().getNostrEventById(quotedRelation.quotedNostrEventId)
if (repostedPost == null) {
// Persist PlaceHolder and sync
database.postDao().insert(
Post(
id = repost.repostedPostId,
val quotedProfilePublicKey = if (quotedNostrEvent == null) {
eventIdsToSync.add(quotedRelation.quotedNostrEventId)
database.nostrEventDao().insert(
NostrEvent(
id = quotedRelation.quotedNostrEventId,
content = "Pending Sync",
createdAt = GENESIS_AT,
nostrEventId = nostrEvent.id, // Will get overwriting by sync handled by tagged events...
content = repost.repostedPostId,
profilePublicKey = nostrEvent.pubKey // Will get overwriting by sync handled by tagged events...
pubKey = nostrEvent.pubKey,
sig = "",
kind = nostrEvent.kind,
tags = emptyArray()
)
)
quotedRelation.quotedProfilePublicKey
} else {
quotedNostrEvent.pubKey
}
val repostedPostProfile = database.profileDao().getProfileByPublicKey(repost.repostedPostAuthorPublicKey)
val quotedProfile = quotedProfilePublicKey?.let { database.profileDao().getProfileByPublicKey(it) }
if (repostedPostProfile == null) {
// Persist PlaceHolder and sync
if (quotedProfilePublicKey != null && quotedProfile == null) {
database.profileDao().insertPlaceholderProfile(
Profile(
publicKey = repost.repostedPostAuthorPublicKey,
publicKey = quotedProfilePublicKey,
createdAt = GENESIS_AT,
nostrEventId = nostrEvent.id, // Will get overwriting by sync handled by tagged events...
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
)
)
}
// TODO: Might need to store placeholders
database.quotedRelationDao().upsert(
quotedRelation.copy(
quotedProfilePublicKey = quotedProfilePublicKey
)
)
}
nostrEvent.toRepostedRelation()?.let { repostedRelation ->
// Find or create placeHolder note that is being reposted
val repostedNostrEvent = database.nostrEventDao().getNostrEventById(repostedRelation.repostedNostrEventId)
val repostedProfilePublicKey = if (repostedNostrEvent == null) {
// Sync event...
eventIdsToSync.add(repostedRelation.repostedNostrEventId)
database.nostrEventDao().insert(
NostrEvent(
id = repostedRelation.repostedNostrEventId,
content = "Pending Sync",
createdAt = GENESIS_AT,
pubKey = nostrEvent.pubKey,
sig = "",
kind = nostrEvent.kind,
tags = emptyArray()
)
)
// Request a sync for the post being replied to
// database.synchronizeNostrEventRequestDao().insert(
// synchronizationRelayURLs.map { synchronizationRelayURL ->
// SynchronizeNostrEventRequest(
// purpose = "synchronization",
// synchronizationFilters = arrayOf(
//
// ),
// relayURL = synchronizationRelayURL
// )
// }
// )
// synchronizationFilters.add(
// SynchronizationFilter(
// authors = arrayOf(repost.repostedPostAuthorPublicKey),
// kinds = profileEventKinds
// )
// )
profilePublicKeysToSync.add(
repost.repostedPostAuthorPublicKey
)
} else if (repostedPostProfile.createdAt == GENESIS_AT && level == 0) {
logger.i("${level} This is a placeholder profile... that might need to get synced...: $repostedPostProfile")
profilePublicKeysToSync.add(repost.repostedPostAuthorPublicKey)
repostedRelation.repostedProfilePublicKey
} else {
repostedNostrEvent.pubKey
}
val repostedProfile = repostedProfilePublicKey?.let { database.profileDao().getProfileByPublicKey(it) }
if (repostedProfilePublicKey != null && repostedProfile == null) {
database.profileDao().insertPlaceholderProfile(
Profile(
publicKey = repostedProfilePublicKey,
createdAt = GENESIS_AT,
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
)
)
}
database.repostedRelationDao().upsert(
repostedRelation.copy(
repostedProfilePublicKey = repostedProfilePublicKey
)
)
}
nostrEvent.toInReplyToRelation()?.let { inReplyToRelation ->
val inReplyToNostrEvent = database.nostrEventDao().getNostrEventById(inReplyToRelation.inReplyToNostrEventId)
val inReplyToProfilePublicKey = if (inReplyToNostrEvent == null) {
eventIdsToSync.add(inReplyToRelation.inReplyToNostrEventId)
database.nostrEventDao().insert(
NostrEvent(
id = inReplyToRelation.inReplyToNostrEventId,
content = "Pending Sync",
createdAt = GENESIS_AT,
pubKey = nostrEvent.pubKey,
sig = "",
kind = nostrEvent.kind,
tags = emptyArray()
)
)
inReplyToRelation.inReplyToProfilePublicKey
} else {
inReplyToNostrEvent.pubKey
}
val inReplyToRootNostrEvent = inReplyToRelation.inReplyToRootNostrEventId?.let { database.nostrEventDao().getNostrEventById(it) }
val inReplyToRootProfilePublicKey = if (inReplyToRootNostrEvent == null) {
eventIdsToSync.add(inReplyToRelation.inReplyToNostrEventId)
inReplyToRelation.inReplyToRootProfilePublicKey
// Might need to save place holder...
} else {
inReplyToRootNostrEvent.pubKey
}
val inReplyToProfile = inReplyToRootProfilePublicKey?.let { database.profileDao().getProfileByPublicKey(it) }
if (inReplyToRootProfilePublicKey != null && inReplyToProfile == null) {
database.profileDao().insertPlaceholderProfile(
Profile(
publicKey = inReplyToRootProfilePublicKey,
createdAt = GENESIS_AT,
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
)
)
}
database.inReplyToRelationDao().upsert(
inReplyToRelation.copy(
inReplyToRootProfilePublicKey = inReplyToRootProfilePublicKey,
inReplyToProfilePublicKey = inReplyToProfilePublicKey
)
)
}
nostrEvent.toZap()?.let { zap ->

View File

@@ -1,18 +0,0 @@
package ac.cord.auxiliary.compose.database.dao
import ac.cord.auxiliary.compose.database.model.Post
import ac.cord.auxiliary.compose.database.model.QuotedPost
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalPost
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Upsert
@Dao
interface QuotedPostDao {
@Insert
suspend fun insert(quotedPost: QuotedPost)
@Upsert
suspend fun upsert(quotedPost: QuotedPost)
}

View File

@@ -0,0 +1,22 @@
package ac.cord.auxiliary.compose.database.dao
import ac.cord.auxiliary.compose.database.model.QuotedRelation
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Upsert
@Dao
interface QuotedRelationDao {
@Query("SELECT * FROM QuotedRelation WHERE quotingNostrEventId = :quotingNostrEventId")
fun getRepostByQuotingNostrEventId(quotingNostrEventId: String): QuotedRelation?
@Query("SELECT * FROM QuotedRelation WHERE quotedNostrEventId = :quotedNostrEventId")
fun getRepostByQuotedNostrEventId(quotedNostrEventId: String): QuotedRelation?
@Insert
suspend fun insert(quotedRelation: QuotedRelation)
@Upsert
suspend fun upsert(quotedRelation: QuotedRelation)
}

View File

@@ -1,19 +0,0 @@
package ac.cord.auxiliary.compose.database.dao
import ac.cord.auxiliary.compose.database.model.Repost
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Upsert
import kotlinx.coroutines.flow.Flow
@Dao
interface RepostDao {
@Query("SELECT * FROM Repost")
fun getAllReposts(): List<Repost>
@Query("SELECT * FROM Repost WHERE id = :id")
fun getRepostById(id: String): Flow<Repost?>
@Upsert
suspend fun upsert(repost: Repost)
}

View File

@@ -0,0 +1,21 @@
package ac.cord.auxiliary.compose.database.dao
import ac.cord.auxiliary.compose.database.model.RepostedRelation
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Upsert
@Dao
interface RepostedRelationDao {
@Query("SELECT * FROM RepostedRelation")
fun getAllReposts(): List<RepostedRelation>
@Query("SELECT * FROM RepostedRelation WHERE repostingNostrEventId = :repostingNostrEventId")
fun getRepostByRepostingId(repostingNostrEventId: String): RepostedRelation?
@Query("SELECT * FROM RepostedRelation WHERE repostedNostrEventId = :repostedNostrEventId")
fun getRepostByRepostedId(repostedNostrEventId: String): RepostedRelation?
@Upsert
suspend fun upsert(repostedRelation: RepostedRelation)
}

View File

@@ -1,120 +0,0 @@
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.ProfilePublicKeyEntity
import ac.cord.auxiliary.compose.database.model.traits.SoftDeletableEntity
import ac.cord.auxiliary.compose.database.model.traits.TimestampedEntity
import ac.cord.auxiliary.compose.database.model.traits.UserViewableEntity
import androidx.room.Entity
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.TagArray
import kotlin.time.Clock
import kotlin.time.Instant
@Entity(
foreignKeys = [
ForeignKey(
entity = Profile::class,
parentColumns = ["publicKey"],
childColumns = ["profilePublicKey"],
onDelete = ForeignKey.CASCADE,
),
ForeignKey(
entity = Profile::class,
parentColumns = ["publicKey"],
childColumns = ["inReplyToPostAuthorPublicKey"],
onDelete = ForeignKey.CASCADE,
),
// This might cause problems... but we gonna make the feed so cool if it works...
ForeignKey(
entity = NostrEvent::class,
parentColumns = ["id"],
childColumns = ["nostrEventId"],
onDelete = ForeignKey.CASCADE,
),
// Support post replies
ForeignKey(
entity = Post::class,
parentColumns = ["id"],
childColumns = ["inReplyToPostId"],
onDelete = ForeignKey.CASCADE,
),
],
indices = [
Index("profilePublicKey"),
Index("nostrEventId"),
Index("inReplyToPostId")
],
)
data class InReplyToPost(
@PrimaryKey
val id: HexKey,
val inReplyToPostId: HexKey,
val inReplyToPostTags: TagArray,
val inReplyToPostCreatedAt: Instant,
val inReplyToPostContent: String,
val inReplyToPostAuthorPublicKey: HexKey,
val content: String,
override val nostrEventId: HexKey, // Might be a replaceable nostr event?
override val profilePublicKey: HexKey,
override val createdAt: Instant = Clock.System.now(),
override val updatedAt: Instant = Clock.System.now(),
override val savedAt: Instant = Clock.System.now(),
override val viewedAt: Instant? = null,
override val deletedAt: Instant? = null,
override val broadcastedAt: Instant? = null,
): NostrEventEntity, ProfilePublicKeyEntity, TimestampedEntity, LocalStoreEntity, UserViewableEntity, BroadcastableEntity, SoftDeletableEntity {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as InReplyToPost
if (id != other.id) return false
if (inReplyToPostId != other.inReplyToPostId) return false
if (!inReplyToPostTags.contentDeepEquals(other.inReplyToPostTags)) return false
if (inReplyToPostCreatedAt != other.inReplyToPostCreatedAt) return false
if (inReplyToPostContent != other.inReplyToPostContent) return false
if (inReplyToPostAuthorPublicKey != other.inReplyToPostAuthorPublicKey) return false
if (content != other.content) return false
if (nostrEventId != other.nostrEventId) return false
if (profilePublicKey != other.profilePublicKey) return false
if (createdAt != other.createdAt) return false
if (updatedAt != other.updatedAt) return false
if (savedAt != other.savedAt) return false
if (viewedAt != other.viewedAt) return false
if (deletedAt != other.deletedAt) return false
if (broadcastedAt != other.broadcastedAt) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + inReplyToPostId.hashCode()
result = 31 * result + inReplyToPostTags.contentDeepHashCode()
result = 31 * result + inReplyToPostCreatedAt.hashCode()
result = 31 * result + inReplyToPostContent.hashCode()
result = 31 * result + inReplyToPostAuthorPublicKey.hashCode()
result = 31 * result + content.hashCode()
result = 31 * result + nostrEventId.hashCode()
result = 31 * result + profilePublicKey.hashCode()
result = 31 * result + createdAt.hashCode()
result = 31 * result + updatedAt.hashCode()
result = 31 * result + savedAt.hashCode()
result = 31 * result + (viewedAt?.hashCode() ?: 0)
result = 31 * result + (deletedAt?.hashCode() ?: 0)
result = 31 * result + (broadcastedAt?.hashCode() ?: 0)
return result
}
}

View File

@@ -0,0 +1,68 @@
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.SoftDeletableEntity
import ac.cord.auxiliary.compose.database.model.traits.TimestampedEntity
import ac.cord.auxiliary.compose.database.model.traits.UserViewableEntity
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import kotlin.time.Clock
import kotlin.time.Instant
@Entity(
foreignKeys = [
ForeignKey(
entity = Profile::class,
parentColumns = ["publicKey"],
childColumns = ["inReplyToProfilePublicKey"],
onDelete = ForeignKey.CASCADE,
),
ForeignKey(
entity = Profile::class,
parentColumns = ["publicKey"],
childColumns = ["inReplyToRootProfilePublicKey"],
onDelete = ForeignKey.CASCADE,
),
// This might cause problems... but we gonna make the feed so cool if it works...
ForeignKey(
entity = NostrEvent::class,
parentColumns = ["id"],
childColumns = ["replyingNostrEventId"],
onDelete = ForeignKey.CASCADE,
),
ForeignKey(
entity = NostrEvent::class,
parentColumns = ["id"],
childColumns = ["inReplyToNostrEventId"],
onDelete = ForeignKey.CASCADE,
),
ForeignKey(
entity = NostrEvent::class,
parentColumns = ["id"],
childColumns = ["inReplyToRootNostrEventId"],
onDelete = ForeignKey.CASCADE,
),
],
primaryKeys = ["replyingNostrEventId", "inReplyToNostrEventId"]
)
data class InReplyToRelation(
val replyingNostrEventId: HexKey, // Might be a replaceable nostr event?
val inReplyToNostrEventId: HexKey,
val inReplyToProfilePublicKey: HexKey?,
val inReplyToRootNostrEventId: HexKey?,
val inReplyToRootProfilePublicKey: HexKey?,
override val createdAt: Instant = Clock.System.now(),
override val updatedAt: Instant = Clock.System.now(),
override val savedAt: Instant = Clock.System.now(),
override val viewedAt: Instant? = null,
override val deletedAt: Instant? = null,
): TimestampedEntity, LocalStoreEntity, UserViewableEntity, SoftDeletableEntity {
}

View File

@@ -16,7 +16,7 @@ import kotlin.time.Instant
ForeignKey(
entity = Profile::class,
parentColumns = ["publicKey"],
childColumns = ["publicKey"],
childColumns = ["mentionedPublicKey"],
onDelete = ForeignKey.CASCADE,
),
ForeignKey(
@@ -26,10 +26,10 @@ import kotlin.time.Instant
onDelete = ForeignKey.CASCADE,
),
],
primaryKeys = ["nostrEventId", "publicKey"]
primaryKeys = ["nostrEventId", "mentionedPublicKey"]
)
data class Mention(
val publicKey: HexKey,
val mentionedPublicKey: HexKey,
val relayUrl: String? = null,

View File

@@ -5,6 +5,7 @@ import ac.cord.auxiliary.compose.database.model.traits.LocalStoreEntity
import ac.cord.auxiliary.compose.database.model.traits.SoftDeletableEntity
import ac.cord.auxiliary.compose.database.model.traits.TimestampedEntity
import ac.cord.auxiliary.compose.database.model.traits.UnsignedNostrEventEntity
import ac.cord.auxiliary.compose.database.model.types.SynchronizationFilter
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
@@ -16,9 +17,12 @@ 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.nip01Core.tags.events.firstTaggedEvent
import com.vitorpamplona.quartz.nip01Core.tags.people.firstTaggedUser
import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEvents
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import com.vitorpamplona.quartz.nip18Reposts.quotes.QAddressableTag
import com.vitorpamplona.quartz.nip18Reposts.quotes.QEventTag
import com.vitorpamplona.quartz.nip18Reposts.quotes.firstTaggedQuote
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.utils.EventFactory
@@ -37,8 +41,8 @@ import kotlin.time.Instant
Index("kind"),
Index("pubKey"),
Index("quotedNostrEventId"),
Index("rootNostrEventId"),
Index("replyNostrEventId"),
Index("inReplyToNostrEventId"),
Index("inReplyToRootNostrEventId"),
]
)
data class NostrEvent(
@@ -51,12 +55,16 @@ data class NostrEvent(
val sig: HexKey,
val quotedNostrEventId: HexKey? = null,
val quotedAuthorPublicKey: HexKey? = null,
val rootNostrEventId: HexKey? = null,
val replyNostrEventId: HexKey? = null,
val inReplyToNostrEventId: HexKey? = null,
val inReplyToAuthorPublicKey: HexKey? = null,
val taggedNostrEventId: HexKey? = null,
val taggedNostrEventRelayUrl: String? = null,
val inReplyToRootNostrEventId: HexKey? = null,
val inReplyToRootAuthorPublicKey: HexKey? = null,
val repostedNostrEventId: HexKey? = null,
val repostedAuthorPublicKey: HexKey? = null,
override val unsignedNostrEventId: Long? = null,
@@ -202,7 +210,7 @@ data class NostrEvent(
return null
}
fun toRepost(): Repost? = try {
fun toRepostedRelation(): RepostedRelation? = try {
if (kind == RepostEvent.KIND) {
val repostEvent = EventFactory.create<RepostEvent>(
id = id,
@@ -215,17 +223,11 @@ data class NostrEvent(
)
return repostEvent.containedPost()?.let { containedPost ->
Repost(
id = repostEvent.id,
repostedPostId = containedPost.id,
repostedPostTags = containedPost.tags,
repostedPostContent = containedPost.content,
repostedPostCreatedAt = Instant.fromEpochSeconds(containedPost.createdAt),
content = containedPost.content,
RepostedRelation(
repostedNostrEventId = containedPost.id,
repostedProfilePublicKey = containedPost.pubKey,
nostrEventId = id,
profilePublicKey = repostEvent.pubKey,
repostedPostAuthorPublicKey = containedPost.pubKey,
repostingNostrEventId = repostEvent.id,
createdAt = Instant.fromEpochSeconds(repostEvent.createdAt),
updatedAt = Instant.fromEpochSeconds(repostEvent.createdAt),
@@ -240,6 +242,80 @@ data class NostrEvent(
return null
}
fun toQuotedRelation(): QuotedRelation? = try {
if (kind == TextNoteEvent.KIND) {
tags.firstTaggedQuote()?.let { taggedQuote ->
when (taggedQuote) {
is QEventTag -> {
Triple(
taggedQuote.eventId,
taggedQuote.author,
taggedQuote.relay,
)
}
is QAddressableTag -> {
Triple(
taggedQuote.address.dTag,
taggedQuote.address.pubKeyHex,
taggedQuote.relay,
)
}
else -> null
}?.let { quotedEventData ->
return QuotedRelation(
quotedNostrEventId = quotedEventData.first,
quotedProfilePublicKey = quotedEventData.second,
quotingNostrEventId = id,
createdAt = createdAt
)
}
}
}
// TODO: Get quoted event from content...
return null
} catch (e: Throwable) {
logger.e("Failed to return Repost: ", e)
return null
}
fun toInReplyToRelation(): InReplyToRelation? = try {
if (kind == RepostEvent.KIND) {
val textNoteEvent = EventFactory.create<TextNoteEvent>(
id = id,
pubKey = pubKey,
createdAt = createdAt.epochSeconds,
kind = kind,
tags = tags,
content = content,
sig = sig
)
val rootReply = textNoteEvent.markedRoot()
textNoteEvent.markedReply()?.let { replyTag ->
return InReplyToRelation(
inReplyToNostrEventId = replyTag.eventId,
inReplyToProfilePublicKey = replyTag.author,
inReplyToRootNostrEventId = rootReply?.eventId,
inReplyToRootProfilePublicKey = rootReply?.author,
replyingNostrEventId = id
)
}
}
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>(
@@ -299,6 +375,58 @@ data class NostrEvent(
// TODO: Check for unsupported kind
val taggedEvent = event.firstTaggedEvent()
val quotedEvent = when (val taggedQuote = event.firstTaggedQuote()) {
is QEventTag -> {
Triple(
taggedQuote.eventId,
taggedQuote.author,
taggedQuote.relay,
)
}
is QAddressableTag -> {
Triple(
taggedQuote.address.dTag,
taggedQuote.address.pubKeyHex,
taggedQuote.relay,
)
}
else -> null
}
val repostedEvent = if (event.kind == RepostEvent.KIND) {
val repostedEvent = event.taggedEvents().firstOrNull()
when {
(repostedEvent != null) -> {
repostedEvent
}
else -> null
}
} else {
null
}
val (replyToRoot, replyTo) = if (event.kind == TextNoteEvent.KIND) {
val textNoteEvent = TextNoteEvent(
id = event.id,
content = event.content,
tags = event.tags,
createdAt = event.createdAt,
sig = event.sig,
pubKey = event.pubKey
)
Pair(
textNoteEvent.markedRoot(),
textNoteEvent.markedReply()
)
} else {
Pair(
null,
null
)
}
return NostrEvent(
id = event.id,
pubKey = event.pubKey,
@@ -309,8 +437,17 @@ data class NostrEvent(
unsignedNostrEventId = synchronizeNostrEventRequest?.unsignedNostrEventId,
sig = event.sig,
taggedNostrEventId = taggedEvent?.eventId,
taggedNostrEventRelayUrl = taggedEvent?.relay?.url,
quotedNostrEventId = quotedEvent?.first,
quotedAuthorPublicKey = quotedEvent?.second,
repostedNostrEventId = repostedEvent?.eventId,
repostedAuthorPublicKey = repostedEvent?.author,
inReplyToRootNostrEventId = replyToRoot?.eventId,
inReplyToRootAuthorPublicKey = replyToRoot?.author,
inReplyToNostrEventId = replyTo?.eventId,
inReplyToAuthorPublicKey = replyTo?.author,
)
} catch (e: Throwable) {
return null

View File

@@ -1,188 +0,0 @@
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.ProfilePublicKeyEntity
import ac.cord.auxiliary.compose.database.model.traits.SoftDeletableEntity
import ac.cord.auxiliary.compose.database.model.traits.TimestampedEntity
import ac.cord.auxiliary.compose.database.model.traits.UserViewableEntity
import ac.cord.auxiliary.compose.extensions.toFormattedTimeAndDateString
import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileAvatar
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.room.Entity
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.TagArray
import kotlin.time.Clock
import kotlin.time.Instant
@Entity(
foreignKeys = [
ForeignKey(
entity = Profile::class,
parentColumns = ["publicKey"],
childColumns = ["profilePublicKey"],
onDelete = ForeignKey.CASCADE,
),
ForeignKey(
entity = Profile::class,
parentColumns = ["publicKey"],
childColumns = ["quotedPostAuthorPublicKey"],
onDelete = ForeignKey.CASCADE,
),
// This might cause problems... but we gonna make the feed so cool if it works...
ForeignKey(
entity = NostrEvent::class,
parentColumns = ["id"],
childColumns = ["nostrEventId"],
onDelete = ForeignKey.CASCADE,
),
// Support post replies
ForeignKey(
entity = Post::class,
parentColumns = ["id"],
childColumns = ["quotedPostId"],
onDelete = ForeignKey.CASCADE,
),
],
indices = [
Index("profilePublicKey"),
Index("nostrEventId"),
Index("quotedPostId")
],
)
data class QuotedPost(
@PrimaryKey
val id: HexKey,
val quotedPostId: HexKey,
val quotedPostTags: TagArray,
val quotedPostCreatedAt: Instant,
val quotedPostContent: String,
val quotedPostAuthorPublicKey: HexKey,
val content: String,
override val nostrEventId: HexKey, // Might be a replaceable nostr event?
override val profilePublicKey: HexKey,
override val createdAt: Instant = Clock.System.now(),
override val updatedAt: Instant = Clock.System.now(),
override val savedAt: Instant = Clock.System.now(),
override val viewedAt: Instant? = null,
override val deletedAt: Instant? = null,
override val broadcastedAt: Instant? = null,
): NostrEventEntity, ProfilePublicKeyEntity, TimestampedEntity, LocalStoreEntity, UserViewableEntity, BroadcastableEntity, SoftDeletableEntity {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as QuotedPost
if (id != other.id) return false
if (quotedPostId != other.quotedPostId) return false
if (!quotedPostTags.contentDeepEquals(other.quotedPostTags)) return false
if (quotedPostCreatedAt != other.quotedPostCreatedAt) return false
if (quotedPostContent != other.quotedPostContent) return false
if (quotedPostAuthorPublicKey != other.quotedPostAuthorPublicKey) return false
if (content != other.content) return false
if (nostrEventId != other.nostrEventId) return false
if (profilePublicKey != other.profilePublicKey) return false
if (createdAt != other.createdAt) return false
if (updatedAt != other.updatedAt) return false
if (savedAt != other.savedAt) return false
if (viewedAt != other.viewedAt) return false
if (deletedAt != other.deletedAt) return false
if (broadcastedAt != other.broadcastedAt) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + quotedPostId.hashCode()
result = 31 * result + quotedPostTags.contentDeepHashCode()
result = 31 * result + quotedPostCreatedAt.hashCode()
result = 31 * result + quotedPostContent.hashCode()
result = 31 * result + quotedPostAuthorPublicKey.hashCode()
result = 31 * result + content.hashCode()
result = 31 * result + nostrEventId.hashCode()
result = 31 * result + profilePublicKey.hashCode()
result = 31 * result + createdAt.hashCode()
result = 31 * result + updatedAt.hashCode()
result = 31 * result + savedAt.hashCode()
result = 31 * result + (viewedAt?.hashCode() ?: 0)
result = 31 * result + (deletedAt?.hashCode() ?: 0)
result = 31 * result + (broadcastedAt?.hashCode() ?: 0)
return result
}
@Composable
fun RenderNotePreview() {
Column {
Row(
modifier = Modifier.fillMaxWidth().padding(12.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically
) {
// TODO ProfileAvatar(
// profile = profile,
// publicKey = pubKey
// )
// Column(
// modifier = Modifier.weight(1f)
// ) {
// Text(
// text = profile?.displayName ?: profile?.userName ?: nostrEvent.pubKey,
// style = MaterialTheme.typography.labelLarge
// )
//
// profile?.nip05?.let {
// Text(
// text = profile.nip05 ?: "",
// style = MaterialTheme.typography.labelMedium
// )
// }
// }
Text(
text = "Loading Profile information"
)
}
Text(
modifier = Modifier.fillMaxWidth().padding(
horizontal = 20.dp
),
style = MaterialTheme.typography.labelSmall,
textAlign = TextAlign.Start,
overflow = TextOverflow.Ellipsis,
text = content
)
Row(
modifier = Modifier.fillMaxWidth().padding(20.dp),
horizontalArrangement = Arrangement.Start
) {
Text(
text = createdAt.toFormattedTimeAndDateString(),
style = MaterialTheme.typography.labelSmall
)
}
}
}
}

View File

@@ -0,0 +1,57 @@
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.SoftDeletableEntity
import ac.cord.auxiliary.compose.database.model.traits.TimestampedEntity
import ac.cord.auxiliary.compose.database.model.traits.UserViewableEntity
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import kotlin.time.Clock
import kotlin.time.Instant
@Entity(
foreignKeys = [
ForeignKey(
entity = Profile::class,
parentColumns = ["publicKey"],
childColumns = ["quotedProfilePublicKey"],
onDelete = ForeignKey.CASCADE,
),
// This might cause problems... but we gonna make the feed so cool if it works...
ForeignKey(
entity = NostrEvent::class,
parentColumns = ["id"],
childColumns = ["quotingNostrEventId"],
onDelete = ForeignKey.CASCADE,
),
// Support post replies
ForeignKey(
entity = NostrEvent::class,
parentColumns = ["id"],
childColumns = ["quotedNostrEventId"],
onDelete = ForeignKey.CASCADE,
),
],
primaryKeys = [
"quotingNostrEventId", "quotedNostrEventId"
]
)
data class QuotedRelation(
val quotingNostrEventId: HexKey,
val quotedNostrEventId: HexKey,
val quotedProfilePublicKey: HexKey?,
override val createdAt: Instant = Clock.System.now(),
override val updatedAt: Instant = createdAt,
override val savedAt: Instant = Clock.System.now(),
override val viewedAt: Instant? = null,
override val deletedAt: Instant? = null,
override val broadcastedAt: Instant? = null,
): TimestampedEntity, LocalStoreEntity, UserViewableEntity, BroadcastableEntity, SoftDeletableEntity {
}

View File

@@ -1,115 +0,0 @@
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.ProfilePublicKeyEntity
import ac.cord.auxiliary.compose.database.model.traits.SoftDeletableEntity
import ac.cord.auxiliary.compose.database.model.traits.TimestampedEntity
import ac.cord.auxiliary.compose.database.model.traits.UserViewableEntity
import androidx.room.Entity
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.TagArray
import kotlin.time.Clock
import kotlin.time.Instant
@Entity(
foreignKeys = [
ForeignKey(
entity = Profile::class,
parentColumns = ["publicKey"],
childColumns = ["profilePublicKey"],
onDelete = ForeignKey.CASCADE,
),
ForeignKey(
entity = Profile::class,
parentColumns = ["publicKey"],
childColumns = ["repostedPostAuthorPublicKey"],
onDelete = ForeignKey.CASCADE,
),
// This might cause problems... but we gonna make the feed so cool if it works...
ForeignKey(
entity = NostrEvent::class,
parentColumns = ["id"],
childColumns = ["nostrEventId"],
onDelete = ForeignKey.CASCADE,
),
// Support post replies
ForeignKey(
entity = Post::class,
parentColumns = ["id"],
childColumns = ["repostedPostId"],
onDelete = ForeignKey.CASCADE,
),
],
indices = [
Index("profilePublicKey"),
Index("nostrEventId"),
Index("repostedPostId")
],
)
data class Repost(
@PrimaryKey
val id: HexKey,
val repostedPostId: HexKey,
val repostedPostTags: TagArray,
val repostedPostCreatedAt: Instant,
val repostedPostContent: String,
val repostedPostAuthorPublicKey: HexKey,
val content: String,
override val nostrEventId: HexKey, // Might be a replaceable nostr event?
override val profilePublicKey: HexKey,
override val createdAt: Instant = Clock.System.now(),
override val updatedAt: Instant = Clock.System.now(),
override val savedAt: Instant = Clock.System.now(),
override val viewedAt: Instant? = null,
override val deletedAt: Instant? = null,
override val broadcastedAt: Instant? = null,
): NostrEventEntity, ProfilePublicKeyEntity, TimestampedEntity, LocalStoreEntity, UserViewableEntity, BroadcastableEntity, SoftDeletableEntity {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as Repost
if (id != other.id) return false
if (repostedPostId != other.repostedPostId) return false
if (repostedPostAuthorPublicKey != other.repostedPostAuthorPublicKey) return false
if (!repostedPostTags.contentDeepEquals(other.repostedPostTags)) return false
if (content != other.content) return false
if (nostrEventId != other.nostrEventId) return false
if (profilePublicKey != other.profilePublicKey) return false
if (createdAt != other.createdAt) return false
if (updatedAt != other.updatedAt) return false
if (savedAt != other.savedAt) return false
if (viewedAt != other.viewedAt) return false
if (deletedAt != other.deletedAt) return false
if (broadcastedAt != other.broadcastedAt) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + repostedPostId.hashCode()
result = 31 * result + repostedPostAuthorPublicKey.hashCode()
result = 31 * result + repostedPostTags.contentDeepHashCode()
result = 31 * result + content.hashCode()
result = 31 * result + nostrEventId.hashCode()
result = 31 * result + profilePublicKey.hashCode()
result = 31 * result + createdAt.hashCode()
result = 31 * result + updatedAt.hashCode()
result = 31 * result + savedAt.hashCode()
result = 31 * result + (viewedAt?.hashCode() ?: 0)
result = 31 * result + (deletedAt?.hashCode() ?: 0)
result = 31 * result + (broadcastedAt?.hashCode() ?: 0)
return result
}
}

View File

@@ -0,0 +1,56 @@
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.SoftDeletableEntity
import ac.cord.auxiliary.compose.database.model.traits.TimestampedEntity
import ac.cord.auxiliary.compose.database.model.traits.UserViewableEntity
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import kotlin.time.Clock
import kotlin.time.Instant
@Entity(
foreignKeys = [
ForeignKey(
entity = Profile::class,
parentColumns = ["publicKey"],
childColumns = ["repostedProfilePublicKey"],
onDelete = ForeignKey.CASCADE,
),
ForeignKey(
entity = NostrEvent::class,
parentColumns = ["id"],
childColumns = ["repostedNostrEventId"],
onDelete = ForeignKey.CASCADE,
),
// This might cause problems... but we gonna make the feed so cool if it works...
ForeignKey(
entity = NostrEvent::class,
parentColumns = ["id"],
childColumns = ["repostingNostrEventId"],
onDelete = ForeignKey.CASCADE,
),
],
primaryKeys = [
"repostingNostrEventId", "repostedNostrEventId"
]
)
data class RepostedRelation(
val repostingNostrEventId: HexKey,
val repostedNostrEventId: HexKey,
val repostedProfilePublicKey: HexKey?,
override val createdAt: Instant = Clock.System.now(),
override val updatedAt: Instant = Clock.System.now(),
override val savedAt: Instant = Clock.System.now(),
override val viewedAt: Instant? = null,
override val deletedAt: Instant? = null,
override val broadcastedAt: Instant? = null,
): TimestampedEntity, LocalStoreEntity, UserViewableEntity, BroadcastableEntity, SoftDeletableEntity {
}

View File

@@ -1,11 +1,11 @@
package ac.cord.auxiliary.compose.database.model.intermdiate
import ac.cord.auxiliary.compose.database.model.InReplyToPost
import ac.cord.auxiliary.compose.database.model.InReplyToRelation
import ac.cord.auxiliary.compose.database.model.Mention
import ac.cord.auxiliary.compose.database.model.NostrEvent
import ac.cord.auxiliary.compose.database.model.Profile
import ac.cord.auxiliary.compose.database.model.QuotedPost
import ac.cord.auxiliary.compose.database.model.Repost
import ac.cord.auxiliary.compose.database.model.QuotedRelation
import ac.cord.auxiliary.compose.database.model.RepostedRelation
import ac.cord.auxiliary.compose.extensions.toFormattedTimeAndDateString
import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileAvatar
import androidx.compose.foundation.layout.Arrangement
@@ -22,6 +22,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.room.Embedded
import androidx.room.Ignore
import androidx.room.Junction
import androidx.room.Relation
@@ -36,32 +37,59 @@ data class LocalNostrEvent(
@Relation(
parentColumn = "id",
entityColumn = "nostrEventId"
entityColumn = "repostingNostrEventId"
)
val repost: Repost? = null,
val repostedRelation: RepostedRelation? = null,
@Relation(
parentColumn = "id",
entityColumn = "nostrEventId"
entityColumn = "quotingNostrEventId"
)
val quotedPost: QuotedPost? = null,
val quotedRelation: QuotedRelation? = null,
@Relation(
parentColumn = "id",
entityColumn = "nostrEventId"
entityColumn = "id",
associateBy = Junction(
value = QuotedRelation::class,
parentColumn = "quotingNostrEventId",
entityColumn = "quotedNostrEventId"
)
)
val inReplyToNostrEvent: InReplyToPost? = null,
val quotedNostrEvent: NostrEvent? = null,
@Relation(
parentColumn = "id",
entityColumn = "publicKey",
associateBy = Junction(
value = QuotedRelation::class,
parentColumn = "quotingNostrEventId",
entityColumn = "quotedProfilePublicKey"
)
)
val quotedAuthorProfile: Profile? = null,
@Relation(
parentColumn = "id",
entityColumn = "replyingNostrEventId"
)
val inReplyToNostrEvent: InReplyToRelation? = null,
// @Relation(
// parentColumn = "id",
// entityColumn = "nostrEventId",
// associateBy = Junction(
// value = Mention::class,
// parentColumn = "mentionedPublicKey",
// entityColumn = "mentionedPublicKey"
// )
// )
// val mentionedProfiles: List<Profile> = emptyList()
@Relation(
parentColumn = "id",
entityColumn = "nostrEventId",
associateBy = Junction(
value = Mention::class,
parentColumn = "publicKey",
entityColumn = "publicKey"
)
)
val mentionedProfiles: List<Profile> = emptyList()
val mentions: List<Mention> = emptyList()
) {
@Composable
fun RenderNotePreview() {

View File

@@ -0,0 +1,99 @@
package ac.cord.auxiliary.compose.database.model.intermdiate
import ac.cord.auxiliary.compose.database.model.InReplyToRelation
import ac.cord.auxiliary.compose.database.model.Mention
import ac.cord.auxiliary.compose.database.model.NostrEvent
import ac.cord.auxiliary.compose.database.model.Profile
import ac.cord.auxiliary.compose.database.model.QuotedRelation
import ac.cord.auxiliary.compose.database.model.RepostedRelation
import ac.cord.auxiliary.compose.extensions.toFormattedTimeAndDateString
import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileAvatar
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation
data class LocalQuotedNostrEvent(
@Embedded val nostrEvent: NostrEvent,
@Relation(
parentColumn = "pubKey",
entityColumn = "publicKey"
)
val profile: Profile?,
@Relation(
parentColumn = "id",
entityColumn = "nostrEventId",
associateBy = Junction(
value = Mention::class,
parentColumn = "publicKey",
entityColumn = "publicKey"
)
)
val mentionedProfiles: List<Profile> = emptyList()
) {
@Composable
fun RenderNotePreview() {
Column {
Row(
modifier = Modifier.fillMaxWidth().padding(12.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically
) {
val profile = profile
ProfileAvatar(
profile = profile,
publicKey = nostrEvent.pubKey
)
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = profile?.displayName ?: profile?.userName ?: nostrEvent.pubKey,
style = MaterialTheme.typography.labelLarge
)
profile?.nip05?.let {
Text(
text = profile.nip05 ?: "",
style = MaterialTheme.typography.labelMedium
)
}
}
}
Text(
modifier = Modifier.fillMaxWidth().padding(
horizontal = 20.dp
),
style = MaterialTheme.typography.labelSmall,
textAlign = TextAlign.Start,
overflow = TextOverflow.Ellipsis,
text = nostrEvent.content
)
Row(
modifier = Modifier.fillMaxWidth().padding(20.dp),
horizontalArrangement = Arrangement.Start
) {
Text(
text = nostrEvent.createdAt.toFormattedTimeAndDateString(),
style = MaterialTheme.typography.labelSmall
)
}
}
}
}

View File

@@ -3,5 +3,5 @@ package ac.cord.auxiliary.compose.database.model.traits
import com.vitorpamplona.quartz.nip01Core.core.HexKey
interface ProfilePublicKeyEntity {
val profilePublicKey: HexKey
val profilePublicKey: HexKey?
}

View File

@@ -14,7 +14,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
@@ -41,9 +40,9 @@ internal fun QuotedAddressableNote(
// }
// }
if (localNostrEvent?.quotedPost != null) {
if (localNostrEvent?.quotedRelation != null) {
QuotedNote(
quotedPost = localNostrEvent.quotedPost,
quotedNostrEvent = localNostrEvent.quotedRelation,
onNoteClick = onNoteClick,
noteActions = noteActions
)

View File

@@ -1,12 +1,10 @@
package ac.cord.auxiliary.compose.ui.composable.widgets.content
import ac.cord.auxiliary.compose.database.model.QuotedPost
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalNostrEvent
import ac.cord.auxiliary.compose.ui.composable.widgets.content.Helpers.isGalleryEvent
import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileAvatar
import ac.cord.auxiliary.compose.database.model.NostrEvent
import ac.cord.auxiliary.compose.database.model.Profile
import ac.cord.auxiliary.compose.database.model.QuotedRelation
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@@ -19,16 +17,14 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@Composable
fun QuotedNote(
quotedPost: QuotedPost? = null,
quotedNostrEvent: QuotedRelation? = null,
quotedProfile: Profile? = null,
relayHints: List<String> = emptyList(),
onNoteClick: ((String) -> Unit)? = null,
noteActions: NoteActions? = null
@@ -54,7 +50,7 @@ fun QuotedNote(
// }
// }
if (quotedPost != null && noteActions != null) {
if (quotedNostrEvent != null && noteActions != null) {
// Full rendering with all interactive features
Surface(
@@ -65,9 +61,10 @@ fun QuotedNote(
.fillMaxWidth()
.padding(vertical = 6.dp)
) {
quotedPost.RenderNotePreview()
Text("Quote render coming soon")
// quotedNostrEvent.RenderNotePreview()
}
} else if (quotedPost != null) {
} else if (quotedNostrEvent != null) {
// Simple fallback rendering (no noteActions available)
Surface(
shape = RoundedCornerShape(12.dp),
@@ -77,11 +74,12 @@ fun QuotedNote(
.fillMaxWidth()
.padding(vertical = 6.dp)
.then(
if (effectiveNoteClick != null) Modifier.clickable { effectiveNoteClick(quotedPost.nostrEventId) }
if (effectiveNoteClick != null) Modifier.clickable { effectiveNoteClick(quotedNostrEvent.quotedNostrEventId) }
else Modifier
)
) {
quotedPost.RenderNotePreview()
Text("Quote render coming soon")
// quotedPost.RenderNotePreview()
}
} else {
// Loading state

View File

@@ -101,11 +101,11 @@ fun RichContent(
val profileNames = remember(profilePubkeys) {
// TODO: Get these from taggedUsers...
val names = mutableMapOf<String, String>()
for (pubkey in profilePubkeys) {
val profile = localNostrEvent.mentionedProfiles.find { taggedAuthor -> taggedAuthor.publicKey == pubkey }
names[pubkey] = profile?.humanReadableNameOrPubkey()
?: "${pubkey.take(8)}...${pubkey.takeLast(4)}"
}
// TODO for (pubkey in profilePubkeys) {
// val profile = localNostrEvent.mentionedProfiles.find { taggedAuthor -> taggedAuthor.publicKey == pubkey }
// names[pubkey] = profile?.humanReadableNameOrPubkey()
// ?: "${pubkey.take(8)}...${pubkey.takeLast(4)}"
// }
names
}
// TODO Queue fetches for any missing profiles
@@ -263,9 +263,9 @@ fun RichContent(
LinkPreview(url = segment.url)
}
is ContentSegment.NostrNoteSegment -> {
if (localNostrEvent.quotedPost != null) {
if (localNostrEvent.quotedRelation != null) {
QuotedNote(
quotedPost = localNostrEvent.quotedPost,
quotedNostrEvent = localNostrEvent.quotedRelation,
relayHints = segment.relayHints,
onNoteClick = onNoteClick,
noteActions = noteActions

View File

@@ -4,7 +4,6 @@ import ac.cord.auxiliary.compose.database.model.NostrEvent
import ac.cord.auxiliary.compose.database.model.Profile
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalNostrEvent
import ac.cord.auxiliary.compose.extensions.toFormattedTimeAndDateString
import ac.cord.auxiliary.compose.ui.composable.widgets.content.RichContent
import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileAvatar
import ac.cord.auxiliary.compose.ui.theme.AuxTheme
import androidx.compose.foundation.clickable
@@ -62,7 +61,7 @@ fun LocalNostrEvent.ListView(
style = MaterialTheme.typography.labelSmall)
}
if (repost != null) {
if (repostedRelation != null) {
TextNoteFeedItem(
localNostrEvent = this@ListView,
onNavigateToEvent = onNavigateToEvent
@@ -110,7 +109,7 @@ private fun TextNoteFeedItem(
onNavigateToEvent: (HexKey) -> Unit
) {
val nostrEvent = localNostrEvent.nostrEvent
if (localNostrEvent.repost != null) {
if (localNostrEvent.repostedRelation != null) {
Row(
modifier = Modifier.fillMaxWidth().padding(
horizontal = 20.dp
@@ -127,11 +126,11 @@ private fun TextNoteFeedItem(
Text(
modifier = Modifier.clickable(
onClick = {
onNavigateToEvent.invoke(localNostrEvent.repost.repostedPostId)
onNavigateToEvent.invoke(localNostrEvent.repostedRelation.repostedNostrEventId)
}
),
style = MaterialTheme.typography.labelLargeEmphasized,
text = localNostrEvent.repost.repostedPostAuthorPublicKey ?: "loading information..."
text = localNostrEvent.repostedRelation.repostedProfilePublicKey ?: "loading information..."
)
}
}
@@ -178,9 +177,12 @@ private fun TextNoteFeedItem(
}
// TODO: RichContent here...
RichContent(
modifier = Modifier.fillMaxWidth(),
localNostrEvent = localNostrEvent,
// RichContent(
// modifier = Modifier.fillMaxWidth(),
// localNostrEvent = localNostrEvent,
// )
Text(
text = localNostrEvent.nostrEvent.content
)
LazyRow(
@@ -250,10 +252,6 @@ private fun EventListViewPreview() {
nostrEventId = "referenced",
displayName = "Frank Talk"
),
repost = null,
quotedPost = null,
inReplyToNostrEvent = null,
mentionedProfiles = emptyList()
).ListView(
onNavigateToEvent = {}
)