feat: index parsed JSON fields of profile/channel/app-handler events

Kinds 0 (profile), 40/41 (channel create/metadata) and 31990 (app handler)
store their data as JSON in content. Implement SearchableEvent on them by
parsing the JSON (via the existing UserMetadata/ChannelData/AppMetadata
accessors) and indexing only the meaningful fields — names, bio/about, and
the addresses people search by: nip05 email, lightning addresses
(lud06/lud16), and website/picture/banner URLs. This avoids indexing the
JSON keys and structural punctuation that raw-content indexing would add.

Updates the FsSearchTest "non-searchable" case to use an unknown kind, since
MetadataEvent is now searchable, and adds SearchTest coverage for profile
and channel JSON fields (name, about, email, lightning, URL).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RFdWREvyvixRXnmNXNzmmN
This commit is contained in:
Claude
2026-06-18 19:33:10 +00:00
parent a689d5cb19
commit e400a48dbf
6 changed files with 107 additions and 11 deletions
@@ -20,7 +20,7 @@
*/
package com.vitorpamplona.quartz.nip01Core.store.fs
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
@@ -117,17 +117,18 @@ class FsSearchTest {
@Test
fun `non-searchable event does not produce fts entries`() =
runBlocking {
val meta =
signer.sign<MetadataEvent>(
// An event whose kind has no SearchableEvent mapping must not be indexed.
val nonSearchable =
signer.sign<Event>(
createdAt = 1,
kind = MetadataEvent.KIND,
kind = 9999,
tags = emptyArray(),
content = "{\"name\":\"vitor\"}",
content = "this text must not be indexed",
)
store.insert(meta)
store.insert(nonSearchable)
val ftsRoot = root.resolve("idx/fts")
assertEquals(0, ftsRoot.listDirectoryEntries().size, "MetadataEvent is not SearchableEvent")
assertEquals(0, ftsRoot.listDirectoryEntries().size, "unknown kind is not SearchableEvent")
}
@Test