diff --git a/handlers.go b/handlers.go index d6d2ea4..d151e87 100644 --- a/handlers.go +++ b/handlers.go @@ -308,7 +308,7 @@ func (s *Server) handleWebsocket(w http.ResponseWriter, r *http.Request) { func (s *Server) handleNIP11(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - supportedNIPs := []int{9, 11, 12, 15, 16, 20} + supportedNIPs := []int{9, 11, 12, 15, 16, 20, 33} if _, ok := s.relay.(Auther); ok { supportedNIPs = append(supportedNIPs, 42) } diff --git a/storage/elasticsearch/elasticsearch.go b/storage/elasticsearch/elasticsearch.go index 12103a8..74e2185 100644 --- a/storage/elasticsearch/elasticsearch.go +++ b/storage/elasticsearch/elasticsearch.go @@ -97,7 +97,12 @@ func (ess *ElasticsearchStorage) Init() error { } func (ess *ElasticsearchStorage) DeleteEvent(id string, pubkey string) error { - // todo: is pubkey match required? + // first do get by ID and check that pubkeys match + // this is cheaper than doing delete by query, which also doesn't work with bulk indexer. + found, _ := ess.getByID(&nostr.Filter{IDs: []string{id}}) + if len(found) == 0 || found[0].PubKey != pubkey { + return nil + } done := make(chan error) err := ess.bi.Add( @@ -132,9 +137,9 @@ func (ess *ElasticsearchStorage) DeleteEvent(id string, pubkey string) error { return err } -func (ess *ElasticsearchStorage) SaveEvent(event *nostr.Event) error { +func (ess *ElasticsearchStorage) SaveEvent(evt *nostr.Event) error { ie := &IndexedEvent{ - Event: *event, + Event: *evt, } // post processing: index for FTS @@ -144,8 +149,8 @@ func (ess *ElasticsearchStorage) SaveEvent(event *nostr.Event) error { // - if it's valid JSON just index the "values" and not the keys // - more content introspection: language detection // - denormalization... attach profile + ranking signals to events - if event.Kind != 4 { - ie.ContentSearch = event.Content + if evt.Kind != 4 { + ie.ContentSearch = evt.Content } data, err := json.Marshal(ie) @@ -155,13 +160,57 @@ func (ess *ElasticsearchStorage) SaveEvent(event *nostr.Event) error { done := make(chan error) + // delete replaceable events + deleteIDs := []string{} + queryForDelete := func(filter *nostr.Filter) { + toDelete, _ := ess.QueryEvents(filter) + for _, e := range toDelete { + // KindRecommendServer: we can't query ES for exact content match + // so query by kind and loop over results to compare content + if evt.Kind == nostr.KindRecommendServer { + if e.Content == evt.Content { + deleteIDs = append(deleteIDs, e.ID) + } + } else { + deleteIDs = append(deleteIDs, e.ID) + } + } + } + if evt.Kind == nostr.KindSetMetadata || evt.Kind == nostr.KindContactList || evt.Kind == nostr.KindRecommendServer || (10000 <= evt.Kind && evt.Kind < 20000) { + // delete past events from this user + queryForDelete(&nostr.Filter{ + Authors: []string{evt.PubKey}, + Kinds: []int{evt.Kind}, + }) + } else if evt.Kind >= 30000 && evt.Kind < 40000 { + // NIP-33 + d := evt.Tags.GetFirst([]string{"d"}) + if d != nil { + queryForDelete(&nostr.Filter{ + Authors: []string{evt.PubKey}, + Kinds: []int{evt.Kind}, + Tags: nostr.TagMap{ + "d": []string{d.Value()}, + }, + }) + } + } + for _, id := range deleteIDs { + ess.bi.Add( + context.Background(), + esutil.BulkIndexerItem{ + Action: "delete", + DocumentID: id, + }) + } + // adapted from: // https://github.com/elastic/go-elasticsearch/blob/main/_examples/bulk/indexer.go#L196 err = ess.bi.Add( context.Background(), esutil.BulkIndexerItem{ Action: "index", - DocumentID: event.ID, + DocumentID: evt.ID, Body: bytes.NewReader(data), OnSuccess: func(ctx context.Context, item esutil.BulkIndexerItem, res esutil.BulkIndexerResponseItem) { close(done) diff --git a/storage/elasticsearch/query.go b/storage/elasticsearch/query.go index 00df2e1..2d3788b 100644 --- a/storage/elasticsearch/query.go +++ b/storage/elasticsearch/query.go @@ -172,6 +172,7 @@ func isGetByID(filter *nostr.Filter) bool { len(filter.Authors) == 0 && len(filter.Kinds) == 0 && len(filter.Tags) == 0 && + len(filter.Search) == 0 && filter.Since == nil && filter.Until == nil diff --git a/storage/postgresql/save.go b/storage/postgresql/save.go index 811cd3f..e160ebf 100644 --- a/storage/postgresql/save.go +++ b/storage/postgresql/save.go @@ -16,6 +16,13 @@ func (b *PostgresBackend) SaveEvent(evt *nostr.Event) error { // delete past recommend_server events equal to this one b.DB.Exec(`DELETE FROM event WHERE pubkey = $1 AND kind = $2 AND content = $3`, evt.PubKey, evt.Kind, evt.Content) + } else if evt.Kind >= 30000 && evt.Kind < 40000 { + // NIP-33 + d := evt.Tags.GetFirst([]string{"d"}) + if d != nil { + b.DB.Exec(`DELETE FROM event WHERE pubkey = $1 AND kind = $2 AND tagvalues && ARRAY[$3]`, + evt.PubKey, evt.Kind, d.Value()) + } } // insert diff --git a/storage/sqlite3/save.go b/storage/sqlite3/save.go index 514aba9..78df76f 100644 --- a/storage/sqlite3/save.go +++ b/storage/sqlite3/save.go @@ -2,6 +2,7 @@ package sqlite3 import ( "encoding/json" + "fmt" "github.com/fiatjaf/relayer/storage" "github.com/nbd-wtf/go-nostr" @@ -16,6 +17,13 @@ func (b *SQLite3Backend) SaveEvent(evt *nostr.Event) error { // delete past recommend_server events equal to this one b.DB.Exec(`DELETE FROM event WHERE pubkey = $1 AND kind = $2 AND content = $3`, evt.PubKey, evt.Kind, evt.Content) + } else if evt.Kind >= 30000 && evt.Kind < 40000 { + // NIP-33 + d := evt.Tags.GetFirst([]string{"d"}) + if d != nil { + tagsLike := fmt.Sprintf(`%%"d","%s"%%`, d.Value()) + b.DB.Exec(`DELETE FROM event WHERE pubkey = $1 AND kind = $2 AND tags LIKE $3`, evt.PubKey, evt.Kind, tagsLike) + } } // insert