From 89b8948195f24df127b7ae656ab3f60bd1b49ac7 Mon Sep 17 00:00:00 2001 From: bndw Date: Fri, 13 Feb 2026 20:36:12 -0800 Subject: refactor: simplify deletion handling (remove NIP-09 processing) Remove deletion processing logic in favor of simpler approach: - Remove deletions table from schema - Delete deletions.go and deletions_test.go - Remove ProcessDeletion from EventStore interface - Kind 5 events now stored like any other event (no special handling) - Update storage test to expect 2 tables instead of 3 - All 41 tests passing --- README.md | 1 - internal/handler/grpc/server.go | 11 -- internal/handler/websocket/handler.go | 8 -- internal/storage/deletions.go | 70 ------------- internal/storage/deletions_test.go | 184 ---------------------------------- internal/storage/storage.go | 12 --- internal/storage/storage_test.go | 6 +- 7 files changed, 3 insertions(+), 289 deletions(-) delete mode 100644 internal/storage/deletions.go delete mode 100644 internal/storage/deletions_test.go diff --git a/README.md b/README.md index d2fbd41..61fa0d4 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,6 @@ See [proto/nostr/v1/nostr.proto](proto/nostr/v1/nostr.proto) for the full API. - **WebSocket** (NIP-01 - standard Nostr protocol) - ✅ Subscribe/streaming (real-time event delivery) - ✅ Subscription management (filter matching, fan-out) -- ✅ **NIP-09** - Event deletion (authors can delete their own events) - ✅ **NIP-11** - Relay info document (GET with `Accept: application/nostr+json`) **Compatible with:** diff --git a/internal/handler/grpc/server.go b/internal/handler/grpc/server.go index b1ffd96..b65b527 100644 --- a/internal/handler/grpc/server.go +++ b/internal/handler/grpc/server.go @@ -13,7 +13,6 @@ import ( type EventStore interface { StoreEvent(context.Context, *storage.EventData) error QueryEvents(context.Context, []*pb.Filter, *storage.QueryOptions) ([]*pb.Event, error) - ProcessDeletion(context.Context, *pb.Event) error } type Server struct { @@ -76,16 +75,6 @@ func (s *Server) PublishEvent(ctx context.Context, req *pb.PublishEventRequest) return nil, fmt.Errorf("failed to store event: %w", err) } - if req.Event.Kind == 5 { - if err := s.store.ProcessDeletion(ctx, req.Event); err != nil { - return &pb.PublishEventResponse{ - Accepted: false, - Message: fmt.Sprintf("deletion processing failed: %v", err), - CanonicalJson: canonicalJSON, - }, nil - } - } - s.subs.MatchAndFan(req.Event) return &pb.PublishEventResponse{ diff --git a/internal/handler/websocket/handler.go b/internal/handler/websocket/handler.go index 4a7db0d..38d4fa6 100644 --- a/internal/handler/websocket/handler.go +++ b/internal/handler/websocket/handler.go @@ -17,7 +17,6 @@ import ( type EventStore interface { StoreEvent(context.Context, *storage.EventData) error QueryEvents(context.Context, []*pb.Filter, *storage.QueryOptions) ([]*pb.Event, error) - ProcessDeletion(context.Context, *pb.Event) error } type Handler struct { @@ -131,13 +130,6 @@ func (h *Handler) handleEvent(ctx context.Context, conn *websocket.Conn, raw []j return nil } - if pbEvent.Kind == 5 { - if err := h.store.ProcessDeletion(ctx, pbEvent); err != nil { - h.sendOK(ctx, conn, event.ID, false, fmt.Sprintf("deletion failed: %v", err)) - return nil - } - } - h.subs.MatchAndFan(pbEvent) h.sendOK(ctx, conn, event.ID, true, "") diff --git a/internal/storage/deletions.go b/internal/storage/deletions.go deleted file mode 100644 index 1a07e3c..0000000 --- a/internal/storage/deletions.go +++ /dev/null @@ -1,70 +0,0 @@ -package storage - -import ( - "context" - "fmt" - - pb "northwest.io/nostr-grpc/api/nostr/v1" -) - -const KindDeletion = 5 - -func (s *Storage) ProcessDeletion(ctx context.Context, deletionEvent *pb.Event) error { - if deletionEvent.Kind != KindDeletion { - return fmt.Errorf("not a deletion event") - } - - var eventIDsToDelete []string - for _, tag := range deletionEvent.Tags { - if len(tag.Values) >= 2 && tag.Values[0] == "e" { - eventIDsToDelete = append(eventIDsToDelete, tag.Values[1]) - } - } - - if len(eventIDsToDelete) == 0 { - return nil - } - - tx, err := s.db.BeginTx(ctx, nil) - if err != nil { - return fmt.Errorf("failed to begin transaction: %w", err) - } - defer tx.Rollback() - - for _, eventID := range eventIDsToDelete { - var eventPubkey string - err := tx.QueryRowContext(ctx, - "SELECT pubkey FROM events WHERE id = ? AND deleted = 0", - eventID, - ).Scan(&eventPubkey) - - if err != nil { - continue - } - - if eventPubkey != deletionEvent.Pubkey { - continue - } - - _, err = tx.ExecContext(ctx, - "UPDATE events SET deleted = 1 WHERE id = ?", - eventID, - ) - if err != nil { - return fmt.Errorf("failed to mark event as deleted: %w", err) - } - - _, err = tx.ExecContext(ctx, - "INSERT OR IGNORE INTO deletions (deleted_event_id, deletion_event_id, pubkey, created_at) VALUES (?, ?, ?, ?)", - eventID, - deletionEvent.Id, - deletionEvent.Pubkey, - deletionEvent.CreatedAt, - ) - if err != nil { - return fmt.Errorf("failed to record deletion: %w", err) - } - } - - return tx.Commit() -} diff --git a/internal/storage/deletions_test.go b/internal/storage/deletions_test.go deleted file mode 100644 index 47e6547..0000000 --- a/internal/storage/deletions_test.go +++ /dev/null @@ -1,184 +0,0 @@ -package storage - -import ( - "context" - "fmt" - "testing" - "time" - - pb "northwest.io/nostr-grpc/api/nostr/v1" -) - -func TestProcessDeletion(t *testing.T) { - store, err := New(":memory:") - if err != nil { - t.Fatalf("failed to create storage: %v", err) - } - defer store.Close() - - ctx := context.Background() - - event := &pb.Event{ - Id: "event123", - Pubkey: "alice", - CreatedAt: time.Now().Unix(), - Kind: 1, - Tags: []*pb.Tag{}, - Content: "to be deleted", - Sig: "sig1", - } - - err = store.StoreEvent(ctx, &EventData{ - Event: event, - CanonicalJSON: []byte(`[0,"alice",1234567890,1,[],"to be deleted"]`), - }) - if err != nil { - t.Fatalf("failed to store event: %v", err) - } - - retrieved, err := store.GetEvent(ctx, "event123") - if err != nil { - t.Fatalf("event should exist before deletion: %v", err) - } - if retrieved.Id != "event123" { - t.Errorf("expected event123, got %s", retrieved.Id) - } - - deletionEvent := &pb.Event{ - Id: "deletion123", - Pubkey: "alice", - CreatedAt: time.Now().Unix(), - Kind: KindDeletion, - Tags: []*pb.Tag{ - {Values: []string{"e", "event123"}}, - }, - Content: "deleting my event", - Sig: "sig2", - } - - err = store.ProcessDeletion(ctx, deletionEvent) - if err != nil { - t.Fatalf("failed to process deletion: %v", err) - } - - _, err = store.GetEvent(ctx, "event123") - if err != ErrEventNotFound { - t.Errorf("event should be deleted, got error: %v", err) - } -} - -func TestProcessDeletionWrongAuthor(t *testing.T) { - store, err := New(":memory:") - if err != nil { - t.Fatalf("failed to create storage: %v", err) - } - defer store.Close() - - ctx := context.Background() - - event := &pb.Event{ - Id: "event456", - Pubkey: "alice", - CreatedAt: time.Now().Unix(), - Kind: 1, - Tags: []*pb.Tag{}, - Content: "alice's event", - Sig: "sig1", - } - - err = store.StoreEvent(ctx, &EventData{ - Event: event, - CanonicalJSON: []byte(`[0,"alice",1234567890,1,[],"alice's event"]`), - }) - if err != nil { - t.Fatalf("failed to store event: %v", err) - } - - deletionEvent := &pb.Event{ - Id: "deletion456", - Pubkey: "bob", - CreatedAt: time.Now().Unix(), - Kind: KindDeletion, - Tags: []*pb.Tag{ - {Values: []string{"e", "event456"}}, - }, - Content: "trying to delete alice's event", - Sig: "sig2", - } - - err = store.ProcessDeletion(ctx, deletionEvent) - if err != nil { - t.Fatalf("process deletion should succeed but not delete: %v", err) - } - - retrieved, err := store.GetEvent(ctx, "event456") - if err != nil { - t.Fatalf("event should still exist: %v", err) - } - if retrieved.Id != "event456" { - t.Errorf("expected event456, got %s", retrieved.Id) - } -} - -func TestProcessDeletionMultipleEvents(t *testing.T) { - store, err := New(":memory:") - if err != nil { - t.Fatalf("failed to create storage: %v", err) - } - defer store.Close() - - ctx := context.Background() - - for i := 1; i <= 3; i++ { - event := &pb.Event{ - Id: fmt.Sprintf("event%d", i), - Pubkey: "alice", - CreatedAt: time.Now().Unix(), - Kind: 1, - Tags: []*pb.Tag{}, - Content: fmt.Sprintf("event %d", i), - Sig: fmt.Sprintf("sig%d", i), - } - - err = store.StoreEvent(ctx, &EventData{ - Event: event, - CanonicalJSON: []byte(fmt.Sprintf(`[0,"alice",1234567890,1,[],"event %d"]`, i)), - }) - if err != nil { - t.Fatalf("failed to store event: %v", err) - } - } - - deletionEvent := &pb.Event{ - Id: "deletion789", - Pubkey: "alice", - CreatedAt: time.Now().Unix(), - Kind: KindDeletion, - Tags: []*pb.Tag{ - {Values: []string{"e", "event1"}}, - {Values: []string{"e", "event2"}}, - }, - Content: "deleting multiple events", - Sig: "sig_del", - } - - err = store.ProcessDeletion(ctx, deletionEvent) - if err != nil { - t.Fatalf("failed to process deletion: %v", err) - } - - _, err = store.GetEvent(ctx, "event1") - if err != ErrEventNotFound { - t.Error("event1 should be deleted") - } - - _, err = store.GetEvent(ctx, "event2") - if err != ErrEventNotFound { - t.Error("event2 should be deleted") - } - - _, err = store.GetEvent(ctx, "event3") - if err != nil { - t.Error("event3 should still exist") - } -} diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 806acac..d00d7bf 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -99,18 +99,6 @@ func (s *Storage) initSchema(ctx context.Context) error { ON events(tags) WHERE deleted = 0; - -- Deletion events (NIP-09) - CREATE TABLE IF NOT EXISTS deletions ( - deleted_event_id TEXT PRIMARY KEY, -- ID of event being deleted - deletion_event_id TEXT NOT NULL, -- ID of deletion event - pubkey TEXT NOT NULL, -- Who requested deletion - created_at INTEGER NOT NULL, - FOREIGN KEY (deleted_event_id) REFERENCES events(id) - ) STRICT; - - CREATE INDEX IF NOT EXISTS idx_deleted_event - ON deletions(deleted_event_id); - -- Replaceable events tracking (NIP-16, NIP-33) CREATE TABLE IF NOT EXISTS replaceable_events ( kind INTEGER NOT NULL, diff --git a/internal/storage/storage_test.go b/internal/storage/storage_test.go index f2fe401..89369f4 100644 --- a/internal/storage/storage_test.go +++ b/internal/storage/storage_test.go @@ -19,14 +19,14 @@ func TestNew(t *testing.T) { // Verify schema was created by checking if tables exist var count int - query := `SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name IN ('events', 'deletions', 'replaceable_events')` + query := `SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name IN ('events', 'replaceable_events')` err = store.DB().QueryRow(query).Scan(&count) if err != nil { t.Fatalf("failed to query tables: %v", err) } - if count != 3 { - t.Errorf("expected 3 main tables, got %d", count) + if count != 2 { + t.Errorf("expected 2 main tables, got %d", count) } } -- cgit v1.2.3