-
Notifications
You must be signed in to change notification settings - Fork 234
feat: Add NIP-50 full-text search support #587
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
ea52a30
feat: implement NIP-50 full-text search support
Anshumancanrock a358b79
fix: use timingSafeEqual and zod for nodeless HMAC check
Anshumancanrock 5d3092c
refactor: only register nodeless route when enabled
Anshumancanrock 907e9b9
chore: sync with upstream main
Anshumancanrock 2008048
feat(nip-50): add full-text search support
Anshumancanrock f411a59
fix(nodeless): harden webhook verification
Anshumancanrock ed666e2
test: cover NIP-50 search, nodeless security, and NIP-11 fields
Anshumancanrock cecdc40
docs: fix GIN index name in nip50.language note
Anshumancanrock 3614343
test: update assertions for parameterized regconfig
Anshumancanrock 9f8efb4
chore: add new files from upstream main
Anshumancanrock ba853cc
chore: merge main into feat/nip-50-search
Anshumancanrock f29aa0d
refactor: remove unrelated nodeless changes from NIP-50 PR
Anshumancanrock 4831dd6
Merge branch 'main' into feat/nip-50-search
Anshumancanrock 09d171a
fix(ci): enable NIP-50 in integration test settings
Anshumancanrock 31cfcb4
Merge branch 'main' into feat/nip-50-search
cameri 1b6438a
Merge branch 'main' into feat/nip-50-search
cameri File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| --- | ||
| "nostream": minor | ||
| --- | ||
|
|
||
| Add NIP-50 full-text search support with PostgreSQL `tsvector`/`GIN` indexing. | ||
|
|
||
| Clients can now include a `search` field in REQ filter objects to perform full-text | ||
| queries against event content. Results are ranked by relevance (`ts_rank`) instead | ||
| of the usual `created_at` ordering, per the NIP-50 specification. | ||
|
|
||
| Features: | ||
| - New `search` filter field accepted in REQ messages | ||
| - PostgreSQL GIN index on `to_tsvector('simple', event_content)` for fast full-text lookups | ||
| - Configurable text-search language (defaults to `simple`, supports `english`, `spanish`, etc.) | ||
| - Configurable max search query length for abuse prevention | ||
| - NIP-50 listed in NIP-11 relay information document | ||
| - Search can be combined with all existing filter fields (kinds, authors, tags, etc.) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| exports.config = { transaction: false } | ||
|
|
||
| exports.up = function (knex) { | ||
| return knex.raw( | ||
| "CREATE INDEX CONCURRENTLY IF NOT EXISTS events_content_fts_idx ON events USING gin (to_tsvector('simple', event_content))", | ||
| ) | ||
| } | ||
|
|
||
| exports.down = function (knex) { | ||
| return knex.raw('DROP INDEX CONCURRENTLY IF EXISTS events_content_fts_idx') | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,6 +24,7 @@ | |
| 40, | ||
| 44, | ||
| 45, | ||
| 50, | ||
| 65 | ||
| ], | ||
| "supportedNipExtensions": [], | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| Feature: NIP-50 | ||
| Scenario: Alice searches for events by content | ||
| Given someone called Alice | ||
| And someone called Bob | ||
| When Bob sends a text_note event with content "Bitcoin and Lightning Network are great" | ||
| And Bob sends a text_note event with content "Nostr is a decentralized protocol" | ||
| And Alice subscribes to search for "bitcoin lightning" | ||
| Then Alice receives 1 text_note event from Bob with search match and EOSE | ||
|
|
||
| Scenario: Alice gets no results for a search with no matches | ||
| Given someone called Alice | ||
| And someone called Bob | ||
| When Bob sends a text_note event with content "Hello world from Nostr" | ||
| And Alice subscribes to search for "ethereum solana" | ||
| Then Alice receives 0 events for search and EOSE | ||
|
|
||
| Scenario: Alice combines search with kind filter | ||
| Given someone called Alice | ||
| And someone called Bob | ||
| When Bob sends a text_note event with content "Bitcoin is freedom" | ||
| And Alice subscribes to search for "bitcoin" with kinds 1 | ||
| Then Alice receives 1 text_note event from Bob with search match and EOSE |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import { Then, When, World } from '@cucumber/cucumber' | ||
| import chai from 'chai' | ||
| import sinonChai from 'sinon-chai' | ||
| import { WebSocket } from 'ws' | ||
|
|
||
| import { | ||
| createSubscription, | ||
| waitForEOSE, | ||
| waitForEventCount, | ||
| } from '../helpers' | ||
|
|
||
| chai.use(sinonChai) | ||
| const { expect } = chai | ||
|
|
||
| When( | ||
| /^(\w+) subscribes to search for "([^"]+)"$/, | ||
| async function (this: World<Record<string, any>>, name: string, searchQuery: string) { | ||
| const ws = this.parameters.clients[name] as WebSocket | ||
| const subscription = { name: `test-${Math.random()}`, filters: [{ search: searchQuery }] } | ||
| this.parameters.subscriptions[name].push(subscription) | ||
|
|
||
| await createSubscription(ws, subscription.name, subscription.filters) | ||
| }, | ||
| ) | ||
|
|
||
| When( | ||
| /^(\w+) subscribes to search for "([^"]+)" with kinds (\d+)$/, | ||
| async function (this: World<Record<string, any>>, name: string, searchQuery: string, kind: string) { | ||
| const ws = this.parameters.clients[name] as WebSocket | ||
| const subscription = { | ||
| name: `test-${Math.random()}`, | ||
| filters: [{ search: searchQuery, kinds: [Number(kind)] }], | ||
| } | ||
| this.parameters.subscriptions[name].push(subscription) | ||
|
|
||
| await createSubscription(ws, subscription.name, subscription.filters) | ||
| }, | ||
| ) | ||
|
|
||
| Then( | ||
| /^(\w+) receives (\d+) text_note events? from (\w+) with search match and EOSE$/, | ||
| async function (this: World<Record<string, any>>, name: string, count: string, author: string) { | ||
| const ws = this.parameters.clients[name] as WebSocket | ||
| const subscription = this.parameters.subscriptions[name][this.parameters.subscriptions[name].length - 1] | ||
| const events = await waitForEventCount(ws, subscription.name, Number(count), true) | ||
|
|
||
| expect(events.length).to.equal(Number(count)) | ||
| for (const event of events) { | ||
| expect(event.kind).to.equal(1) | ||
| expect(event.pubkey).to.equal(this.parameters.identities[author].pubkey) | ||
| } | ||
| }, | ||
| ) | ||
|
|
||
| Then( | ||
| /^(\w+) receives 0 events for search and EOSE$/, | ||
| async function (this: World<Record<string, any>>, name: string) { | ||
| const ws = this.parameters.clients[name] as WebSocket | ||
| const subscription = this.parameters.subscriptions[name][this.parameters.subscriptions[name].length - 1] | ||
|
|
||
| await waitForEOSE(ws, subscription.name) | ||
| }, | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # Integration test settings override. | ||
| # Enables features that are disabled by default so integration tests can | ||
| # exercise them. | ||
| nip50: | ||
| enabled: true | ||
| language: simple | ||
| maxQueryLength: 256 |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some events contain other events (gift wraps) in the content field, no content at all, and should likely not be indexed. Can you go over https://github.com/nostr-protocol/nips#event-kinds and ensure we are only indexing events that will contain text in the content field?